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

Java tutorial

Introduction

Here is the source code for edu.stanford.epad.common.pixelmed.SegmentationObjectsFileWriter.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.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

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.AttributeTag;
import com.pixelmed.dicom.AttributeTagAttribute;
import com.pixelmed.dicom.CodeStringAttribute;
import com.pixelmed.dicom.CompositeInstanceContext;
import com.pixelmed.dicom.DateAttribute;
import com.pixelmed.dicom.DecimalStringAttribute;
import com.pixelmed.dicom.DicomDictionary;
import com.pixelmed.dicom.DicomException;
import com.pixelmed.dicom.DicomInputStream;
import com.pixelmed.dicom.DicomOutputStream;
import com.pixelmed.dicom.FileMetaInformation;
import com.pixelmed.dicom.GeometryOfSliceFromAttributeList;
import com.pixelmed.dicom.IntegerStringAttribute;
import com.pixelmed.dicom.LongStringAttribute;
import com.pixelmed.dicom.OtherByteAttribute;
import com.pixelmed.dicom.OtherByteAttributeOnDisk;
import com.pixelmed.dicom.PersonNameAttribute;
import com.pixelmed.dicom.SOPClass;
import com.pixelmed.dicom.SequenceAttribute;
import com.pixelmed.dicom.SequenceItem;
import com.pixelmed.dicom.ShortStringAttribute;
import com.pixelmed.dicom.ShortTextAttribute;
import com.pixelmed.dicom.TagFromName;
import com.pixelmed.dicom.TimeAttribute;
import com.pixelmed.dicom.TransferSyntax;
import com.pixelmed.dicom.UIDGenerator;
import com.pixelmed.dicom.UniqueIdentifierAttribute;
import com.pixelmed.dicom.UnsignedLongAttribute;
import com.pixelmed.dicom.UnsignedShortAttribute;
import com.pixelmed.dicom.VersionAndConstants;
import com.pixelmed.geometry.GeometryOfSlice;
import com.pixelmed.utils.CopyStream;

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

/**
 * A class for saving segmentation results.
 * 
 * @author Wei Lu (luwei@tju.edu.cn)
 * @date 2012-12
 */
public class SegmentationObjectsFileWriter {
    public static final String Manufacturer = "Stanford University";
    public static final String ManufacturerModelName = "ePAD";
    public static final String DeviceSerialNumber = "SN123456";
    public static final String SoftwareVersion = "2.0.1";
    public static final String SeriesDescription = "ePAD Generated DSO";
    public static final String prefix = "ePAD DSO";
    public static final String SourceApplicationEntityTitle = "Default title";

    private static final EPADLogger log = EPADLogger.getInstance();

    private final AttributeList list = new AttributeList();
    private AttributeList geometry_list = new AttributeList();
    private final SequenceAttribute segment_sequence = new SequenceAttribute(TagFromName.SegmentSequence);
    private final AttributeList shared_functional_groups_item = new AttributeList();
    private final SequenceAttribute per_frame_functional_groups_sequence = new SequenceAttribute(
            TagFromName.PerFrameFunctionalGroupsSequence);
    private final Attribute pixel_data = new OtherByteAttribute(TagFromName.PixelData);;
    private int current_segment = 0;
    private int frame_counter = 0;
    private boolean use_temp_file = false;
    private String temp_file = "temp_pixel_data_file.tmp"; // Will be renamed to a unique value per instance.
    private SimpleDateFormat timestamp = new SimpleDateFormat("MMM-dd-HHmm");
    private String seriesUID = null;
    private String imageUID = null;

    //moved declaration from line 521 for slicer per frame
    private String orig_class_uid;
    //moved declaration from line 540 for slicer per frame
    private String[] orig_inst_uids;
    //added for slicer positions per frame
    private double[][] seg_positions;

    /**
     * Initialize the list with constant attributes and inherited attributes.
     * 
     * @param original_attrs is the whole attributes list.
     * @param patient_orientation
     * @param pixel_spacing
     * @param slice_thickness
     * @throws DicomException
     */
    public SegmentationObjectsFileWriter(AttributeList[] original_attrs_list, short[] patient_orientation,
            double[] pixel_spacing, double slice_thickness) throws DicomException {
        this(original_attrs_list, patient_orientation, pixel_spacing, slice_thickness, null, null, null);
    }

