edu.stanford.epad.common.pixelmed.TIFFMasksToDSOConverter.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.epad.common.pixelmed.TIFFMasksToDSOConverter.java

Source

/*******************************************************************************
 * Copyright (c) 2015 The Board of Trustees of the Leland Stanford Junior University
 * BY CLICKING ON "ACCEPT," DOWNLOADING, OR OTHERWISE USING EPAD, YOU AGREE TO THE FOLLOWING TERMS AND CONDITIONS:
 * STANFORD ACADEMIC SOFTWARE SOURCE CODE LICENSE FOR
 * "ePAD Annotation Platform for Radiology Images"
 *
 * This Agreement covers contributions to and downloads from the ePAD project ("ePAD") maintained by The Board of Trustees 
 * of the Leland Stanford Junior University ("Stanford"). 
 *
 * *   Part A applies to downloads of ePAD source code and/or data from ePAD. 
 *
 * *   Part B applies to contributions of software and/or data to ePAD (including making revisions of or additions to code 
 * and/or data already in ePAD), which may include source or object code. 
 *
 * Your download, copying, modifying, displaying, distributing or use of any ePAD software and/or data from ePAD 
 * (collectively, the "Software") is subject to Part A. Your contribution of software and/or data to ePAD (including any 
 * that occurred prior to the first publication of this Agreement) is a "Contribution" subject to Part B. Both Parts A and 
 * B shall be governed by and construed in accordance with the laws of the State of California without regard to principles 
 * of conflicts of law. Any legal action involving this Agreement or the Research Program will be adjudicated in the State 
 * of California. This Agreement shall supersede and replace any license terms that you may have agreed to previously with 
 * respect to ePAD.
 *
 * PART A. DOWNLOADING AGREEMENT - LICENSE FROM STANFORD WITH RIGHT TO SUBLICENSE ("SOFTWARE LICENSE").
 * 1. As used in this Software License, "you" means the individual downloading and/or using, reproducing, modifying, 
 * displaying and/or distributing Software and the institution or entity which employs or is otherwise affiliated with you. 
 * Stanford  hereby grants you, with right to sublicense, with respect to Stanford's rights in the Software, a 
 * royalty-free, non-exclusive license to use, reproduce, make derivative works of, display and distribute the Software, 
 * provided that: (a) you adhere to all of the terms and conditions of this Software License; (b) in connection with any 
 * copy, distribution of, or sublicense of all or any portion of the Software, the terms and conditions in this Software 
 * License shall appear in and shall apply to such copy and such sublicense, including without limitation all source and 
 * executable forms and on any user documentation, prefaced with the following words: "All or portions of this licensed 
 * product  have been obtained under license from The Board of Trustees of the Leland Stanford Junior University. and are 
 * subject to the following terms and conditions" AND any user interface to the Software or the "About" information display 
 * in the Software will display the following: "Powered by ePAD http://epad.stanford.edu;" (c) you preserve and maintain 
 * all applicable attributions, copyright notices and licenses included in or applicable to the Software; (d) modified 
 * versions of the Software must be clearly identified and marked as such, and must not be misrepresented as being the 
 * original Software; and (e) you consider making, but are under no obligation to make, the source code of any of your 
 * modifications to the Software freely available to others on an open source basis.
 *
 * 2. The license granted in this Software License includes without limitation the right to (i) incorporate the Software 
 * into your proprietary programs (subject to any restrictions applicable to such programs), (ii) add your own copyright 
 * statement to your modifications of the Software, and (iii) provide additional or different license terms and conditions 
 * in your sublicenses of modifications of the Software; provided that in each case your use, reproduction or distribution 
 * of such modifications otherwise complies with the conditions stated in this Software License.
 * 3. This Software License does not grant any rights with respect to third party software, except those rights that 
 * Stanford has been authorized by a third party to grant to you, and accordingly you are solely responsible for (i) 
 * obtaining any permissions from third parties that you need to use, reproduce, make derivative works of, display and 
 * distribute the Software, and (ii) informing your sublicensees, including without limitation your end-users, of their 
 * obligations to secure any such required permissions.
 * 4. You agree that you will use the Software in compliance with all applicable laws, policies and regulations including, 
 * but not limited to, those applicable to Personal Health Information ("PHI") and subject to the Institutional Review 
 * Board requirements of the your institution, if applicable. Licensee acknowledges and agrees that the Software is not 
 * FDA-approved, is intended only for research, and may not be used for clinical treatment purposes. Any commercialization 
 * of the Software is at the sole risk of you and the party or parties engaged in such commercialization. You further agree 
 * to use, reproduce, make derivative works of, display and distribute the Software in compliance with all applicable 
 * governmental laws, regulations and orders, including without limitation those relating to export and import control.
 * 5. You or your institution, as applicable, will indemnify, hold harmless, and defend Stanford against any third party 
 * claim of any kind made against Stanford arising out of or related to the exercise of any rights granted under this 
 * Agreement, the provision of Software, or the breach of this Agreement. Stanford provides the Software AS IS and WITH ALL 
 * FAULTS.  Stanford makes no representations and extends no warranties of any kind, either express or implied.  Among 
 * other things, Stanford disclaims any express or implied warranty in the Software:
 * (a)  of merchantability, of fitness for a particular purpose,
 * (b)  of non-infringement or 
 * (c)  arising out of any course of dealing.
 *
 * Title and copyright to the Program and any associated documentation shall at all times remain with Stanford, and 
 * Licensee agrees to preserve same. Stanford reserves the right to license the Program at any time for a fee.
 * 6. None of the names, logos or trademarks of Stanford or any of Stanford's affiliates or any of the Contributors, or any 
 * funding agency, may be used to endorse or promote products produced in whole or in part by operation of the Software or 
 * derived from or based on the Software without specific prior written permission from the applicable party.
 * 7. Any use, reproduction or distribution of the Software which is not in accordance with this Software License shall 
 * automatically revoke all rights granted to you under this Software License and render Paragraphs 1 and 2 of this 
 * Software License null and void.
 * 8. This Software License does not grant any rights in or to any intellectual property owned by Stanford or any 
 * Contributor except those rights expressly granted hereunder.
 *
 * PART B. CONTRIBUTION AGREEMENT - LICENSE TO STANFORD WITH RIGHT TO SUBLICENSE ("CONTRIBUTION AGREEMENT").
 * 1. As used in this Contribution Agreement, "you" means an individual providing a Contribution to ePAD and the 
 * institution or entity which employs or is otherwise affiliated with you.
 * 2. This Contribution Agreement applies to all Contributions made to ePAD at any time. By making a Contribution you 
 * represent that: (i) you are legally authorized and entitled by ownership or license to make such Contribution and to 
 * grant all licenses granted in this Contribution Agreement with respect to such Contribution; (ii) if your Contribution 
 * includes any patient data, all such data is de-identified in accordance with U.S. confidentiality and security laws and 
 * requirements, including but not limited to the Health Insurance Portability and Accountability Act (HIPAA) and its 
 * regulations, and your disclosure of such data for the purposes contemplated by this Agreement is properly authorized and 
 * in compliance with all applicable laws and regulations; and (iii) you have preserved in the Contribution all applicable 
 * attributions, copyright notices and licenses for any third party software or data included in the Contribution.
 * 3. Except for the licenses you grant in this Agreement, you reserve all right, title and interest in your Contribution.
 * 4. You hereby grant to Stanford, with the right to sublicense, a perpetual, worldwide, non-exclusive, no charge, 
 * royalty-free, irrevocable license to use, reproduce, make derivative works of, display and distribute the Contribution. 
 * If your Contribution is protected by patent, you hereby grant to Stanford, with the right to sublicense, a perpetual, 
 * worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under your interest in patent rights embodied in 
 * the Contribution, to make, have made, use, sell and otherwise transfer your Contribution, alone or in combination with 
 * ePAD or otherwise.
 * 5. You acknowledge and agree that Stanford ham may incorporate your Contribution into ePAD and may make your 
 * Contribution as incorporated available to members of the public on an open source basis under terms substantially in 
 * accordance with the Software License set forth in Part A of this Agreement. You further acknowledge and agree that 
 * Stanford shall have no liability arising in connection with claims resulting from your breach of any of the terms of 
 * this Agreement.
 * 6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION DOES NOT CONTAIN ANY CODE OBTAINED BY YOU UNDER AN 
 * OPEN SOURCE LICENSE THAT REQUIRES OR PRESCRIBES DISTRBUTION OF DERIVATIVE WORKS UNDER SUCH OPEN SOURCE LICENSE. (By way 
 * of non-limiting example, you will not contribute any code obtained by you under the GNU General Public License or other 
 * so-called "reciprocal" license.)
 *******************************************************************************/
