edu.stanford.epad.epadws.handlers.dicom.DSOUtil.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.epad.epadws.handlers.dicom.DSOUtil.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.epadws.handlers.dicom;

import ij.ImagePlus;
import ij.ImageStack;
import ij.io.Opener;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferDouble;
import java.awt.image.DataBufferFloat;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.util.ajax.JSON;

import com.google.gson.Gson;
import com.pixelmed.dicom.Attribute;
import com.pixelmed.dicom.AttributeList;
import com.pixelmed.dicom.AttributeTag;
import com.pixelmed.dicom.DicomException;
import com.pixelmed.dicom.DicomInputStream;
import com.pixelmed.dicom.SequenceAttribute;
import com.pixelmed.dicom.TagFromName;
import com.pixelmed.dicom.UnsignedShortAttribute;
import com.pixelmed.display.ConsumerFormatImageMaker;
import com.pixelmed.display.SourceImage;

import edu.stanford.epad.common.dicom.DCM4CHEEImageDescription;
import edu.stanford.epad.common.dicom.DCM4CHEEUtil;
import edu.stanford.epad.common.dicom.DicomFileUtil;
import edu.stanford.epad.common.dicom.DicomSegmentationObject;
import edu.stanford.epad.common.pixelmed.PixelMedUtils;
import edu.stanford.epad.common.pixelmed.TIFFMasksToDSOConverter;
import edu.stanford.epad.common.util.EPADConfig;
import edu.stanford.epad.common.util.EPADFileUtils;
import edu.stanford.epad.common.util.EPADLogger;
import edu.stanford.epad.common.util.RunSystemCommand;
import edu.stanford.epad.dtos.DSOEditRequest;
import edu.stanford.epad.dtos.DSOEditResult;
import edu.stanford.epad.dtos.EPADAIM;
import edu.stanford.epad.dtos.EPADDSOFrame;
import edu.stanford.epad.dtos.EPADFrame;
import edu.stanford.epad.dtos.EPADFrameList;
import edu.stanford.epad.dtos.PNGFileProcessingStatus;
import edu.stanford.epad.dtos.SeriesProcessingStatus;
import edu.stanford.epad.dtos.TaskStatus;
import edu.stanford.epad.dtos.internal.DICOMElement;
import edu.stanford.epad.dtos.internal.DICOMElementList;
import edu.stanford.epad.epadws.aim.AIMQueries;
import edu.stanford.epad.epadws.aim.AIMSearchType;
import edu.stanford.epad.epadws.aim.AIMUtil;
import edu.stanford.epad.epadws.dcm4chee.Dcm4CheeDatabase;
import edu.stanford.epad.epadws.dcm4chee.Dcm4CheeDatabaseOperations;
import edu.stanford.epad.epadws.dcm4chee.Dcm4CheeDatabaseUtils;
import edu.stanford.epad.epadws.epaddb.EpadDatabase;
import edu.stanford.epad.epadws.epaddb.EpadDatabaseOperations;
import edu.stanford.epad.epadws.handlers.HandlerUtil;
import edu.stanford.epad.epadws.handlers.core.ImageReference;
import edu.stanford.epad.epadws.models.EpadFile;
import edu.stanford.epad.epadws.models.FileType;
import edu.stanford.epad.epadws.queries.Dcm4CheeQueries;
import edu.stanford.epad.epadws.queries.DefaultEpadOperations;
import edu.stanford.epad.epadws.queries.EpadOperations;
import edu.stanford.epad.epadws.service.DefaultEpadProjectOperations;
import edu.stanford.epad.epadws.service.EpadProjectOperations;
import edu.stanford.hakan.aim4api.compability.aimv3.ImageAnnotation;

/**
 * Code for handling DICOM Segmentation Objects
 * 
 * 
 * @author martin
 */
public class DSOUtil {
    private static final EPADLogger log = EPADLogger.getInstance();

    private static final String baseDicomDirectory = EPADConfig.getEPADWebServerPNGDir();

    private final static Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
            .getDcm4CheeDatabaseOperations();

    /**
     * Take an existing DSO and generate a new one (with new UIDs) with substituted masked frames.
     */
    private static DSOEditResult createEditedDSO(DSOEditRequest dsoEditRequest, List<File> editFramesPNGMaskFiles,
            String referencedSeriesUID) {
        try {
            //try fix for empty study id 
            if (dsoEditRequest.studyUID == null || dsoEditRequest.studyUID.equals("")) {
                dsoEditRequest.studyUID = dcm4CheeDatabaseOperations.getStudyUIDForSeries(referencedSeriesUID);
            }
            List<DCM4CHEEImageDescription> imageDescriptions = dcm4CheeDatabaseOperations
                    .getImageDescriptions(dsoEditRequest.studyUID, referencedSeriesUID);
            int width = 0;
            int height = 0;
            List<String> dicomFilePaths = new ArrayList<String>();
            for (DCM4CHEEImageDescription imageDescription : imageDescriptions) {
                try {
                    File temporaryDICOMFile = File.createTempFile(imageDescription.imageUID, ".dcm");
                    //log.info("Downloading source DICOM file for image " + imageDescription.imageUID);
                    DCM4CHEEUtil.downloadDICOMFileFromWADO(dsoEditRequest.studyUID, dsoEditRequest.seriesUID,
                            imageDescription.imageUID, temporaryDICOMFile);
                    if (width == 0) {
                        DicomInputStream dicomInputStream = null;
                        try {
                            dicomInputStream = new DicomInputStream(new FileInputStream(temporaryDICOMFile));
                            AttributeList localDICOMAttributes = new AttributeList();
                            localDICOMAttributes.read(dicomInputStream);
                            width = (short) Attribute.getSingleIntegerValueOrDefault(localDICOMAttributes,
                                    TagFromName.Columns, 1);
                            height = (short) Attribute.getSingleIntegerValueOrDefault(localDICOMAttributes,
                                    TagFromName.Rows, 1);
                        } finally {
                            IOUtils.closeQuietly(dicomInputStream);
                        }
                    }
                    dicomFilePaths.add(temporaryDICOMFile.getAbsolutePath());
                } catch (IOException e) {
                    log.warning("Error downloading DICOM file for referenced image " + imageDescription.imageUID
                            + " for series " + dsoEditRequest.seriesUID, e);
                    throw new Exception("Error downloading DICOM file for referenced image "
                            + imageDescription.imageUID + " for series " + dsoEditRequest.seriesUID);
                }
            }
            ImageReference imageReference = new ImageReference(dsoEditRequest);
            log.info("DSO to be edited, UID:" + imageReference.seriesUID);
            DICOMElementList dicomElements = Dcm4CheeQueries.getDICOMElementsFromWADO(imageReference.studyUID,
                    imageReference.seriesUID, imageReference.imageUID);
            int numberOfSegments = getNumberOfSegments(dicomElements);
            if (numberOfSegments > 1)
                throw new Exception(
                        "Editing of Multi-Segment DSOs not supported, number of segments:" + numberOfSegments);
            String seriesDescription = null;
            String seriesUID = null;
            String instanceUID = null;
            for (DICOMElement dicomElement : dicomElements.ResultSet.Result) {
                if (dicomElement.tagCode.equalsIgnoreCase(PixelMedUtils.SeriesDescriptionCode)) {
                    log.info("DSO to be edited, tag:" + dicomElement.tagName + " value:" + dicomElement.value);
                    seriesDescription = dicomElement.value;
                }

            }
            // Always 'clobber' the orginal DSO
            //         if (seriesDescription != null && seriesDescription.toLowerCase().contains("epad"))
            {
                //check if the image reference series is *
                if (imageReference.seriesUID.equals("*")) {
                    log.info("why still * " + dsoEditRequest.toJSON());
                }

                seriesUID = imageReference.seriesUID;
                instanceUID = imageReference.imageUID;
            }
            //         else
            //            seriesDescription = null;
            List<File> editFramesTIFFMaskFiles = generateTIFFsFromPNGs(editFramesPNGMaskFiles);

            List<File> dsoTIFFMaskFiles = new ArrayList<>();
            for (int i = 0; i < imageDescriptions.size(); i++) {
                String fileName = "EmptyMask_" + i + "_";
                if (i == 0) {
                    dsoTIFFMaskFiles
                            .add(copyEmptyTiffFile(editFramesTIFFMaskFiles.get(0), fileName, width, height));
                } else {
                    File tifFile = File.createTempFile(fileName, ".tif");
                    EPADFileUtils.copyFile(dsoTIFFMaskFiles.get(0), tifFile);
                    dsoTIFFMaskFiles.add(tifFile);
                }
            }
            List<File> existingDSOTIFFMaskFiles = DSOUtil.getDSOTIFFMaskFiles(imageReference, dsoTIFFMaskFiles);
            int frameMaskFilesIndex = 0;
            for (Integer frameNumber : dsoEditRequest.editedFrameNumbers) {
                if (imageDescriptions.size() == 1 && frameNumber > imageDescriptions.size()) {
                    //just one mask, but frame number is larger
                    log.info("Editing frame: " + frameNumber + " in new DSO");
                    // For some reason the original DSO Masks are in reverse order
                    int editMaskFileIndex = 0;
                    File prev = dsoTIFFMaskFiles.get(editMaskFileIndex);
                    deleteQuietly(prev);
                    dsoTIFFMaskFiles.set(editMaskFileIndex, editFramesTIFFMaskFiles.get(frameMaskFilesIndex++));
                } else if (frameNumber >= 0 && frameNumber < imageDescriptions.size()) {
                    log.info("Editing frame: " + frameNumber + " in new DSO");
                    // For some reason the original DSO Masks are in reverse order
                    int editMaskFileIndex = existingDSOTIFFMaskFiles.size() - frameNumber - 1;
                    File prev = dsoTIFFMaskFiles.get(editMaskFileIndex);
                    deleteQuietly(prev);
                    dsoTIFFMaskFiles.set(editMaskFileIndex, editFramesTIFFMaskFiles.get(frameMaskFilesIndex++));
                } else {
                    log.warning("Frame number " + frameNumber + " is out of range for DSO image "
                            + dsoEditRequest.imageUID + " in series " + dsoEditRequest.seriesUID
                            + " which has only " + existingDSOTIFFMaskFiles.size() + " frames");
                    return null;
                }
            }
            if (DSOUtil.createDSO(imageReference, dsoTIFFMaskFiles, dicomFilePaths, seriesDescription, seriesUID,
                    instanceUID, dsoEditRequest.property, dsoEditRequest.color)) {
                Integer firstFrame = TIFFMasksToDSOConverter.firstFrames.get(instanceUID);
                TIFFMasksToDSOConverter.firstFrames.remove(instanceUID);
                log.info("Finished generating DSO. First frame is:" + firstFrame);
                for (File file : dsoTIFFMaskFiles) {
                    deleteQuietly(file);
                }
                for (String dicom : dicomFilePaths) {
                    deleteQuietly(new File(dicom));
                }
                return new DSOEditResult(imageReference.projectID, imageReference.subjectID,
                        imageReference.studyUID, imageReference.seriesUID, imageReference.imageUID,
                        dsoEditRequest.aimID, firstFrame);
            } else
                return null;
        } catch (Exception e) {
            log.warning("Error generating DSO image " + dsoEditRequest.imageUID + " in series "
                    + dsoEditRequest.seriesUID, e);
            return null;
        }
    }

