Created new plugin that listens for dicoms pushed to it, sorts them as adc/dwi, runs...
authormccreedy@NIH.GOV <mccreedy@NIH.GOV@ba61647d-9d00-f842-95cd-605cb4296b96>
Wed, 21 Mar 2018 15:46:48 +0000 (15:46 +0000)
committermccreedy@NIH.GOV <mccreedy@NIH.GOV@ba61647d-9d00-f842-95cd-605cb4296b96>
Wed, 21 Mar 2018 15:46:48 +0000 (15:46 +0000)
git-svn-id: https://citdcbmipav.cit.nih.gov/repos-pub/mipav/trunk@15424 ba61647d-9d00-f842-95cd-605cb4296b96

mipav/src/plugins/PlugInAlgorithmStrokeSegmentation.java
mipav/src/plugins/PlugInDialogStrokeSegmentation.java
mipav/src/plugins/PlugInDialogStrokeSegmentationListener.java [new file with mode: 0644]
mipav/src/plugins/PlugInStrokeSegmentationListener.java [new file with mode: 0644]
mipav/src/plugins/StrokeSegmentationDicomReceiver.java [new file with mode: 0644]

index b18ac92..c3ef480 100644 (file)
@@ -1,4 +1,5 @@
 import gov.nih.mipav.model.algorithms.*;\r
+import gov.nih.mipav.model.algorithms.utilities.AlgorithmRGBConcat;\r
 import gov.nih.mipav.model.file.*;\r
 \r
 import gov.nih.mipav.model.structures.*;\r
@@ -7,7 +8,6 @@ import gov.nih.mipav.view.*;
 import gov.nih.mipav.view.dialogs.JDialogFuzzyCMeans;\r
 \r
 import java.io.*;\r
-import java.nio.file.*;\r
 import java.util.Stack;\r
 import java.util.Vector;\r
 \r
@@ -48,6 +48,11 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
     \r
     private String coreOutputDir;\r
     \r
+    private float lightboxOpacity = 0.5f;\r
+    \r
+    private File adcLightboxFile;\r
+    private File dwiLightboxFile;\r
+    \r
     /**\r
      * Constructor.\r
      *\r
@@ -157,7 +162,7 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
         fireProgressStateChanged("Saving DWI mask ...");\r
         fireProgressStateChanged(50);\r
 \r
-        saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_DWI_seg");\r
+        saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_DWI_seg", FileUtility.XML);\r
         \r
         // get pixels from ADC within mask with intensity < 620\r
         fireProgressStateChanged("Thresholding ADC ...");\r
@@ -182,7 +187,14 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
             return;\r
         }\r
         \r
-        saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_ADC_thresh");\r
+        saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_ADC_thresh", FileUtility.XML);\r
+        \r
+        // combine threshold with ADC and save lightbox\r
+        ModelImage adcLightbox = generateLightbox(adcImage, dwiSeg, lightboxOpacity);\r
+\r
+        adcLightboxFile = saveImageFile(adcLightbox, coreOutputDir, outputBasename + "_ADC_thresh_lightbox", FileUtility.PNG);\r
+        \r
+        adcLightbox.disposeLocal();\r
         \r
         // select largest object\r
         fireProgressStateChanged("Finding core lesion ...");\r
@@ -242,7 +254,7 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
                 return;\r
             }\r
             \r
-            saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_ADC_thresh_removed");\r
+            saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_ADC_thresh_removed", FileUtility.XML);\r
         }\r
         \r
         short[] objectBuffer = keepOnlyLargestObject(dwiSeg, dwiSegBuffer);\r
@@ -257,19 +269,28 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
             return;\r
         }\r
         \r
-        saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_ADC_thresh_only_largest");\r
+        saveImageFile(dwiSeg, coreOutputDir, outputBasename + "_ADC_thresh_only_largest", FileUtility.XML);\r
         \r
-        // output core object to VOI on disk\r
-        fireProgressStateChanged("Saving core VOI ...");\r
-        fireProgressStateChanged(80);\r
+        // combine core mask with DWI and save lightbox\r
+        ModelImage dwiLightbox = generateLightbox(dwiImage, dwiSeg, lightboxOpacity);\r
         \r
-        coreVOI = maskToVOI(dwiSeg);\r
-        if (!saveVOI(dwiSeg, coreVOI, coreOutputDir, outputBasename + "_VOI")) {\r
-            // problem saving voi\r
-            displayError("Error saving core VOI");\r
-            setCompleted(false);\r
-            return;\r
-        }\r
+        dwiLightboxFile = saveImageFile(dwiLightbox, coreOutputDir, outputBasename + "_DWI_core_lightbox", FileUtility.PNG);\r
+        \r
+        dwiLightbox.disposeLocal();\r
+        \r
+        // commented out because masks seem just as useful to users\r
+        \r
+//        // output core object to VOI on disk\r
+//        fireProgressStateChanged("Saving core VOI ...");\r
+//        fireProgressStateChanged(80);\r
+//        \r
+//        coreVOI = maskToVOI(dwiSeg);\r
+//        if (!saveVOI(dwiSeg, coreVOI, coreOutputDir, outputBasename + "_VOI")) {\r
+//            // problem saving voi\r
+//            displayError("Error saving core VOI");\r
+//            setCompleted(false);\r
+//            return;\r
+//        }\r
         \r
         // save core stats to tab-delmited file\r
         if (!saveCoreStats(coreOutputDir, dwiImage.getImageFileName(), adcImage.getImageFileName(), outputBasename + "_VOI" + voiExtension, largestObject.size, adcImage.getResolutions(0))) {\r
@@ -297,7 +318,7 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
         return coreVOI;\r
     }\r
     \r
-    private void saveImageFile(final ModelImage img, final String dir, final String fileBasename) {\r
+    private File saveImageFile(final ModelImage img, final String dir, final String fileBasename, int fileType) {\r
         if (fileIO == null) {\r
             fileIO = new FileIO();\r
             fileIO.setQuiet(true);\r
@@ -316,13 +337,16 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
             opts.setEndTime(img.getExtents()[3] - 1);\r
         }\r
 \r
-        opts.setFileType(FileUtility.XML);\r
-        opts.setFileName(fileBasename + ".xml");\r
+        opts.setFileType(fileType);\r
+        final String ext = FileTypeTable.getFileTypeInfo(fileType).getDefaultExtension();\r
+        opts.setFileName(fileBasename + ext);\r
 \r
         opts.setOptionsSet(true);\r
         opts.setMultiFile(false);\r
 \r
         fileIO.writeImage(img, opts, false, false);\r
+        \r
+        return new File(dir + File.separator + fileBasename + ext);\r
     }\r
     \r
     public short[] keepOnlyLargestObject(final ModelImage img, final short[] imgBuffer) {\r
@@ -529,7 +553,7 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
      * Simple class to temporarily store the object's size, ID and seed index value. This class is used by the\r
      * identifyObjects and deleteObjects methods of AlgorithmMorphology3D class.\r
      */\r