package edu.stanford.epad.common.pixelmed;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import javax.imageio.ImageIO;

import org.apache.commons.io.IOUtils;

import com.pixelmed.anatproc.CodedConcept;
import com.pixelmed.dicom.Attribute;
import com.pixelmed.dicom.AttributeList;
import com.pixelmed.dicom.DicomException;
import com.pixelmed.dicom.DicomInputStream;
import com.pixelmed.dicom.TagFromName;

import edu.stanford.epad.common.util.EPADLogger;

/**
 * <p>
 * A class for converting segmentation results in TIFF files to DICOM segmentation objects.
 * 
 * @author Wei Lu (luwei@tju.edu.cn)
 * @date 2013-5
 */
public class TIFFMasksToDSOConverter {
    private AttributeList[] dicomAttributes;
    private final short[] orientation = new short[] { 1, 0, 0, 0, 0, 1 };
    private double[] spacing = new double[] { 0.65, 0.8 };
    private double thickness = 0.5;
    private double[][] positions = null;
    private double[] sliceLocations = null;
    private short imageWidth = 0, imageHeight = 0, numberOfFrames = 0;

    private static final EPADLogger log = EPADLogger.getInstance();
    public static HashMap<String, Integer> firstFrames = new HashMap<>();

    /**
     * @param maskFiles: Array of the TIFF files which contain the masks.
     * @param dicomFiles: Array of the original DICOM files.
     * @param outputFile: Name of the output segmentation objects file.
     * @return uids: uids[0] = Series UID uids[1] = ImageUID/InstanceUID
     * @throws DicomException
     */
    public String[] generateDSO(List<String> maskFilePaths, List<String> dicomFilePaths, String outputFilePath)
            throws DicomException {
        return generateDSO(maskFilePaths, dicomFilePaths, outputFilePath, null, null, null);
    }

    public String[] generateDSO(List<String> maskFilePaths, List<String> dicomFilePaths, String outputFilePath,
            String seriesDescription) throws DicomException {
        return generateDSO(maskFilePaths, dicomFilePaths, outputFilePath, seriesDescription, null, null);
    }

    public String[] generateDSO(List<String> maskFilePaths, List<String> dicomFilePaths, String outputFilePath,
            String dsoSeriesDescription, String dsoSeriesUID, String dsoInstanceUID) throws DicomException {
        return generateDSO(maskFilePaths, dicomFilePaths, outputFilePath, dsoSeriesDescription, dsoSeriesUID,
                dsoInstanceUID, false);
    }

    /**
     * @param maskFiles: Array of the TIFF files which contain the masks.
     * @param dicomFiles: Array of the original DICOM files.
     * @param outputFile: Name of the output segmentation objects file.
     * @param dsoSeriesDescription: Series Name of created segmentation object.
     * @param dsoSeriesUID: Series UID.
     * @param dsoInstanceUID: SOP Instance UID.
     * @param removeEmptyFrames: if true, optimize size by removing empty frames.
     * @return uids: uids[0] = Series UID uids[1] = ImageUID/InstanceUID
     * @throws DicomException
     */