    private static int getNumberOfSegments(DICOMElementList dicomElements) {
        int segments = 0;
        for (DICOMElement dicomElement : dicomElements.ResultSet.Result) {
            if (dicomElement.tagName.equalsIgnoreCase("Segment Number")) {
                segments++;
            }
        }
        return segments;
    }

    /**
     * Generate a new DSO from scratch given series and masked frames.
     */
    private static DSOEditResult createNewDSO(String dsoName, DSOEditRequest dsoEditRequest,
            List<File> editFramesPNGMaskFiles, String projectID, String username) {
        try {
            List<DCM4CHEEImageDescription> imageDescriptions = dcm4CheeDatabaseOperations
                    .getImageDescriptions(dsoEditRequest.studyUID, dsoEditRequest.seriesUID);
            List<String> dicomFilePaths = new ArrayList<String>();
            int width = 0;
            int height = 0;
            for (DCM4CHEEImageDescription imageDescription : imageDescriptions) {
                try {
                    File temporaryDICOMFile = File.createTempFile(imageDescription.imageUID, ".dcm");
                    //log.info("Downloading source DICOM file for image " + imageDescription.imageUID);
                    DCM4CHEEUtil.downloadDICOMFileFromWADO(dsoEditRequest.studyUID, dsoEditRequest.seriesUID,
                            imageDescription.imageUID, temporaryDICOMFile);
                    //               if (width == 0) {
                    //                  DicomInputStream dicomInputStream = null;
                    //                  try {
                    //                     dicomInputStream = new DicomInputStream(new FileInputStream(temporaryDICOMFile));
                    //                     AttributeList localDICOMAttributes = new AttributeList();
                    //                     localDICOMAttributes.read(dicomInputStream);
                    //                     width = (short)Attribute.getSingleIntegerValueOrDefault(localDICOMAttributes, TagFromName.Columns, 1);
                    //                     height = (short)Attribute.getSingleIntegerValueOrDefault(localDICOMAttributes, TagFromName.Rows, 1);
                    //                  } finally {
                    //                     IOUtils.closeQuietly(dicomInputStream);
                    //                  }
                    //               }
                    dicomFilePaths.add(temporaryDICOMFile.getAbsolutePath());
                } catch (IOException e) {
                    log.warning("Error downloading DICOM file for referenced image " + imageDescription.imageUID
                            + " for series " + dsoEditRequest.seriesUID, e);
                    throw new Exception("Error downloading DICOM file for referenced image "
                            + imageDescription.imageUID + " for series " + dsoEditRequest.seriesUID);
                }
            }
            List<File> tiffMaskFiles = generateTIFFsFromPNGs(editFramesPNGMaskFiles);
            log.info("Generating DSO for series " + dsoEditRequest.seriesUID + " with " + tiffMaskFiles.size()
                    + " TIFF mask file(s)...");
            if (dicomFilePaths.size() != tiffMaskFiles.size()) {
                log.warning(
                        "Source dicom frames: " + dicomFilePaths.size() + " mask files: " + tiffMaskFiles.size());
            }

            File temporaryDSOFile = File.createTempFile(dsoEditRequest.seriesUID, ".dso");
            log.info("Found " + dicomFilePaths.size() + " source DICOM file(s) for series "
                    + dsoEditRequest.seriesUID);
            List<File> dsoTIFFMaskFiles = new ArrayList<>();
            for (int i = 0; i < dicomFilePaths.size(); i++) {
                String fileName = "EmptyMask_" + i + "_";
                if (i == 0) {
                    dsoTIFFMaskFiles.add(copyEmptyTiffFile(tiffMaskFiles.get(0), fileName, width, height));
                } else {
                    File tifFile = File.createTempFile(fileName, ".tif");
                    EPADFileUtils.copyFile(dsoTIFFMaskFiles.get(0), tifFile);
                    dsoTIFFMaskFiles.add(tifFile);
                }
            }
            int frameMaskFilesIndex = 0;
            for (Integer frameNumber : dsoEditRequest.editedFrameNumbers) {
                if (dicomFilePaths.size() == 1 && frameNumber > dicomFilePaths.size()) {
                    //just one mask, but frame number is larger
                    log.info("Creating frame: " + frameNumber + " in new DSO");
                    int editMaskFileIndex = 0;
                    dsoTIFFMaskFiles.set(editMaskFileIndex, tiffMaskFiles.get(frameMaskFilesIndex++));

                } else if (frameNumber >= 0 && frameNumber < dicomFilePaths.size()) {
                    log.info("Creating frame: " + frameNumber + " in new DSO");
                    // For some reason the original DSO Masks are in reverse order
                    int editMaskFileIndex = dicomFilePaths.size() - frameNumber - 1;
                    dsoTIFFMaskFiles.set(editMaskFileIndex, tiffMaskFiles.get(frameMaskFilesIndex++));
                } else {
                    log.warning("Frame number " + frameNumber + " is out of range for DSO image "
                            + dsoEditRequest.imageUID + " in series " + dsoEditRequest.seriesUID
                            + " which has only " + dicomFilePaths.size() + " frames");
                    return null;
                }
            }
            // For some reason the frames need to be in reverse order
            //         List<File> reverseMaskFiles = new ArrayList<File>();
            //         for (int i = dsoTIFFMaskFiles.size(); i > 0 ; i--)
            //         {
            //            reverseMaskFiles.add(dsoTIFFMaskFiles.get(i-1));
            //         }

            log.info("Generating new DSO for series " + dsoEditRequest.seriesUID);
            TIFFMasksToDSOConverter converter = new TIFFMasksToDSOConverter();
            boolean removeEmptyMasks = false;
            if ("true".equals(EPADConfig.getParamValue("OptimizedDSOs", "true")))
                removeEmptyMasks = true;
            String[] seriesImageUids = converter.generateDSO(files2FilePaths(dsoTIFFMaskFiles), dicomFilePaths,
                    temporaryDSOFile.getAbsolutePath(), dsoName, null, null, removeEmptyMasks,
                    dsoEditRequest.property, dsoEditRequest.color);
            String dsoSeriesUID = seriesImageUids[0];
            String dsoImageUID = seriesImageUids[1];
            log.info("Sending generated DSO " + temporaryDSOFile.getAbsolutePath() + " dsoImageUID:" + dsoImageUID
                    + " dsoSeriesUID:" + dsoSeriesUID + " to dcm4chee...");
            DCM4CHEEUtil.dcmsnd(temporaryDSOFile.getAbsolutePath(), false);
            EpadDatabaseOperations epadDatabaseOperations = EpadDatabase.getInstance().getEPADDatabaseOperations();
            EPADAIM ea = null;
            if (dsoEditRequest.aimID != null && dsoEditRequest.aimID.trim().length() > 0) {
                ea = epadDatabaseOperations.getAIM(dsoEditRequest.aimID);
                if (ea != null)
                    epadDatabaseOperations.updateAIMDSOSeries(ea.aimID, dsoSeriesUID, username);
            }
            if (ea == null) {
                ImageAnnotation aim = AIMUtil.generateAIMFileForDSO(temporaryDSOFile, username, projectID);
                log.info("DSO AimID:" + aim.getUniqueIdentifier());
                ea = epadDatabaseOperations.getAIM(aim.getUniqueIdentifier());
                ea.dsoFrameNo = dsoEditRequest.editedFrameNumbers.get(0);
                epadDatabaseOperations.updateAIMDSOFrameNo(ea.aimID, ea.dsoFrameNo);
            }
            for (File mask : dsoTIFFMaskFiles) {
                mask.delete();
            }
            for (String dicom : dicomFilePaths) {
                deleteQuietly(new File(dicom));
            }
            for (int i = 0; i < dsoEditRequest.editedFrameNumbers.size(); i++) {
                Integer frameNumber = dsoEditRequest.editedFrameNumbers.get(i);
                String pngMaskDirectoryPath = baseDicomDirectory + "/studies/" + ea.studyUID + "/series/"
                        + dsoSeriesUID + "/images/" + dsoImageUID + "/masks/";
                File pngFilesDirectory = new File(pngMaskDirectoryPath);
                pngFilesDirectory.mkdirs();
                String pngMaskFilePath = pngMaskDirectoryPath + frameNumber + ".png";
                EPADFileUtils.copyFile(editFramesPNGMaskFiles.get(i), new File(pngMaskFilePath));
                editFramesPNGMaskFiles.get(i).delete();
                log.info("File copied:" + pngMaskFilePath);
            }
            return new DSOEditResult(dsoEditRequest.projectID, dsoEditRequest.patientID, dsoEditRequest.studyUID,
                    dsoSeriesUID, dsoImageUID, ea.aimID);

        } catch (Exception e) {
            log.warning("Error generating DSO image for series " + dsoEditRequest.seriesUID, e);
            if (e.getMessage() != null)
                throw new RuntimeException(e.getMessage());
            else
                return null;
        }
    }

