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()
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