    //ml imgType added for saving color in dso
    public String[] generateDSO(List<String> maskFilePaths, List<String> dicomFilePaths, String outputFilePath,
            String dsoSeriesDescription, String dsoSeriesUID, String dsoInstanceUID, boolean removeEmptyFrames,
            String imgType) throws DicomException {
        try {
            List<String> originalFilePaths = new ArrayList<String>();
            for (String path : dicomFilePaths)
                originalFilePaths.add(path);
            // Following call fills in: dicomAttributes, orientation, spacing, thickness, positions, pixels, imageWidth,
            // imageHeight, imageFrames
            log.info("Getting attributes from DICOM files");
            int minInstanceNo = getAttributesFromDICOMFiles(dicomFilePaths);
            if (minInstanceNo > 1)
                removeEmptyFrames = false;
            log.info("Reading pixels from mask files");
            //send dso instance uid so it can be used in the map for framenumbers
            byte[] pixels = getPixelsFromMaskFiles(maskFilePaths, dicomFilePaths, removeEmptyFrames,
                    dsoInstanceUID);
            if (dicomFilePaths.size() != dicomAttributes.length) {
                AttributeList[] dicomAttributesNew = new AttributeList[dicomFilePaths.size()];
                int i = 0;
                for (AttributeList attrs : dicomAttributes) {
                    if (attrs == null)
                        continue;
                    dicomAttributesNew[i++] = attrs;
                }
                dicomAttributes = dicomAttributesNew;
                this.numberOfFrames = (short) dicomFilePaths.size();
            }
            log.debug("Dicom Files:" + dicomFilePaths.size() + " attributeLists:" + dicomAttributes.length);
            return generateDSO(pixels, dicomFilePaths, outputFilePath, dsoSeriesDescription, dsoSeriesUID,
                    dsoInstanceUID, imgType);
        } catch (Exception e) {
            e.printStackTrace();
            log.warning("Error generating DSO: " + e);
            throw (new DicomException("Error generating DSO: " + e.getMessage()));
        }
    }

    /**
     * @param maskFiles: Array of the TIFF files which contain the masks.
     * @param dicomFiles: Array of the original DICOM files.
     * @param outputFile: Name of the output segmentation objects file.
     * @param dsoSeriesDescription: Series Name of created segmentation object.
     * @param dsoSeriesUID: Series UID.
     * @param dsoInstanceUID: SOP Instance UID.
     * @param removeEmptyFrames: if true, optimize size by removing empty frames.
     * @return uids: uids[0] = Series UID uids[1] = ImageUID/InstanceUID
     * @throws DicomException
     */
    public String[] generateDSO(List<String> maskFilePaths, List<String> dicomFilePaths, String outputFilePath,
            String dsoSeriesDescription, String dsoSeriesUID, String dsoInstanceUID, boolean removeEmptyFrames)
            throws DicomException {
        try {
            List<String> originalFilePaths = new ArrayList<String>();
            for (String path : dicomFilePaths)
                originalFilePaths.add(path);
            // Following call fills in: dicomAttributes, orientation, spacing, thickness, positions, pixels, imageWidth,
            // imageHeight, imageFrames
            log.info("Getting attributes from DICOM files");
            int minInstanceNo = getAttributesFromDICOMFiles(dicomFilePaths);
            if (minInstanceNo > 1)
                removeEmptyFrames = false;
            log.info("Reading pixels from mask files");
            byte[] pixels = getPixelsFromMaskFiles(maskFilePaths, dicomFilePaths, removeEmptyFrames);
            if (dicomFilePaths.size() != dicomAttributes.length) {
                AttributeList[] dicomAttributesNew = new AttributeList[dicomFilePaths.size()];
                int i = 0;
                for (AttributeList attrs : dicomAttributes) {
                    if (attrs == null)
                        continue;
                    dicomAttributesNew[i++] = attrs;
                }
                dicomAttributes = dicomAttributesNew;
                this.numberOfFrames = (short) dicomFilePaths.size();
            }
            log.debug("Dicom Files:" + dicomFilePaths.size() + " attributeLists:" + dicomAttributes.length);
            return generateDSO(pixels, dicomFilePaths, outputFilePath, dsoSeriesDescription, dsoSeriesUID,
                    dsoInstanceUID);
        } catch (Exception e) {
            e.printStackTrace();
            log.warning("Error generating DSO: " + e);
            throw (new DicomException("Error generating DSO: " + e.getMessage()));
        }
    }

    //ml imgType added for saving color in dso
    public String[] generateDSO(byte[] pixeldata, List<String> dicomFilePaths, String outputFilePath,
            String dsoSeriesDescription, String dsoSeriesUID, String dsoInstanceUID, String imgType)
            throws DicomException {
        try {
            if (dicomAttributes == null)
                getAttributesFromDICOMFiles(dicomFilePaths);
            SegmentationObjectsFileWriter dsoWriter = new SegmentationObjectsFileWriter(dicomAttributes,
                    orientation, spacing, thickness, dsoSeriesDescription, dsoSeriesUID, dsoInstanceUID);
            CodedConcept category = new CodedConcept("C0085089" /* conceptUniqueIdentifier */,
                    "260787004" /* SNOMED CID */, "SRT" /* codingSchemeDesignator */,
                    "SNM3" /* legacyCodingSchemeDesignator */, null /* codingSchemeVersion */,
                    "A-00004" /* codeValue */, "Physical Object" /* codeMeaning */, null /* codeStringEquivalent */,
                    null /* synonynms */);
            CodedConcept type = new CodedConcept("C0018787" /* conceptUniqueIdentifier */,
                    "80891009" /* SNOMED CID */, "SRT" /* codingSchemeDesignator */,
                    null /* legacyCodingSchemeDesignator */, null /* codingSchemeVersion */,
                    "T-32000" /* codeValue */, "Heart" /* codeMeaning */, null /* codeStringEquivalent */,
                    null /* synonynms */);
            log.info("Adding One Segment...");
            dsoWriter.addOneSegment("Segment No.1 is for ...", category, type);
            log.info("Adding All Frames...");
            dsoWriter.addAllFrames(pixeldata, numberOfFrames, imageWidth, imageHeight, imgType, (short) 0,
                    positions);
            log.info("Saving Dicom File...");
            dsoWriter.saveDicomFile(outputFilePath);
            String[] seriesImageUids = new String[2];
            seriesImageUids[0] = dsoWriter.getSeriesUID();
            seriesImageUids[1] = dsoWriter.getImageUID();
            return seriesImageUids;
        } catch (Exception e) {
            e.printStackTrace();
            log.warning("Error generating DSO: " + e);
            throw (new DicomException("Error generating DSO: " + e.getMessage()));
        }
    }