    private static File copyEmptyTiffFile(File original, String newFileName, int width, int height) {

        BufferedImage orjImg;
        try {
            orjImg = ImageIO.read(original);

            if (width != orjImg.getWidth() || height != orjImg.getHeight()) {
                log.warning("Width and height not right. Old width:" + width + " updated:" + orjImg.getWidth()
                        + " old height:" + height + " updated:" + orjImg.getHeight());
                width = orjImg.getWidth();
                height = orjImg.getHeight();

            }
        } catch (IOException e1) {
            log.warning("failed to read original tiff;", e1);
        }

        File newFile = null;
        try {
            long len = original.length();
            long rgbLen = width * height * 4;
            long greyLen = width * height;
            long bwLen = width * height / 8;
            int imagetype = BufferedImage.TYPE_BYTE_BINARY;
            if (len > greyLen)
                imagetype = BufferedImage.TYPE_BYTE_GRAY;
            if (len > rgbLen)
                imagetype = BufferedImage.TYPE_4BYTE_ABGR;
            newFile = File.createTempFile(newFileName, ".tif");
            //log.info("Creating empty tiff:" + newFile.getAbsolutePath() + " width:" + width + " height:" + height);
            BufferedImage bufferedImage = new BufferedImage(width, height, imagetype);
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    bufferedImage.setRGB(x, y, Color.black.getRGB());
                }
            }
            ImageIO.write(bufferedImage, "tif", newFile);
        } catch (IOException e) {
            log.warning("Error creating empty TIFF  file" + newFile.getAbsolutePath());
        }
        return newFile;
    }

    private static void deleteQuietly(File file) {
        try {
            //log.info("Deleting temp file:" + file.getAbsolutePath());
            file.delete();
        } catch (Exception x) {
        }
    }

    public static boolean createDSO(ImageReference imageReference, List<File> tiffMaskFiles,
            List<String> dicomFilePaths, String dsoSeriesDescription, String dsoSeriesUID, String dsoInstanceUID) {
        return createDSO(imageReference, tiffMaskFiles, dicomFilePaths, dsoSeriesDescription, dsoSeriesUID,
                dsoInstanceUID, null, null);
    }

    public static boolean createDSO(ImageReference imageReference, List<File> tiffMaskFiles,
            List<String> dicomFilePaths, String dsoSeriesDescription, String dsoSeriesUID, String dsoInstanceUID,
            String property, String color) {
        log.info("Generating DSO " + imageReference.imageUID + " with " + tiffMaskFiles.size()
                + " TIFF mask file(s)...");
        try {
            File temporaryDSOFile = File.createTempFile(imageReference.imageUID, ".dso");
            //         List<String> dicomFilePaths = downloadDICOMFilesForDSO(imageReference);
            //         log.info("Found " + dicomFilePaths.size() + " source DICOM file(s) for DSO " + imageReference.imageUID);

            log.info("Generating new edited DSO from original DSO " + imageReference.imageUID);
            TIFFMasksToDSOConverter converter = new TIFFMasksToDSOConverter();
            boolean removeEmptyMasks = false;
            if ("true".equals(EPADConfig.getParamValue("OptimizedDSOs", "true")))
                removeEmptyMasks = true;
            String[] seriesImageUids = converter.generateDSO(files2FilePaths(tiffMaskFiles), dicomFilePaths,
                    temporaryDSOFile.getAbsolutePath(), dsoSeriesDescription, dsoSeriesUID, dsoInstanceUID,
                    removeEmptyMasks, "binary", property, color);
            imageReference.seriesUID = seriesImageUids[0];
            imageReference.imageUID = seriesImageUids[1];
            log.info("Sending generated DSO " + temporaryDSOFile.getAbsolutePath() + " imageUID:"
                    + imageReference.imageUID + " to dcm4chee...");
            DCM4CHEEUtil.dcmsnd(temporaryDSOFile.getAbsolutePath(), false);
            if (dsoSeriesUID != null && EPADConfig.xnatServer.contains("dev6")) {
                // No longer needed since we are updating masks already
                EpadDatabaseOperations epadDatabaseOperations = EpadDatabase.getInstance()
                        .getEPADDatabaseOperations();
                epadDatabaseOperations.deleteSeries(dsoSeriesUID);
            }
            return true;
        } catch (Exception e) {
            log.warning(
                    "Error generating DSO " + imageReference.imageUID + " in series " + imageReference.seriesUID,
                    e);
            return false;
        }
    }

    public static void writeMultiFramePNGs(String studyUID, String seriesUID, String imageUID, File dicomFile)
            throws Exception {
        String pngFilePath = "";
        EpadDatabaseOperations databaseOperations = EpadDatabase.getInstance().getEPADDatabaseOperations();
        try {
            int numberOfFrames = 1;

            String pngDirectoryPath = baseDicomDirectory + "/studies/" + studyUID + "/series/" + seriesUID
                    + "/images/" + imageUID + "/frames/";
            File pngFilesDirectory = new File(pngDirectoryPath);
            pngFilePath = pngDirectoryPath + "0.png";

            log.info("Writing PNGs for MultiFrame DICOM " + imageUID + " in series " + seriesUID);

            pngFilesDirectory.mkdirs();
            Opener opener = new Opener();
            ImagePlus image = null;
            try {
                image = opener.openImage(dicomFile.getAbsolutePath());
            } catch (Throwable t) {
                log.warning("ImageJ failed", t);
            }
            if (image != null) {
                numberOfFrames = image.getNFrames();
                int numberOfSlices = image.getNSlices();
                log.info("Multiframe dicom, frames:" + numberOfFrames + " slices:" + numberOfSlices + " stack size:"
                        + image.getImageStackSize());
                ImageStack stack = image.getImageStack();

                for (int frameNumber = 0; frameNumber < numberOfSlices; frameNumber++) {
                    BufferedImage bufferedImage = stack.getProcessor(frameNumber + 1).getBufferedImage();
                    pngFilePath = pngDirectoryPath + frameNumber + ".png";
                    File pngFile = new File(pngFilePath);
                    try {
                        insertEpadFile(databaseOperations, pngFilePath, 0, imageUID);
                        log.info("Writing PNG frame " + frameNumber + " in multi-frame image " + imageUID
                                + " in series " + seriesUID);
                        ImageIO.write(bufferedImage, "png", pngFile);
                        databaseOperations.updateEpadFileRow(pngFilePath, PNGFileProcessingStatus.DONE,
                                pngFile.length(), "");
                    } catch (IOException e) {
                        log.warning("Failure writing PNG file " + pngFilePath + " for frame " + frameNumber
                                + " in multi-frame image " + imageUID + " in series " + seriesUID, e);
                    }
                }
            } else {
                log.info("Using pixelmed:" + pngFilePath + " Dir:" + pngFilesDirectory.getAbsolutePath());
                //part from pixelmed, trying to read pixel data

                //if we use the old version, it writes all the properties (annotations) on the image, that is why the last parameter is null
                ConsumerFormatImageMaker.convertFileToEightBitImage(dicomFile.getAbsolutePath(), pngFilePath, "png",
                        0, 0, 0, 0, 100, null);
                log.info("png path:" + pngDirectoryPath);
                //old version convertFileToEightBitImage(dicomFile.getAbsolutePath(), pngFilePath, "png", 0);

                SourceImage sImg = new SourceImage(dicomFile.getAbsolutePath());
                File[] pngs = pngFilesDirectory.listFiles();
                for (File png : pngs) {
                    if (!png.getName().endsWith(".png")) {
                        deleteQuietly(png);
                    } else {
                        String name = png.getName().replace("0_", "");
                        File newFile = new File(pngDirectoryPath, name);
                        png.renameTo(newFile);
                        insertEpadFile(databaseOperations, newFile.getAbsolutePath(), 0, imageUID);
                        int frameNum = 0;
                        try {
                            log.info("name is:" + name.replace(".png", ""));
                            frameNum = Integer.parseInt(name.replace(".png", ""));
                            databaseOperations.insertPixelValues(newFile.getAbsolutePath(), frameNum,
                                    getPixelValues(sImg, frameNum - 1), imageUID);
                        } catch (NumberFormatException ne) {
                            log.warning("Could not parse the file name to get the frame number");
                        }

                        databaseOperations.updateEpadFileRow(newFile.getAbsolutePath(),
                                PNGFileProcessingStatus.DONE, png.length(), "");
                    }
                }
            }
            log.info("Finished writing PNGs for multi-frame DICOM " + imageUID + " in series " + seriesUID);
        } catch (Exception e) {
            log.warning("Exception writing multi-frame PNGs", e);
            insertEpadFile(databaseOperations, pngFilePath, 0, imageUID);
            databaseOperations.updateEpadFileRow(pngFilePath, PNGFileProcessingStatus.ERROR, 0, e.getMessage());
            databaseOperations.updateOrInsertSeries(seriesUID, SeriesProcessingStatus.ERROR);
            throw e;
        }
    }

    public static String getPixelValues(SourceImage sImg, int frameNum) {
        int signMask = 0;
        int signBit = 0;

        BufferedImage src = sImg.getBufferedImage(frameNum);
        if (sImg.isSigned()) {
            // the source image will already have been sign extended to the data type size
            // so we don't need to worry about other than exactly 8 and 16 bits
            if (src.getSampleModel().getDataType() == DataBuffer.TYPE_BYTE) {
                signBit = 0x0080;
                signMask = 0xffffff80;
            } else { // assume short or ushort
                signBit = 0x8000;
                signMask = 0xffff8000;
            }
        }
        double[] storedPixelValueArray;
        if (src.getRaster().getDataBuffer() instanceof DataBufferFloat) {
            float[] storedPixelValues = src.getSampleModel().getPixels(0, 0, src.getWidth(), src.getHeight(),
                    (float[]) null, src.getRaster().getDataBuffer());
            //copy to double array
            storedPixelValueArray = new double[storedPixelValues.length];
            for (int i = 0; i < storedPixelValues.length; i++) {
                storedPixelValueArray[i] = storedPixelValues[i];
            }
        } else if (src.getRaster().getDataBuffer() instanceof DataBufferDouble) {
            double[] storedPixelValues = src.getSampleModel().getPixels(0, 0, src.getWidth(), src.getHeight(),
                    (double[]) null, src.getRaster().getDataBuffer());
            storedPixelValueArray = storedPixelValues;
        } else {
            int[] storedPixelValues = src.getSampleModel().getPixels(0, 0, src.getWidth(), src.getHeight(),
                    (int[]) null, src.getRaster().getDataBuffer());
            int storedPixelValueInt = 0;
            //copy to double array
            storedPixelValueArray = new double[storedPixelValues.length];
            for (int i = 0; i < storedPixelValues.length; i++) {
                storedPixelValueInt = storedPixelValues[i];

                if (sImg.isSigned() && (storedPixelValueInt & signBit) != 0) {
                    storedPixelValueInt |= signMask; // sign extend
                }
                storedPixelValueArray[i] = storedPixelValueInt;
            }

        }
        return JSON.toString(storedPixelValueArray);

    }

    public static boolean checkDSOMaskPNGs(File dsoFile) {
        String seriesUID = "";
        try {
            EpadDatabaseOperations databaseOperations = EpadDatabase.getInstance().getEPADDatabaseOperations();
            DicomSegmentationObject dso = new DicomSegmentationObject();
            SourceImage sourceDSOImage = dso.convert(dsoFile.getAbsolutePath());
            int numberOfFrames = sourceDSOImage.getNumberOfBufferedImages();
            AttributeList dicomAttributes = PixelMedUtils.readAttributeListFromDicomFile(dsoFile.getAbsolutePath());
            String studyUID = Attribute.getSingleStringValueOrEmptyString(dicomAttributes,
                    TagFromName.StudyInstanceUID);
            seriesUID = Attribute.getSingleStringValueOrEmptyString(dicomAttributes, TagFromName.SeriesInstanceUID);
            String imageUID = Attribute.getSingleStringValueOrEmptyString(dicomAttributes,
                    TagFromName.SOPInstanceUID);

            String pngMaskDirectoryPath = baseDicomDirectory + "/studies/" + studyUID + "/series/" + seriesUID
                    + "/images/" + imageUID + "/masks/";
            File pngMaskFilesDirectory = new File(pngMaskDirectoryPath);
            if (!pngMaskFilesDirectory.exists())
                return false;
            int numMaskFiles = pngMaskFilesDirectory.list().length;
            if (numMaskFiles >= numberOfFrames) {
                return true;
            } else {
                // One more check - find number of referenced images
                DICOMElementList dicomElementList = Dcm4CheeQueries.getDICOMElementsFromWADO(studyUID, seriesUID,
                        imageUID);
                List<DICOMElement> referencedSOPInstanceUIDDICOMElements = getDICOMElementsByCode(dicomElementList,
                        PixelMedUtils.ReferencedSOPInstanceUIDCode);
                for (int i = 0; i < referencedSOPInstanceUIDDICOMElements.size(); i++) {
                    String referencedUID = dcm4CheeDatabaseOperations
                            .getSeriesUIDForImage(referencedSOPInstanceUIDDICOMElements.get(i).value);
                    if (referencedUID == null || referencedUID.length() == 0) {
                        referencedSOPInstanceUIDDICOMElements.remove(i);
                        i--;
                    }
                }
                log.info("DSO Series:" + seriesUID + " numberOfReferencedImages:"
                        + referencedSOPInstanceUIDDICOMElements.size());
                if (pngMaskFilesDirectory.list().length >= referencedSOPInstanceUIDDICOMElements.size()) {
                    // Some referenced series are missing, but pngs are ok
                    databaseOperations.updateOrInsertSeries(seriesUID, SeriesProcessingStatus.ERROR);
                    return true;
                }
                log.info("DSO Series:" + seriesUID + " numberOfFrames:" + numberOfFrames + " mask files:"
                        + pngMaskFilesDirectory.list().length + " dir:" + pngMaskDirectoryPath);
                return false;
            }
        } catch (Exception e) {
            log.warning("Exception checking DSO PNGs, series:" + seriesUID + " file:" + dsoFile.getAbsolutePath(),
                    e);
            return false;
        }
    }

    public static void writeDSOMaskPNGs(File dsoFile) throws Exception {
        writeDSOMaskPNGs(dsoFile, null);
    }

    public static void writeDSOMaskPNGs(File dsoFile, String username) throws Exception {
        log.info("Start generating DSO PNGs: " + dsoFile.getName());
        String seriesUID = "";
        File tmpDSO = File.createTempFile("DSO_" + dsoFile.getName(), ".dcm");
        try {
            EPADFileUtils.copyFile(dsoFile, tmpDSO);
            EpadDatabaseOperations databaseOperations = EpadDatabase.getInstance().getEPADDatabaseOperations();
            EpadProjectOperations projectOperations = DefaultEpadProjectOperations.getInstance();
            DicomSegmentationObject dso = new DicomSegmentationObject();
            SourceImage sourceDSOImage = dso.convert(tmpDSO.getAbsolutePath());
            int numberOfFrames = sourceDSOImage.getNumberOfBufferedImages();
            AttributeList dicomAttributes = PixelMedUtils.readAttributeListFromDicomFile(tmpDSO.getAbsolutePath());
            String studyUID = Attribute.getSingleStringValueOrEmptyString(dicomAttributes,
                    TagFromName.StudyInstanceUID);
            seriesUID = Attribute.getSingleStringValueOrEmptyString(dicomAttributes, TagFromName.SeriesInstanceUID);
            String imageUID = Attribute.getSingleStringValueOrEmptyString(dicomAttributes,
                    TagFromName.SOPInstanceUID);
            String pngMaskDirectoryPath = baseDicomDirectory + "/studies/" + studyUID + "/series/" + seriesUID
                    + "/images/" + imageUID + "/masks/";
            String pngContourDirectoryPath = baseDicomDirectory + "/studies/" + studyUID + "/series/" + seriesUID
                    + "/images/" + imageUID + "/contours/";
            File pngMaskFilesDirectory = new File(pngMaskDirectoryPath);
            pngMaskFilesDirectory.mkdirs();
            if ("true".equalsIgnoreCase(EPADConfig.getParamValue("GenerateDSOContours"))) {
                File pngContourFilesDirectory = new File(pngContourDirectoryPath);
                pngContourFilesDirectory.mkdirs();
            }

            DICOMElementList dicomElementList = Dcm4CheeQueries.getDICOMElementsFromWADO(studyUID, seriesUID,
                    imageUID);
            AttributeList dsoDICOMAttributes = PixelMedUtils.readDICOMAttributeList(tmpDSO);
            //SequenceAttribute segmentSequence = (SequenceAttribute) dsoDICOMAttributes.get(TagFromName.SegmentSequence);
            //AttributeTag t = TagFromName.SegmentNumber;
            //Attribute a = new UnsignedShortAttribute(t);

            int nonblankFrame = 0;
            String nonBlankImageUID = "";

            List<DICOMElement> referencedSOPInstanceUIDDICOMElements = getDICOMElementsByCode(dicomElementList,
                    PixelMedUtils.ReferencedSOPInstanceUIDCode);
            String[] segNums = SequenceAttribute
                    .getArrayOfSingleStringValueOrEmptyStringOfNamedAttributeWithinSequenceItems(dsoDICOMAttributes,
                            TagFromName.SegmentSequence, TagFromName.SegmentNumber);
            if (segNums == null || segNums.length <= 1) {
                String referencedSeriesUID = "";
                log.info("Writing PNG masks for DSO " + imageUID + " number of referenced instances:"
                        + referencedSOPInstanceUIDDICOMElements.size());

                //dcm4CheeDatabaseOperations.getSeriesUIDForImage(referencedSOPInstanceUIDDICOMElements.get(0).value);
                for (int i = 0; i < referencedSOPInstanceUIDDICOMElements.size(); i++) {
                    String referencedUID = dcm4CheeDatabaseOperations
                            .getSeriesUIDForImage(referencedSOPInstanceUIDDICOMElements.get(i).value);
                    if (referencedUID == null || referencedUID.length() == 0) {
                        referencedSOPInstanceUIDDICOMElements.remove(i);
                        i--;
                    } else
                        referencedSeriesUID = referencedUID;
                }
                log.info("Writing PNG masks for DSO " + imageUID + " number of valid referenced instances:"
                        + referencedSOPInstanceUIDDICOMElements.size());
                if (referencedSeriesUID == null || referencedSeriesUID.length() == 0) {
                    try {
                        // Best to delete this if the source series is missing ???
                        //SeriesReference seriesReference = new SeriesReference(EPADConfig.xnatUploadProjectID, null, studyUID, seriesUID);
                        //DefaultEpadOperations.getInstance().deleteSeries(seriesReference, true);
                    } catch (Exception x) {
                    }
                    throw new Exception("Referenced series for DSO " + seriesUID + " not found");
                }
                int frameNumber = 0;

                File[] oldFiles = pngMaskFilesDirectory.listFiles();
                //         for (File oldFile: oldFiles)
                //         {
                //            try
                //            {
                //               if (oldFile.getName().contains("png"))
                //                  oldFile.delete();
                //            } catch (Exception x) {};
                //         }

                log.info("Writing PNG masks for DSO " + imageUID + " in series " + seriesUID + " DSOFile:"
                        + dsoFile.getAbsolutePath() + " number of frames:" + numberOfFrames + " ...");
                List<DCM4CHEEImageDescription> referencedImages = new ArrayList<DCM4CHEEImageDescription>();
                List<DCM4CHEEImageDescription> imageDescriptions = dcm4CheeDatabaseOperations
                        .getImageDescriptions(studyUID, referencedSeriesUID);
                //starting offset of instances (for series that doesn't start from 1)
                int instanceOffset = imageDescriptions.size();
                Map<String, DCM4CHEEImageDescription> descMap = new HashMap<String, DCM4CHEEImageDescription>();
                for (DCM4CHEEImageDescription imageDescription : imageDescriptions) {
                    descMap.put(imageDescription.imageUID, imageDescription);
                    if (imageDescription.instanceNumber < instanceOffset)
                        instanceOffset = imageDescription.instanceNumber;
                }
                if (referencedSOPInstanceUIDDICOMElements.size() < imageDescriptions.size())
                    instanceOffset = 1;
                if (instanceOffset == 0)
                    instanceOffset = 1;

                for (DICOMElement dicomElement : referencedSOPInstanceUIDDICOMElements) {
                    String referencedImageUID = dicomElement.value;
                    DCM4CHEEImageDescription dcm4cheeReferencedImageDescription = descMap.get(referencedImageUID);
                    referencedImages.add(dcm4cheeReferencedImageDescription);
                }
                int index = 0;
                boolean onefound = false;
                int instanceCount = 0;
                log.info("Number of valid referenced Instances:" + referencedSOPInstanceUIDDICOMElements.size()
                        + " instance offset:" + instanceOffset);
                for (DICOMElement dicomElement : referencedSOPInstanceUIDDICOMElements) {
                    String referencedImageUID = dicomElement.value;
                    DCM4CHEEImageDescription dcm4cheeReferencedImageDescription = referencedImages.get(index);
                    index++;
                    if (dcm4cheeReferencedImageDescription == null) {
                        log.info("Did not find referenced image, seriesuid:" + referencedSeriesUID + " imageuid:"
                                + referencedImageUID + " for DSO seriesUID:" + seriesUID + " DSO imageUID:"
                                + imageUID);
                        continue;
                    }

                    //log.info("Image dimensions - width " + bufferedImage.getWidth() + ", height " + bufferedImage.getHeight());
                    int instanceNumber = dcm4cheeReferencedImageDescription.instanceNumber;
                    if (instanceNumber == 1 && onefound) // These are dicoms where all instance numbers are one !
                    {
                        instanceCount++;
                        instanceNumber = instanceCount;
                    }
                    if (instanceNumber == 1 && !onefound) {
                        onefound = true;
                        instanceCount = 1;
                    }
                    int refFrameNumber = instanceNumber - instanceOffset; // Frames 0-based, instances 1 or more
                    if (refFrameNumber < 0)
                        continue;
                    log.info("FrameNumber:" + frameNumber + " refFrameNumber:" + refFrameNumber
                            + " instance number:" + dcm4cheeReferencedImageDescription.instanceNumber);
                    projectOperations.updateUserTaskStatus(username, TaskStatus.TASK_DSO_PNG_GEN, seriesUID,
                            "Generating PNGs, frame:" + frameNumber, null, null);
                    String pngMaskFilePath = pngMaskDirectoryPath + refFrameNumber + ".png";
                    try {
                        log.info("buffered image ");
                        BufferedImage bufferedImage = sourceDSOImage.getBufferedImage(frameNumber);
                        log.info("buffered image " + bufferedImage.toString());

                        BufferedImage bufferedImageWithTransparency = generateTransparentImage(bufferedImage);
                        log.info(" bufferedImageWithTransparency " + bufferedImageWithTransparency.toString());

                        if (nonBlank.get()) {
                            nonblankFrame = refFrameNumber;
                            nonBlankImageUID = dcm4cheeReferencedImageDescription.imageUID;
                        }
                        log.info(" nonblankFrame " + nonblankFrame);

                        File pngMaskFile = new File(pngMaskFilePath);
                        log.info(" pngMaskFile " + pngMaskFile.getAbsolutePath());

                        insertEpadFile(databaseOperations, pngMaskFilePath, pngMaskFile.length(), imageUID);
                        log.info("Writing PNG mask file frame " + frameNumber + " of " + numberOfFrames
                                + " for DSO " + imageUID + " in series " + seriesUID + " file:" + pngMaskFilePath
                                + " nonBlank:" + nonBlank.get());
                        ImageIO.write(bufferedImageWithTransparency, "png", pngMaskFile);
                        databaseOperations.updateEpadFileRow(pngMaskFilePath, PNGFileProcessingStatus.DONE, 0, "");
                    } catch (Exception e) {
                        log.warning("Failure writing PNG mask file " + pngMaskFilePath + " for frame " + frameNumber
                                + " of DSO " + imageUID + " in series " + seriesUID, e);
                    }

                    // Contours are currently never set to true, so never used
                    if ("true".equalsIgnoreCase(EPADConfig.getParamValue("GenerateDSOContours"))) {
                        String pngContourFilePath = pngContourDirectoryPath + refFrameNumber + ".png";
                        try {
                            RunSystemCommand rsc = new RunSystemCommand("convert " + pngMaskFilePath
                                    + " -negate -edge 1 -negate " + pngContourFilePath);
                            rsc.run();
                        } catch (Exception e) {
                            log.warning("Failure writing PNG contour file " + pngContourFilePath + " for frame "
                                    + frameNumber + " of DSO " + imageUID + " in series " + seriesUID, e);
                        }
                    }
                    frameNumber++;
                }
            } else {
                log.info("Oh my God, this is a stupid multi-segment DSO:" + seriesUID + " number of segments:"
                        + segNums.length);
                SequenceAttribute perFrameSeq = (SequenceAttribute) dsoDICOMAttributes
                        .get(TagFromName.PerFrameFunctionalGroupsSequence);
                int numFrames = perFrameSeq.getNumberOfItems();
                String[] referencedSOPInstanceUIDs = new String[numFrames];
                String[] segmentNumbers = new String[numFrames];
                for (int i = 0; i < numFrames; i++) {
                    AttributeList segAttrs = SequenceAttribute
                            .getAttributeListFromSelectedItemWithinSequence(perFrameSeq, i);
                    SequenceAttribute derImageSeq = (SequenceAttribute) segAttrs
                            .get(TagFromName.DerivationImageSequence);
                    AttributeList derImageAttrs = SequenceAttribute
                            .getAttributeListFromSelectedItemWithinSequence(derImageSeq, 0);
                    String[] refUDIDs = SequenceAttribute
                            .getArrayOfSingleStringValueOrEmptyStringOfNamedAttributeWithinSequenceItems(
                                    derImageAttrs, TagFromName.SourceImageSequence,
                                    TagFromName.ReferencedSOPInstanceUID);
                    String[] segNos = SequenceAttribute
                            .getArrayOfSingleStringValueOrEmptyStringOfNamedAttributeWithinSequenceItems(segAttrs,
                                    TagFromName.SegmentIdentificationSequence, TagFromName.ReferencedSegmentNumber);
                    referencedSOPInstanceUIDs[i] = refUDIDs[0];
                    segmentNumbers[i] = segNos[0];
                }
                String referencedSeriesUID = dcm4CheeDatabaseOperations
                        .getSeriesUIDForImage(referencedSOPInstanceUIDs[0]);
                if (referencedSeriesUID == null || referencedSeriesUID.length() == 0)
                    throw new Exception("Referenced series for DSO " + seriesUID + " not found");
                List<DCM4CHEEImageDescription> imageDescriptions = dcm4CheeDatabaseOperations
                        .getImageDescriptions(studyUID, referencedSeriesUID);
                Map<String, DCM4CHEEImageDescription> descMap = new HashMap<String, DCM4CHEEImageDescription>();
                for (DCM4CHEEImageDescription imageDescription : imageDescriptions) {
                    descMap.put(imageDescription.imageUID, imageDescription);
                }
                numberOfFrames = referencedSOPInstanceUIDs.length;
                for (int i = 0; i < referencedSOPInstanceUIDs.length; i++) {
                    DCM4CHEEImageDescription dcm4cheeReferencedImageDescription = descMap
                            .get(referencedSOPInstanceUIDs[i]);
                    int instanceNumber = dcm4cheeReferencedImageDescription.instanceNumber;
                    int frameNumber = instanceNumber - 1;
                    BufferedImage bufferedImage = sourceDSOImage.getBufferedImage(i);
                    BufferedImage bufferedImageWithTransparency = generateTransparentImage(bufferedImage);
                    if (nonBlank.get()) {
                        nonblankFrame = frameNumber;
                        nonBlankImageUID = dcm4cheeReferencedImageDescription.imageUID;
                    }
                    String pngMaskFilePath = pngMaskDirectoryPath + frameNumber + "_" + segmentNumbers[i] + ".png";

                    File pngMaskFile = new File(pngMaskFilePath);
                    try {
                        insertEpadFile(databaseOperations, pngMaskFilePath, pngMaskFile.length(), imageUID);
                        log.info("Writing PNG mask file frame " + frameNumber + " of " + numberOfFrames
                                + " for DSO " + imageUID + " in series " + seriesUID + " file:" + pngMaskFilePath
                                + " nonBlank:" + nonBlank.get());
                        ImageIO.write(bufferedImageWithTransparency, "png", pngMaskFile);
                        databaseOperations.updateEpadFileRow(pngMaskFilePath, PNGFileProcessingStatus.DONE, 0, "");
                    } catch (IOException e) {
                        log.warning("Failure writing PNG mask file " + pngMaskFilePath + " for frame " + frameNumber
                                + " of DSO " + imageUID + " in series " + seriesUID, e);
                    }
                }

            }

            EpadDatabaseOperations epadDatabaseOperations = EpadDatabase.getInstance().getEPADDatabaseOperations();
            List<EPADAIM> aims = epadDatabaseOperations.getAIMsByDSOSeries(seriesUID);
            for (EPADAIM aim : aims) {
                epadDatabaseOperations.updateAIMDSOFrameNo(aim.aimID, nonblankFrame);
                AIMUtil.updateDSOStartIndex(aim, nonblankFrame);
            }
            log.info("... finished writing PNG " + numberOfFrames + " masks for DSO image " + imageUID
                    + " in series " + seriesUID + " nonBlankFrame:" + nonblankFrame);
        } catch (DicomException e) {
            log.warning("DICOM exception writing DSO PNG masks, series:" + seriesUID, e);
            throw new Exception("DICOM exception writing DSO PNG masks, series:" + seriesUID, e);
        } catch (IOException e) {
            log.warning("IO exception writing DSO PNG masks, series:" + seriesUID, e);
            throw new Exception("IO exception writing DSO PNG masks, series:" + seriesUID, e);
        } catch (Exception e) {
            log.warning("Exception writing DSO PNG masks, series:" + seriesUID, e);
            throw new Exception("Exception writing DSO PNG masks, series:" + seriesUID, e);
        } finally {
            try {
                tmpDSO.delete();
            } catch (Exception e) {
            }
            ;
        }
    }

    private static List<DICOMElement> getDICOMElementsByCode(DICOMElementList dicomElementList, String tagCode) {
        Set<DICOMElement> matchingDICOMElements = new LinkedHashSet<>(); // Maintain insertion order

        for (DICOMElement dicomElement : dicomElementList.ResultSet.Result) {
            // Do not allow duplicates.
            if (dicomElement.tagCode.equals(tagCode) && !matchingDICOMElements.contains(dicomElement))
                matchingDICOMElements.add(dicomElement);
        }

        return new ArrayList<>(matchingDICOMElements);
    }

    public static void writePNGMasksForNiftiDSO(String subjectID, String studyUID, String seriesUID,
            String imageUID, File niftiFile) throws Exception {
        String pngMaskDirectoryPath = baseDicomDirectory + "/studies/" + studyUID + "/series/" + seriesUID
                + "/images/" + imageUID + "/masks/";
        File path = new File(pngMaskDirectoryPath);
        path.mkdirs();
        RunSystemCommand rsc = new RunSystemCommand(
                "miconv " + niftiFile.getAbsolutePath() + " " + pngMaskDirectoryPath + imageUID + ".png");
        rsc.run();
    }

    public static boolean handleDSOFramesEdit(String projectID, String subjectID, String studyUID, String seriesUID,
            String imageUID, HttpServletRequest httpRequest, PrintWriter responseStream) { // See http://www.tutorialspoint.com/servlets/servlets-file-uploading.htm
        boolean uploadError = false;

        log.info("Received DSO edit request for series " + seriesUID);
        String confirm = dcm4CheeDatabaseOperations.getSeriesUIDForImage(imageUID);
        //ml if ui do not know series uid (new dso)
        if (seriesUID.equals("*")) {
            seriesUID = confirm;
        }
        //ml if ui do not know study uid (new dso)
        if (studyUID.equals("*")) {
            studyUID = dcm4CheeDatabaseOperations.getStudyUIDForSeries(seriesUID);
        }
        if (!confirm.equals(seriesUID)) {
            log.warning("Invalid ImageUID for series:" + seriesUID);
            return true;
        }
        try {
            ServletFileUpload servletFileUpload = new ServletFileUpload();
            FileItemIterator fileItemIterator = servletFileUpload.getItemIterator(httpRequest);

            DSOEditRequest dsoEditRequest = null;
            String editedFrameNumbers = httpRequest.getParameter("editedFrameNumbers");
            if (editedFrameNumbers == null || editedFrameNumbers.length() == 0) {
                dsoEditRequest = extractDSOEditRequest(fileItemIterator);
                //ui doesn't send editedFrameNumbers, but the series uid is *
                if (dsoEditRequest.seriesUID.equals("*")) {
                    dsoEditRequest.seriesUID = confirm;
                }
                if (dsoEditRequest.studyUID.equals("*")) {
                    dsoEditRequest.studyUID = dcm4CheeDatabaseOperations.getStudyUIDForSeries(seriesUID);
                }
            } else {
                log.info("Uploaded mask frame numbers:" + editedFrameNumbers);
                String[] frameNumbers = editedFrameNumbers.split(",");
                List<Integer> numbers = new ArrayList<Integer>();
                for (String frameNumber : frameNumbers) {
                    if (frameNumber.trim().length() == 0)
                        continue;
                    numbers.add(new Integer(frameNumber.trim()));
                }
                dsoEditRequest = new DSOEditRequest(projectID, subjectID, studyUID, seriesUID, imageUID,
                        httpRequest.getParameter("aimID"), numbers);
            }

            if (dsoEditRequest != null) {
                //need to pass this all the way to segmentation writer, put into edit request
                String property = httpRequest.getParameter("property");
                String color = httpRequest.getParameter("color");
                dsoEditRequest.property = property;
                dsoEditRequest.color = color;

                log.info("DSOEditRequest, imageUID:" + dsoEditRequest.imageUID + " aimID:" + dsoEditRequest.aimID
                        + " number Frames:" + dsoEditRequest.editedFrameNumbers.size());
                EpadDatabaseOperations epadDatabaseOperations = EpadDatabase.getInstance()
                        .getEPADDatabaseOperations();
                String username = httpRequest.getParameter("username");
                EPADAIM aim = epadDatabaseOperations.getAIM(dsoEditRequest.aimID);
                if (aim != null && username != null) {
                    EpadProjectOperations projectOperations = DefaultEpadProjectOperations.getInstance();
                    if (!projectOperations.isAdmin(username) && !username.equals(aim.userName)
                            && !projectOperations.isOwner(username, projectID)) {
                        log.warning("No permissions to update AIM:" + aim.aimID + " for user " + username);
                        throw new Exception("No permissions to update AIM:" + aim.aimID + " for user " + username);
                    }
                }
                List<File> editedFramesPNGMaskFiles = HandlerUtil.extractFiles(fileItemIterator, "DSOEditedFrame",
                        ".PNG");
                if (editedFramesPNGMaskFiles.isEmpty()) {
                    log.warning("No PNG masks supplied in DSO edit request for image " + imageUID + " in series "
                            + seriesUID);
                    uploadError = true;
                } else {
                    log.info("Extracted " + editedFramesPNGMaskFiles.size()
                            + " file mask(s) for DSO edit for image " + imageUID + " in  series " + seriesUID);
                    if (editedFramesPNGMaskFiles.size() != dsoEditRequest.editedFrameNumbers.size())
                        throw new IOException("Number of files and frames number do not match");
                    //               if (aim != null && (aim.dsoFrameNo == 0 || aim.dsoFrameNo < dsoEditRequest.editedFrameNumbers.get(0))) {
                    //                  aim.dsoFrameNo = dsoEditRequest.editedFrameNumbers.get(0);
                    //                  epadDatabaseOperations.updateAIMDSOFrameNo(aim.aimID, aim.dsoFrameNo);
                    //               }
                    DSOEditResult dsoEditResult = DSOUtil.createEditedDSO(dsoEditRequest, editedFramesPNGMaskFiles,
                            aim.seriesUID);
                    if (dsoEditResult != null) {
                        log.info("Copying edited frame pngs: " + dsoEditRequest.editedFrameNumbers.size());
                        for (int i = 0; i < dsoEditRequest.editedFrameNumbers.size(); i++) {
                            Integer frameNumber = dsoEditRequest.editedFrameNumbers.get(i);
                            String pngMaskDirectoryPath = baseDicomDirectory + "/studies/" + studyUID + "/series/"
                                    + seriesUID + "/images/" + imageUID + "/masks/";
                            String pngMaskFilePath = pngMaskDirectoryPath + frameNumber + ".png";
                            EPADFileUtils.copyFile(editedFramesPNGMaskFiles.get(i), new File(pngMaskFilePath));
                            editedFramesPNGMaskFiles.get(i).delete();
                        }
                        if (dsoEditResult.aimID != null && dsoEditResult.aimID.length() > 0) {
                            if (dsoEditResult.firstFrame != null) {
                                log.info("update aim table dso first frame with " + dsoEditResult.firstFrame
                                        + "for aim " + dsoEditResult.aimID);
                                epadDatabaseOperations.updateAIMDSOFrameNo(dsoEditResult.aimID,
                                        dsoEditResult.firstFrame);
                            }
                            List<ImageAnnotation> aims = AIMQueries.getAIMImageAnnotations(
                                    AIMSearchType.ANNOTATION_UID, dsoEditResult.aimID, "admin");
                            if (aims.size() > 0) {

                                log.info("DSO Annotation: " + dsoEditResult.aimID);
                                //                        String sessionID = XNATSessionOperations.getJSessionIDFromRequest(httpRequest);
                                //                        ImageAnnotation imageAnnotation =  aims.get(0);
                                //                        PluginAIMUtil.addSegmentToImageAnnotation(imageAnnotation.getSegmentationCollection().getSegmentationList().get(0).getSopClassUID(), dsoEditResult.imageUID, imageAnnotation.getSegmentationCollection().getSegmentationList().get(0).getReferencedSopInstanceUID(),
                                //                              imageAnnotation);
                                //                        DICOMImageReference dsoDICOMImageReference = PluginAIMUtil.createDICOMImageReference(dsoEditResult.studyUID, dsoEditResult.seriesUID,
                                //                              dsoEditResult.imageUID);
                                //                        imageAnnotation.addImageReference(dsoDICOMImageReference);
                                //                        try {
                                //                           AIMUtil.saveImageAnnotationToServer(imageAnnotation, sessionID);
                                //                        } catch (AimException e) {
                                //                           // TODO Auto-generated catch block
                                //                           e.printStackTrace();
                                //                        } catch (edu.stanford.hakan.aim4api.base.AimException e) {
                                //                           // TODO Auto-generated catch block
                                //                           e.printStackTrace();
                                //                        }
                            }
                        }

                        responseStream.append(dsoEditResult.toJSON());
                    } else {
                        log.info("Null return from createEditDSO");
                        uploadError = true;
                    }
                }
            } else {
                log.warning("Invalid JSON header in DSO edit request for image " + imageUID + " in  series "
                        + seriesUID);
                uploadError = true;
            }
        } catch (IOException e) {
            log.warning("IO exception handling DSO edits for series " + seriesUID, e);
            uploadError = true;
        } catch (FileUploadException e) {
            log.warning("File upload exception handling DSO edits for series " + seriesUID, e);
            uploadError = true;
        } catch (Exception e) {
            log.warning("Exception handling DSO edits for series " + seriesUID, e);
            uploadError = true;
        }
        if (!uploadError)
            log.info("DSO successfully edited");
        return uploadError;
    }

    public static boolean handleCreateDSO(String projectID, String subjectID, String studyUID, String seriesUID,
            HttpServletRequest httpRequest, PrintWriter responseStream, String username) { // See http://www.tutorialspoint.com/servlets/servlets-file-uploading.htm
        boolean uploadError = false;

        log.info("Received DSO create request for series " + seriesUID);
        try {
            ServletFileUpload servletFileUpload = new ServletFileUpload();
            FileItemIterator fileItemIterator = servletFileUpload.getItemIterator(httpRequest);
            DSOEditRequest dsoEditRequest = null;
            String editedFrameNumbers = httpRequest.getParameter("editedFrameNumbers");
            if (editedFrameNumbers == null || editedFrameNumbers.length() == 0) {
                dsoEditRequest = extractDSOEditRequest(fileItemIterator);
            } else {
                log.info("Uploaded mask frame numbers:" + editedFrameNumbers);
                String[] frameNumbers = editedFrameNumbers.split(",");
                List<Integer> numbers = new ArrayList<Integer>();
                for (String frameNumber : frameNumbers) {
                    if (frameNumber.trim().length() == 0)
                        continue;
                    numbers.add(new Integer(frameNumber.trim()));
                }
                dsoEditRequest = new DSOEditRequest(projectID, subjectID, studyUID, seriesUID, "", "", numbers);
            }

            //need to pass this all the way to segmentation writer, put into edit request
            String property = httpRequest.getParameter("property");
            String color = httpRequest.getParameter("color");
            dsoEditRequest.property = property;
            dsoEditRequest.color = color;

            log.info("DSOCreateRequest, seriesUID:" + dsoEditRequest.seriesUID + " imageUID:"
                    + dsoEditRequest.imageUID + " aimID:" + dsoEditRequest.aimID + " number Frames:"
                    + dsoEditRequest.editedFrameNumbers.size());

            if (dsoEditRequest != null) {
                List<File> framesPNGMaskFiles = HandlerUtil.extractFiles(fileItemIterator, "DSOFrame", ".PNG");
                if (framesPNGMaskFiles.isEmpty()) {
                    log.warning("No PNG masks supplied in DSO create request for series " + seriesUID);
                    uploadError = true;
                } else {
                    framesPNGMaskFiles = framesPNGMaskFiles.subList(0, dsoEditRequest.editedFrameNumbers.size());
                    log.info("Extracted " + framesPNGMaskFiles.size() + " file mask(s) for DSO create for series "
                            + seriesUID);
                    String name = httpRequest.getParameter("name");
                    DSOEditResult dsoEditResult = DSOUtil.createNewDSO(name, dsoEditRequest, framesPNGMaskFiles,
                            projectID, username);
                    if (dsoEditResult != null) {
                        responseStream.append(dsoEditResult.toJSON());
                    } else {
                        log.info("Null return from createNewDSO");
                        uploadError = true;
                    }
                }
            } else {
                log.warning("Invalid JSON header in DSO edit request for series " + seriesUID);
                uploadError = true;
            }

        } catch (IOException e) {
            log.warning("IO exception handling DSO edits for series " + seriesUID, e);
            uploadError = true;
        } catch (FileUploadException e) {
            log.warning("File upload exception handling DSO edits for series " + seriesUID, e);
            uploadError = true;
        }
        if (!uploadError)
            log.info("DSO successfully created ...");
        return uploadError;
    }

    public static String getNiftiDSOComparison(File standardDSO, File testDSO) throws Exception {
        String command = EPADConfig.getEPADWebServerBaseDir() + "bin/EvaluateSegmentation "
                + standardDSO.getAbsolutePath() + " " + testDSO.getAbsolutePath()
                + " -use DICE,JACRD,AUC,KAPPA,RNDIND,ADJRIND,ICCORR,VOLSMTY,MUTINF,MAHLNBS,VARINFO,GCOERR,PROBDST,SNSVTY,SPCFTY,PRCISON,ACURCY,FALLOUT,HDRFDST@0.96@,FMEASR@0.5@ -xml "
                + EPADConfig.getEPADWebServerBaseDir() + "bin/result.xml";
        log.info(command);
        String[] args = command.split(" ");
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(args);
            processBuilder.directory(new File(EPADConfig.getEPADWebServerBaseDir() + "bin/"));
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();
            is = process.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);

            String line;
            StringBuilder sb = new StringBuilder();
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
                log.debug("./eval_seg output: " + line);
            }

            int exitValue = process.waitFor();
            log.info("Evaluate Segmentation exit value is: " + exitValue);
            return sb.toString();
        } catch (Exception e) {
            log.warning("Error evaluating dsos", e);
            throw e;
        }
    }

    public static String getDSOImagesComparison(String studyUID, String seriesUID1, String seriesUID2)
            throws Exception {
        File inputDir = null;
        try {
            EpadProjectOperations projectOperations = DefaultEpadProjectOperations.getInstance();
            //NonDicomSeries series1 = projectOperations.getNonDicomSeries(seriesUID1);
            //NonDicomSeries series2 = projectOperations.getNonDicomSeries(seriesUID2);
            List<EpadFile> files1 = projectOperations.getEpadFiles(null, null, studyUID, seriesUID1, FileType.IMAGE,
                    true);
            List<EpadFile> files2 = projectOperations.getEpadFiles(null, null, studyUID, seriesUID2, FileType.IMAGE,
                    true);
            String inputDirPath = EPADConfig.getEPADWebServerResourcesDir() + "download/" + "temp"
                    + Long.toString(System.currentTimeMillis()) + "/";
            inputDir = new File(inputDirPath);
            inputDir.mkdirs();
            File[] niftis = null;
            if (files1.size() != 1 && files2.size() != 1) {
                List<DCM4CHEEImageDescription> imageDescriptions1 = dcm4CheeDatabaseOperations
                        .getImageDescriptions(studyUID, seriesUID1);
                if (imageDescriptions1.size() > 1)
                    throw new Exception("Invalid DSO " + seriesUID1 + " has multiple images");
                if (imageDescriptions1.size() == 0)
                    throw new Exception("DSO " + seriesUID1 + " not found");
                List<DCM4CHEEImageDescription> imageDescriptions2 = dcm4CheeDatabaseOperations
                        .getImageDescriptions(studyUID, seriesUID2);
                if (imageDescriptions2.size() > 1)
                    throw new Exception("Invalid DSO " + seriesUID2 + " has multiple images");
                File dicom1 = new File(inputDir, imageDescriptions1.get(0).imageUID + ".dcm");
                DCM4CHEEUtil.downloadDICOMFileFromWADO(studyUID, seriesUID1, imageDescriptions1.get(0).imageUID,
                        dicom1);
                File dicom2 = new File(inputDir, imageDescriptions2.get(0).imageUID + ".dcm");
                DCM4CHEEUtil.downloadDICOMFileFromWADO(studyUID, seriesUID2, imageDescriptions2.get(0).imageUID,
                        dicom2);
                niftis = DicomFileUtil.convertDicomsToNifti(inputDir);
                if (niftis.length != 2)
                    throw new Exception("Error converting dicoms to nifi");
            } else {
                niftis = new File[2];
                niftis[0] = new File(files1.get(0).getFilePath());
                niftis[1] = new File(files2.get(0).getFilePath());
            }
            String result = getNiftiDSOComparison(niftis[0], niftis[1]); // TODO: need to check which is which
            return result;
        } finally {
            if (inputDir != null)
                EPADFileUtils.deleteDirectoryAndContents(inputDir);
        }
    }

    private static DSOEditRequest extractDSOEditRequest(FileItemIterator fileItemIterator)
            throws FileUploadException, IOException, UnsupportedEncodingException {
        DSOEditRequest dsoEditRequest = null;
        Gson gson = new Gson();
        FileItemStream headerJSONItemStream = fileItemIterator.next();
        InputStream headerJSONStream = headerJSONItemStream.openStream();
        InputStreamReader isr = null;
        BufferedReader br = null;

        try {
            isr = new InputStreamReader(headerJSONStream, "UTF-8");
            br = new BufferedReader(isr);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = br.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
            String json = sb.toString();
            log.info("DSOEditRequest:" + json);
            dsoEditRequest = gson.fromJson(json, DSOEditRequest.class);
        } finally {
            IOUtils.closeQuietly(br);
            IOUtils.closeQuietly(isr);
        }
        return dsoEditRequest;
    }

    private static List<String> downloadDICOMFilesForDSO(ImageReference dsoImageReference) {
        EpadOperations epadOperations = DefaultEpadOperations.getInstance();
        List<String> dicomFilePaths = new ArrayList<>();

        EPADFrameList frameList = epadOperations.getFrameDescriptions(dsoImageReference);
        for (EPADFrame frame : frameList.ResultSet.Result) {
            if (frame instanceof EPADDSOFrame) {
                EPADDSOFrame dsoFrame = (EPADDSOFrame) frame;
                String studyUID = dsoFrame.studyUID;
                String sourceSeriesUID = dsoFrame.sourceSeriesUID;
                String sourceImageUID = dsoFrame.sourceImageUID;

                try {
                    File temporaryDICOMFile = File.createTempFile(sourceImageUID, ".dcm");
                    //               log.info("Downloading source DICOM file for image " + sourceImageUID + " referenced by DSO image "
                    //                     + dsoImageReference.imageUID);
                    DCM4CHEEUtil.downloadDICOMFileFromWADO(studyUID, sourceSeriesUID, sourceImageUID,
                            temporaryDICOMFile);
                    dicomFilePaths.add(temporaryDICOMFile.getAbsolutePath());
                } catch (IOException e) {
                    log.warning("Error downloading DICOM file for referenced image " + sourceImageUID + " for DSO "
                            + dsoImageReference.imageUID, e);
                }
            } else {
                log.warning("Image " + dsoImageReference.imageUID + " in series " + dsoImageReference.seriesUID
                        + " does not appear to be a DSO");
            }
        }
        return dicomFilePaths;
    }

    private static List<File> getDSOTIFFMaskFiles(ImageReference imageReference, List<File> dsoMaskFiles)
            throws Exception {
        EpadOperations epadOperations = DefaultEpadOperations.getInstance();

        EPADFrameList frameList = epadOperations.getFrameDescriptions(imageReference);

        for (EPADFrame frame : frameList.ResultSet.Result) {
            String maskFilePath = baseDicomDirectory + frame.losslessImage;
            File maskFile = new File(maskFilePath);
            if (!maskFile.exists())
                continue;
            //log.info("Creating TIFF mask file " + maskFilePath + " for frame " + frame.frameNumber + " for DSO "
            //      + imageReference.imageUID);
            log.debug("Existing DSO masks, frameNo:" + frame.frameNumber + " maskFile:" + maskFile.getName());
            try {
                BufferedImage bufferedImage = ImageIO.read(maskFile);
                File tiffFile = File.createTempFile(imageReference.imageUID + "_frame_" + frame.frameNumber + "_",
                        ".tif");
                ImageIO.write(bufferedImage, "tif", tiffFile);
                //ml very bad fix for just one frame
                if (dsoMaskFiles.size() == 1)
                    dsoMaskFiles.set(0, tiffFile);
                else if (dsoMaskFiles.size() - frame.frameNumber - 1 < 0) {
                    //TODO fix for templates starting from a number greater than 0, but has more than one frames
                    throw new Exception(
                            "DSO edit with frame numbers not starting from 1 and has more than one frames is not supported ");
                } else {
                    dsoMaskFiles.set(dsoMaskFiles.size() - frame.frameNumber - 1, tiffFile);
                }
            } catch (IOException e) {
                log.warning("Error creating TIFF mask file " + maskFilePath + " for frame " + frame.frameNumber
                        + " for DSO " + imageReference.imageUID, e);
                throw e;
            }
        }
        log.debug("Total masks" + dsoMaskFiles.size() + " Existing masks:" + frameList.ResultSet.Result.size());
        return dsoMaskFiles;
    }

    private static List<File> generateTIFFsFromPNGs(List<File> pngFiles) {
        List<File> tiffFiles = new ArrayList<>();

        for (File pngFile : pngFiles) {
            log.debug("PNG FIle:" + pngFile.getAbsolutePath());
            try {
                BufferedImage bufferedImage = ImageIO.read(pngFile);
                File tiffFile = File.createTempFile(pngFile.getName(), ".tif");
                ImageIO.write(bufferedImage, "tif", tiffFile);
                tiffFiles.add(tiffFile);
            } catch (Exception e) {
                log.warning("Error creating TIFF  file from PNG " + pngFile.getAbsolutePath(), e);
            }
        }
        return tiffFiles;
    }

    private static BufferedImage generateTransparentImage(BufferedImage source) {
        Image image = makeColorOpaque(source, Color.BLACK); // Because somebody said to convert DSOs to white
        //      Image image = makeAnyColorWhite(source); // To retain DSO color comment this out and uncomment previous line
        BufferedImage transparent = imageToBufferedImage(image);
        Image image2 = makeColorTransparent(transparent, Color.BLACK);
        BufferedImage transparent2 = imageToBufferedImage(image2);
        return transparent2;
    }

    private static BufferedImage imageToBufferedImage(Image image) {
        BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null),
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = bufferedImage.createGraphics();
        g2.drawImage(image, 0, 0, null);
        g2.dispose();
        return bufferedImage;
    }

    private static ThreadLocal<Boolean> nonBlank = new ThreadLocal<Boolean>();

    private static Image makeAnyColorWhite(BufferedImage im) {
        nonBlank.set(false);
        ImageFilter filter = new RGBImageFilter() {

            @Override
            public final int filterRGB(int x, int y, int rgb) {
                if ((rgb & 0x00FFFFFF) != 0) {
                    nonBlank.set(true);
                    return 0xFFFFFFFF;
                } else {
                    return rgb;
                }
            }
        };

        ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(ip);
    }

    private static Image makeColorOpaque(BufferedImage im, final Color color) {
        nonBlank.set(false);
        ImageFilter filter = new RGBImageFilter() {
            public int markerRGB = color.getRGB() | 0xFF000000;

            @Override
            public final int filterRGB(int x, int y, int rgb) {
                if ((rgb & 0x00FFFFFF) != 0) {
                    nonBlank.set(true);
                }
                if ((rgb | 0xFF000000) == markerRGB) {
                    //               nonBlank.set(true);
                    return 0xFF000000 | rgb;
                } else {
                    return rgb;
                }
            }
        };

        ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(ip);
    }

    private static Image makeColorTransparent(BufferedImage im, final Color color) {
        ImageFilter filter = new RGBImageFilter() {
            public int markerRGB = color.getRGB() | 0xFF000000;

            @Override
            public final int filterRGB(int x, int y, int rgb) {
                if ((rgb | 0xFF000000) == markerRGB) {
                    return 0x00FFFFFF & rgb;
                } else {
                    return rgb;
                }
            }
        };

        ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
        return Toolkit.getDefaultToolkit().createImage(ip);
    }

    private static List<String> files2FilePaths(List<File> files) {
        List<String> filePaths = new ArrayList<>();

        for (File file : files)
            filePaths.add(file.getAbsolutePath());

        return filePaths;
    }

    private static void insertEpadFile(EpadDatabaseOperations epadDatabaseOperations, String outputFilePath,
            long fileSize, String imageUID) {
        Map<String, String> epadFilesRow = Dcm4CheeDatabaseUtils.createEPadFilesRowData(outputFilePath, fileSize,
                imageUID);
        epadFilesRow.put("file_status", "" + PNGFileProcessingStatus.IN_PIPELINE.getCode());
        epadDatabaseOperations.insertEpadFileRow(epadFilesRow);
    }
}