    /**
     * Initialize the list with constant attributes and inherited attributes.
     * 
     * @param original_attrs is the whole attributes list.
     * @param patient_orientation
     * @param pixel_spacing
     * @param slice_thickness
     * @throws DicomException
     */
    public SegmentationObjectsFileWriter(AttributeList[] original_attrs_list, short[] patient_orientation,
            double[] pixel_spacing, double slice_thickness, String dsoSeriesDescription, String dsoSeriesUID,
            String dsoInstanceUID) throws DicomException {
        log.info("Generating DICOM attributes for DSO");
        // Temporary
        //dsoSeriesUID = null; // Always create a new Series UID
        //dsoSeriesDescription = null; // Always create a new description
        log.info("Create DSO, seriesDesc:" + dsoSeriesDescription + " seriesUID:" + dsoSeriesUID);
        if (original_attrs_list == null || original_attrs_list.length == 0)
            throw (new DicomException("The original attributes must not be null!"));
        AttributeList original_attrs = original_attrs_list[0];
        /*********************************************************************
         * Generate a unique name for the temporary pixel data file.
         *********************************************************************/
        {
            int rand = (int) (Math.random() * Integer.MAX_VALUE);
            temp_file = Integer.toString(rand) + ".tmp"; // temp_file has a unique name.
        }

        if (original_attrs == null)
            throw (new DicomException("The original attributes must not be null!"));

        if (patient_orientation == null || patient_orientation.length != 6)
            throw (new DicomException("Invalid patient orientation!"));

        if (pixel_spacing == null || pixel_spacing.length != 2)
            throw (new DicomException("Invalid pixel spacing!"));

        if (slice_thickness <= 0)
            throw (new DicomException("Invalid slice thickness!"));

        /*********************************************************************
         * Save the geometric attributes to geometry_list. 
         *********************************************************************/
        {
            Attribute a = new DecimalStringAttribute(TagFromName.ImageOrientationPatient);
            for (int i = 0; i < patient_orientation.length; i++)
                a.addValue(patient_orientation[i]);
            geometry_list.put(a);
            Attribute spacing = new DecimalStringAttribute(TagFromName.PixelSpacing);
            spacing.addValue(pixel_spacing[0]);
            spacing.addValue(pixel_spacing[1]);
            Attribute thickness = new DecimalStringAttribute(TagFromName.SliceThickness);
            thickness.addValue(slice_thickness);
            geometry_list.put(spacing);
            geometry_list.put(thickness);
        }

        /*********************************************************************
         * Add constant attributes of segmentation objects.
         *********************************************************************/
        {
            Attribute a = new UniqueIdentifierAttribute(TagFromName.MediaStorageSOPClassUID);
            a.addValue(SOPClass.SegmentationStorage);
            list.put(a);
        }
        {
            Attribute a = new UniqueIdentifierAttribute(TagFromName.TransferSyntaxUID);
            a.addValue(TransferSyntax.ExplicitVRLittleEndian);
            list.put(a);
        }
        {
            Attribute a = new UniqueIdentifierAttribute(TagFromName.ImplementationClassUID);
            a.addValue(SOPClass.SegmentationStorage);
            list.put(a);
        }
        {
            Attribute a = new UniqueIdentifierAttribute(TagFromName.SOPClassUID);
            a.addValue(SOPClass.SegmentationStorage);
            list.put(a);
        }
        {
            Attribute a = new CodeStringAttribute(TagFromName.Modality);
            a.addValue("SEG");
            list.put(a);
        }
        {
            Attribute a = new CodeStringAttribute(TagFromName.ContentLabel);
            a.addValue("ROI");
            list.put(a);
        }
        {
            Attribute a = new UnsignedShortAttribute(TagFromName.SamplesPerPixel);
            a.addValue(1);
            list.put(a);
        }
        {
            Attribute a = new CodeStringAttribute(TagFromName.PhotometricInterpretation);
            a.addValue("MONOCHROME2");
            list.put(a);
        }
        {
            Attribute a = new CodeStringAttribute(TagFromName.ImageType);
            a.addValue("DERIVED");
            a.addValue("PRIMARY");
            list.put(a);
        }
        {
            Attribute a = new UnsignedShortAttribute(TagFromName.PixelRepresentation);
            a.addValue(0);
            list.put(a);
        }
        {
            Attribute a = new CodeStringAttribute(TagFromName.LossyImageCompression);
            a.addValue("00");
            list.put(a);
        }

        /*********************************************************************
         * Other attributes.
         *********************************************************************/
        { // Date and time.
            java.util.Date currentDateTime = new java.util.Date();
            String date = new java.text.SimpleDateFormat("yyyyMMdd").format(currentDateTime);
            String time = new java.text.SimpleDateFormat("HHmmss.SSS").format(currentDateTime);
            {
                Attribute a = new DateAttribute(TagFromName.StudyDate);
                a.addValue(date);
                list.put(a);
            }
            {
                Attribute a = new DateAttribute(TagFromName.SeriesDate);
                a.addValue(date);
                list.put(a);
            }
            {
                Attribute a = new DateAttribute(TagFromName.AcquisitionDate);
                a.addValue(date);
                list.put(a);
            }
            {
                Attribute a = new DateAttribute(TagFromName.ContentDate);
                a.addValue(date);
                list.put(a);
            }
            {
                Attribute a = new TimeAttribute(TagFromName.StudyTime);
                a.addValue(time);
                list.put(a);
            }
            {
                Attribute a = new TimeAttribute(TagFromName.SeriesTime);
                a.addValue(time);
                list.put(a);
            }
            {
                Attribute a = new TimeAttribute(TagFromName.AcquisitionTime);
                a.addValue(time);
                list.put(a);
            }
            {
                Attribute a = new TimeAttribute(TagFromName.ContentTime);
                a.addValue(time);
                list.put(a);
            }
        }
        // Device specific information.
        {
            Attribute a = new UniqueIdentifierAttribute(TagFromName.InstanceCreatorUID);
            a.addValue(VersionAndConstants.instanceCreatorUID);
            list.put(a);
        }
        {
            Attribute a = new ShortStringAttribute(TagFromName.AccessionNumber);
            list.put(a);
        }
        {
            Attribute a = new LongStringAttribute(TagFromName.SeriesDescription);
            if (dsoSeriesDescription == null || dsoSeriesDescription.trim().length() == 0
                    || dsoSeriesDescription.startsWith(SeriesDescription)) {
                a.addValue(SeriesDescription + " " + timestamp.format(new Date()));
            } else {
                if (!dsoSeriesDescription.startsWith(prefix))
                    dsoSeriesDescription = prefix + "-" + dsoSeriesDescription;
                a.addValue(dsoSeriesDescription);
            }
            list.put(a);
        }
        {
            Attribute a = new LongStringAttribute(TagFromName.Manufacturer);
            a.addValue(Manufacturer);
            list.put(a);
        }
        {
            Attribute a = new LongStringAttribute(TagFromName.ManufacturerModelName);
            a.addValue(ManufacturerModelName);
            list.put(a);
        }
        {
            Attribute a = new LongStringAttribute(TagFromName.DeviceSerialNumber);
            a.addValue(DeviceSerialNumber);
            list.put(a);
        }
        {
            Attribute a = new LongStringAttribute(TagFromName.SoftwareVersion);
            a.addValue(SoftwareVersion);
            list.put(a);
        }
        {
            Attribute a = new LongStringAttribute(TagFromName.ContentDescription);
            a.addValue(SeriesDescription);
            list.put(a);
        }
        {
            Attribute a = new PersonNameAttribute(TagFromName.ContentCreatorName);
            a.addValue(ManufacturerModelName + "^" + SoftwareVersion);
            list.put(a);
        }

        UIDGenerator u = new UIDGenerator();
        String study_id = "1"; // Attribute.getSingleStringValueOrEmptyString(original_attrs, TagFromName.StudyID);
        String series_number = "1000"; // Attribute.getSingleStringValueOrEmptyString(original_attrs,
                                       // TagFromName.SeriesNumber);
        String instance_number = "1"; // Attribute.getSingleStringValueOrEmptyString(original_attrs,
                                      // TagFromName.InstanceNumber);

        // The following is the original code that generates new series and image UIDs.
        String uid = Attribute.getSingleStringValueOrEmptyString(original_attrs, TagFromName.StudyID);
        {
            Attribute a = new UniqueIdentifierAttribute(TagFromName.StudyInstanceUID);
            a.addValue(u.getNewStudyInstanceUID(study_id));
            list.put(a);
        }
        {
            uid = u.getNewSeriesInstanceUID(study_id, series_number);
            Attribute a = new UniqueIdentifierAttribute(TagFromName.SeriesInstanceUID);
            if (dsoSeriesUID != null && dsoSeriesUID.trim().length() > 0) {
                a.addValue(dsoSeriesUID);
                seriesUID = dsoSeriesUID;
            } else {
                a.addValue(uid); // Use new uid
                seriesUID = uid;
            }
            list.put(a);
            a = new UniqueIdentifierAttribute(TagFromName.FrameOfReferenceUID);
            a.addValue(uid);
            list.put(a);
        }
        {
            Attribute a = new UniqueIdentifierAttribute(TagFromName.SOPInstanceUID);
            if (dsoInstanceUID != null && dsoInstanceUID.trim().length() > 0) {
                a.addValue(dsoInstanceUID);
                imageUID = dsoInstanceUID;
            } else {
                String instanceUID = u.getNewSOPInstanceUID(study_id, series_number, instance_number);
                a.addValue(instanceUID);
                imageUID = instanceUID;
            }
            list.put(a);
        }

        // If we want to generate a DSO with the same study.series/image UID we copy the original UID attributes.
        // String study_uid = Attribute.getSingleStringValueOrEmptyString(original_attrs, TagFromName.StudyInstanceUID);
        // String series_uid = Attribute.getSingleStringValueOrEmptyString(original_attrs, TagFromName.SeriesInstanceUID);
        // String image_uid = Attribute.getSingleStringValueOrEmptyString(original_attrs, TagFromName.SOPInstanceUID);
        // {
        // Attribute a = new UniqueIdentifierAttribute(TagFromName.StudyInstanceUID);
        // a.addValue(study_uid);
        // list.put(a);
        // }
        // {
        // Attribute a = new UniqueIdentifierAttribute(TagFromName.SeriesInstanceUID);
        // a.addValue(series_uid);
        // list.put(a);
        // }
        // {
        // Attribute a = new UniqueIdentifierAttribute(TagFromName.SOPInstanceUID);
        // a.addValue(image_uid);
        // list.put(a);
        // }

        {
            Attribute a = new ShortStringAttribute(TagFromName.StudyID);
            a.addValue(study_id);
            list.put(a);
        }
        {
            Attribute a = new IntegerStringAttribute(TagFromName.SeriesNumber);
            a.addValue(series_number);
            list.put(a);
        }
        {
            Attribute a = new IntegerStringAttribute(TagFromName.InstanceNumber);
            a.addValue(instance_number);
            list.put(a);
        }

        final DicomDictionary dicomDictionary = new DicomDictionary();

        orig_class_uid = Attribute.getSingleStringValueOrEmptyString(original_attrs, TagFromName.SOPClassUID);

        for (AttributeTag key : original_attrs.keySet()) {
            log.debug("ATTRIBUTE TAG: " + key);
            log.debug("ATTRIBUTE NAME " + dicomDictionary.getFullNameFromTag(key));
            log.debug("VALUE " + original_attrs.get(key));
        }

        SequenceAttribute referencedSeriesSequence = new SequenceAttribute(TagFromName.ReferencedSeriesSequence);
        AttributeList referencedSeriesSequenceAttributes = new AttributeList();

        {
            Attribute siu = new UniqueIdentifierAttribute(TagFromName.SeriesInstanceUID);
            String origSeriesInstanceUid = Attribute.getSingleStringValueOrEmptyString(original_attrs,
                    TagFromName.SeriesInstanceUID);
            siu.addValue(origSeriesInstanceUid);
            referencedSeriesSequenceAttributes.put(siu);
        }

        SequenceAttribute referencedInstanceSequence = new SequenceAttribute(
                TagFromName.ReferencedInstanceSequence);
        seg_positions = new double[original_attrs_list.length][3];
        orig_inst_uids = new String[original_attrs_list.length];
        for (int instanceIndex = 0; instanceIndex < original_attrs_list.length; instanceIndex++) {
            AttributeList referencedInstanceSequenceAttributes = new AttributeList();
            {
                Attribute a = new UniqueIdentifierAttribute(TagFromName.ReferencedSOPClassUID);
                a.addValue(orig_class_uid);
                referencedInstanceSequenceAttributes.put(a);
            }
            {
                Attribute a = new UniqueIdentifierAttribute(TagFromName.ReferencedSOPInstanceUID);
                String orig_inst_uid = Attribute.getSingleStringValueOrEmptyString(
                        original_attrs_list[instanceIndex], TagFromName.SOPInstanceUID);
                //log.info("" + instanceIndex + " ReferencedSOPInstanceUID:" + orig_inst_uid);
                orig_inst_uids[instanceIndex] = orig_inst_uid;

                //get positions for segmentation frames
                seg_positions[instanceIndex] = Attribute.getDoubleValues(original_attrs_list[instanceIndex],
                        TagFromName.ImagePositionPatient);

                a.addValue(orig_inst_uid);
                referencedInstanceSequenceAttributes.put(a);
            }
            // Need this for multiframe source DICOM
            //         {
            //            Attribute a = new UniqueIdentifierAttribute(TagFromName.ReferencedFrameNumber);
            //            a.addValue(0);
            //            referencedInstanceSequenceAttributes.put(a);
            //         }
            referencedInstanceSequence.addItem(referencedInstanceSequenceAttributes);
        }
        referencedSeriesSequenceAttributes.put(referencedInstanceSequence);
        referencedSeriesSequence.addItem(referencedSeriesSequenceAttributes);

        list.put(referencedSeriesSequence);

        // Add meta information header.
        FileMetaInformation.addFileMetaInformation(list, TransferSyntax.ExplicitVRLittleEndian,
                SourceApplicationEntityTitle);

        /*********************************************************************
         * Extract attributes from the original attributes list.
         *********************************************************************/
        @SuppressWarnings("deprecation")
        CompositeInstanceContext cic = new CompositeInstanceContext(original_attrs);
        cic.removeInstance();
        cic.removeSeries();
        cic.removeEquipment();
        list.putAll(cic.getAttributeList());
        list.remove(TagFromName.ClinicalTrialSiteID);
        list.remove(TagFromName.ClinicalTrialSubjectID);
        list.remove(TagFromName.SliceThickness); // Not useful per Andriy, Sandy,  Clunie

        { // Define dimensions as (stack id, in-stack position, segment number).
            SequenceAttribute seq = new SequenceAttribute(TagFromName.DimensionOrganizationSequence);
            AttributeList a = SequenceAttribute.getAttributeListFromWithinSequenceWithSingleItem(original_attrs,
                    TagFromName.DimensionOrganizationSequence);
            Attribute org_uid = new UniqueIdentifierAttribute(TagFromName.DimensionOrganizationUID);
            if (a == null) {
                a = new AttributeList();
                org_uid.addValue(u.getNewUID());
                a.put(org_uid);
            } else {
                org_uid.addValue(Attribute.getSingleStringValueOrDefault(a, TagFromName.DimensionOrganizationUID,
                        u.getNewUID()));
            }
            seq.addItem(a);
            list.put(seq);

            seq = new SequenceAttribute(TagFromName.DimensionIndexSequence);
            a = SequenceAttribute.getAttributeListFromWithinSequenceWithSingleItem(original_attrs,
                    TagFromName.DimensionIndexSequence);
            if (a == null) {
                a = new AttributeList();
                a.put(org_uid);
                AttributeTagAttribute t = new AttributeTagAttribute(TagFromName.DimensionIndexPointer);
                t.addValue(0x0020, 0x9056); // Stack ID as a dimension
                a.put(t);
                t = new AttributeTagAttribute(TagFromName.FunctionalGroupPointer);
                t.addValue(0x0020, 0x9111); // Frame Content Sequence
                a.put(t);
                LongStringAttribute lo = new LongStringAttribute(TagFromName.DimensionDescriptionLabel);
                lo.addValue("Stack ID");
                a.put(t);
                seq.addItem(a);

                a = new AttributeList();
                a.put(org_uid);
                t = new AttributeTagAttribute(TagFromName.DimensionIndexPointer);
                t.addValue(0x0020, 0x9057); // In-Stack Position Number as a dimension
                a.put(t);
                t = new AttributeTagAttribute(TagFromName.FunctionalGroupPointer);
                t.addValue(0x0020, 0x9111); // Frame Content Sequence
                a.put(t);
                lo = new LongStringAttribute(TagFromName.DimensionDescriptionLabel);
                lo.addValue("In-Stack Position Number");
                a.put(t);
                seq.addItem(a);

                /*
                 * Temporal dimension is rarely used, so it is not supported here. a = new AttributeList(); a.put(org_uid); t =
                 * new AttributeTagAttribute(TagFromName.DimensionIndexPointer); t.addValue(0x0020, 0x9128); // Temporal
                 * Position Index as a dimension a.put(t); t= new AttributeTagAttribute(TagFromName.FunctionalGroupPointer);
                 * t.addValue(0x0020, 0x9111); // Frame Content Sequence a.put(t); lo = new
                 * LongStringAttribute(TagFromName.DimensionDescriptionLabel); lo.addValue("Temporal Position Index"); a.put(t);
                 * seq.addItem(a);
                 */

                a = new AttributeList();
                a.put(org_uid);
                t = new AttributeTagAttribute(TagFromName.DimensionIndexPointer);
                t.addValue(0x0062, 0x000b); // Referenced Segment Number as a dimension
                a.put(t);
                t = new AttributeTagAttribute(TagFromName.FunctionalGroupPointer);
                t.addValue(0x0062, 0x000a); // Segment Identification Sequence
                a.put(t);
                lo = new LongStringAttribute(TagFromName.DimensionDescriptionLabel);
                lo.addValue("Referenced Segment Number");
                a.put(t);
                seq.addItem(a);
            } else {
                seq.addItem(a);
            }
            list.put(seq);
        }

        // Attributes below are not mandatory, so special check needs to be done.
        {
            String temp = Attribute.getSingleStringValueOrEmptyString(original_attrs,
                    TagFromName.PositionReferenceIndicator);
            Attribute a = new LongStringAttribute(TagFromName.PositionReferenceIndicator);
            a.addValue(temp);
            list.put(a);
        }

        // Following attributes are inherited.

        // Generate Shared Functional Groups Sequence
        { // DerivationImageSequence - TODO - Need to verify if this is correct???
            SequenceAttribute derivation_image_seq = new SequenceAttribute(TagFromName.DerivationImageSequence);
            AttributeList reference = new AttributeList();
            {
                AttributeList item = new AttributeList();
                {
                    Attribute a = new ShortStringAttribute(TagFromName.CodeValue);
                    a.addValue("113076");
                    item.put(a);
                }
                {
                    Attribute a = new ShortStringAttribute(TagFromName.CodingSchemeDesignator);
                    a.addValue("DCM");
                    item.put(a);
                }
                {
                    Attribute a = new LongStringAttribute(TagFromName.CodeMeaning);
                    a.addValue("Segmentation");
                    item.put(a);
                }
                SequenceAttribute seq = new SequenceAttribute(TagFromName.DerivationCodeSequence);
                seq.addItem(item);
                reference.put(seq);
            }
            SequenceAttribute source_image_seq = new SequenceAttribute(TagFromName.SourceImageSequence);
            for (int i = 0; i < orig_inst_uids.length; i++) { // SourceImageSequence
                AttributeList image = new AttributeList();
                AttributeList item = new AttributeList();
                {
                    Attribute a = new ShortStringAttribute(TagFromName.CodeValue);
                    a.addValue("121322");
                    item.put(a);
                }
                {
                    Attribute a = new ShortStringAttribute(TagFromName.CodingSchemeDesignator);
                    a.addValue("DCM");
                    item.put(a);
                }
                {
                    Attribute a = new LongStringAttribute(TagFromName.CodeMeaning);
                    a.addValue("Source image for image processing operation");
                    item.put(a);
                }
                SequenceAttribute seq = new SequenceAttribute(TagFromName.PurposeOfReferenceCodeSequence);
                seq.addItem(item);
                image.put(seq);
                {
                    Attribute a = new UniqueIdentifierAttribute(TagFromName.ReferencedSOPClassUID);
                    a.addValue(orig_class_uid);
                    image.put(a);
                }
                {
                    //log.info("SourceImageSequence:" + i + " ReferencedSOPInstanceUID:" + orig_inst_uids[i]);
                    Attribute a = new UniqueIdentifierAttribute(TagFromName.ReferencedSOPInstanceUID);
                    a.addValue(orig_inst_uids[i]);
                    image.put(a);
                }
                // Need this for multiframe source DICOM???
                {
                    Attribute a = new IntegerStringAttribute(TagFromName.ReferencedFrameNumber);
                    a.addValue("1");
                    image.put(a);
                }
                source_image_seq.addItem(image);
            }
            reference.put(source_image_seq);
            derivation_image_seq.addItem(reference);
            shared_functional_groups_item.put(derivation_image_seq);
        }
        { // PlaneOrientationSequence
            SequenceAttribute plane_orientation_seq = new SequenceAttribute(TagFromName.PlaneOrientationSequence);
            AttributeList item = new AttributeList();
            Attribute a = new DecimalStringAttribute(TagFromName.ImageOrientationPatient);
            for (int i = 0; i < patient_orientation.length; i++)
                a.addValue(patient_orientation[i]);
            item.put(a);
            plane_orientation_seq.addItem(item);
            shared_functional_groups_item.put(plane_orientation_seq);
        }
        { // PixelMeasuresSequence
            SequenceAttribute pixel_measures_seq = new SequenceAttribute(TagFromName.PixelMeasuresSequence);
            AttributeList item = new AttributeList();
            Attribute spacing = new DecimalStringAttribute(TagFromName.PixelSpacing);
            spacing.addValue(pixel_spacing[0]);
            spacing.addValue(pixel_spacing[1]);
            Attribute thickness = new DecimalStringAttribute(TagFromName.SliceThickness);
            thickness.addValue(slice_thickness);
            item.put(spacing);
            item.put(thickness);
            pixel_measures_seq.addItem(item);
            shared_functional_groups_item.put(pixel_measures_seq);
        }
    }