-    private class MaskObject {\r
+    public class MaskObject {\r
 \r
         /** DOCUMENT ME! */\r
         public short id = 0;\r
@@ -619,4 +643,69 @@ public class PlugInAlgorithmStrokeSegmentation extends AlgorithmBase {
         \r
         return true;\r
     }\r
+    \r
+    private ModelImage generateLightbox(final ModelImage imgA, final ModelImage mask, float blendingOpacity) {\r
+        ModelImage lightbox = null;\r
+        \r
+        ModelImage newRGB;\r
+        if (imgA.isColorImage()) {\r
+            newRGB = (ModelImage) imgA.clone();\r
+        } else {\r
+            newRGB = new ModelImage(ModelImage.ARGB, imgA.getExtents(), imgA.getImageName());\r
+            final AlgorithmRGBConcat mathAlgo = new AlgorithmRGBConcat(imgA, imgA, imgA, newRGB, true, true, 255.0f, true);\r
+            mathAlgo.run();\r
+        }\r
+        \r
+        // set any values to fully red if set in the mask\r
+        for (int i = 0; i < mask.getDataSize(); i++) {\r
+            if (mask.getBoolean(i) == true) {\r
+                // TODO set value with opacity blending\r
+                \r
+                newRGB.setC(i, 1, 255.0f);\r
+                newRGB.setC(i, 2, 0f);\r
+                newRGB.setC(i, 3, 0f);\r
+            }\r
+        }\r
+        \r
+        // TODO change based on x/y dim size\r
+        final int zoomPercent = 125;\r
+        \r
+        // TODO change based on slice num\r
+        final int columns = 8;\r
+        final int rows = 5;\r
+        \r
+        final int rBorderVal = 0;\r
+        final int gBorderVal = 0;\r
+        final int bBorderVal = 0;\r
+        final int borderThick = 0;\r
+        \r
+        int startSlice = 0;\r
+        int endSlice = imgA.getExtents()[2] - 1;\r
+        LightboxGenerator lightGen;\r
+\r
+        try {\r
+            lightGen = new LightboxGenerator(newRGB, startSlice, endSlice, zoomPercent, rows, columns, rBorderVal, gBorderVal, bBorderVal, false, borderThick);\r
+            lightGen.run();\r
+            lightbox = lightGen.getImage();\r
+            lightbox.calcMinMax();\r
+        } catch (final Exception e) {\r
+            e.printStackTrace();\r
+        }\r
+        \r
+        newRGB.disposeLocal();\r
+        \r
+        return lightbox;\r
+    }\r
+    \r
+    public File getAdcTheshLightboxFile() {\r
+        return adcLightboxFile;\r
+    }\r
+    \r
+    public File getDwiTheshLightboxFile() {\r
+        return dwiLightboxFile;\r
+    }\r
+    \r
+    public MaskObject getLargestObject() {\r
+        return largestObject;\r
+    }\r
 }\r
index 53d3f41..0b94028 100644 (file)
@@ -2,11 +2,13 @@ import gov.nih.mipav.plugins.JDialogStandaloneScriptablePlugin;
 \r
 import gov.nih.mipav.model.algorithms.*;\r
 import gov.nih.mipav.model.file.*;\r
+import gov.nih.mipav.model.file.FileInfoBase.Unit;\r
 import gov.nih.mipav.model.scripting.*;\r
 import gov.nih.mipav.model.scripting.parameters.ParameterFactory;\r
 import gov.nih.mipav.model.structures.*;\r
 \r
 import gov.nih.mipav.view.*;\r
+import gov.nih.mipav.view.components.WidgetFactory;\r
 \r
 import java.awt.*;\r
 import java.awt.event.ActionEvent;\r
@@ -60,6 +62,10 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
     \r
     private PlugInAlgorithmStrokeSegmentation segAlgo = null;\r
     \r
+    private boolean isDicomListenerRun = false;\r
+    \r
+    private StrokeSegmentationDicomReceiver listenerParent;\r
+    \r
     private static final String svnVersion = "$Rev$";\r
 \r
     private static final String svnLastUpdate = "$Date$";\r
@@ -106,6 +112,60 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
     }\r
     \r
     /**\r
+     * Constructor for DICOM catcher.\r
+     */\r
+    public PlugInDialogStrokeSegmentation(final StrokeSegmentationDicomReceiver parent, final String dicomDir) {\r
+        super(false);\r
+\r
+        setVisible(false);\r
+        \r
+        isDicomListenerRun = true;\r
+        \r
+        listenerParent = parent;\r
+        \r
+        dirFileString = dicomDir;\r
+        \r
+        // set dir and find files\r
+        findVolumesInDir(dirFileString);\r
+        \r
+        if (adcPath != null && !adcPath.equals("")) {\r
+            final File adcFile = new File(adcPath);\r
+            adcImage = openImage(adcFile, adcImageMultifile);\r
+            \r
+            if (adcImage == null) {\r
+                System.err.println("Error opening ADC volume from file: " + adcPath);\r
+                return;\r
+            }\r
+        } else {\r
+            System.err.println("No ADC volume selected.");\r
+            return;\r
+        }\r
+        \r
+        if (dwiPath != null && !dwiPath.equals("")) {\r
+            final File dwiFile = new File(dwiPath);\r
+            dwiImage = openImage(dwiFile, dwiImageMultifile);\r
+            \r
+            if (dwiImage == null) {\r
+                System.err.println("Error opening DWI volume from file: " + dwiPath);\r
+                return;\r
+            }\r
+        } else {\r
+            System.err.println("No DWI volume selected.");\r
+            return;\r
+        }\r
+        \r
+        // default values\r
+        adcThreshold = 620;\r
+        doSymmetryRemoval = true;\r
+        doCerebellumSkip = true;\r
+        cerebellumSkipSliceMax = 7;\r
+        \r
+        if (adcImage != null && dwiImage != null) {\r
+            callAlgorithm();\r
+        }\r
+    }\r
+    \r
+    /**\r
      * Closes dialog box when the OK button is pressed and calls the algorithm.\r
      *\r
      * @param  event  Event that triggers function.\r
@@ -147,16 +207,33 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
      * @param  algorithm  Algorithm that caused the event.\r
      */\r
     public void algorithmPerformed(AlgorithmBase algorithm) {\r
-        if (algorithm instanceof PlugInAlgorithmStrokeSegmentation) {\r
-            Preferences.debug("Stroke segmentation Elapsed: " + algorithm.getElapsedTime());\r
+        PlugInAlgorithmStrokeSegmentation segAlgo = (PlugInAlgorithmStrokeSegmentation) algorithm;\r
+        \r
+        if (segAlgo instanceof PlugInAlgorithmStrokeSegmentation) {\r
+            Preferences.debug("Stroke segmentation Elapsed: " + segAlgo.getElapsedTime());\r
+            \r
+            float[] resol = adcImage.getResolutions(0);\r
+            int[] units = adcImage.getUnitsOfMeasure();\r
+            \r
+            Unit unit = Unit.getUnitFromLegacyNum(units[0]);\r
+            double[] resolCC = new double[resol.length];\r
+            for (int i = 0; i < resol.length; i++) {\r
+                resolCC[i] = unit.convertTo(resol[i], Unit.CENTIMETERS);\r
+            }\r
+            \r
+            double coreVolCC = segAlgo.getLargestObject().size * resolCC[0] * resolCC[1] * resolCC[2];\r
+            \r
+            if (listenerParent != null) {\r
+                listenerParent.emailReport(adcImage, segAlgo.getAdcTheshLightboxFile(), segAlgo.getDwiTheshLightboxFile(), coreVolCC);\r
+            }\r
 \r
-            if (algorithm.isCompleted()) {\r
+            if (segAlgo.isCompleted()) {\r
                 insertScriptLine();\r
             }\r
 \r
-            if (algorithm != null) {\r
-                algorithm.finalize();\r
-                algorithm = null;\r
+            if (segAlgo != null) {\r
+                segAlgo.finalize();\r
+                segAlgo = null;\r
             }\r
             \r
             // if running from a script, the script runner manages the adc/dwi image cleanup\r
@@ -171,11 +248,13 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
                 }\r
             }\r
 \r
-            if (isExitRequired()) {\r
+            if (!isDicomListenerRun && isExitRequired()) {\r
                 System.exit(0);\r
             } else {\r
                 dispose();\r
             }\r
+            \r
+            log("Segmentation algorithm complete.");\r
         }\r
 \r
     }\r
@@ -284,7 +363,7 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
             return;\r
         }\r
 \r
-    } // end callAlgorithm()\r
+    }\r
 \r
     protected void setGUIFromParams() {\r
         adcImage = scriptParameters.retrieveImage("adc_image");\r
@@ -294,7 +373,8 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
         \r
         doSymmetryRemoval = scriptParameters.getParams().getBoolean("do_symmetry_removal");\r
         \r
-        // TODO cerebellum skip\r
+        doCerebellumSkip = scriptParameters.getParams().getBoolean("do_cerebellum_skip");\r
+        cerebellumSkipSliceMax = scriptParameters.getParams().getInt("cerebellum_skip_slice_max");\r
         \r
         outputDir = adcImage.getImageDirectory() + File.separator;\r
     }\r