    public String[] generateDSO(byte[] pixeldata, List<String> dicomFilePaths, String outputFilePath,
            String dsoSeriesDescription, String dsoSeriesUID, String dsoInstanceUID) throws DicomException {
        try {
            if (dicomAttributes == null)
                getAttributesFromDICOMFiles(dicomFilePaths);
            SegmentationObjectsFileWriter dsoWriter = new SegmentationObjectsFileWriter(dicomAttributes,
                    orientation, spacing, thickness, dsoSeriesDescription, dsoSeriesUID, dsoInstanceUID);
            CodedConcept category = new CodedConcept("C0085089" /* conceptUniqueIdentifier */,
                    "260787004" /* SNOMED CID */, "SRT" /* codingSchemeDesignator */,
                    "SNM3" /* legacyCodingSchemeDesignator */, null /* codingSchemeVersion */,
                    "A-00004" /* codeValue */, "Physical Object" /* codeMeaning */, null /* codeStringEquivalent */,
                    null /* synonynms */);
            CodedConcept type = new CodedConcept("C0018787" /* conceptUniqueIdentifier */,
                    "80891009" /* SNOMED CID */, "SRT" /* codingSchemeDesignator */,
                    null /* legacyCodingSchemeDesignator */, null /* codingSchemeVersion */,
                    "T-32000" /* codeValue */, "Heart" /* codeMeaning */, null /* codeStringEquivalent */,
                    null /* synonynms */);
            log.info("Adding One Segment...");
            dsoWriter.addOneSegment("Segment No.1 is for ...", category, type);
            log.info("Adding All Frames...");
            dsoWriter.addAllFrames(pixeldata, numberOfFrames, imageWidth, imageHeight, "binary", (short) 0,
                    positions);
            log.info("Saving Dicom File...");
            dsoWriter.saveDicomFile(outputFilePath);
            String[] seriesImageUids = new String[2];
            seriesImageUids[0] = dsoWriter.getSeriesUID();
            seriesImageUids[1] = dsoWriter.getImageUID();
            return seriesImageUids;
        } catch (Exception e) {
            e.printStackTrace();
            log.warning("Error generating DSO: " + e);
            throw (new DicomException("Error generating DSO: " + e.getMessage()));
        }
    }

    /**
     * <code>
     * 
     * </code>
     */
    /**
     * List the files in the assigned path and sort the filenames in alphabetic order.
     * 
     * @param folderPath
     * @return Names of the files.
     */
    private static List<String> listFilesInAlphabeticOrder(String folderPath) {
        final File folderFile = new File(folderPath);
        File[] listOfFiles = folderFile.listFiles();

        List<String> fileList = new ArrayList<String>();

        for (int i = 0; i < listOfFiles.length; i++) {
            if (listOfFiles[i].isFile()) {
                fileList.add(listOfFiles[i].toString());
            }
        }
        Collections.sort(fileList);

        return fileList;
    }