    /**
     * Add a segment.
     * 
     * @param description is the user defined string which may be the purpose of segmenting.
     * @param category should be a value defined in future SegmentationPropertyCategories.
     * @param type should be a value defined in future SegmentationPropertyTypes.
     * @throws DicomException
     */
    public void addOneSegment(String description, CodedConcept category, CodedConcept type) throws DicomException {
        // Validate the parameters.
        current_segment++;

        if (description == null) {
            description = "Segment No." + Integer.toString(current_segment);
        }

        // SegmentSequence attribute
        AttributeList list = new AttributeList();
        {
            Attribute a = new UnsignedShortAttribute(TagFromName.SegmentNumber);
            a.addValue(current_segment);
            list.put(a);
        }
        {
            Attribute a = new ShortTextAttribute(TagFromName.SegmentDescription);
            a.addValue(description);
            list.put(a);
        }
        {
            String[] context = parse_context(type);
            Attribute a = new LongStringAttribute(TagFromName.SegmentLabel);
            a.addValue(context[2]);
            list.put(a);
        }
        {
            Attribute a = new CodeStringAttribute(TagFromName.SegmentAlgorithmType);
            a.addValue("SEMIAUTOMATIC");
            list.put(a);
        }
        {
            Attribute a = new LongStringAttribute(TagFromName.SegmentAlgorithmName);
            a.addValue(ManufacturerModelName + SoftwareVersion);
            list.put(a);
        }
        {
            String[] context = parse_context(category);
            AttributeList item = new AttributeList();
            {
                Attribute a = new ShortStringAttribute(TagFromName.CodingSchemeDesignator);
                a.addValue(context[0]);
                item.put(a);
            }
            {
                Attribute a = new ShortStringAttribute(TagFromName.CodeValue);
                a.addValue(context[1]);
                item.put(a);
            }
            {
                Attribute a = new LongStringAttribute(TagFromName.CodeMeaning);
                a.addValue(context[2]);
                item.put(a);
            }
            SequenceAttribute seq = new SequenceAttribute(TagFromName.AnatomicRegionSequence);
            seq.addItem(item);
            list.put(seq);
        }
        {
            String[] context = parse_context(category);
            AttributeList item = new AttributeList();
            {
                Attribute a = new ShortStringAttribute(TagFromName.CodingSchemeDesignator);
                a.addValue(context[0]);
                item.put(a);
            }
            {
                Attribute a = new ShortStringAttribute(TagFromName.CodeValue);
                a.addValue(context[1]);
                item.put(a);
            }
            {
                Attribute a = new LongStringAttribute(TagFromName.CodeMeaning);
                a.addValue(context[2]);
                item.put(a);
            }
            SequenceAttribute seq = new SequenceAttribute(TagFromName.SegmentedPropertyCategoryCodeSequence);
            seq.addItem(item);
            list.put(seq);
        }
        {
            String[] context = parse_context(type);
            AttributeList item = new AttributeList();
            {
                Attribute a = new ShortStringAttribute(TagFromName.CodingSchemeDesignator);
                a.addValue(context[0]);
                item.put(a);
            }
            {
                Attribute a = new ShortStringAttribute(TagFromName.CodeValue);
                a.addValue(context[1]);
                item.put(a);
            }
            {
                Attribute a = new LongStringAttribute(TagFromName.CodeMeaning);
                a.addValue(context[2]);
                item.put(a);
            }
            SequenceAttribute seq = new SequenceAttribute(TagFromName.SegmentedPropertyTypeCodeSequence);
            seq.addItem(item);
            list.put(seq);
        }

        SequenceItem item = new SequenceItem(list);
        segment_sequence.addItem(item);
    }