@@ -304,7 +384,8 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
         scriptParameters.storeImage(dwiImage, "dwi_image");\r
         scriptParameters.getParams().put(ParameterFactory.newParameter("adc_threshold", adcThreshold));\r
         scriptParameters.getParams().put(ParameterFactory.newParameter("do_symmetry_removal", doSymmetryRemoval));\r
-        // TODO cerebellum skip\r
+        scriptParameters.getParams().put(ParameterFactory.newParameter("do_cerebellum_skip", doCerebellumSkip));\r
+        scriptParameters.getParams().put(ParameterFactory.newParameter("cerebellum_skip_slice_max", cerebellumSkipSliceMax));\r
     }\r
     \r
     /**\r
@@ -355,8 +436,6 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
         symmetryCheckbox.setFont(serif12);\r
         mainPanel.add(symmetryCheckbox, gbc);\r
         \r
-        // TODO cerebellum skip fields\r
-        \r
         gbc.gridy++;\r
         gbc.gridx = 0;\r
         \r
@@ -705,6 +784,11 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
         \r
         boolean checkedFirstFile = false;\r
         for (File file : dirContents) {\r
+            // skip previously generated files\r
+            if (file.getName().equalsIgnoreCase("core_seg_report.html")) {\r
+                continue;\r
+            }\r
+            \r
             if (file.isDirectory()) {\r
                 findDicomFilesRecursive(file);\r
             } else {\r
@@ -736,4 +820,15 @@ public class PlugInDialogStrokeSegmentation extends JDialogStandaloneScriptableP
             }\r
         }\r
     }\r
+    \r
+    /**\r
+     * Append a line to the log output area in the DICOM listener Log tab.\r
+     * \r
+     * @param line The line to append (do not include the trailing newline).\r
+     */\r
+    public void log(final String line) {\r
+        if (listenerParent != null) {\r
+            listenerParent.log(line);\r
+        }\r
+    }\r
 }\r