    private int getAttributesFromDICOMFiles(List<String> dicomFilePaths)
            throws FileNotFoundException, IOException, DicomException {
        AttributeList localDICOMAttributes = new AttributeList();
        String dicomInputFile = dicomFilePaths.get(0);

        DicomInputStream dicomInputStream = null;
        try {
            dicomInputStream = new DicomInputStream(new FileInputStream(dicomInputFile));
            localDICOMAttributes.read(dicomInputStream);
        } finally {
            IOUtils.closeQuietly(dicomInputStream);
        }
        if (dicomAttributes == null)
            dicomAttributes = new AttributeList[dicomFilePaths.size()];
        this.dicomAttributes[0] = (AttributeList) localDICOMAttributes.clone();
        this.imageWidth = (short) Attribute.getSingleIntegerValueOrDefault(localDICOMAttributes,
                TagFromName.Columns, 1);
        this.imageHeight = (short) Attribute.getSingleIntegerValueOrDefault(localDICOMAttributes, TagFromName.Rows,
                1);
        this.numberOfFrames = (short) dicomFilePaths.size();
        log.info("Number of frames in DICOM file " + this.numberOfFrames);

        { // Get geometric info.
            Attribute dicomAttribute = localDICOMAttributes.get(TagFromName.SliceThickness);
            this.thickness = dicomAttribute == null ? 0.1 : dicomAttribute.getSingleDoubleValueOrDefault(0.1);
            dicomAttribute = localDICOMAttributes.get(TagFromName.PixelSpacing);

            if (dicomAttribute != null)
                this.spacing = dicomAttribute.getDoubleValues();

            dicomAttribute = localDICOMAttributes.get(TagFromName.ImageOrientationPatient);
            if (dicomAttribute != null) {
                String[] s = dicomAttribute.getStringValues();
                for (int i = 0; i < s.length; i++) {
                    this.orientation[i] = (short) Float.parseFloat(s[i]);
                }
            }
        }

        { // Check Clinical Trial info.
            Attribute siteID = localDICOMAttributes.get(TagFromName.ClinicalTrialSiteID);
            if (siteID != null) {
                Attribute siteName = localDICOMAttributes.get(TagFromName.ClinicalTrialSiteName);
                Attribute sponsorName = localDICOMAttributes.get(TagFromName.ClinicalTrialSponsorName);
                Attribute protocolID = localDICOMAttributes.get(TagFromName.ClinicalTrialProtocolID);
                Attribute protocolName = localDICOMAttributes.get(TagFromName.ClinicalTrialProtocolName);
                if (siteName == null || sponsorName == null || protocolID == null || protocolName == null) {
                    log.warning("Missing Clinical Trial Attributes in Source DICOM");
                }
            }
        }

        try { // Get sequence format. Get position of each frame.
            positions = new double[numberOfFrames][3];
            sliceLocations = new double[numberOfFrames];
            int[] instanceNos = new int[dicomFilePaths.size()];
            for (int i = 0; i < dicomFilePaths.size(); i++) {
                dicomInputFile = dicomFilePaths.get(i);
                dicomInputStream = new DicomInputStream(new FileInputStream(dicomInputFile));
                localDICOMAttributes.clear();
                localDICOMAttributes.read(dicomInputStream);
                Attribute attribute = localDICOMAttributes.get(TagFromName.ImagePositionPatient);
                if (attribute != null)
                    this.positions[i] = attribute.getDoubleValues();
                sliceLocations[i] = Attribute.getSingleDoubleValueOrDefault(localDICOMAttributes,
                        TagFromName.SliceLocation, 0);
                if (i > 0) {
                    dicomAttributes[i] = (AttributeList) localDICOMAttributes.clone();
                }
                instanceNos[i] = Attribute.getSingleIntegerValueOrDefault(localDICOMAttributes,
                        TagFromName.InstanceNumber, 1);
                log.info("instance " + i + " no:" + instanceNos[i]);

            }
            //added slicelocation for sorting, we should actually use position and orientation for sorting 
            //but the defaultdcm4che operations do not have that info

            boolean fixInstanceNumbers = false;
            // trigger logic in default dcm4chee operations is last-first+1!=size 
            // should we do this?? as it is dso not the image 
            //         if (instanceNos[instanceNos.length-1] - instanceNos[0] +1 != instanceNos.length)
            //            fixInstanceNumbers = true;
            //order by instance numbers
            int mininstance = instanceNos.length;
            for (int i = 0; i < instanceNos.length; i++) {
                for (int j = i; j < instanceNos.length; j++) {
                    int instance = instanceNos[i];
                    if (instance < mininstance)
                        mininstance = instance;
                    double[] position = positions[i];
                    double sliceLocation = sliceLocations[i];
                    AttributeList alist = dicomAttributes[i];
                    //if instance numbers are incorrect trigger fixing it, do not throw error
                    // update instance numbers when there are more than one with the same number
                    // trigger logic in default dcm4chee operations is last-first+1!=size 
                    if (instanceNos[j] == instance && j != i) {
                        fixInstanceNumbers = true;
                        i = instanceNos.length - 1;
                        break;
                        //                  throw new RuntimeException("Invalid source dicom, it has duplicate instances numbers: " + instance);
                    }
                    if (instanceNos[j] > instance) {
                        instanceNos[i] = instanceNos[j];
                        positions[i] = positions[j];
                        dicomAttributes[i] = dicomAttributes[j];
                        instanceNos[j] = instance;
                        positions[j] = position;
                        dicomAttributes[j] = alist;
                        sliceLocations[i] = sliceLocations[j];
                        sliceLocations[j] = sliceLocation;

                    }
                }
            }

            if (fixInstanceNumbers) {
                //order by slice location in descending order
                for (int i = 0; i < instanceNos.length; i++) {
                    for (int j = i; j < instanceNos.length; j++) {
                        int instance = instanceNos[i];
                        double[] position = positions[i];
                        double sliceLocation = sliceLocations[i];
                        AttributeList alist = dicomAttributes[i];

                        if (sliceLocations[j] <= sliceLocation) {
                            instanceNos[i] = instanceNos[j];
                            positions[i] = positions[j];
                            dicomAttributes[i] = dicomAttributes[j];
                            instanceNos[j] = instance;
                            positions[j] = position;
                            dicomAttributes[j] = alist;
                            sliceLocations[i] = sliceLocations[j];
                            sliceLocations[j] = sliceLocation;

                        }
                    }
                }
                //go through the ordered list and fix instance numbers
                for (int i = 0; i < instanceNos.length; i++) {
                    instanceNos[i] = i + 1;
                }
                mininstance = 1;

            }
            return mininstance;
        } finally {
            IOUtils.closeQuietly(dicomInputStream);
        }
    }

    public static BufferedImage convertRGBAToIndexed(BufferedImage src) {
        BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);

