Example #1
0
    def __calculateEnhancedVolume__(self):
        """ Calculate the enhanced volume for the first time
        :return:
        """
        logic = slicer.modules.volumes.logic()
        self.currentEnhancedVolume = logic.CloneVolume(
            self.current2DVectorVolume,
            self.current2DVectorVolume.GetName() + "_enh")
        #self.currentEnhancedVectorVolume = slicer.vtkMRMLVectorVolumeNode()
        #self.currentEnhancedVectorVolume.Copy(self.current2DVectorVolume)
        #slicer.mrmlScene.AddNode(self.currentEnhancedVectorVolume)
        self.currentEnhancedImageArray = slicer.util.array(
            self.currentEnhancedVolume.GetID())

        # Set a storage node for the volume and load the data
        #image_array = slicer.util.array(self.current2DVectorVolume.GetName())
        #image_array = image_array[0]    # Remove "extra" dimension
        self.enhancer = Enhancer(self.currentEnhancedImageArray[0])
        self.enhancementFineTuning()
Example #2
0
class EyeSpotLogic(ScriptedLoadableModuleLogic):
    """This class should implement all the actual
    computation done by your module.    The interface
    should be such that other python code can import
    this class and make use of the functionality without
    requiring an instance of the Widget.
    Uses ScriptedLoadableModuleLogic base class, available at:
    https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
    """
    def __init__(self):
        """Constructor. """
        ScriptedLoadableModuleLogic.__init__(self)

        self.current2DVectorVolume = None  # Original 2D image
        self.currentScalarVolume = None  # 3D volume to represent the original image
        self.currentLabelmapVolume = None  # Labelmap
        self.currentEnhancedVolume = None  # Enhanced 3D volume
        self.currentEnhancedImageArray = None  # Numpy array associated with the enhanced volume
        self.currentReportData = None  # Dictionary for the report data (lesions, comments, etc.)

        self.enhancer = None  # Enhancer object

        # Customized colors for the labelmap
        p = self.getResourcePath("EyeSpot_Colors.ctbl")
        self.colorTableNode = slicer.modules.colors.logic().LoadColorFile(p)

        self.isCurrentReportSaved = False  # Current report has been saved

    def loadCase(self, directory):
        """ Load a new eye image and create a labelmap for it.
        It also sets the images as the active ones in the scene
        :param filePath: directory where all the images/data are stored
        :return: True if the image and has been successfully loaded
        """
        properties = {}
        properties['singleFile'] = True
        dirName = os.path.basename(os.path.normpath(directory)).lower()
        print("dirName: " + dirName)
        for f in os.listdir(directory):
            print("Analyzing " + f)
            # fileExtension = f.split(".")[-1]
            if f.lower() in (dirName + ".png", dirName + ".tiff",
                             dirName + ".jpg"):
                # Load the main image
                p = os.path.join(directory, f)
                print("Loading file " + p)
                (loaded, volume) = slicer.util.loadVolume(p,
                                                          properties,
                                                          returnNode=True)
                if loaded:
                    logging.debug("volume loaded")
                    self.current2DVectorVolume = volume
                    # Make sure that the volume is named as the directory to avoid wrong names
                    self.current2DVectorVolume.SetName(dirName)
                    self.currentCaseDir = directory
                    # Convert the 2D image in a scalar node
                    self.currentScalarVolume = self.getScalarNodeFrom2DNode(
                        volume)
            elif f == dirName + "_label.nrrd":
                # Load a previously existing labelmap
                p = os.path.join(directory, f)
                (loaded, volume) = slicer.util.loadLabelVolume(p,
                                                               returnNode=True)
                if loaded:
                    self.currentLabelmapVolume = volume
            # TODO: reuse previously calculated enhanced volume. We will need to calculate the needed matrixes
            # elif f == dirName + "_enh.nrrd":
            #     # Load a previously existing enhanced volume
            #     p = os.path.join(directory, f)
            #     (loaded, volume) = slicer.util.loadVolume(p, returnNode=True)
            #     if loaded:
            #         # Create all the required structures for the Enhanced volume
            #         self.currentEnhancedVolume = volume
            #         self.currentEnhancedImageArray = slicer.util.array(self.currentEnhancedVolume.GetID())
            #         self.enhancer = Enhancer(self.currentEnhancedImageArray[0])
            elif f == "report.json":
                p = os.path.join(directory, f)
                with open(p) as f:
                    self.currentReportData = json.load(f)

        # If the labelmap didn't exist, create a new one
        if self.currentLabelmapVolume is None:
            # Remove file extension
            nodeName = os.path.basename(
                self.__getReportImagePath__(1)).replace(".png", "")
            self.currentLabelmapVolume = self.createNewLabelmap(
                self.currentScalarVolume, nodeName)

        # Use our customized colors
        displayNode = self.currentLabelmapVolume.GetDisplayNode()
        displayNode.SetAndObserveColorNodeID(self.colorTableNode.GetID())

        return self.currentScalarVolume is not None

    def getCurrentDataFolder(self):
        """ Get the folder where the original image was loaded from, where we will store all the related information
        :return:
        """
        return os.path.dirname(
            self.current2DVectorVolume.GetStorageNode().GetFileName())

    def getScalarNodeFrom2DNode(self, vectorNode):
        """ Get a 3D vtkMRMLScalarVolumeNode (1 slice) from a vtkMRMLVectorVolumeNode
        :param vectorNode:
        :return: output volume
        """
        extract = vtk.vtkImageExtractComponents()
        extract.SetComponents(0, 1, 2)
        luminance = vtk.vtkImageLuminance()
        extract.SetInputConnection(vectorNode.GetImageDataConnection())
        luminance.SetInputConnection(extract.GetOutputPort())
        luminance.Update()
        ijkToRAS = vtk.vtkMatrix4x4()
        vectorNode.GetIJKToRASMatrix(ijkToRAS)

        outputVolume = slicer.mrmlScene.CreateNodeByClass(
            "vtkMRMLScalarVolumeNode")
        slicer.mrmlScene.AddNode(outputVolume)
        outputVolume.SetName(vectorNode.GetName() + "_3D")
        outputVolume.SetIJKToRASMatrix(ijkToRAS)
        outputVolume.SetImageDataConnection(luminance.GetOutputPort())
        return outputVolume

    def createNewLabelmap(self, scalarNode, nodeName):
        """ Create a new labelmap based on a scalar node
        :param scalarNode: scalar node
        :param nodeName: name of the node
        :return: new labelmap node
        """
        logic = slicer.modules.volumes.logic()
        node = logic.CreateAndAddLabelVolume(scalarNode, nodeName)
        # Make sure that the node name is correct, because sometimes the scene adds a suffix
        node.SetName(nodeName)
        return node

    def getEnhancedVolume(self):
        """ Get the enhanced volume for the current case.
        Note the volume will be cached
        :return: enhanced volume
        """
        if self.currentEnhancedVolume is None:
            self.__calculateEnhancedVolume__()
        return self.currentEnhancedVolume

    def __calculateEnhancedVolume__(self):
        """ Calculate the enhanced volume for the first time
        :return:
        """
        logic = slicer.modules.volumes.logic()
        self.currentEnhancedVolume = logic.CloneVolume(
            self.current2DVectorVolume,
            self.current2DVectorVolume.GetName() + "_enh")
        #self.currentEnhancedVectorVolume = slicer.vtkMRMLVectorVolumeNode()
        #self.currentEnhancedVectorVolume.Copy(self.current2DVectorVolume)
        #slicer.mrmlScene.AddNode(self.currentEnhancedVectorVolume)
        self.currentEnhancedImageArray = slicer.util.array(
            self.currentEnhancedVolume.GetID())

        # Set a storage node for the volume and load the data
        #image_array = slicer.util.array(self.current2DVectorVolume.GetName())
        #image_array = image_array[0]    # Remove "extra" dimension
        self.enhancer = Enhancer(self.currentEnhancedImageArray[0])
        self.enhancementFineTuning()
        #self.currentEnhancedVectorVolume = self.currentScalarVolume

    def enhancementFineTuning(self, vascularFactor=0.5, enhancementFactor=0.5):
        """ Adjust the enhancement with fine tuning params
        :param vascularFactor: 0-1 value
        :param enhancementFactor: 0-1 value
        """
        output_array = self.enhancer.execute_enhancement(
            vascularFactor, enhancementFactor)
        self.currentEnhancedImageArray[0, :, :, :] = output_array[:, :, :]
        self.currentEnhancedVolume.Modified()  # Refresh display

    def getResourcePath(self, fileName=""):
        """ Get a full path for the current resoure file name
        :param fileName:
        :return: full path to the corresponding file name in the Resources folder
        """
        return os.path.join(os.path.dirname(os.path.realpath(__file__)),
                            "Resources", fileName)

    def saveReport(self, currentValuesDict):
        """ Save a JSON text file with the current values of the GUI stored in a dictionary.
        It also saves the current enhanced and labelmap volumes
        :param currentValuesDict: dictionary of values
        :return: file where the report was stored
        """
        p = os.path.join(self.getCurrentDataFolder(), "report.json")
        with open(p, "w+b") as f:
            json.dump(currentValuesDict, f)

        # Save the labelmap
        if self.currentLabelmapVolume.GetStorageNode() is None:
            SlicerUtil.saveNewNode(
                self.currentLabelmapVolume,
                os.path.join(self.getCurrentDataFolder(),
                             self.currentLabelmapVolume.GetName() + ".nrrd"))
        else:
            self.currentLabelmapVolume.GetStorageNode().WriteData(
                self.currentLabelmapVolume)

        # Save the enhanced volume
        if self.getEnhancedVolume().GetStorageNode() is None:
            SlicerUtil.saveNewNode(
                self.currentEnhancedVolume,
                os.path.join(self.getCurrentDataFolder(),
                             self.currentEnhancedVolume.GetName() + ".nrrd"))
        else:
            self.currentEnhancedVolume.GetStorageNode().WriteData(
                self.currentEnhancedVolume)

        self.isCurrentReportSaved = True
        return p

    def getKey(self, description):
        """ Get a string formatted with the chosen format for keys used in the template.
        Ex: getKey("Cotton lesions") = "@@COTTON_LESIONS@@"
        :param description:
        :return:
        """
        return "@@{0}@@".format(description.replace(" ", "_").upper())

    def __printSnapshots__(self):
        """ Generate snapshots of all the volumes to be displayed in the report
        """
        # Save a png file from different images.
        # We manipulate the widget to have the aspect that we want for the screenshot
        lm = slicer.app.layoutManager()

        # Take original image with Labelmap
        # Change to Red
        SlicerUtil.changeLayoutRedSingle()
        sliceWidget = lm.sliceWidget("Red")
        controller = sliceWidget.sliceController()
        controller.fitSliceToBackground()
        # Hide the slider bar (just show the picture)
        controller.hide()
        sliceView = sliceWidget.sliceView()
        sliceView.cornerAnnotation().ClearAllTexts()
        sliceView.scheduleRender()
        # Take the snapshot
        SlicerUtil.takeSnapshot(os.path.join(self.getCurrentDataFolder(),
                                             self.__getReportImagePath__(1)),
                                type=slicer.qMRMLScreenShotDialog.Red,
                                hideAnnotations=True)

        # Restore the regular controller
        controller.show()

        # Enhanced (with and without labelmap)
        SlicerUtil.changeLayoutYellowSingle()

        # If the user didn't open the enhanced volume yet, force it
        enhancedVol = self.getEnhancedVolume()
        yellowCompositeNode = slicer.mrmlScene.GetNodeByID(
            'vtkMRMLSliceCompositeNodeYellow')
        if yellowCompositeNode.GetBackgroundVolumeID() != enhancedVol.GetID():
            yellowCompositeNode.SetBackgroundVolumeID(enhancedVol.GetID())
            yellowSliceNode = slicer.mrmlScene.GetNodeByID(
                'vtkMRMLSliceNodeYellow')
            yellowSliceNode.SetOrientationToAxial()
            # Assign the labelmap
            yellowCompositeNode.SetLabelVolumeID(
                self.currentLabelmapVolume.GetID())

        controller = lm.sliceWidget("Yellow").sliceController()
        controller.fitSliceToBackground()
        # Hide the slider bar (just show the picture)
        controller.hide()
        # Take the snapshot
        SlicerUtil.takeSnapshot(os.path.join(self.getCurrentDataFolder(),
                                             self.__getReportImagePath__(3)),
                                type=slicer.qMRMLScreenShotDialog.Yellow,
                                hideAnnotations=True)

        # Remove the labelmap
        controller.setLabelMapHidden(True)
        # Take the snapshot
        SlicerUtil.takeSnapshot(os.path.join(self.getCurrentDataFolder(),
                                             self.__getReportImagePath__(2)),
                                type=slicer.qMRMLScreenShotDialog.Yellow,
                                hideAnnotations=True)

        # Restore the regular controller
        controller.show()
        controller.setLabelMapHidden(False)

    def printReport(self, currentValuesDict, callback):
        """ Generate a html report file and print it
        :param currentValuesDict: dictionary of GUI values
        """
        if not self.isCurrentReportSaved:
            # Save the report first
            self.saveReport(currentValuesDict)

        # Generate the snapshots of the images
        self.__printSnapshots__()

        self.callback = callback
        # Generate the html code
        html = self.__generateHtml__(currentValuesDict)

        # Write the html to a temp file
        p = os.path.join(self.getCurrentDataFolder(), "report.html")
        with open(p, "w+b") as f:
            f.write(html)

        self.webView = qt.QWebView()
        self.webView.settings().setAttribute(
            qt.QWebSettings.DeveloperExtrasEnabled, True)
        self.webView.connect('loadFinished(bool)',
                             self.__webViewFormLoadedCallback__)
        self.webView.show()
        u = qt.QUrl(p)
        self.webView.setUrl(u)

    def __webViewFormLoadedCallback__(self, loaded):
        """ Function that is invoked when a webview has finished loading a URL
        :param loaded:
        """
        if loaded:
            outputFileName = os.path.join(self.getCurrentDataFolder(),
                                          "report.pdf")
            printer = qt.QPrinter(qt.QPrinter.HighResolution)
            printer.setOutputFormat(qt.QPrinter.PdfFormat)
            printer.setOutputFileName(outputFileName)
            self.webView.print_(printer)
            self.webView.close()
            # Call the callback
            self.callback(outputFileName)
            self.callback = None

    def __getReportImagePath__(self, imageType):
        if imageType == 0:
            # Original image
            return self.current2DVectorVolume.GetStorageNode().GetFileName()
        elif imageType == 1:
            # Original + Labelmap
            return self.current2DVectorVolume.GetStorageNode().GetFileName(
            ) + "_label.png"
        elif imageType == 2:
            # Enhanced only
            return self.current2DVectorVolume.GetStorageNode().GetFileName(
            ) + "_enh.png"
        elif imageType == 3:
            # Enhanced labeled
            return self.current2DVectorVolume.GetStorageNode().GetFileName(
            ) + "_enh_label.png"
        else:
            raise AttributeError("Invalid image type ({0})".format(imageType))

    def __generateHtml__(self, currentValuesDict):
        """ Generate an html report based on the default template
        :param currentValuesDict: text of the report
        :return: path of the html generated file
        """
        templatePath = self.getResourcePath("EyeSpot_ReportTemplate.html")
        with open(templatePath, "r+b") as f:
            html = f.read()

        # myText = '''<div id="divDescription">{0}</div>'''.format(self.__toHtml__(reportText))
        html = html.replace(self.getKey("Resources Folder"),
                            self.getResourcePath())

        # VA
        for key in ("OS", "OD"):
            key = self.getKey(key)
            repl = currentValuesDict[key] if currentValuesDict[key] else "-"
            html = html.replace(key, repl)
        repl = currentValuesDict[self.getKey(
            "VA Modality")] if currentValuesDict[self.getKey(
                "VA Modality")] else ""
        html = html.replace(self.getKey("VA Modality"), repl)

        # Problems
        for key in (self.getKey("Microaneurysms"), self.getKey("Exudates"),
                    self.getKey("Haemorrhages"),
                    self.getKey("Cotton wool spots"),
                    self.getKey("Neovascularisation")):
            html = html.replace(
                key, "<span style='color: red; font-weight: bold'>YES</span>"
                if currentValuesDict[key] else "NO")

        # Score
        html = html.replace(
            self.getKey("Diabetic Retinopathy Score"),
            str(currentValuesDict[self.getKey("Diabetic Retinopathy Score")]))

        # Images
        html = html.replace(self.getKey("Image Original"),
                            self.__getReportImagePath__(0))
        html = html.replace(self.getKey("Image Labeled"),
                            self.__getReportImagePath__(1))
        html = html.replace(self.getKey("Image Enhanced"),
                            self.__getReportImagePath__(2))
        html = html.replace(self.getKey("Image Enhanced Labeled"),
                            self.__getReportImagePath__(3))

        # Additional comments
        html = html.replace(
            self.getKey("Additional comments"),
            self.__textEncodeToHtml__(
                currentValuesDict[self.getKey("Additional comments")]))

        return html

    def __textEncodeToHtml__(self, text):
        """ Encode a plain text in html
        :param text:
        :return:
        """
        text = text.encode('ascii', 'xmlcharrefreplace')
        text = text.replace("\n", "<br/>")
        return text