diff --git a/mipav/src/plugins/PlugInDialogStrokeSegmentationListener.java b/mipav/src/plugins/PlugInDialogStrokeSegmentationListener.java
new file mode 100644 (file)
index 0000000..785c097
--- /dev/null
@@ -0,0 +1,287 @@
+import gov.nih.mipav.plugins.JDialogStandaloneScriptablePlugin;\r
+\r
+import gov.nih.mipav.model.scripting.ParserException;\r
+import gov.nih.mipav.model.scripting.parameters.ParameterFactory;\r
+\r
+import gov.nih.mipav.view.MipavUtil;\r
+import gov.nih.mipav.view.ViewUserInterface;\r
+import gov.nih.mipav.view.components.WidgetFactory;\r
+import gov.nih.mipav.view.dialogs.JDialogBase;\r
+\r
+import java.io.IOException;\r
+import java.net.InetAddress;\r
+import java.net.UnknownHostException;\r
+\r
+import java.awt.*;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.WindowEvent;\r
+import java.io.File;\r
+\r
+import javax.swing.*;\r
+\r
+\r
+public class PlugInDialogStrokeSegmentationListener extends JDialogStandaloneScriptablePlugin {\r
+    private JTextField aeField;\r
+    private JTextField portField;\r
+    private JTextField outputDirField;\r
+    private JTextField emailField;\r
+    \r
+    private WidgetFactory.ScrollTextArea logOutputArea;\r
+    \r
+    private String ae = "MIPAV-stroke";\r
+\r
+    private String ipAddress;\r
+    \r
+    private int port = 11115;\r
+\r
+    private String outputDir = new String(System.getProperty("user.home") + File.separator + "mipav" + File.separator + "dicom_catcher" + File.separator);\r
+    \r
+    private String emailAddress = "";\r
+    \r
+    private StrokeSegmentationDicomReceiver dicomReceiver;\r
+    \r
+    private static final String svnVersion = "$Rev$";\r
+\r
+    private static final String svnLastUpdate = "$Date$";\r
+    \r
+    private static final String pluginVersion = MipavUtil.getSVNChangedDate(svnLastUpdate);\r
+\r
+    public PlugInDialogStrokeSegmentationListener() {\r
+        try {\r
+            ipAddress = InetAddress.getLocalHost().getHostAddress();\r
+        } catch (UnknownHostException e) {\r
+            // TODO Auto-generated catch block\r
+            e.printStackTrace();\r
+        }\r
+        \r
+        init();\r
+    }\r
+    \r
+    private void init() {\r
+        setForeground(Color.black);\r
+        setTitle("Stroke DICOM receiver - IP: " + ipAddress + " - " + pluginVersion);\r
+        \r
+        setTitle("Stroke Segmentation " + pluginVersion);\r
+\r
+        GridBagConstraints gbc = new GridBagConstraints();\r
+        gbc.gridwidth = 1;\r
+        gbc.gridheight = 1;\r
+        gbc.anchor = GridBagConstraints.WEST;\r
+        gbc.weightx = 1;\r
+        gbc.insets = new Insets(3, 3, 3, 3);\r
+        gbc.fill = GridBagConstraints.HORIZONTAL;\r
+        gbc.gridx = 0;\r
+        gbc.gridy = 0;\r
+\r
+        JPanel mainPanel = new JPanel(new GridBagLayout());\r
+        mainPanel.setForeground(Color.black);\r
+        mainPanel.setBorder(buildTitledBorder("DICOM receiver parameters"));\r
+        \r
+        JLabel labelAE = new JLabel("AE title");\r
+        labelAE.setForeground(Color.black);\r
+        labelAE.setFont(serif12);\r
+        mainPanel.add(labelAE, gbc);\r
+        \r
+        aeField = new JTextField(20);\r
+        aeField.setText("" + ae);\r
+        gbc.fill = GridBagConstraints.NONE;\r
+        gbc.gridx++;\r
+        mainPanel.add(aeField, gbc);\r
+        \r
+        gbc.fill = GridBagConstraints.HORIZONTAL;\r
+        \r
+        gbc.gridy++;\r
+        gbc.gridx = 0;\r
+        \r
+        JLabel labelPort = new JLabel("Port");\r
+        labelPort.setForeground(Color.black);\r
+        labelPort.setFont(serif12);\r
+        mainPanel.add(labelPort, gbc);\r
+        \r
+        portField = new JTextField(20);\r
+        portField.setText("" + port);\r
+        gbc.fill = GridBagConstraints.NONE;\r
+        gbc.gridx++;\r
+        mainPanel.add(portField, gbc);\r
+        \r
+        gbc.fill = GridBagConstraints.HORIZONTAL;\r
+        \r
+        gbc.gridy++;\r
+        gbc.gridx = 0;\r
+        \r
+        JLabel labelOutput = new JLabel("Output dir");\r
+        labelOutput.setForeground(Color.black);\r
+        labelOutput.setFont(serif12);\r
+        mainPanel.add(labelOutput, gbc);\r
+        \r
+        outputDirField = new JTextField(40);\r
+        outputDirField.setText("" + outputDir);\r
+        gbc.fill = GridBagConstraints.NONE;\r
+        gbc.gridx++;\r
+        mainPanel.add(outputDirField, gbc);\r
+        \r
+        JButton dirFileButton = new JButton("Browse");\r
+        dirFileButton.setActionCommand("BrowseDir");\r
+        dirFileButton.addActionListener(this);\r
+        dirFileButton.setForeground(Color.black);\r
+        dirFileButton.setFont(serif12B);\r
+        gbc.gridx++;\r
+        mainPanel.add(dirFileButton, gbc);\r
+        \r
+        gbc.gridy++;\r
+        gbc.gridx = 0;\r
+        \r
+        JLabel labelEmail = new JLabel("Send email report to");\r
+        labelEmail.setForeground(Color.black);\r
+        labelEmail.setFont(serif12);\r
+        mainPanel.add(labelEmail, gbc);\r
+        \r
+        emailField = new JTextField(40);\r
+        emailField.setText("" + emailAddress);\r
+        gbc.fill = GridBagConstraints.NONE;\r
+        gbc.gridx++;\r
+        mainPanel.add(emailField, gbc);\r
+  \r
+        getContentPane().add(mainPanel, BorderLayout.NORTH);\r
+        \r
+        logOutputArea = WidgetFactory.buildScrollTextArea(Color.white);\r
+        logOutputArea.setBorder(JDialogBase.buildTitledBorder("DICOM receiver log"));\r
+        logOutputArea.getTextArea().setEditable(false);\r
+        logOutputArea.getTextArea().setRows(10);\r
+        \r
+        getContentPane().add(logOutputArea, BorderLayout.CENTER);\r
+\r
+        JPanel buttonPanel = new JPanel(new GridLayout());\r
+        \r
+        JButton startButton = new JButton("Start");\r
+        startButton.setActionCommand("Start");\r
+        startButton.addActionListener(this);\r
+        startButton.setForeground(Color.black);\r
+        startButton.setFont(serif12B);\r
+        buttonPanel.add(startButton);\r
+        \r
+        JButton stopButton = new JButton("Stop");\r
+        stopButton.setActionCommand("Stop");\r
+        stopButton.addActionListener(this);\r
+        stopButton.setForeground(Color.black);\r
+        stopButton.setFont(serif12B);\r
+        buttonPanel.add(stopButton);\r
+        \r
+        JButton exitButton = new JButton("Exit");\r
+        exitButton.setActionCommand("Exit");\r
+        exitButton.addActionListener(this);\r
+        exitButton.setForeground(Color.black);\r
+        exitButton.setFont(serif12B);\r
+        buttonPanel.add(exitButton);\r
+\r
+        getContentPane().add(buttonPanel, BorderLayout.SOUTH);\r
+\r
+        pack();\r
+        setVisible(true);\r
+        setResizable(true);\r
+        System.gc();\r
+    }\r
+    \r
+    public void actionPerformed(final ActionEvent event) {\r
+        if (event.getActionCommand().equalsIgnoreCase("Start")) {\r
+            if (setVariables()) {\r
+                callAlgorithm();\r
+            }\r
+        } else if (event.getActionCommand().equalsIgnoreCase("Stop")) {\r
+            stopAlgorithm();\r
+        } else if (event.getActionCommand().equalsIgnoreCase("Exit")) {\r
+            this.windowClosing(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));\r
+        } else if (event.getActionCommand().equalsIgnoreCase("BrowseDir")) {\r
+            browseDir();\r
+        }\r
+    }\r
+    \r
+    protected void setGUIFromParams() {\r
+        ae = scriptParameters.getParams().getString("AE_title");\r
+        port = scriptParameters.getParams().getInt("port");\r
+        outputDir = scriptParameters.getParams().getString("output_dir");\r
+        emailAddress = scriptParameters.getParams().getString("email_address");\r
+    }\r
+\r
+    protected void storeParamsFromGUI() throws ParserException {\r
+        scriptParameters.getParams().put(ParameterFactory.newParameter("AE_title", ae));\r
+        scriptParameters.getParams().put(ParameterFactory.newParameter("port", port));\r
+        scriptParameters.getParams().put(ParameterFactory.newParameter("output_dir", outputDir));\r
+        scriptParameters.getParams().put(ParameterFactory.newParameter("email_address", emailAddress));\r
+    }\r
+    \r
+    /**\r
+     * Perform any actions required after the running of the algorithm is complete.\r
+     */\r
+    protected void doPostAlgorithmActions() {\r
+        // nothing to do\r
+    }\r
+    \r
+    private boolean setVariables() {\r
+        ae = aeField.getText();\r
+        port = Integer.parseInt(portField.getText());\r
+        outputDir = outputDirField.getText();\r
+        emailAddress = emailField.getText();\r
+        \r
+        return true;\r
+    }\r
+    \r
+    protected void callAlgorithm() {\r
+        try {\r
+            log("Starting DICOM receiver: " + ae + " @ " + ipAddress + ":" + port);\r
+            \r
+            dicomReceiver = new StrokeSegmentationDicomReceiver(ipAddress, port, ae, outputDir, emailAddress, logOutputArea);\r
+        } catch (IOException e) {\r
+            // TODO Auto-generated catch block\r
+            e.printStackTrace();\r
+            \r
+            stopAlgorithm();\r
+        }\r
+    }\r
+    \r
+    private void stopAlgorithm() {\r
+        if (dicomReceiver != null) {\r
+            log("Stopping DICOM receiver: " + ae + " @ " + ipAddress + ":" + port);\r
+            if (!dicomReceiver.shutdownReceiver()) {\r
+                MipavUtil.displayError("Unable to shutdown DICOM receiver thread.");\r
+            }\r
+        }\r
+    }\r
+    \r
+    public void windowClosing(WindowEvent event) {\r
+        stopAlgorithm();\r
+        \r
+        super.windowClosing(event);\r
+    }\r
+    \r
+    private boolean browseDir() {\r
+        String initDir = outputDirField.getText();\r
+        if (initDir.equals("") || !(new File(initDir).exists())) {\r
+            initDir = ViewUserInterface.getReference().getDefaultDirectory();\r
+        }\r
+        \r
+        final JFileChooser chooser = new JFileChooser(initDir);\r
+\r
+        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\r
+        chooser.setDialogTitle("Choose directory containing ADC and DWI volumes");\r
+        final int returnValue = chooser.showOpenDialog(this);\r
+        if (returnValue == JFileChooser.APPROVE_OPTION) {\r
+            outputDirField.setText(chooser.getSelectedFile().getAbsolutePath() + File.separator);\r
+\r
+            outputDir = chooser.getSelectedFile().getAbsolutePath() + File.separator;\r
+            \r
+            return true;\r
+        }\r
+        \r
+        return false;\r
+    }\r
+    \r
+    /**\r
+     * Append a line to the log output area in the Log tab.\r
+     * \r
+     * @param line The line to append (do not include the trailing newline).\r
+     */\r
+    public void log(final String line) {\r
+        logOutputArea.getTextArea().append(line + "\n");\r
+    }\r
+}
\ No newline at end of file
diff --git a/mipav/src/plugins/PlugInStrokeSegmentationListener.java b/mipav/src/plugins/PlugInStrokeSegmentationListener.java
new file mode 100644 (file)
index 0000000..4ceb95e
--- /dev/null
@@ -0,0 +1,10 @@
+import gov.nih.mipav.plugins.*;\r
+\r
+\r
+public class PlugInStrokeSegmentationListener implements PlugInGeneric {\r
+    public static final String[] CATEGORY = {"Stroke"};\r
+\r
+    public void run() {\r
+        new PlugInDialogStrokeSegmentationListener();\r
+    }\r
+}\r
diff --git a/mipav/src/plugins/StrokeSegmentationDicomReceiver.java b/mipav/src/plugins/StrokeSegmentationDicomReceiver.java
new file mode 100644 (file)
index 0000000..1960d70
--- /dev/null
@@ -0,0 +1,714 @@
+/* ***** BEGIN LICENSE BLOCK *****\r
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1\r
+ *\r
+ * The contents of this file are subject to the Mozilla Public License Version\r
+ * 1.1 (the "License"); you may not use this file except in compliance with\r
+ * the License. You may obtain a copy of the License at\r
+ * http://www.mozilla.org/MPL/\r
+ *\r
+ * Software distributed under the License is distributed on an "AS IS" basis,\r
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\r
+ * for the specific language governing rights and limitations under the\r
+ * License.\r
+ *\r
+ * The Original Code is part of dcm4che, an implementation of DICOM(TM) in\r
+ * Java(TM), hosted at https://github.com/dcm4che.\r
+ *\r
+ * The Initial Developer of the Original Code is\r
+ * Agfa Healthcare.\r
+ * Portions created by the Initial Developer are Copyright (C) 2011\r
+ * the Initial Developer. All Rights Reserved.\r
+ *\r
+ * Contributor(s):\r
+ * See @authors listed below\r
+ *\r
+ * Alternatively, the contents of this file may be used under the terms of\r
+ * either the GNU General Public License Version 2 or later (the "GPL"), or\r
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),\r
+ * in which case the provisions of the GPL or the LGPL are applicable instead\r
+ * of those above. If you wish to allow use of your version of this file only\r
+ * under the terms of either the GPL or the LGPL, and not to allow others to\r
+ * use your version of this file under the terms of the MPL, indicate your\r
+ * decision by deleting the provisions above and replace them with the notice\r
+ * and other provisions required by the GPL or the LGPL. If you do not delete\r
+ * the provisions above, a recipient may use your version of this file under\r
+ * the terms of any one of the MPL, the GPL or the LGPL.\r
+ *\r
+ * ***** END LICENSE BLOCK ***** */\r
+\r
+\r
+import gov.nih.mipav.model.file.FileInfoDicom;\r
+import gov.nih.mipav.model.structures.ModelImage;\r
+\r
+import gov.nih.mipav.view.components.WidgetFactory;\r
+\r
+import java.io.*;\r
+import java.security.GeneralSecurityException;\r
+import java.util.*;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.ScheduledExecutorService;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import org.dcm4che3.data.Tag;\r
+import org.dcm4che3.data.Attributes;\r
+import org.dcm4che3.data.VR;\r
+import org.dcm4che3.io.DicomInputStream;\r
+import org.dcm4che3.io.DicomOutputStream;\r
+import org.dcm4che3.io.DicomInputStream.IncludeBulkData;\r
+import org.dcm4che3.net.*;\r
+import org.dcm4che3.net.pdu.PresentationContext;\r
+import org.dcm4che3.net.service.BasicCEchoSCP;\r
+import org.dcm4che3.net.service.BasicCStoreSCP;\r
+import org.dcm4che3.net.service.DicomServiceException;\r
+import org.dcm4che3.net.service.DicomServiceRegistry;\r
+import org.dcm4che3.util.AttributesFormat;\r
+import org.dcm4che3.util.SafeClose;\r
+import org.dcm4che3.util.TagUtils;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+\r
+\r
+/**\r
+ * Adapted from dcm4che's StoreSCP tool to catch files, sort them, and run them through the stroke segmentation plugin.\r
+ */\r
+public class StrokeSegmentationDicomReceiver {\r
+\r
+    private static final Logger LOG = LoggerFactory.getLogger(StrokeSegmentationDicomReceiver.class);\r
+\r
+    private static ResourceBundle rb =\r
+        ResourceBundle.getBundle("org.dcm4che3.tool.storescp.messages");\r
+    private static final String PART_EXT = ".part";\r
+\r
+    private final Device device = new Device("storescp");\r
+    private final ApplicationEntity ae = new ApplicationEntity("*");\r
+    private final Connection conn = new Connection();\r
+    private File storageDir;\r
+    private AttributesFormat filePathFormat;\r
+    private int status;\r
+    private final BasicCStoreSCP cstoreSCP = new BasicCStoreSCP("*") {\r
+\r
+        @Override\r
+        protected void store(Association as, PresentationContext pc,\r
+                Attributes rq, PDVInputStream data, Attributes rsp)\r
+                throws IOException {\r
+            rsp.setInt(Tag.Status, VR.US, status);\r
+            if (storageDir == null)\r
+                return;\r
+            \r
+            if (!addedCloseListener) {\r
+                as.addAssociationListener(new AssociationClose());\r
+                receivedFileList = new Vector<File>();\r
+                addedCloseListener = true;\r
+            }\r
+\r
+            String cuid = rq.getString(Tag.AffectedSOPClassUID);\r
+            String iuid = rq.getString(Tag.AffectedSOPInstanceUID);\r
+            String tsuid = pc.getTransferSyntax();\r
+            File file = new File(storageDir, iuid + PART_EXT);\r
+            try {\r
+                storeTo(as, as.createFileMetaInformation(iuid, cuid, tsuid),\r
+                        data, file);\r
+                final String outFileName = new String(filePathFormat == null ? iuid : filePathFormat.format(parse(file)));\r
+                final File outFile = new File(storageDir, outFileName);\r
+                renameTo(as, file, outFile);\r
+                log("Received file: " + outFileName);\r
+                receivedFileList.add(outFile);\r
+                \r
+            } catch (Exception e) {\r
+                deleteFile(as, file);\r
+                throw new DicomServiceException(Status.ProcessingFailure, e);\r
+            }\r
+        }\r
+\r
+    };\r
+    \r
+    private boolean addedCloseListener = false;\r
+    \r
+    private Vector<File> receivedFileList;\r
+    \r
+    private String serverIP;\r
+    private int serverPort;\r
+    private String serverAE;\r
+    \r
+    private String emailAddress;\r
+    \r
+    private WidgetFactory.ScrollTextArea logOutputArea;\r
+\r
+    public StrokeSegmentationDicomReceiver(final String ip, final int port, final String curAE, final String outputDir, final String email, final WidgetFactory.ScrollTextArea area) throws IOException {\r
+        serverIP = ip;\r
+        serverPort = port;\r
+        serverAE = curAE;\r
+        \r
+        emailAddress = email;\r
+        \r
+        logOutputArea = area;\r
+        \r
+        conn.setBindAddress(serverIP);\r
+        conn.setPort(serverPort);\r
+        \r
+        device.setDimseRQHandler(createServiceRegistry());\r
+        device.addConnection(conn);\r
+        device.addApplicationEntity(ae);\r
+        ae.setAssociationAcceptor(true);\r
+        ae.addConnection(conn);\r
+        \r
+        ae.setAETitle(serverAE);\r
+        \r
+        conn.setReceivePDULength(Connection.DEF_MAX_PDU_LENGTH);\r
+        conn.setSendPDULength(Connection.DEF_MAX_PDU_LENGTH);\r
+        conn.setMaxOpsInvoked(0);\r
+        conn.setMaxOpsPerformed(0);\r
+        conn.setPackPDV(false);\r
+        conn.setConnectTimeout(0);\r
+        conn.setRequestTimeout(0);\r
+        conn.setAcceptTimeout(0);\r
+        conn.setReleaseTimeout(0);\r
+        conn.setResponseTimeout(0);\r
+        conn.setRetrieveTimeout(0);\r
+        conn.setIdleTimeout(0);\r
+        conn.setSocketCloseDelay(Connection.DEF_SOCKETDELAY);\r
+        conn.setSendBufferSize(0);\r
+        conn.setReceiveBufferSize(0);\r
+        conn.setTcpNoDelay(false);\r
+//        configureTLS(conn, cl);\r
+        \r
+        setStatus(0);\r
+        \r
+        ae.addTransferCapability(new TransferCapability(null, "*", TransferCapability.Role.SCP, "*"));\r
+        \r
+        setStorageDirectory(new File(outputDir));\r
+        \r
+        setStorageFilePathFormat("{00080020}.{00080030}/{00200011}.{00200012}.{00200013}.dcm");\r
+        \r
+        // anon versions don't include study/series uids\r
+        //setStorageFilePathFormat("{00100020}/{0020000D}/{0020000E}/{00080008}/{00080018}.dcm");\r
+        \r
+        ExecutorService executorService = Executors.newCachedThreadPool();\r
+        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();\r
+        device.setScheduledExecutor(scheduledExecutorService);\r
+        device.setExecutor(executorService);\r
+        try {\r
+            device.bindConnections();\r
+        } catch (GeneralSecurityException e) {\r
+            // TODO Auto-generated catch block\r
+            e.printStackTrace();\r
+        }\r
+    }\r
+\r
+    private void storeTo(Association as, Attributes fmi, \r
+            PDVInputStream data, File file) throws IOException  {\r
+        LOG.info("{}: M-WRITE {}", as, file);\r
+        file.getParentFile().mkdirs();\r
+        DicomOutputStream out = new DicomOutputStream(file);\r
+        try {\r
+            out.writeFileMetaInformation(fmi);\r
+            data.copyTo(out);\r
+        } finally {\r
+            SafeClose.close(out);\r
+        }\r
+    }\r
+\r
+    private static void renameTo(Association as, File from, File dest)\r
+            throws IOException {\r
+        LOG.info("{}: M-RENAME {} to {}", as, from, dest);\r
+        if (!dest.getParentFile().mkdirs())\r
+            dest.delete();\r
+        if (!from.renameTo(dest))\r
+            throw new IOException("Failed to rename " + from + " to " + dest);\r
+    }\r
+\r
+    private static Attributes parse(File file) throws IOException {\r
+        DicomInputStream in = new DicomInputStream(file);\r
+        try {\r
+            in.setIncludeBulkData(IncludeBulkData.NO);\r
+            return in.readDataset(-1, Tag.PixelData);\r
+        } finally {\r
+            SafeClose.close(in);\r
+        }\r
+    }\r
+\r
+    private static void deleteFile(Association as, File file) {\r
+        if (file.delete())\r
+            LOG.info("{}: M-DELETE {}", as, file);\r
+        else\r
+            LOG.warn("{}: M-DELETE {} failed!", as, file);\r
+    }\r
+\r
+    private DicomServiceRegistry createServiceRegistry() {\r
+        DicomServiceRegistry serviceRegistry = new DicomServiceRegistry();\r
+        serviceRegistry.addDicomService(new BasicCEchoSCP());\r
+        serviceRegistry.addDicomService(cstoreSCP);\r
+        return serviceRegistry;\r
+    }\r
+\r
+    public void setStorageDirectory(File storageDir) {\r
+        if (storageDir != null)\r
+            storageDir.mkdirs();\r
+        this.storageDir = storageDir;\r
+    }\r
+\r
+    public void setStorageFilePathFormat(String pattern) {\r
+        this.filePathFormat = new AttributesFormat(pattern);\r
+    }\r
+\r
+    public void setStatus(int status) {\r
+        this.status = status;\r
+    }\r
+\r
+    private class AssociationClose implements AssociationListener {\r
+        public void onClose(Association association) {\r
+            log("Received association close request");\r
+            \r
+            boolean foundADC = false;\r
+            boolean foundDWI = false;\r
+            \r
+            Vector<File> adcFiles = new Vector<File>();\r
+            Vector<File> dwiFiles = new Vector<File>();\r
+            \r
+            for (File file : receivedFileList) {\r
+                try {\r
+                    Attributes attr = parse(file);\r
+                    \r
+                    final String[] imageTypes = attr.getStrings(TagUtils.toTag(0x0008, 0x0008));\r
+                    \r
+                    for (String val : imageTypes) {\r
+                        if (val.equalsIgnoreCase("ADC") || val.equalsIgnoreCase("ADC_UNSPECIFIED")) {\r
+                            adcFiles.add(file);\r
+                            foundADC = true;\r
+                            break;\r
+                        } else if (val.equalsIgnoreCase("SE") || val.equalsIgnoreCase("M_SE") || val.equalsIgnoreCase("TRACEW")) {\r
+                            dwiFiles.add(file);\r
+                            foundDWI = true;\r
+                            break;\r
+                        }\r
+                    }\r
+                } catch (IOException e) {\r
+                    // TODO Auto-generated catch block\r
+                    e.printStackTrace();\r
+                }\r
+            }\r
+            \r
+            String baseDicomDir = null;\r
+            \r
+            // move ADC and DWI files to their own dir under parent inside outputDir\r
+            if (foundADC) {\r
+                log("Found ADC volume in completed transfer.");\r
+                \r
+                for (File file : adcFiles) {\r
+                    try {\r
+                        if (baseDicomDir == null) {\r
+                            baseDicomDir = file.getParent();\r
+                        }\r
+                        \r
+                        renameTo(association, file, new File(file.getParent() + File.separator + "ADC" + File.separator + file.getName()));\r
+                    } catch (IOException e) {\r
+                        // TODO Auto-generated catch block\r
+                        e.printStackTrace();\r
+                    }\r
+                }\r
+            }\r
+            \r
+            if (foundDWI) {\r
+                log("Found DWI volume in completed transfer.");\r
+                \r
+                for (File file : dwiFiles) {\r
+                    try {\r
+                        if (baseDicomDir == null) {\r
+                            baseDicomDir = file.getParent();\r
+                        }\r
+                        \r
+                        renameTo(association, file, new File(file.getParent() + File.separator + "DWI" + File.separator + file.getName()));\r
+                    } catch (IOException e) {\r
+                        // TODO Auto-generated catch block\r
+                        e.printStackTrace();\r
+                    }\r
+                }\r
+            }\r
+            \r
+            if (foundADC && foundDWI) {\r
+                log("Running segmentation on datasets in " + baseDicomDir);\r
+                new PlugInDialogStrokeSegmentation(StrokeSegmentationDicomReceiver.this, baseDicomDir);\r
+            }\r
+            \r
+            addedCloseListener = false;\r
+            receivedFileList.removeAllElements();\r
+            receivedFileList = null;\r
+        }\r
+    }\r
+    \r
+    public boolean shutdownReceiver() {\r
+        try {\r
+            device.waitForNoOpenConnections();\r
+            \r
+            device.unbindConnections();\r
+        } catch (Exception e) {\r
+            // TODO Auto-generated catch block\r
+            e.printStackTrace();\r
+            return false;\r
+        }\r
+        \r
+        return true;\r
+    }\r
+    \r
+    /**\r
+     * Append a line to the log output area in the Log tab.\r
+     * \r
+     * @param line The line to append (do not include the trailing newline).\r
+     */\r
+    public void log(final String line) {\r
+        logOutputArea.getTextArea().append(line + "\n");\r
+    }\r
+    \r
+    public void emailReport(ModelImage adcImage, File adcLightboxFile, File dwiLightboxFile, double coreVolCC) {\r
+        String reportPath = generateReport(adcImage, adcLightboxFile, dwiLightboxFile, coreVolCC);\r
+        \r
+        if (emailAddress == null || emailAddress.equals("")) {\r
+            return;\r
+        }\r
+        \r
+        // TODO\r
+    }\r
+    \r
+    private String generateReport(ModelImage adcImage, File adcLightboxFile, File dwiLightboxFile, double coreVolCC) {\r
+        FileInfoDicom fileInfoDicom = (FileInfoDicom) adcImage.getFileInfo(0);\r
+        \r
+        // TODO create PDF and write to disk, return path (or null if error)\r
+        \r
+        //File pdfFile = new File(dwiLightbox.getImageDirectory() + File.separator + "core_seg_report.pdf");\r
+        //File pdfFile = new File("C:\\Users\\mccreedy\\mipav\\dicom_catcher\\20180302.100509" + File.separator + "core_seg_report.pdf");\r
+        \r
+        //String dateStr = "2000-01-01";\r
+        String dateStr = (String) fileInfoDicom.getTagTable().getValue("0008,0020");\r
+        String timeStr = (String) fileInfoDicom.getTagTable().getValue("0008,0030");\r
+        String patientName = (String) fileInfoDicom.getTagTable().getValue("0010,0010");\r
+        String coreSegVol = "" + coreVolCC;\r
+        \r
+        String outputDir = adcLightboxFile.getParent();\r
+        \r
+        String dwiPdfImage = dwiLightboxFile.getName();\r
+        String adcPdfImage = adcLightboxFile.getName();\r
+        //String dwiPdfImage = "20180302_1.100509_CoreSeg_DWI_core_lightbox.png";\r
+        //String adcPdfImage = "20180302_1.100509_CoreSeg_ADC_thresh_lightbox.png";\r
+        \r
+        // study date and time\r
+        // first initial of last name\r
+        // core seg volume in CC\r
+        // DWI with core seg + red lut\r
+        // ADC with thesh vol + red lut\r
+        // volumes generated by lightbox 8x5 (for 40 slices) 100 or 125 zoom, no border, include segmentation imageB w/ red lut\r
+        //pdfCreate(pdfFile, dateStr, timeStr, patientName, coreSegVol, dwiPdfImagePath, adcPdfImagePath);\r
+        \r
+        final int imgDisplay = 1027;\r
+        \r
+        String reportTxt = "<html>\n";\r
+        reportTxt += "<h1>" + "MIPAV Stroke Core Segmentation Report" + "</h1>\n";\r
+        reportTxt += "<ul>\n";\r
+        reportTxt += "<li>" + "<b>" + "Study date and time: " + "</b>" + convertDateTimeToISOFormat(dateStr, timeStr) + "</li>\n";\r
+        reportTxt += "<li>" + "<b>" + "Patient last name initial: " + "</b>" + getInitialFromName(patientName) + "</li>\n";\r
+        reportTxt += "<li>" + "<b>" + "Core segmentation volume (CC): " + "</b>" + coreSegVol + "</li>\n";\r
+        //reportTxt += "<li>" + "<b>" + "" + "</b>" + "" + "</li>";\r
+        reportTxt += "</ul>\n";\r
+        reportTxt += "<h3>" + "DWI volume with core segmentation" + "</h3>\n";\r
+        reportTxt += "<a href='" + adcPdfImage + "'><img src='" + dwiPdfImage + "' alt='DWI volume with core segmentation' width='" + imgDisplay + "'/></a>\n";\r
+        reportTxt += "<h3>" + "ADC volume with thresholded regions" + "</h3>\n";\r
+        reportTxt += "<a href='" + adcPdfImage + "'><img src='" + adcPdfImage + "' alt='ADC volume with thresholded regions' width='" + imgDisplay + "'/></a>\n";\r
+        reportTxt += "</html>\n";\r
+        \r
+        final String htmlReportPath = outputDir + File.separator + "core_seg_report.html";\r
+        \r
+        PrintWriter out;\r
+        try {\r
+            out = new PrintWriter(htmlReportPath);\r
+            out.print(reportTxt);\r
+            out.close();\r
+        } catch (FileNotFoundException e) {\r
+            // TODO Auto-generated catch block\r
+            e.printStackTrace();\r
+        }\r
+        \r
+        \r
+        return htmlReportPath;\r
+    }\r
+    \r
+    /**\r
+     * Converts from DICOM/US date and time format (MM/DD/YYYY) or M/D/YYYY to ISO 8601 format (YYYY-MM-DDThh:mm:ss).\r
+     * \r
+     * @param date A date string in the format MM/DD/YYYY or M/D/YYYY.\r
+     * @param time A time string in the format hh:mm:ss.fract or hhmmss.fract.\r
+     * @return An ISO 8601 formatted version of the given date and time (or the original string if not in the DICOM/US\r
+     *         date format).\r
+     */\r
+    private static final String convertDateTimeToISOFormat(final String date, String time) {\r
+        if (date == null) {\r
+            return "";\r
+        }\r
+        if (time == null) {\r
+            time = "";\r
+        }\r
+\r
+        String isoDate = date.trim();\r
+        String isoTime = time.trim();\r
+\r
+        final String datePattern = "^(\\d{1,2})[/-]*(\\d{1,2})[/-]*(\\d{4})$";\r
+        final String timePattern = "^(\\d{2})[:]?(\\d{2})[:]?(\\d{2})[.]?(\\d*)$";\r
+\r
+        Pattern p = Pattern.compile(datePattern);\r
+        Matcher m = p.matcher(isoDate);\r
+        if (m.find()) {\r
+            String month = m.group(1);\r
+            String day = m.group(2);\r
+            final String year = m.group(3);\r
+            // add leading zeroes, if necessary\r
+            if (month.length() == 1) {\r
+                month = "0" + month;\r
+            }\r
+            if (day.length() == 1) {\r
+                day = "0" + day;\r
+            }\r
+            isoDate = year + "-" + month + "-" + day;\r
+        }\r
+\r
+        p = Pattern.compile(timePattern);\r
+        m = p.matcher(isoTime);\r
+        if (m.find()) {\r
+            String hour = m.group(1);\r
+            String min = m.group(2);\r
+            String sec = m.group(3);\r
+            final String frac = m.group(4);\r
+            // add leading zeroes, if necessary\r
+            if (hour.length() == 1) {\r
+                hour = "0" + hour;\r
+            }\r
+            if (min.length() == 1) {\r
+                min = "0" + min;\r
+            }\r
+            if (sec.length() == 1) {\r
+                sec = "0" + sec;\r
+            }\r
+            isoTime = hour + ":" + min + ":" + sec;\r
+            if (frac.length() > 0) {\r
+                isoTime += "." + frac;\r
+            }\r
+        }\r
+\r
+        if (isoTime.equals("")) {\r
+            return isoDate;\r
+        } else {\r
+            return isoDate + "T" + isoTime;\r
+        }\r
+    }\r
+\r
+    private String getInitialFromName(final String dicomName) {\r
+        return dicomName.substring(0, 1);\r
+    }\r
+    \r
+/*\r
+    *//**\r
+     * Creates the PDF file and creates several tables for scanner information as well\r
+     * as the VOI statistic information\r
+     *\r
+     * @return the content stream for adding statistics to the first page\r
+     *//*\r
+    protected void pdfCreate(File pdfFile, String dateStr, String timeStr, String patientName, String coreSegVol, String dwiPdfImagePath, String adcPdfImagePath) {\r
+        PDDocument doc;\r
+        \r
+        // the document\r
+        doc = null;\r
+\r
+        //initialize blank PDF\r
+        doc = new PDDocument();\r
+        \r
+        try {\r
+            doc.save(pdfFile.toString());\r
+        } catch (IOException e) {\r
+            System.out.println("44 IOException " + e + " on doc.save(pdfFile.toString())");\r
+            return;\r
+        }\r
+        \r
+        try {\r
+            doc.close();\r
+        } catch (IOException e) {\r
+            System.out.println("45 IOException " + e + " on doc.close()");\r
+            return;\r
+        }\r
+        \r
+        PDPageContentStream contentStream = null;\r
+        //System.out.println("46 Have executed PDPageContentStream contentStream = null");\r
+        \r
+        try {\r
+            try {\r
+                doc = PDDocument.load( pdfFile );\r
+            } catch(IOException e) {\r
+                System.out.println("47 IOException "+ e + " on doc = PDDocument.load( pdfFile )");\r
+                return;\r
+            }\r
+            \r
+            PDFont boldFont = PDType1Font.HELVETICA_BOLD;\r
+            int headerFontSize = 18;\r
+            PDFont regFont = PDType1Font.HELVETICA;\r
+            int regFontSize = 12;\r
+\r
+            doc.addPage(new PDPage());\r
+            doc.addPage(new PDPage());\r
+            doc.addPage(new PDPage());\r
+            \r
+            PDPageTree pageTree = doc.getPages();\r
+            \r
+            PDPage textPage = pageTree.get(0);\r
+            \r
+            PDRectangle pageSize = textPage.getMediaBox();\r
+            \r
+            String title = "MIPAV Stroke Core Segmentation Report";\r
+            \r
+            float stringWidth = boldFont.getStringWidth( title );\r
+            \r
+            //System.out.println("70 stringWidth = " + stringWidth);\r
+            \r
+            float centeredPosition = (pageSize.getWidth() - (stringWidth*headerFontSize)/1000f)/2f;\r
+            // (float) (PDPage.PAGE_SIZE_LETTER.getWidth()/2.0)-120\r
+            \r
+            float valueOffset = 250f;\r
+            float newLineOffset = -15f;\r
+            \r
+            //System.out.println("71 centeredPosition = " + centeredPosition);\r
+            \r
+            try {\r
+                contentStream = new PDPageContentStream(doc, textPage);\r
+            } catch(IOException e) {\r
+                System.out.println("72 IOException " + e + " on contentStream = new PDPageContentStream(doc, page, false, true)");\r
+                return;\r
+            }\r
+            \r
+            try {\r
+                contentStream.beginText();\r
+            } catch(IOException e) {\r
+                System.out.println("73 IOException " + e + "on contentStream.beginText()");\r
+                return;\r
+            }\r
+\r
+            contentStream.setFont(boldFont, headerFontSize);\r
+            contentStream.moveTextPositionByAmount(centeredPosition, 750 );\r
+            contentStream.drawString( title );\r
+\r
+            // move to left side\r
+            contentStream.moveTextPositionByAmount(-centeredPosition + 10, newLineOffset - 10);\r
+            \r
+            // study date and time\r
+            contentStream.setFont(boldFont, regFontSize);\r
+            contentStream.drawString("Study date and time: ");\r
+            contentStream.moveTextPositionByAmount(valueOffset, 0);\r
+            contentStream.setFont(regFont, regFontSize);\r
+            contentStream.drawString(dateStr + " @ " + timeStr);\r
+\r
+            contentStream.moveTextPositionByAmount(-valueOffset, newLineOffset);\r
+            \r
+            // first initial of last name\r
+            contentStream.setFont(boldFont, regFontSize);\r
+            contentStream.drawString("Patient last name initial: ");\r
+            contentStream.moveTextPositionByAmount(valueOffset, 0);\r
+            contentStream.setFont(regFont, regFontSize);\r
+            contentStream.drawString(patientName);\r
+            \r
+            contentStream.moveTextPositionByAmount(-valueOffset, newLineOffset);\r
+            \r
+            // core seg volume in CC\r
+            contentStream.setFont(boldFont, regFontSize);\r
+            contentStream.drawString("Core segmentation volume (CC): ");\r
+            contentStream.moveTextPositionByAmount(valueOffset, 0);\r
+            contentStream.setFont(regFont, regFontSize);\r
+            contentStream.drawString(coreSegVol);\r
+            \r
+            contentStream.endText();\r
+            \r
+            contentStream.close();\r
+            \r
+            PDPage dwiPage = pageTree.get(1);\r
+            \r
+            try {\r
+                contentStream = new PDPageContentStream(doc, dwiPage);\r
+            } catch(IOException e) {\r
+                System.out.println("72 IOException " + e + " on contentStream = new PDPageContentStream(doc, page, false, true)");\r
+                return;\r
+            }\r
+\r
+            // DWI with core seg + red lut\r
+            \r
+            PDImageXObject pdDwiImage = PDImageXObject.createFromFile(dwiPdfImagePath, doc);\r
+            \r
+//            InputStream is = new BufferedInputStream(new FileInputStream(dwiPdfImagePath));\r
+//            PDXObjectImage pdDwiImage = new PDPixelMap(new PDStream(doc, is));\r
+//            is.close();\r
+//            is = null;\r
+//            \r
+//            contentStream.drawImage(pdDwiImage, pdDwiImage.getWidth(), pdDwiImage.getHeight());\r
+            contentStream.drawImage(pdDwiImage, pageSize.getWidth(), pageSize.getHeight());\r
+            \r
+            contentStream.close();\r
+            \r
+            // ADC with thesh vol + red lut\r
+            \r
+            PDPage adcPage = pageTree.get(2);\r
+            \r
+            try {\r
+                contentStream = new PDPageContentStream(doc, adcPage);\r
+            } catch(IOException e) {\r
+                System.out.println("72 IOException " + e + " on contentStream = new PDPageContentStream(doc, page, false, true)");\r
+                return;\r
+            }\r
+            \r
+            PDImageXObject pdAdcImage = PDImageXObject.createFromFile(adcPdfImagePath, doc);\r
+            \r
+//            is = new BufferedInputStream(new FileInputStream(adcPdfImagePath));\r
+//            PDXObjectImage pdAdcImage = new PDPixelMap(new PDStream(doc, is));\r
+//            is.close();\r
+//            is = null;\r
+            \r
+//            contentStream.drawImage(pdAdcImage, pdAdcImage.getWidth(), pdAdcImage.getHeight());\r
+            contentStream.drawImage(pdAdcImage, pageSize.getWidth(), pageSize.getHeight());\r
+            \r
+            contentStream.close();\r
+    \r
+            //System.out.println("200 pdfFile.toString() = " + pdfFile.toString());\r
+            try {\r
+                doc.save( pdfFile.toString() );\r
+            } catch (IOException e) {\r
+                System.out.println("202 IOException " + e + " on doc.save( pdfFile.toString()");\r
+                return;\r
+            }\r
+            \r
+            MipavUtil.displayInfo("PDF saved to: " + pdfFile);\r
+            ViewUserInterface.getReference().getMessageFrame().append("PDF saved to: " + pdfFile +  "\n", ViewJFrameMessage.DATA);\r
+        } catch (Exception e) {\r
+            ViewUserInterface.getReference().getMessageFrame().append("Error occured in PDF generation calling method\n", ViewJFrameMessage.DEBUG);\r
+            e.printStackTrace();\r
+            \r
+            if(contentStream != null) {\r
+                try {\r
+                    contentStream.close();\r
+                } catch (IOException e1) {\r
+                    e.printStackTrace();\r
+                    MipavUtil.displayError("Content stream could not be closed, please restart MIPAV.");\r
+                }\r
+            }\r
+            if( doc != null ) {\r
+                try {\r
+                    doc.close();\r
+                } catch (IOException e1) {\r
+                    e.printStackTrace();\r
+                    MipavUtil.displayError("PDF document could not be closed, please restart MIPAV.");\r
+                }\r
+            }\r
+        }  finally {\r
+            if( doc != null ) {\r
+                try {\r
+                    doc.close();\r
+                } catch (IOException e) {\r
+                    // TODO Auto-generated catch block\r
+                    e.printStackTrace();\r
+                }\r
+            }\r
+        }\r
+    }*/\r
+}
\ No newline at end of file