    /**
     * Add a sequence of frames to current segment.
     * 
     * @param frames contains a width*height*frame_num sequence;
     * @param type is either "PROBABILITY" or "OCCUPANCY" for fractional type, or "BINARY"/null for binary type.
     * @param stack_id is the index of current stack.
     * @param positions is the position of each frame.
     * @throws DicomException
     * @throws IOException
     */
    public void addAllFrames(byte[] frames, int frame_num, int image_width, int image_height, String type,
            short stack_id, double[][] positions) throws DicomException, IOException {
        if (frames == null)
            throw (new DicomException("There is no pixel data!"));

        if (frame_num <= 0 || image_width <= 0 || image_height <= 0)
            throw (new DicomException("Image size is not correct!"));
        //      log.info("pixel data length:" + frames.length + " frames:" + frame_num + " width:" + image_width + " height:" + image_height);
        //      log.info("(image_width * image_height * frame_num - 1) / 8 + 1): " + ((image_width * image_height * frame_num - 1) / 8 + 1));
        //      log.info("image_width * image_height * frame_num :" + image_width * image_height * frame_num);
        if (type == null)
            type = "BINARY";
        else if (!type.equalsIgnoreCase("PROBABILITY") && !type.equalsIgnoreCase("OCCUPANCY"))
            type = "BINARY";

        if (!type.equalsIgnoreCase("BINARY")) { // 8-bit/pixel
            System.err.println("type:" + type + " frames.length " + frames.length + " width:" + image_width
                    + " height:" + image_height + " frames:" + frame_num);
            System.err.println("image_width * image_height * frame_num " + image_width * image_height * frame_num);
            log.warning("type:" + type + " frames.length " + frames.length + " width:" + image_width + " height:"
                    + image_height + " frames:" + frame_num);
            log.warning("image_width * image_height * frame_num " + image_width * image_height * frame_num);
            if (frames.length != image_width * image_height * frame_num) {
                throw (new DicomException("Image size or type is not correct!"));
            }
        } else if (frames.length != (image_width * image_height * frame_num - 1) / 8 + 1) {
            System.err.println("type:" + type + " frames.length " + frames.length + " width:" + image_width
                    + " height:" + image_height + " frames:" + frame_num);
            System.err.println("image_width * image_height * frame_num - 1) / 8 + 1 :"
                    + ((image_width * image_height * frame_num - 1) / 8 + 1));
            log.warning("type:" + type + " frames.length " + frames.length + " width:" + image_width + " height:"
                    + image_height + " frames:" + frame_num);
            log.warning("image_width * image_height * frame_num - 1) / 8 + 1 :"
                    + ((image_width * image_height * frame_num - 1) / 8 + 1));
            throw (new DicomException("Image size or type is not correct!"));
        }

        if (frame_counter == 0) { // Record the segmentation type at the first time.
            if (!type.equalsIgnoreCase("BINARY")) { // 8-bit/pixel
                {
                    Attribute a = new CodeStringAttribute(TagFromName.SegmentationType);
                    a.addValue("FRACTIONAL");
                    list.put(a);
                }
                {
                    Attribute a = new UnsignedShortAttribute(TagFromName.BitsAllocated);
                    a.addValue(8);
                    list.put(a);
                }
                {
                    Attribute a = new UnsignedShortAttribute(TagFromName.MaximumFractionalValue);
                    a.addValue(0xFF);
                    list.put(a);
                }
                {
                    Attribute a = new UnsignedShortAttribute(TagFromName.BitsStored);
                    a.addValue(8);
                    list.put(a);
                }
                {
                    Attribute a = new UnsignedShortAttribute(TagFromName.HighBit);
                    a.addValue(7);
                    list.put(a);
                }
                {
                    Attribute a = new CodeStringAttribute(TagFromName.SegmentationFractionalType);
                    a.addValue(type.toUpperCase());
                    list.put(a);
                }
            } else { // 1-bit/pixel
                {
                    Attribute a = new CodeStringAttribute(TagFromName.SegmentationType);
                    a.addValue("BINARY");
                    list.put(a);
                }
                {
                    Attribute a = new UnsignedShortAttribute(TagFromName.BitsAllocated);
                    a.addValue(1);
                    list.put(a);
                }
                {
                    Attribute a = new UnsignedShortAttribute(TagFromName.BitsStored);
                    a.addValue(1);
                    list.put(a);
                }
                {
                    Attribute a = new UnsignedShortAttribute(TagFromName.HighBit);
                    a.addValue(0);
                    list.put(a);
                }
            }
        } else { // All the segmentation objects MUST have the same data type.
            String s_type = Attribute.getSingleStringValueOrDefault(list, TagFromName.SegmentationFractionalType,
                    "BINARY");
            if (!s_type.equalsIgnoreCase(type))
                throw (new DicomException("All the segmentation objects MUST have same data type!"));
        }

        if (frame_counter == 0) { // Record the rows and columns at the first time.
            Attribute a = new UnsignedShortAttribute(TagFromName.Rows);
            a.addValue(image_height);
            list.put(a);
            geometry_list.put(a);
            a = new UnsignedShortAttribute(TagFromName.Columns);
            a.addValue(image_width);
            list.put(a);
            geometry_list.put(a);
        } else { // Check whether the new frames have the same resolution.
            short height = (short) Attribute.getSingleIntegerValueOrDefault(list, TagFromName.Rows, 1);
            short width = (short) Attribute.getSingleIntegerValueOrDefault(list, TagFromName.Columns, 1);
            if (height != image_height || width != image_width)
                throw (new DicomException("Image size MUST be same!"));
        }

        // Add new data to the end of the existing data.
        if (use_temp_file == false) { // Save pixel data to memory.
            try {
                byte[] current = pixel_data.getByteValues();
                if (current != null) {
                    byte[] temp = new byte[current.length + frames.length];
                    System.arraycopy(current, 0, temp, 0, current.length);
                    System.arraycopy(frames, 0, temp, current.length, frames.length);
                    pixel_data.setValues(temp);
                } else {
                    byte[] temp = frames.clone();
                    pixel_data.setValues(temp);
                }
            } catch (java.lang.OutOfMemoryError e) {
                use_temp_file = true;
                byte[] data = pixel_data.getByteValues();
                if (data != null)
                    save_pixeldata_to_temp_file(data, temp_file, false);
                if (frames != null)
                    save_pixeldata_to_temp_file(frames, temp_file, true);
            }
        } else { // Save pixel data to a temporary file.
            if (frames != null)
                save_pixeldata_to_temp_file(frames, temp_file, true);
        }

        // Sort the frames according to their positions.
        int[] in_stack_position = sort_frames_by_position(geometry_list, positions, frame_num);
        log.info("frame num " + frame_num);
        // Generate Per-frame attributes. Refer to Table A.51-2 SEGMENTATION FUNCTIONAL GROUP MACROS
        for (int k = 0; k < frame_num; k++) {
            AttributeList item2 = new AttributeList();

            {
                AttributeList item = new AttributeList();
                SequenceAttribute seq = new SequenceAttribute(TagFromName.SegmentIdentificationSequence);
                Attribute a = new UnsignedShortAttribute(TagFromName.ReferencedSegmentNumber);
                a.addValue(current_segment);
                item.put(a);
                seq.addItem(item);
                item2.put(seq);
            }

            { // Assign FrameContentSequence.
                SequenceAttribute seq = new SequenceAttribute(TagFromName.FrameContentSequence);
                AttributeList item = new AttributeList();
                Attribute a = new ShortStringAttribute(TagFromName.StackID);
                stack_id = stack_id > 0 ? stack_id : 1;
                a.addValue(Integer.toString(stack_id));
                item.put(a);
                a = new UnsignedLongAttribute(TagFromName.InStackPositionNumber);
                a.addValue(in_stack_position[k]);
                item.put(a);
                a = new UnsignedLongAttribute(TagFromName.DimensionIndexValues);
                a.addValue(stack_id);
                a.addValue(in_stack_position[k]);
                a.addValue(current_segment);
                item.put(a);
                seq.addItem(item);
                item2.put(seq);
            }

            { // Assign PlanePositionSequence
                SequenceAttribute seq = new SequenceAttribute(TagFromName.PlanePositionSequence);
                AttributeList item = new AttributeList();
                Attribute a = new DecimalStringAttribute(TagFromName.ImagePositionPatient);
                if (positions != null) {
                    //for slicer get seg positions
                    a.addValue(seg_positions[k][0]);
                    a.addValue(seg_positions[k][1]);
                    a.addValue(seg_positions[k][2]);
                    //               a.addValue(positions[k][0]);
                    //               a.addValue(positions[k][1]);
                    //               a.addValue(positions[k][2]);
                } else {
                    a.addValue(0);
                    a.addValue(0);
                    a.addValue(0);
                }
                item.put(a);
                seq.addItem(item);
                item2.put(seq);

            }
            //for slicer add derivation to per frame
            { // DerivationImageSequence - TODO - Need to verify if this is correct???
                SequenceAttribute derivation_image_seq = new SequenceAttribute(TagFromName.DerivationImageSequence);
                AttributeList reference = new AttributeList();
                {
                    AttributeList item = new AttributeList();
                    {
                        Attribute a = new ShortStringAttribute(TagFromName.CodeValue);
                        a.addValue("113076");
                        item.put(a);
                    }
                    {
                        Attribute a = new ShortStringAttribute(TagFromName.CodingSchemeDesignator);
                        a.addValue("DCM");
                        item.put(a);
                    }
                    {
                        Attribute a = new LongStringAttribute(TagFromName.CodeMeaning);
                        a.addValue("Segmentation");
                        item.put(a);
                    }
                    SequenceAttribute seq = new SequenceAttribute(TagFromName.DerivationCodeSequence);
                    seq.addItem(item);
                    reference.put(seq);
                }
                SequenceAttribute source_image_seq = new SequenceAttribute(TagFromName.SourceImageSequence);
                { // SourceImageSequence
                    AttributeList image = new AttributeList();
                    AttributeList item = new AttributeList();
                    {
                        Attribute a = new ShortStringAttribute(TagFromName.CodeValue);
                        a.addValue("121322");
                        item.put(a);
                    }
                    {
                        Attribute a = new ShortStringAttribute(TagFromName.CodingSchemeDesignator);
                        a.addValue("DCM");
                        item.put(a);
                    }
                    {
                        Attribute a = new LongStringAttribute(TagFromName.CodeMeaning);
                        a.addValue("Source image for image processing operation");
                        item.put(a);
                    }
                    SequenceAttribute seq = new SequenceAttribute(TagFromName.PurposeOfReferenceCodeSequence);
                    seq.addItem(item);
                    image.put(seq);
                    {
                        Attribute a = new UniqueIdentifierAttribute(TagFromName.ReferencedSOPClassUID);
                        a.addValue(orig_class_uid);
                        image.put(a);
                    }
                    {
                        //log.info("SourceImageSequence:" + i + " ReferencedSOPInstanceUID:" + orig_inst_uids[i]);
                        Attribute a = new UniqueIdentifierAttribute(TagFromName.ReferencedSOPInstanceUID);

                        a.addValue(orig_inst_uids[k]);
                        image.put(a);
                    }
                    // Need this for multiframe source DICOM???
                    {
                        Attribute a = new IntegerStringAttribute(TagFromName.ReferencedFrameNumber);
                        a.addValue("1");
                        image.put(a);
                    }
                    source_image_seq.addItem(image);
                }
                reference.put(source_image_seq);
                derivation_image_seq.addItem(reference);
                item2.put(derivation_image_seq);
            }

            per_frame_functional_groups_sequence.addItem(item2);
        }

        // Update the frame counter (NumberOfFrames).
        frame_counter += frame_num;
        {
            Attribute a = new IntegerStringAttribute(TagFromName.NumberOfFrames);
            a.addValue(frame_counter);
            list.put(a);
        }
    }