        dest.createGraphics().drawImage(src, 0, 0, null);
        return dest;
    }

    /**
     * get pixels and fill firstFrame for dso
     * @param maskFilePaths
     * @param dicomFilePaths
     * @param removeEmpty
     * @return
     * @throws FileNotFoundException
     * @throws IOException
     * @throws DicomException
     */
    private byte[] getPixelsFromMaskFiles(List<String> maskFilePaths, List<String> dicomFilePaths,
            boolean removeEmpty, String dsoUID) throws FileNotFoundException, IOException, DicomException {
        Integer firstFrame = -1;
        byte[] pixels = null;
        List<Integer> emptyFileIndex = new ArrayList<Integer>();
        for (int i = 0; i < maskFilePaths.size(); i++) {
            File maskFile = new File(maskFilePaths.get(i));
            BufferedImage maskImage = ImageIO.read(maskFile);
            // BufferedImage bufferedImage = new BufferedImage(image.getWidth(), image.getHeight(),
            // BufferedImage.TYPE_BYTE_BINARY);
            byte[] new_frame = ((DataBufferByte) maskImage.getRaster().getDataBuffer()).getData();
            byte[] pixel_data = new_frame;
            long rgbLen = maskImage.getWidth() * maskImage.getHeight() * 4;
            long bwLen = maskImage.getWidth() * maskImage.getHeight() / 8;
            long greyLen = maskImage.getWidth() * maskImage.getHeight();
            if (i == 0) {
                System.out.println("Expected length, RGB:" + rgbLen + " BW:" + bwLen + " Grey:" + greyLen
                        + " Actual tiff data len:" + new_frame.length);
                log.info("Expected length, RGB:" + rgbLen + " BW:" + bwLen + " Grey:" + greyLen
                        + " Actual tiff data len:" + new_frame.length);
            }
            boolean nonzerodata = false;

            // looks like 4 bytes/pixel, compress to 1 bit/pixel (else assume it is already 1 bit/pixel)
            if (new_frame.length == rgbLen) {
                if (i % 10 == 0) {
                    System.out.println("Compressing tiff mask from rgb, mask:" + i);
                    log.debug("Compressing tiff mask from rgb, mask:" + i);
                }

                int numpixels = new_frame.length / 4;
                int numbytes = numpixels / 8;
                pixel_data = new byte[numbytes];
                for (int k = 0; k < numbytes; k++) {
                    int index = k * 8 * 4;
                    pixel_data[k] = 0;
                    for (int l = 0; l < 4 * 8; l = l + 4) {
                        if (new_frame[index + l] != 0) {
                            int setBit = pixel_data[k] + (1 << (l / 4));
                            pixel_data[k] = (byte) setBit;
                            nonzerodata = true;
                        }
                    }
                    if (pixel_data[k] != 0)
                        log.info("maskfile" + i + ": " + k + " pixel:" + pixel_data[k] + " compress rgb");
                }

                //            

            }
            // ml if 
            else if (new_frame.length == greyLen || maskImage.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
                if (maskImage.getType() == BufferedImage.TYPE_BYTE_INDEXED) { //if not indexed old version should work
                    if (i % 10 == 0) {
                        System.out.println("indexed tiff mask from rgb, mask:" + i);
                        log.debug("indexed tiff mask from rgb, mask:" + i);
                    }

                    //            pixel_data = ((DataBufferByte)convertRGBAToIndexed(maskImage).getRaster().getDataBuffer()).getData();
                    int numpixels = new_frame.length;
                    pixel_data = new byte[numpixels];
                    log.info("maskfile data");
                    for (int k = 0; k < numpixels; k++) {
                        pixel_data[k] = 0;
                        if (new_frame[k] != 0) {
                            pixel_data[k] = new_frame[k];
                            nonzerodata = true;
                        }

                        //               byte red = (byte)((new_frame[index] * 8) / 256);
                        //               byte green = (byte)((new_frame[index+1] * 8) / 256);
                        //               byte blue = (byte)((new_frame[index+2] * 4) / 256);
                        //               pixel_data[k] =(byte) ((red << 5) | (green << 2) | blue);

                        //               for (int l = 0; l < 4; l=l+4)
                        //               {
                        //                  if (new_frame[index + l] != 0)
                        //                  {
                        //                     int setBit =  pixel_data[k] + (1 << (l));
                        //                     pixel_data[k] =(byte) setBit;
                        //                     nonzerodata = true;
                        //                  }
                        //               }
                        if (pixel_data[k] != 0)
                            log.info("maskfile" + i + ": " + k + " pixel:" + pixel_data[k] + " rgb ");
                    }

                } else {
                    if (i % 10 == 0) {
                        System.out.println("Compressing tiff mask from grey, mask:" + i);
                        log.debug("Compressing tiff mask from grey, mask:" + i);
                    }
                    int numpixels = new_frame.length;
                    int numbytes = numpixels / 8;
                    pixel_data = new byte[numbytes];
                    for (int k = 0; k < numbytes; k++) {
                        int index = k * 8;
                        pixel_data[k] = 0;
                        for (int l = 0; l < 8; l++) {
                            if (new_frame[index + l] != 0) {
                                int setBit = pixel_data[k] + (1 << l);
                                pixel_data[k] = (byte) setBit;
                                nonzerodata = true;
                            }
                        }
                        if (pixel_data[k] != 0)
                            log.info("maskfile" + i + ": " + k + " pixel:" + pixel_data[k]);
                    }
                }

            } else //bw
            {
                if (i % 10 == 0) {
                    System.out.println("Flipping odd bytes of bw tif, mask:" + i);
                    log.debug("Flipping odd bytes of bw tif, mask:" + i);
                }
                int numbytes = maskImage.getWidth() * maskImage.getHeight() / 8;
                pixel_data = new byte[numbytes];
                for (int k = 0; k < numbytes; k++) {
                    // Flip every odd byte. why on earth do we need to do this?
                    if (new_frame[k] != 0)
                        nonzerodata = true;
                    //flip if not indexed image (8 bit color)
                    if (k % 2 != 0 && new_frame[k] != 0) {
                        pixel_data[k] = 0;
                        if ((new_frame[k] & 1) == 1) {
                            int setBit = pixel_data[k] + 128;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 2) == 2) {
                            int setBit = pixel_data[k] + 64;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 4) == 4) {
                            int setBit = pixel_data[k] + 32;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 8) == 8) {
                            int setBit = pixel_data[k] + 16;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 16) == 16) {
                            int setBit = pixel_data[k] + 8;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 32) == 32) {
                            int setBit = pixel_data[k] + 4;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 64) == 64) {
                            int setBit = pixel_data[k] + 2;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 128) == 128) {
                            int setBit = pixel_data[k] + 1;
                            pixel_data[k] = (byte) setBit;
                        }
                        if (new_frame[k] != 0)
                            System.out.println("Old byte:" + new_frame[k] + " New byte:" + pixel_data[k]);
                    } else
                        pixel_data[k] = new_frame[k];
                    if (pixel_data[k] != 0)
                        log.info("maskfile bw-rgb" + i + ": " + k + " pixel:" + pixel_data[k]);
                }
            }
            log.info("maskfile" + i + ": " + maskFilePaths.get(i) + " frame_length:" + pixel_data.length
                    + " nonzero data:" + nonzerodata);
            if (!nonzerodata && removeEmpty) {
                log.debug("Nodata - maskfile" + i + ": " + maskFilePaths.get(i) + " frame_length:"
                        + pixel_data.length);
                emptyFileIndex.add(i);
                continue;
            }
            if (nonzerodata) {
                Integer reverseNum = maskFilePaths.size() - i - 1;
                if (firstFrame == -1 || reverseNum < firstFrame) {
                    firstFrame = reverseNum;
                    log.info("setting firstframe:" + firstFrame);

                }

            }
            if (pixels == null) {
                //pixels = new_frame.clone();
                pixels = pixel_data;
            } else {
                byte[] temp = new byte[pixels.length + pixel_data.length];
                System.arraycopy(pixels, 0, temp, 0, pixels.length);
                System.arraycopy(pixel_data, 0, temp, pixels.length, pixel_data.length);
                //pixels = temp.clone();
                pixels = temp;
            }
        }
        //update the firstframe for this dso
        log.info("first frame for dso:" + dsoUID + " is " + firstFrame);
        firstFrames.put(dsoUID, firstFrame);
        for (int i = 0; i < emptyFileIndex.size(); i++) {
            int index = emptyFileIndex.get(i);
            log.info("Removing dicom " + (dicomAttributes.length - index - 1));
            dicomFilePaths.remove(dicomAttributes.length - index - 1);
            dicomAttributes[index] = null;
        }
        //      for (int i = 0; i < emptyFileIndex.size(); i++)
        //      {
        //         int index = emptyFileIndex.get(i);
        //         log.info("Removing dicom " + (maskFilePaths.size() - index - 1));
        //         dicomFilePaths.remove(maskFilePaths.size() - index - 1); // DicomFiles are in reverse order for!!!
        //      }
        if (pixels == null)
            throw new RuntimeException("The DSO has all empty frames");
        log.info("Number of pixels:" + pixels.length + " dicoms:" + dicomFilePaths.size());
        return pixels;
    }

    private byte[] getPixelsFromMaskFiles(List<String> maskFilePaths, List<String> dicomFilePaths,
            boolean removeEmpty) throws FileNotFoundException, IOException, DicomException {
        byte[] pixels = null;
        List<Integer> emptyFileIndex = new ArrayList<Integer>();
        for (int i = 0; i < maskFilePaths.size(); i++) {
            File maskFile = new File(maskFilePaths.get(i));
            BufferedImage maskImage = ImageIO.read(maskFile);
            // BufferedImage bufferedImage = new BufferedImage(image.getWidth(), image.getHeight(),
            // BufferedImage.TYPE_BYTE_BINARY);
            byte[] new_frame = ((DataBufferByte) maskImage.getRaster().getDataBuffer()).getData();
            byte[] pixel_data = new_frame;
            long rgbLen = maskImage.getWidth() * maskImage.getHeight() * 4;
            long bwLen = maskImage.getWidth() * maskImage.getHeight() / 8;
            long greyLen = maskImage.getWidth() * maskImage.getHeight();
            if (i == 0) {
                System.out.println("Expected length, RGB:" + rgbLen + " BW:" + bwLen + " Grey:" + greyLen
                        + " Actual tiff data len:" + new_frame.length);
                log.info("Expected length, RGB:" + rgbLen + " BW:" + bwLen + " Grey:" + greyLen
                        + " Actual tiff data len:" + new_frame.length);
            }
            boolean nonzerodata = false;

            // looks like 4 bytes/pixel, compress to 1 bit/pixel (else assume it is already 1 bit/pixel)
            if (new_frame.length == rgbLen) {
                if (i % 10 == 0) {
                    System.out.println("Compressing tiff mask from rgb, mask:" + i);
                    log.debug("Compressing tiff mask from rgb, mask:" + i);
                }

                int numpixels = new_frame.length / 4;
                int numbytes = numpixels / 8;
                pixel_data = new byte[numbytes];
                for (int k = 0; k < numbytes; k++) {
                    int index = k * 8 * 4;
                    pixel_data[k] = 0;
                    for (int l = 0; l < 4 * 8; l = l + 4) {
                        if (new_frame[index + l] != 0) {
                            int setBit = pixel_data[k] + (1 << (l / 4));
                            pixel_data[k] = (byte) setBit;
                            nonzerodata = true;
                        }
                    }
                    if (pixel_data[k] != 0)
                        log.info("maskfile" + i + ": " + k + " pixel:" + pixel_data[k] + " compress rgb");
                }

                //            

            }
            // ml if 
            else if (new_frame.length == greyLen || maskImage.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
                if (maskImage.getType() == BufferedImage.TYPE_BYTE_INDEXED) { //if not indexed old version should work
                    if (i % 10 == 0) {
                        System.out.println("indexed tiff mask from rgb, mask:" + i);
                        log.debug("indexed tiff mask from rgb, mask:" + i);
                    }

                    //            pixel_data = ((DataBufferByte)convertRGBAToIndexed(maskImage).getRaster().getDataBuffer()).getData();
                    int numpixels = new_frame.length;
                    pixel_data = new byte[numpixels];
                    log.info("maskfile data");
                    for (int k = 0; k < numpixels; k++) {
                        pixel_data[k] = 0;
                        if (new_frame[k] != 0) {
                            pixel_data[k] = new_frame[k];
                            nonzerodata = true;
                        }

                        //               byte red = (byte)((new_frame[index] * 8) / 256);
                        //               byte green = (byte)((new_frame[index+1] * 8) / 256);
                        //               byte blue = (byte)((new_frame[index+2] * 4) / 256);
                        //               pixel_data[k] =(byte) ((red << 5) | (green << 2) | blue);

                        //               for (int l = 0; l < 4; l=l+4)
                        //               {
                        //                  if (new_frame[index + l] != 0)
                        //                  {
                        //                     int setBit =  pixel_data[k] + (1 << (l));
                        //                     pixel_data[k] =(byte) setBit;
                        //                     nonzerodata = true;
                        //                  }
                        //               }
                        if (pixel_data[k] != 0)
                            log.info("maskfile" + i + ": " + k + " pixel:" + pixel_data[k] + " rgb ");
                    }

                } else {
                    if (i % 10 == 0) {
                        System.out.println("Compressing tiff mask from grey, mask:" + i);
                        log.debug("Compressing tiff mask from grey, mask:" + i);
                    }
                    int numpixels = new_frame.length;
                    int numbytes = numpixels / 8;
                    pixel_data = new byte[numbytes];
                    for (int k = 0; k < numbytes; k++) {
                        int index = k * 8;
                        pixel_data[k] = 0;
                        for (int l = 0; l < 8; l++) {
                            if (new_frame[index + l] != 0) {
                                int setBit = pixel_data[k] + (1 << l);
                                pixel_data[k] = (byte) setBit;
                                nonzerodata = true;
                            }
                        }
                        if (pixel_data[k] != 0)
                            log.info("maskfile" + i + ": " + k + " pixel:" + pixel_data[k]);
                    }
                }

            } else //bw
            {
                if (i % 10 == 0) {
                    System.out.println("Flipping odd bytes of bw tif, mask:" + i);
                    log.debug("Flipping odd bytes of bw tif, mask:" + i);
                }
                int numbytes = maskImage.getWidth() * maskImage.getHeight() / 8;
                pixel_data = new byte[numbytes];
                for (int k = 0; k < numbytes; k++) {
                    // Flip every odd byte. why on earth do we need to do this?
                    if (new_frame[k] != 0)
                        nonzerodata = true;
                    //flip if not indexed image (8 bit color)
                    if (k % 2 != 0 && new_frame[k] != 0) {
                        pixel_data[k] = 0;
                        if ((new_frame[k] & 1) == 1) {
                            int setBit = pixel_data[k] + 128;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 2) == 2) {
                            int setBit = pixel_data[k] + 64;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 4) == 4) {
                            int setBit = pixel_data[k] + 32;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 8) == 8) {
                            int setBit = pixel_data[k] + 16;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 16) == 16) {
                            int setBit = pixel_data[k] + 8;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 32) == 32) {
                            int setBit = pixel_data[k] + 4;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 64) == 64) {
                            int setBit = pixel_data[k] + 2;
                            pixel_data[k] = (byte) setBit;
                        }
                        if ((new_frame[k] & 128) == 128) {
                            int setBit = pixel_data[k] + 1;
                            pixel_data[k] = (byte) setBit;
                        }
                        if (new_frame[k] != 0)
                            System.out.println("Old byte:" + new_frame[k] + " New byte:" + pixel_data[k]);
                    } else
                        pixel_data[k] = new_frame[k];
                    if (pixel_data[k] != 0)
                        log.info("maskfile bw-rgb" + i + ": " + k + " pixel:" + pixel_data[k]);
                }
            }
            log.info("maskfile" + i + ": " + maskFilePaths.get(i) + " frame_length:" + pixel_data.length
                    + " nonzero data:" + nonzerodata);
            if (!nonzerodata && removeEmpty) {
                log.debug("Nodata - maskfile" + i + ": " + maskFilePaths.get(i) + " frame_length:"
                        + pixel_data.length);
                emptyFileIndex.add(i);
                continue;
            }
            if (pixels == null) {
                //pixels = new_frame.clone();
                pixels = pixel_data;
            } else {
                byte[] temp = new byte[pixels.length + pixel_data.length];
                System.arraycopy(pixels, 0, temp, 0, pixels.length);
                System.arraycopy(pixel_data, 0, temp, pixels.length, pixel_data.length);
                //pixels = temp.clone();
                pixels = temp;
            }
        }
        for (int i = 0; i < emptyFileIndex.size(); i++) {
            int index = emptyFileIndex.get(i);
            log.info("Removing dicom " + (dicomAttributes.length - index - 1));
            dicomFilePaths.remove(dicomAttributes.length - index - 1);
            dicomAttributes[index] = null;
        }
        //      for (int i = 0; i < emptyFileIndex.size(); i++)
        //      {
        //         int index = emptyFileIndex.get(i);
        //         log.info("Removing dicom " + (maskFilePaths.size() - index - 1));
        //         dicomFilePaths.remove(maskFilePaths.size() - index - 1); // DicomFiles are in reverse order for!!!
        //      }
        if (pixels == null)
            throw new RuntimeException("The DSO has all empty frames");
        log.info("Number of pixels:" + pixels.length + " dicoms:" + dicomFilePaths.size());
        return pixels;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        if (args.length < 3) {
            System.out.println(
                    "\n\nInvalid arguments.\n\nUsage: java -classpath epad-ws-1.1-jar-with-dependencies.jar edu.stanford.epad.common.pixelmed.TIFFMasksToDSOConverter tiffFolder dicomFolder outputDSO.dcm");
            return;
        }
        String maskFilesDirectory = args[0];
        String dicomFilesDirectory = args[1];
        String outputFileName = args[2];
        //String maskFilesDirectory = "/Stanford/rlabs/data/tiffmasks";
        //String dicomFilesDirectory = "/Stanford/rlabs/data/dicoms";
        //String outputFileName = "/Stanford/rlabs/data/output/dso.dcm";

        List<String> dicomFilePaths = listFilesInAlphabeticOrder(dicomFilesDirectory);
        for (int i = 0; i < dicomFilePaths.size(); i++) {
            if (!dicomFilePaths.get(i).toLowerCase().endsWith(".dcm")) {
                System.out.println("Removing DICOM file " + dicomFilePaths.get(i));
                dicomFilePaths.remove(i);
                i--;
            }
        }
        if (dicomFilePaths.size() == 0) {
            System.out.println("No DICOM files found");
            return;
        }

        List<String> maskFilePaths = listFilesInAlphabeticOrder(maskFilesDirectory);
        for (int i = 0; i < maskFilePaths.size(); i++) {
            if (!maskFilePaths.get(i).toLowerCase().endsWith(".tif")
                    && !maskFilePaths.get(i).toLowerCase().endsWith(".tiff")) {
                System.out.println("Removing tif file " + maskFilePaths.get(i));
                maskFilePaths.remove(i);
                i--;
            }
        }
        // Flip them because the code expects that
        List<String> reverseMaskFilePaths = new ArrayList<String>();
        for (int i = maskFilePaths.size(); i > 0; i--) {
            reverseMaskFilePaths.add(maskFilePaths.get(i - 1));
        }
        if (maskFilePaths.size() == 0) {
            System.out.println("No Tif Mask files found");
            return;
        }

        if (dicomFilePaths.size() > maskFilePaths.size())
            dicomFilePaths = dicomFilePaths.subList(0, reverseMaskFilePaths.size());
        else if (reverseMaskFilePaths.size() > dicomFilePaths.size())
            reverseMaskFilePaths = reverseMaskFilePaths.subList(0, dicomFilePaths.size());

        try {
            TIFFMasksToDSOConverter converter = new TIFFMasksToDSOConverter();
            String[] uids = null;
            if (args.length > 3)
                uids = converter.generateDSO(reverseMaskFilePaths, dicomFilePaths, outputFileName, args[3]);
            else
                uids = converter.generateDSO(reverseMaskFilePaths, dicomFilePaths, outputFileName);
            System.out
                    .println("DICOM Segmentation Object created. SeriesUID:" + uids[0] + " InstanceUID:" + uids[1]);
        } catch (Exception e) {
            System.err.println(e);
            e.printStackTrace(System.err);
            System.exit(0);
        }
    }
}