    /**
     * Finally save the information to a DICOM file.
     * 
     * @param Name of the DICOM file.
     * @throws DicomException
     * @throws IOException
     */
    public void saveDicomFile(String file_name) throws IOException, DicomException {
        // Add object Segment Sequence.
        list.put(TagFromName.SegmentSequence, segment_sequence);

        // Combine attributes that can be shared.
        combine_shared_attributes(shared_functional_groups_item, per_frame_functional_groups_sequence);
        // Add object Shared Functional Groups Sequence.
        SequenceAttribute shared_functional_groups_sequence = new SequenceAttribute(
                TagFromName.SharedFunctionalGroupsSequence);
        shared_functional_groups_sequence.addItem(shared_functional_groups_item);
        list.put(TagFromName.SharedFunctionalGroupsSequence, shared_functional_groups_sequence);
        // Add object Per-frame Functional Groups Sequence.
        list.put(TagFromName.PerFrameFunctionalGroupsSequence, per_frame_functional_groups_sequence);

        if (use_temp_file) {
            list.write(file_name, TransferSyntax.ExplicitVRLittleEndian, true, true);
            append_pixeldata_attribute(temp_file, file_name);
        } else {
            // Add object Pixel Data.
            list.put(pixel_data);
            // Save the whole list.
            list.write(file_name, TransferSyntax.ExplicitVRLittleEndian, true, true);
        }
    }

    /**
     * @param Output strings in scheme, value, meaning order.
     */
    private String[] parse_context(CodedConcept prop) {
        String[] val = { "Unknown Scheme", "Unknown Value", "Unknown Meaning" };

        try {
            val[0] = prop.getCodingSchemeDesignator();
            val[1] = prop.getCodeValue();
            val[2] = prop.getCodeMeaning();
        } catch (Exception e) {
            System.err.println("The property is not a valid CodedConcept object!");
            val[0] = "Unknown Scheme";
            val[1] = "Unknown Value";
            val[2] = "Unknown Meaning";
        }

        return val;
    }

    /**
     * @param pixels contain the pixel data.
     * @param pixel_file is the name of the temporary file.
     * @param append is false when the new file is created and append is true when new data is appended to the existing
     *          file.
     * @throws IOException
     */
    private void save_pixeldata_to_temp_file(byte[] pixels, String pixel_file, boolean append) throws IOException {
        OutputStream outputStream = null;

        try {
            outputStream = new FileOutputStream(pixel_file, append);
            outputStream.write(pixels);
        } finally {
            IOUtils.closeQuietly(outputStream);
        }
    }

    /**
     * This function calls PixelMed methods to generate PixelData attribute and append it to the output.
     * 
     * @param pixel_file: This file stores raw pixel data.
     * @param output_file: This file is the final DICOM segmentation objects output.
     * @throws FileNotFoundException
     * @throws IOException
     * @throws DicomException
     */
    private void append_pixeldata_attribute(String pixel_file_name, String output_file)
            throws FileNotFoundException, IOException, DicomException {
        String tmp_tag_file_name = "TAG_" + pixel_file_name; // File with tag.

        { // Copy raw pixel data to a tagged file.
            File pixelFile = new File(pixel_file_name);
            OutputStream tagOutputStream = null;
            DicomOutputStream dicomOutputStream = null;
            DicomInputStream dicomInputStream = null;

            try {
                tagOutputStream = new FileOutputStream(tmp_tag_file_name);
                dicomOutputStream = new DicomOutputStream(tagOutputStream, TransferSyntax.ExplicitVRLittleEndian,
                        TransferSyntax.ExplicitVRLittleEndian);
                dicomInputStream = new DicomInputStream(pixelFile, TransferSyntax.ExplicitVRLittleEndian, false);
                OtherByteAttributeOnDisk data = new OtherByteAttributeOnDisk(TagFromName.PixelData,
                        pixelFile.length(), dicomInputStream, 0);
                data.write(dicomOutputStream);
            } finally {
                IOUtils.closeQuietly(dicomOutputStream);
                IOUtils.closeQuietly(tagOutputStream);
                IOUtils.closeQuietly(dicomInputStream);
                pixelFile.delete(); // Delete the temporary raw data file.
            }
        }

        { // Append the tagged file to the output.
            File tagFile = new File(tmp_tag_file_name);
            DataInputStream dicomInputStream = null;
            FileOutputStream dicomOutputStream = null;
            try {
                dicomInputStream = new DataInputStream((new FileInputStream(tagFile)));
                dicomOutputStream = new FileOutputStream(output_file, true);
                // Skip the 132-byte DICOM header.
                CopyStream.skipInsistently(dicomInputStream, 132);
                CopyStream.copy(dicomInputStream, dicomOutputStream);
            } finally {
                IOUtils.closeQuietly(dicomInputStream);
                IOUtils.closeQuietly(dicomOutputStream);
                tagFile.delete(); // Delete the temporary file.
            }
        }
    }

    /**
     * Moved common attributes from per-frame functional group to shared functional group.
     * 
     * @param shared_group contains the attributes in SharedFunctionalGroup.
     * @param per_frame_sequence is the attribute sequence of PerFrameFunctionalGroup.
     * @throws DicomException
     */
    private void combine_shared_attributes(AttributeList shared_group, SequenceAttribute per_frame_sequence)
            throws DicomException {
        // Check all the items in per_frame_sequence and move the common attributes to shared_group.
        //plane position sequence removed from check for slicer
        AttributeTag[] checked_attrs = new AttributeTag[] {
                TagFromName.SegmentIdentificationSequence/*,
                                                         TagFromName.PlanePositionSequence*/
                /* , TagFromName.StackID, TagFromName.PlaneOrientationSequence, TagFromName.SliceThickness, TagFromName.PixelSpacing */ };
        Hashtable<AttributeTag, SequenceAttribute> common_attrs = get_common_attributes(checked_attrs,
                per_frame_sequence);
        add_attributes_to_shared_group(common_attrs, shared_group);
        remove_attributes_from_sequence(common_attrs.keySet(), per_frame_sequence);
    }

    /**
     * Test if the attribute is common to all frames.
     * 
     * @param tags provides the TagName of the attributes to be checked.
     * @param sequence is the attribute sequence of all frames.
     * @return Return (key, value) of common attributes.
     * @throws DicomException
     */
    private Hashtable<AttributeTag, SequenceAttribute> get_common_attributes(AttributeTag[] tags,
            SequenceAttribute sequence) throws DicomException {
        Hashtable<AttributeTag, SequenceAttribute> attrs = new Hashtable<AttributeTag, SequenceAttribute>();
        for (AttributeTag tag : tags) {
            attrs.put(tag, new SequenceAttribute(tag));
        }

        @SuppressWarnings("unchecked")
        Iterator<SequenceItem> it = sequence.iterator();
        boolean initial = true;
        while (it.hasNext()) {
            AttributeList l = it.next().getAttributeList();
            if (initial) {
                // Initialize the attributes.
                initial = false;
                Enumeration<AttributeTag> i = attrs.keys();
                while (i.hasMoreElements()) {
                    AttributeTag key = i.nextElement();
                    SequenceAttribute seq = null;
                    // Since some keys are not the top-level attributes, get their parent sequence attributes instead.
                    if (key.equals(TagFromName.StackID)) {
                        seq = (SequenceAttribute) l.get(TagFromName.FrameContentSequence);
                    } else if (key.equals(TagFromName.SliceThickness) || key.equals(TagFromName.PixelSpacing)) {
                        seq = (SequenceAttribute) l.get(TagFromName.PixelMeasuresSequence);
                    } else {
                        seq = (SequenceAttribute) l.get(key);
                    }

                    if (seq == null)
                        seq = new SequenceAttribute(key);
                    attrs.put(key, seq);
                }
            } else {
                // Check if the values are equal.
                Enumeration<AttributeTag> i = attrs.keys();
                while (i.hasMoreElements()) {
                    AttributeTag key = i.nextElement();
                    SequenceAttribute seq = null;
                    // Since some keys are not the top-level attributes, get their parent sequence attributes instead.
                    if (key.equals(TagFromName.StackID))
                        seq = (SequenceAttribute) l.get(TagFromName.FrameContentSequence);
                    else if (key.equals(TagFromName.SliceThickness) || key.equals(TagFromName.PixelSpacing))
                        seq = (SequenceAttribute) l.get(TagFromName.PixelMeasuresSequence);
                    else
                        seq = (SequenceAttribute) l.get(key);
                    if (compare_sequence_attribute(key, attrs.get(key), seq) != true)
                        attrs.remove(key);
                }
            }
        }

        return attrs;
    }

    private boolean compare_sequence_attribute(AttributeTag tag, SequenceAttribute s1, SequenceAttribute s2)
            throws DicomException {
        if (s1 == null || s2 == null)
            return false;

        boolean equal = false;

        if (tag.equals(TagFromName.SegmentIdentificationSequence)) {
            Attribute a1 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s1,
                    TagFromName.ReferencedSegmentNumber);
            Attribute a2 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s2,
                    TagFromName.ReferencedSegmentNumber);
            short[] val1 = a1.getShortValues();
            short[] val2 = a2.getShortValues();
            for (int i = 0; i < val1.length; i++) {
                equal = val1[i] == val2[i];
                if (!equal)
                    break;
            }
        } else if (tag.equals(TagFromName.StackID)) {
            Attribute a1 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s1,
                    TagFromName.StackID);
            Attribute a2 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s2,
                    TagFromName.StackID);
            short[] val1 = a1.getShortValues();
            short[] val2 = a2.getShortValues();
            for (int i = 0; i < val1.length; i++) {
                equal = val1[i] == val2[i];
                if (!equal)
                    break;
            }
        } else if (tag.equals(TagFromName.PlanePositionSequence)) {
            Attribute a1 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s1,
                    TagFromName.ImagePositionPatient);
            Attribute a2 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s2,
                    TagFromName.ImagePositionPatient);
            double[] val1 = a1.getDoubleValues();
            double[] val2 = a2.getDoubleValues();
            for (int i = 0; i < val1.length; i++) {
                equal = val1[i] == val2[i];
                if (!equal)
                    break;
            }
        } else if (tag.equals(TagFromName.PlaneOrientationSequence)) {
            Attribute a1 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s1,
                    TagFromName.ImageOrientationPatient);
            Attribute a2 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s2,
                    TagFromName.ImageOrientationPatient);
            short[] val1 = a1.getShortValues();
            short[] val2 = a2.getShortValues();
            for (int i = 0; i < val1.length; i++) {
                equal = val1[i] == val2[i];
                if (!equal)
                    break;
            }
        } else if (tag.equals(TagFromName.SliceThickness)) {
            Attribute a1 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s1,
                    TagFromName.SliceThickness);
            Attribute a2 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s2,
                    TagFromName.SliceThickness);
            double[] val1 = a1.getDoubleValues();
            double[] val2 = a2.getDoubleValues();
            for (int i = 0; i < val1.length; i++) {
                equal = val1[i] == val2[i];
                if (!equal)
                    break;
            }
        } else if (tag.equals(TagFromName.PixelSpacing)) {
            Attribute a1 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s1,
                    TagFromName.PixelSpacing);
            Attribute a2 = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(s2,
                    TagFromName.PixelSpacing);
            double[] val1 = a1.getDoubleValues();
            double[] val2 = a2.getDoubleValues();
            for (int i = 0; i < val1.length; i++) {
                equal = val1[i] == val2[i];
                if (!equal)
                    break;
            }
        }

        return equal;
    }

    private void remove_attributes_from_sequence(Set<AttributeTag> tags, SequenceAttribute sequence) {
        @SuppressWarnings("unchecked")
        Iterator<SequenceItem> it = sequence.iterator();
        while (it.hasNext()) {
            AttributeList l = it.next().getAttributeList();
            Iterator<AttributeTag> i = tags.iterator();
            while (i.hasNext()) {
                AttributeTag tag = i.next();
                if (tag.equals(TagFromName.StackID)) {
                    AttributeList seq = SequenceAttribute.getAttributeListFromWithinSequenceWithSingleItem(l,
                            TagFromName.FrameContentSequence);
                    seq.remove(tag);
                } else if (tag.equals(TagFromName.SliceThickness) || tag.equals(TagFromName.PixelSpacing)) {
                    AttributeList seq = SequenceAttribute.getAttributeListFromWithinSequenceWithSingleItem(l,
                            TagFromName.PixelMeasuresSequence);
                    seq.remove(tag);
                    if (seq.isEmpty())
                        l.remove(TagFromName.PixelMeasuresSequence); // Delete this attribute if it is empty.
                } else
                    l.remove(tag);
            }
        }
    }

    private void add_attributes_to_shared_group(Hashtable<AttributeTag, SequenceAttribute> attrs,
            AttributeList shared) {
        /*
         * {TagFromName.SegmentIdentificationSequence, TagFromName.StackID, TagFromName.PlanePositionSequence,
         * TagFromName.PlaneOrientationSequence, TagFromName.SliceThickness, TagFromName.PixelSpacing}
         */
        Enumeration<AttributeTag> i = attrs.keys();
        while (i.hasMoreElements()) {
            AttributeTag tag = i.nextElement();
            if (tag.equals(TagFromName.StackID)) {
                Attribute a = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(attrs.get(tag),
                        tag);
                if (a == null)
                    continue;
                SequenceAttribute seq = new SequenceAttribute(TagFromName.FrameContentSequence);
                AttributeList item = new AttributeList();
                item.put(a);
                seq.addItem(item);
                shared.put(seq);
            } else if (tag.equals(TagFromName.SliceThickness) || tag.equals(TagFromName.PixelSpacing)) {
                Attribute a = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(attrs.get(tag),
                        tag);
                if (a == null)
                    continue;
                SequenceAttribute pixel_measures_seq = (SequenceAttribute) shared
                        .get(TagFromName.PixelMeasuresSequence);
                if (pixel_measures_seq != null) { // If PixelMeasuresSequence is already there, add one attribute to it instead
                                                  // of rewriting it.
                    AttributeList l = SequenceAttribute
                            .getAttributeListFromWithinSequenceWithSingleItem(pixel_measures_seq);
                    l.put(a);
                } else {
                    SequenceAttribute seq = new SequenceAttribute(TagFromName.PixelMeasuresSequence);
                    AttributeList item = new AttributeList();
                    item.put(a);
                    seq.addItem(item);
                    shared.put(seq);
                }
            } else
                shared.put(attrs.get(tag));
        }
    }

    /**
     * Sort the frames by their position.
     * 
     * @param positions are the geometry attribute of the input frame.
     * @param frame_num is the number of frames.
     * @return index ordered by frame's position.
     * @throws DicomException
     */
    private int[] sort_frames_by_position(double[][] positions, int frame_num) throws DicomException {
        int[] index = new int[frame_num];

        for (int i = 0; i < frame_num; i++) {
            index[i] = i;
        }

        return index;
    }

    private int[] sort_frames_by_position(AttributeList geometry, double[][] positions, int frame_num)
            throws DicomException {
        int[] index = new int[frame_num];
        double[] distances = new double[frame_num];

        class DoubleArrayComparator implements Comparator<List<Double>> {
            public int compare(List<Double> o1, List<Double> o2) {
                int val;
                if (o1.get(0) > o2.get(0))
                    val = 1;
                else if (o1.get(0) < o2.get(0))
                    val = -1;
                else {
                    if (o1.get(1) > o2.get(1))
                        val = 1;
                    else if (o1.get(1) < o2.get(1))
                        val = -1;
                    else
                        val = 0;
                }
                return val;
            }
        }

        SortedMap<ArrayList<Double>, Integer> sorting = new TreeMap<ArrayList<Double>, Integer>(
                new DoubleArrayComparator());
        GeometryOfSlice slice_geometry;
        AttributeList geometry_list = (AttributeList) geometry.clone();
        ArrayList<Double> key;

        for (int i = 0; i < frame_num; i++) {
            Attribute a = new DecimalStringAttribute(TagFromName.ImagePositionPatient);
            if (positions != null) {
                a.addValue(positions[i][0]);
                a.addValue(positions[i][1]);
                a.addValue(positions[i][2]);
            } else {
                a.addValue(0);
                a.addValue(0);
                a.addValue(0);
            }
            geometry_list.put(a);
            slice_geometry = new GeometryOfSliceFromAttributeList(geometry_list);
            distances[i] = slice_geometry.getDistanceAlongNormalFromOrigin();

            key = new ArrayList<Double>(2);
            key.add(0, distances[i]);
            key.add(1, (double) i);
            sorting.put(key, 0);
        }

        // k will be the index of sorted keys.
        int k = 1;
        for (Map.Entry<ArrayList<Double>, Integer> entry : sorting.entrySet()) {
            sorting.put(entry.getKey(), k++);
        }

        for (int i = 0; i < frame_num; i++) {
            key = new ArrayList<Double>(2);
            key.add(0, distances[i]);
            key.add(1, (double) i);
            index[i] = sorting.get(key);
        }

        return index;
    }

    /**
     * This demo gets segmentation maps from map_file, then inserts the maps twice as two segments.
     * 
     * @param args: java SegmentationObjectsFileWriter dicom_file map_file output_file mode mode is "BINARY" or
     *          "FRACTIONAL".
     */
    public static void main(String[] args) {
        String input_file = args[0];// "./DicomFiles/CT0010";//
        String map_file = args[1];// "./TEMP/CT0010.mapbin";//
        String output_file = args[2];// "./TEMP/CT0010.sobin";//"segmentation_test_out.dcm";
        // String mode = args[3]; //"BINARY";

        byte[] pixels = null;
        SegmentationObjectsFileWriter obj = null;
        short image_width = 0, image_height = 0, image_frames = 0;

        // Read pixel array from the map_file.
        File file = new File(map_file);
        DataInputStream dis = null;
        pixels = new byte[(int) file.length()];
        try {
            dis = new DataInputStream((new FileInputStream(file)));
            dis.readFully(pixels);
            dis.close();
        } catch (Exception e1) {
            e1.printStackTrace();
        } finally {
            IOUtils.closeQuietly(dis);
        }

        try {
            DicomInputStream i_stream = new DicomInputStream(new FileInputStream(input_file));
            AttributeList list = new AttributeList();
            list.read(i_stream);

            { // Get sequence format.
                image_width = (short) Attribute.getSingleIntegerValueOrDefault(list, TagFromName.Columns, 1);
                image_height = (short) Attribute.getSingleIntegerValueOrDefault(list, TagFromName.Rows, 1);
                image_frames = (short) Attribute.getSingleIntegerValueOrDefault(list, TagFromName.NumberOfFrames,
                        1);
            }

            short[] orientation = new short[] { 1, 0, 0, 0, 0, 1 };
            double[] spacing = new double[] { 0.65, 0.8 };
            double thickness = 0.5;
            AttributeList[] lists = new AttributeList[1];
            lists[0] = list;
            obj = new SegmentationObjectsFileWriter(lists, orientation, spacing, thickness);

            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 */);
            double[][] positions = new double[image_frames][3];
            /*
             * void AddAllFrames(byte [] frames, int frame_num, int image_width, int image_height, String type, double [][]
             * positions, short [] patient_orientation, double slice_thickness, double [] pixel_spacing, short stack_id)
             */
            // Segment 1
            obj.addOneSegment("Segment No.1 is for ...", category, type);
            obj.addAllFrames(pixels, image_frames, image_width, image_height, "binary", (short) 0, positions);
            short stack_id = 2;
            positions[0][0] = 0.2;
            obj.addAllFrames(pixels, image_frames, image_width, image_height, "binary", stack_id, positions);
            // Segment 2
            // obj.AddOneSegment(null, null, null);
            obj.addOneSegment("Segment No.2 is for ...", category, type);
            obj.addAllFrames(pixels, image_frames, image_width, image_height, "binary", (short) 1, positions);
            obj.addAllFrames(pixels, image_frames, image_width, image_height, "binary", stack_id, positions);

            obj.saveDicomFile(output_file);

        } catch (Exception e) {
            System.err.println(e);
            e.printStackTrace(System.err);
            System.exit(0);
        }
    }

    public String getSeriesUID() {
        return seriesUID;
    }

    public void setSeriesUID(String seriesUID) {
        this.seriesUID = seriesUID;
    }

    public String getImageUID() {
        return imageUID;
    }

    public void setImageUID(String imageUID) {
        this.imageUID = imageUID;
    }

}