class CIP_CalciumScoringWidget(ScriptedLoadableModuleWidget): def __init__(self, parent=None): ScriptedLoadableModuleWidget.__init__(self, parent) settings = qt.QSettings() self.developerMode = SlicerUtil.IsDevelopment if not parent: self.parent = slicer.qMRMLWidget() self.parent.setLayout(qt.QVBoxLayout()) self.parent.setMRMLScene(slicer.mrmlScene) else: self.parent = parent self.layout = self.parent.layout() if not parent: self.setup() self.parent.show() self.priority = 2 self.calcificationType = 0 self.ThresholdMin = 130.0 self.ThresholdMax = 1000.0 self.MinimumLesionSize = 1 self.MaximumLesionSize = 500 self.croppedVolumeNode = slicer.vtkMRMLScalarVolumeNode() self.threshImage = vtk.vtkImageData() self.marchingCubes = vtk.vtkDiscreteMarchingCubes() self.transformPolyData = vtk.vtkTransformPolyDataFilter() self.selectedLabelList = [] self.labelScores = [] self.selectedLabels = {} self.modelNodes = [] self.voxelVolume = 1. self.sx = 1. self.sy = 1. self.sz = 1. self.selectedRGB = [1,0,0] self.observerTags = [] self.xy = [] self.summary_reports=["Agatston Score","Mass Score","Volume"] self.labelScores = dict() self.totalScores=dict() for sr in self.summary_reports: self.labelScores[sr]=[] self.totalScores[sr]=0 self.columnsDict = OrderedDict() self.columnsDict["CaseID"] = "CaseID" for sr in self.summary_reports: self.columnsDict[sr.replace(" ","")]=sr def __del__(self): for observee,tag in self.observerTags: observee.RemoveObserver(tag) self.observerTags = [] # def enter(self): # print "Enter" # def exit(self): # print "Exit" def setup(self): # Instantiate and connect widgets ... ScriptedLoadableModuleWidget.setup(self) #self.logic = CIP_CalciumScoringLogic() # # Parameters Area # parametersCollapsibleButton = ctk.ctkCollapsibleButton() parametersCollapsibleButton.text = "Parameters" self.layout.addWidget(parametersCollapsibleButton) # Layout within the dummy collapsible button parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton) # # target volume selector # self.inputSelector = slicer.qMRMLNodeComboBox() self.inputSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" ) self.inputSelector.addEnabled = False self.inputSelector.removeEnabled = False self.inputSelector.noneEnabled = False self.inputSelector.showHidden = False self.inputSelector.showChildNodeTypes = False self.inputSelector.setMRMLScene( slicer.mrmlScene ) self.inputSelector.setToolTip( "Pick the input to the algorithm." ) parametersFormLayout.addRow("Target Volume: ", self.inputSelector) self.inputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onVolumeChanged) self.volumeNode = self.inputSelector.currentNode() # # calcification type # # self.calcificationTypeBox = qt.QComboBox() # self.calcificationTypeBox.addItem("Heart") # self.calcificationTypeBox.addItem("Aorta") # parametersFormLayout.addRow("Region", self.calcificationTypeBox) # self.calcificationTypeBox.connect("currentIndexChanged(int)", self.onTypeChanged) self.ThresholdRange = ctk.ctkRangeWidget() self.ThresholdRange.minimum = 0 self.ThresholdRange.maximum = 2000 self.ThresholdRange.setMinimumValue(self.ThresholdMin) self.ThresholdRange.setMaximumValue(self.ThresholdMax) self.ThresholdRange.connect("minimumValueChanged(double)", self.onThresholdMinChanged) self.ThresholdRange.connect("maximumValueChanged(double)", self.onThresholdMaxChanged) parametersFormLayout.addRow("Threshold Value", self.ThresholdRange) self.ThresholdRange.setMinimumValue(self.ThresholdMin) self.ThresholdRange.setMaximumValue(self.ThresholdMax) self.LesionSizeRange= ctk.ctkRangeWidget() self.LesionSizeRange.minimum = 0.5 self.LesionSizeRange.maximum = 1000 self.LesionSizeRange.setMinimumValue(self.MinimumLesionSize) self.LesionSizeRange.setMaximumValue(self.MaximumLesionSize) self.LesionSizeRange.connect("minimumValueChanged(double)", self.onMinSizeChanged) self.LesionSizeRange.connect("maximumValueChanged(double)", self.onMaxSizeChanged) parametersFormLayout.addRow("Lesion Size (mm^3)", self.LesionSizeRange) self.LesionSizeRange.setMinimumValue(self.MinimumLesionSize) self.LesionSizeRange.setMaximumValue(self.MaximumLesionSize) self.scoreField=dict() for sr in self.summary_reports: self.scoreField[sr] = qt.QLineEdit() self.scoreField[sr].setText(0) parametersFormLayout.addRow("Total "+sr, self.scoreField[sr]) # # Update button and Select Table # self.updateButton = qt.QPushButton("Update") self.updateButton.toolTip = "Update calcium score computation" self.updateButton.enabled = True self.updateButton.setFixedSize(100, 50) #parametersFormLayout.addRow("", self.updateButton) self.updateButton.connect('clicked()', self.onUpdate) # # Select table # self.selectLabels = qt.QTableWidget() #self.selectLabels.horizontalHeader().hide() self.selectLabels.verticalHeader().hide() self.selectLabels.setColumnCount(6) self.selectLabels.itemClicked.connect(self.handleItemClicked) #Add row with columns name col_names=["","Agatston Score","Mass Score","Volume (mm^3)","Mean HU","Max HU"] self.selectLabels.setHorizontalHeaderLabels(col_names) parametersFormLayout.addRow(self.updateButton, self.selectLabels) # # Save Widget Area # #self.saveCollapsibleButton = ctk.ctkCollapsibleButton() #self.saveCollapsibleButton.text = "Saving" #self.layout.addWidget(self.saveCollapsibleButton) self.reportsWidget = CaseReportsWidget(self.moduleName, self.columnsDict, parentWidget=self.parent) self.reportsWidget.setup() self.reportsWidget.showPrintButton(False) self.reportsWidget.addObservable(self.reportsWidget.EVENT_SAVE_BUTTON_CLICKED, self.onSaveReport) # # ROI Area # self.roiCollapsibleButton = ctk.ctkCollapsibleButton() self.roiCollapsibleButton.text = "ROI" self.roiCollapsibleButton.setChecked(False) self.layout.addWidget(self.roiCollapsibleButton) # Layout within the dummy collapsible button roiFormLayout = qt.QFormLayout(self.roiCollapsibleButton) # # ROI # self.ROIWidget = slicer.qMRMLAnnotationROIWidget() self.roiNode = slicer.vtkMRMLAnnotationROINode() slicer.mrmlScene.AddNode(self.roiNode) self.ROIWidget.setMRMLAnnotationROINode(self.roiNode) roiFormLayout.addRow("", self.ROIWidget) #self.roiNode.AddObserver("ModifiedEvent", self.onROIChangedEvent, 1) # Add vertical spacer self.layout.addStretch(1) # Add temp nodes self.croppedNode=slicer.vtkMRMLScalarVolumeNode() self.croppedNode.SetHideFromEditors(1) slicer.mrmlScene.AddNode(self.croppedNode) self.labelsNode=slicer.vtkMRMLLabelMapVolumeNode() slicer.mrmlScene.AddNode(self.labelsNode) if self.inputSelector.currentNode(): self.onVolumeChanged(self.inputSelector.currentNode()) #self.createModels() def cleanup(self): self.reportsWidget.cleanup() self.reportsWidget = None def addLabel(self, row, rgb, values): #print "add row", row, rgb self.selectLabels.setRowCount(row+1) item0 = qt.QTableWidgetItem('') item0.setFlags(qt.Qt.ItemIsUserCheckable | qt.Qt.ItemIsEnabled) item0.setCheckState(qt.Qt.Unchecked) self.selectLabels.setItem(row,0,item0) for ii,val in enumerate(values): item1 = qt.QTableWidgetItem('') color=qt.QColor() color.setRgbF(rgb[0],rgb[1],rgb[2]) item1.setData(qt.Qt.BackgroundRole,color) item1.setText("%.02f"%val) self.selectLabels.setItem(row,1+ii,item1) def handleItemClicked(self, item): if item.checkState() == qt.Qt.Checked: self.selectedLabelList[item.row()] = 1 else: self.selectedLabelList[item.row()] = 0 #print "LIST=", self.selectedLabelList self.computeTotalScore() self.updateModels() def computeTotalScore(self): for sr in self.summary_reports: self.totalScores[sr] = 0 for n in range(0, len(self.selectedLabelList)): if self.selectedLabelList[n] == 1: for sr in self.summary_reports: self.totalScores[sr] = self.totalScores[sr] + self.labelScores[sr][n] for sr in self.summary_reports: self.scoreField[sr].setText(self.totalScores[sr]) def updateModels(self): for n in range(0, len(self.selectedLabelList)): model = self.modelNodes[n] dnode = model.GetDisplayNode() rgb = [1,0,0] if self.selectedLabelList[n] == 1: rgb = self.selectedRGB else: ct=slicer.mrmlScene.GetNodeByID('vtkMRMLColorTableNodeLabels') ct.GetLookupTable().GetColor(n+1,rgb) dnode.SetColor(rgb) def setInteractor(self): self.renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow() self.iren = self.renderWindow.GetInteractor() lm = slicer.app.layoutManager() for v in range(lm.threeDViewCount): td = lm.threeDWidget(v) ms = vtk.vtkCollection() td.getDisplayableManagers(ms) for i in range(ms.GetNumberOfItems()): m = ms.GetItemAsObject(i) if m.GetClassName() == "vtkMRMLModelDisplayableManager": self.dispManager = m break self.propPicker = vtk.vtkPropPicker() self.iren.SetPicker(self.propPicker) #self.propPicker.AddObserver("EndPickEvent", self.PickProp) #self.propPicker.AddObserver("PickEvent", self.PickProp) self.renderer = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow().GetRenderers().GetFirstRenderer() tag = self.iren.AddObserver("LeftButtonReleaseEvent", self.processEvent, self.priority) self.observerTags.append([self.iren,tag]) tag = self.iren.AddObserver("MouseMoveEvent", self.processEvent, self.priority) self.observerTags.append([self.iren,tag]) self.mouseInteractor = MouseInteractorActor() self.mouseInteractor.SetDefaultRenderer(self.renderer) self.iterStyleSave = iren.GetInteractorStyle() self.iren.SetInteractorStyle(self.mouseInteractor) def onVolumeChanged(self, value): self.volumeNode = self.inputSelector.currentNode() if self.volumeNode != None: xyz = [0,0,0] c=[0,0,0] slicer.vtkMRMLSliceLogic.GetVolumeRASBox(self.volumeNode,xyz,c) xyz[:]=[x*0.2 for x in xyz] self.roiNode.SetXYZ(c) self.roiNode.SetRadiusXYZ(xyz) sp = self.volumeNode.GetSpacing() self.voxelVolume = sp[0]*sp[1]*sp[2] self.sx=sp[0] self.sy=sp[1] self.sz=sp[2] #self.createModels() def onTypeChanged(self, value): self.calcificationType = value if self.calcificationType == 0: self.roiCollapsibleButton.setEnabled(1) self.ROIWidget.setEnabled(1) self.roiNode.SetDisplayVisibility(1) #self.logic.cropVolumeWithROI(self.volumeNode, self.roiNode, self.croppedVolumeNode) else: self.roiCollapsibleButton.setEnabled(0) self.ROIWidget.setEnabled(0) self.roiNode.SetDisplayVisibility(0) #self.createModels() def onMinSizeChanged(self, value): self.MinimumLesionSize = value #self.createModels() def onMaxSizeChanged(self, value): self.MaximumLesionSize = value #self.createModels() def onThresholdMinChanged(self, value): self.ThresholdMin = value #self.createModels() def onThresholdMaxChanged(self, value): self.ThresholdMax = value #self.createModels() def onROIChangedEvent(self, observee, event): pass #self.createModels() def onUpdate(self): self.createModels() def deleteModels(self): for m in self.modelNodes: m.SetAndObservePolyData(None) slicer.mrmlScene.RemoveNode(m.GetDisplayNode()) slicer.mrmlScene.RemoveNode(m) self.modelNodes = [] self.selectedLabels = {} def PickProp(self, object, event): # print "PICK" pickedActor = self.propPicker.GetActor() poly = pickedActor.GetMapper().GetInput() label = self.selectedLabels[poly] print ("picked label = ", label) def processEvent(self,observee,event): # print "PICK EVENT", event self.xy = self.iren.GetEventPosition() self.propPicker.PickProp(self.xy[0], self.xy[1], self.renderer) pickedActor = self.propPicker.GetActor() if pickedActor: poly = pickedActor.GetMapper().GetInput() label = self.selectedLabels[poly] print ("picked label = ", label) def onSaveReport(self): """ Save the current values in a persistent csv file """ self.statsAsCSV(self.reportsWidget, self.volumeNode) def statsAsCSV(self, repWidget, volumeNode): if self.totalScores is None: qt.QMessageBox.warning(slicer.util.mainWindow(), "Data not existing", "No statistics calculated") return row={} row['CaseID']=volumeNode.GetName() for sr in self.summary_reports: row[sr.replace(" ","")]=self.totalScores[sr] repWidget.insertRow(**row) qt.QMessageBox.information(slicer.util.mainWindow(), 'Data saved', 'The data were saved successfully') def computeDensityScore(self, d): score = 0 if d > 129 and d < 200: score = 1 elif d < 300: score = 2 elif d < 400: score = 3 else: score = 4 return score def createModels(self): self.deleteModels() for sr in self.summary_reports: self.labelScores[sr]=[] self.selectedLabelList = [] if self.calcificationType == 0 and self.volumeNode and self.roiNode: #print 'in Heart Create Models' slicer.vtkSlicerCropVolumeLogic().CropVoxelBased(self.roiNode, self.volumeNode, self.croppedNode) croppedImage = sitk.ReadImage( sitkUtils.GetSlicerITKReadWriteAddress(self.croppedNode.GetName())) thresholdImage = sitk.BinaryThreshold(croppedImage,self.ThresholdMin, self.ThresholdMax, 1, 0) connectedCompImage =sitk.ConnectedComponent(thresholdImage, True) relabelImage =sitk.RelabelComponent(connectedCompImage) labelStatFilter =sitk.LabelStatisticsImageFilter() labelStatFilter.Execute(croppedImage, relabelImage) if relabelImage.GetPixelID() != sitk.sitkInt16: relabelImage = sitk.Cast( relabelImage, sitk.sitkInt16 ) sitk.WriteImage( relabelImage, sitkUtils.GetSlicerITKReadWriteAddress(self.labelsNode.GetName())) nLabels = labelStatFilter.GetNumberOfLabels() #print "Number of labels = ", nLabels self.totalScore = 0 count = 0 #Computation of the score follows this paper: #C. H McCollough, Radiology, 243(2), 2007 for n in range(0,nLabels): max = labelStatFilter.GetMaximum(n) mean = labelStatFilter.GetMean(n) size = labelStatFilter.GetCount(n) volume = size*self.voxelVolume if volume > self.MaximumLesionSize: continue if volume < self.MinimumLesionSize: nLabels = n+1 break density_score = self.computeDensityScore(max) #Agatston score is \sum_i area_i * density_score_i #For now we assume that all the plaques have the same density score score = size*(self.sx*self.sy)*density_score mass_score = mean*volume #print "label = ", n, " max = ", max, " score = ", score, " voxels = ", size self.labelScores["Agatston Score"].append(score) self.labelScores["Mass Score"].append(mass_score) self.labelScores["Volume"].append(volume) self.selectedLabelList.append(0) self.marchingCubes.SetInputData(self.labelsNode.GetImageData()) self.marchingCubes.SetValue(0, n) self.marchingCubes.Update() self.transformPolyData.SetInputData(self.marchingCubes.GetOutput()) mat = vtk.vtkMatrix4x4() self.labelsNode.GetIJKToRASMatrix(mat) trans = vtk.vtkTransform() trans.SetMatrix(mat) self.transformPolyData.SetTransform(trans) self.transformPolyData.Update() poly = vtk.vtkPolyData() poly.DeepCopy(self.transformPolyData.GetOutput()) modelNode = slicer.vtkMRMLModelNode() slicer.mrmlScene.AddNode(modelNode) dnode = slicer.vtkMRMLModelDisplayNode() slicer.mrmlScene.AddNode(dnode) modelNode.AddAndObserveDisplayNodeID(dnode.GetID()) modelNode.SetAndObservePolyData(poly) ct=slicer.mrmlScene.GetNodeByID('vtkMRMLColorTableNodeLabels') rgb = [0,0,0] ct.GetLookupTable().GetColor(count+1,rgb) dnode.SetColor(rgb) #Enable Slice intersection dnode.SetSliceDisplayMode(0) dnode.SetSliceIntersectionVisibility(1) self.addLabel(count, rgb, [score,mass_score,volume,mean,max]) count = count+1 self.modelNodes.append(modelNode) self.selectedLabels[poly] = n #a = slicer.util.array(tn.GetID()) #sa = sitk.GetImageFromArray(a) for sr in self.summary_reports: self.scoreField[sr].setText(self.totalScores[sr]) else: print ("not implemented")
class CIP_ParenchymaAnalysisWidget(ScriptedLoadableModuleWidget): @property def moduleName(self): return os.path.basename(__file__).replace(".py", "") def __init__(self, parent=None): ScriptedLoadableModuleWidget.__init__(self, parent) self.chartOptions = ("LAA%-950", "LAA%-925", "LAA%-910", "LAA%-856", "HAA%-700", "HAA%-600", "HAA%-500", "HAA%-250", "Perc10", "Perc15", "Mean", "Std", "Kurtosis", "Skewness", "Ventilation Heterogeneity", "Mass", "Volume") # Build the column keys. Here all the columns are declared, but an alternative could be just: #self.columnsDict = CaseReportsWidget.getColumnKeysNormalizedDictionary(["Volume Name", "Region", "LAA%-950", ...]) self.columnsDict = OrderedDict() self.columnsDict["VolumeName"] = "Volume Name" self.columnsDict["Region"] = "Region" self.columnsDict["LAA950"] = "LAA%-950" self.columnsDict["LAA925"] = "LAA%-925" self.columnsDict["LAA910"] = "LAA%-910" self.columnsDict["LAA856"] = "LAA%-856" self.columnsDict["HAA700"] = "HAA%-700" self.columnsDict["HAA600"] = "HAA%-600" self.columnsDict["HAA500"] = "HAA%-500" self.columnsDict["HAA250"] = "HAA%-250" self.columnsDict["Perc10"] = "Perc10" self.columnsDict["Perc15"] = "Perc15" self.columnsDict["Mean"] = "Mean" self.columnsDict["Std"] = "Std" self.columnsDict["Kurtosis"] = "Kurtosis" self.columnsDict["Skewness"] = "Skewness" self.columnsDict[ "VentilationHeterogeneity"] = "Ventilation Heterogeneity" self.columnsDict["Mass"] = "Mass" self.columnsDict["Volume"] = "Volume" self.rTags = ("WholeLung", "RightLung", "LeftLung", "RUL", "RLL", "RML", "LUL", "LLL", "LUT", "LMT", "LLT", "RUT", "RMT", "RLT") if not parent: self.parent = slicer.qMRMLWidget() self.parent.setLayout(qt.QVBoxLayout()) self.parent.setMRMLScene(slicer.mrmlScene) else: self.parent = parent self.logic = None self.CTNode = None self.labelNode = None # self.expNode = None # self.explabelNode = None self.fileName = None self.fileDialog = None if not parent: self.setup() self.CTSelector.setMRMLScene(slicer.mrmlScene) # self.expSelector.setMRMLScene(slicer.mrmlScene) self.labelSelector.setMRMLScene(slicer.mrmlScene) # self.explabelSelector.setMRMLScene(slicer.mrmlScene) self.parent.show() def enter(self): if self.labelSelector.currentNode(): for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic( ).GetSliceCompositeNode().SetLabelVolumeID( self.labelSelector.currentNode().GetID()) def exit(self): for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic( ).GetSliceCompositeNode().SetLabelVolumeID('None') def setup(self): ScriptedLoadableModuleWidget.setup(self) # # the inps volume selector # parametersCollapsibleButton = ctk.ctkCollapsibleButton() parametersCollapsibleButton.text = "IO Volumes" self.parent.layout().addWidget(parametersCollapsibleButton) # Layout within the dummy collapsible button parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton) parametersFormLayout.setVerticalSpacing(5) self.CTSelector = slicer.qMRMLNodeComboBox() self.CTSelector.nodeTypes = (("vtkMRMLScalarVolumeNode"), "") self.CTSelector.addAttribute("vtkMRMLScalarVolumeNode", "LabelMap", 0) self.CTSelector.selectNodeUponCreation = False self.CTSelector.addEnabled = False self.CTSelector.removeEnabled = False self.CTSelector.noneEnabled = True self.CTSelector.showHidden = False self.CTSelector.showChildNodeTypes = False self.CTSelector.setMRMLScene(slicer.mrmlScene) self.CTSelector.setToolTip("Pick the CT image to work on.") parametersFormLayout.addRow("Input CT Volume: ", self.CTSelector) # # the label map volume selector # self.labelSelector = slicer.qMRMLNodeComboBox() # self.labelSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" ) # self.labelSelector.addAttribute( "vtkMRMLScalarVolumeNode", "LabelMap", 1 ) self.labelSelector.nodeTypes = (("vtkMRMLLabelMapVolumeNode"), "") self.labelSelector.selectNodeUponCreation = True self.labelSelector.addEnabled = False self.labelSelector.removeEnabled = False self.labelSelector.noneEnabled = True self.labelSelector.showHidden = False self.labelSelector.showChildNodeTypes = False self.labelSelector.setMRMLScene(slicer.mrmlScene) self.labelSelector.setToolTip("Pick the label map to the algorithm.") parametersFormLayout.addRow("Label Map Volume: ", self.labelSelector) # Image filtering section self.preProcessingWidget = PreProcessingWidget( self.moduleName, parentWidget=self.parent) self.preProcessingWidget.setup() # # self.splitRadioButton = qt.QRadioButton() # self.splitRadioButton.setText('Split Label Map') # self.splitRadioButton.setChecked(0) # self.parent.layout().addWidget(self.splitRadioButton, 0, 3) # Apply button self.applyButton = qt.QPushButton("Apply") self.applyButton.toolTip = "Calculate Parenchyma Phenotypes." self.applyButton.enabled = False self.applyButton.setFixedSize(300, 30) self.parent.layout().addWidget(self.applyButton, 0, 4) # model and view for stats table self.view = qt.QTableView() self.view.sortingEnabled = True self.parent.layout().addWidget(self.view) # model and view for EXP stats table """self.viewexp = qt.QTableView() self.viewexp.sortingEnabled = True self.parent.layout().addWidget(self.viewexp)""" # Histogram Selection self.HistSection = qt.QFrame() self.HistSection.setLayout(qt.QVBoxLayout()) self.parent.layout().addWidget(self.HistSection) self.HistSection.setObjectName('HistSection') self.HistSection.setStyleSheet( '#HistSection {border: 0.5px solid lightGray; }') HistSectionTitle = qt.QLabel() HistSectionTitle.setText('Histogram Section') # HistSectionTitle.setStyleSheet('border: 1px solid white; color: black') self.HistSection.layout().addWidget(HistSectionTitle) self.histogramCheckBoxes = [] self.histFrame = qt.QFrame() # self.histFrame.setStyleSheet('border: 1px solid white') self.histFrame.setLayout(qt.QHBoxLayout()) self.GlobalHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.GlobalHistCheckBox) self.histFrame.layout().addWidget(self.GlobalHistCheckBox) self.RightHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.RightHistCheckBox) self.histFrame.layout().addWidget(self.RightHistCheckBox) self.LeftHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.LeftHistCheckBox) self.histFrame.layout().addWidget(self.LeftHistCheckBox) self.RULHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.RULHistCheckBox) self.histFrame.layout().addWidget(self.RULHistCheckBox) self.RLLHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.RLLHistCheckBox) self.histFrame.layout().addWidget(self.RLLHistCheckBox) self.RMLHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.RMLHistCheckBox) self.histFrame.layout().addWidget(self.RMLHistCheckBox) self.LULHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.LULHistCheckBox) self.histFrame.layout().addWidget(self.LULHistCheckBox) self.LLLHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.LLLHistCheckBox) self.histFrame.layout().addWidget(self.LLLHistCheckBox) self.LUTHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.LUTHistCheckBox) self.histFrame.layout().addWidget(self.LUTHistCheckBox) self.LMTHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.LMTHistCheckBox) self.histFrame.layout().addWidget(self.LMTHistCheckBox) self.LLTHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.LLTHistCheckBox) self.histFrame.layout().addWidget(self.LLTHistCheckBox) self.RUTHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.RUTHistCheckBox) self.histFrame.layout().addWidget(self.RUTHistCheckBox) self.RMTHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.RMTHistCheckBox) self.histFrame.layout().addWidget(self.RMTHistCheckBox) self.RLTHistCheckBox = qt.QCheckBox() self.histogramCheckBoxes.append(self.RLTHistCheckBox) self.histFrame.layout().addWidget(self.RLTHistCheckBox) for i in xrange(len(self.histogramCheckBoxes)): self.histogramCheckBoxes[i].setText(self.rTags[i]) self.histogramCheckBoxes[i].hide() self.HistSection.layout().addWidget(self.histFrame) self.HistSection.enabled = False # Chart button self.chartBox = qt.QFrame() self.chartBox.setObjectName("chartBox") self.chartBox.setStyleSheet( '#chartBox {border: 0.5px solid lightGray;}') self.chartBox.setLayout(qt.QVBoxLayout()) self.parent.layout().addWidget(self.chartBox) chartSectionTitle = qt.QLabel() chartSectionTitle.setText('Chart Section') self.chartBox.layout().addWidget(chartSectionTitle) chartFrame = qt.QFrame() chartFrame.setLayout(qt.QHBoxLayout()) self.chartBox.layout().addWidget(chartFrame) self.chartButton = qt.QPushButton("Chart") self.chartButton.toolTip = "Make a chart from the current statistics." chartFrame.layout().addWidget(self.chartButton) self.chartOption = qt.QComboBox() self.chartOption.addItems(self.chartOptions) chartFrame.layout().addWidget(self.chartOption) self.chartBox.enabled = False self.reportsWidget = CaseReportsWidget(self.moduleName, self.columnsDict, parentWidget=self.parent) self.reportsWidget.setup() self.reportsWidget.showPrintButton(True) # self.reportsWidget.saveButton.enabled = False # self.reportsWidget.openButton.enabled = False # self.reportsWidget.exportButton.enabled = False # self.reportsWidget.removeButton.enabled = False # By default, the Print button is hidden # self.reportsWidget.showPrintButton.enabled = False # Add vertical spacer self.parent.layout().addStretch(1) # connections self.applyButton.connect('clicked()', self.onApply) self.chartButton.connect('clicked()', self.onChart) self.reportsWidget.addObservable( self.reportsWidget.EVENT_SAVE_BUTTON_CLICKED, self.onSaveReport) self.reportsWidget.addObservable( self.reportsWidget.EVENT_PRINT_BUTTON_CLICKED, self.onPrintReport) self.CTSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.onCTSelect) self.labelSelector.connect('currentNodeChanged(vtkMRMLNode*)', self.onLabelSelect) self.GlobalHistCheckBox.connect('clicked()', self.onHistogram) self.RightHistCheckBox.connect('clicked()', self.onHistogram) self.LeftHistCheckBox.connect('clicked()', self.onHistogram) self.RULHistCheckBox.connect('clicked()', self.onHistogram) self.RLLHistCheckBox.connect('clicked()', self.onHistogram) self.RMLHistCheckBox.connect('clicked()', self.onHistogram) self.LULHistCheckBox.connect('clicked()', self.onHistogram) self.LLLHistCheckBox.connect('clicked()', self.onHistogram) self.LUTHistCheckBox.connect('clicked()', self.onHistogram) self.LMTHistCheckBox.connect('clicked()', self.onHistogram) self.LLTHistCheckBox.connect('clicked()', self.onHistogram) self.RUTHistCheckBox.connect('clicked()', self.onHistogram) self.RMTHistCheckBox.connect('clicked()', self.onHistogram) self.RLTHistCheckBox.connect('clicked()', self.onHistogram) def cleanup(self): self.reportsWidget.cleanup() self.reportsWidget = None def onCTSelect(self, node): self.CTNode = node self.applyButton.enabled = bool( self.CTNode) # and bool(self.labelNode) self.preProcessingWidget.enableFilteringFrame(bool(self.CTNode)) self.preProcessingWidget.enableLMFrame(bool(not self.labelNode)) if self.CTNode: for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic( ).GetSliceCompositeNode().SetBackgroundVolumeID( self.CTNode.GetID()) else: for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic( ).GetSliceCompositeNode().SetBackgroundVolumeID('None') def onLabelSelect(self, node): self.labelNode = node self.applyButton.enabled = bool( self.CTNode) # and bool(self.labelNode) self.preProcessingWidget.enableFilteringFrame(bool(self.CTNode)) self.preProcessingWidget.enableLMFrame(bool(not self.labelNode)) SlicerUtil.changeLabelmapOpacity(0.5) if self.labelNode: self.preProcessingWidget.filterApplication.setChecked(1) self.preProcessingWidget.filterApplication.setEnabled(0) for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic( ).GetSliceCompositeNode().SetLabelVolumeID( self.labelNode.GetID()) else: self.preProcessingWidget.filterApplication.setChecked(0) self.preProcessingWidget.filterApplication.setEnabled(1) for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic( ).GetSliceCompositeNode().SetLabelVolumeID('None') def inputVolumesAreValid(self): """Verify that volumes are compatible with label calculation algorithm assumptions""" if not self.CTNode: # or not self.labelNode: qt.QMessageBox.warning(slicer.util.mainWindow(), "Parenchyma Analysis", "Please select a CT Input Volume.") return False if not self.CTNode.GetImageData( ): # or not self.labelNode.GetImageData(): qt.QMessageBox.warning(slicer.util.mainWindow(), "Parenchyma Analysis", "Please select a CT Input Volume.") return False if not self.labelNode or not self.labelNode.GetImageData(): warning = self.preProcessingWidget.warningMessageForLM() if warning == 16384: self.createLungLabelMap() else: qt.QMessageBox.warning(slicer.util.mainWindow(), "Parenchyma Analysis", "Please select a Lung Label Map.") return False return True if self.CTNode.GetImageData().GetDimensions( ) != self.labelNode.GetImageData().GetDimensions(): qt.QMessageBox.warning( slicer.util.mainWindow(), "Parenchyma Analysis", "Input Volumes do not have the same geometry.") return False # if self.preProcessingWidget.filterOnRadioButton.checked: # self.preProcessingWidget.filterApplication.setChecked(1) return True def filterInputCT(self): self.applyButton.enabled = False self.applyButton.text = "Filtering..." # TODO: why doesn't processEvents alone make the label text change? self.applyButton.repaint() slicer.app.processEvents() self.preProcessingWidget.filterInputCT(self.CTNode) def createLungLabelMap(self): """Create the lung label map """ self.applyButton.enabled = False if self.preProcessingWidget.filterOnRadioButton.checked: # and not self.preProcessingWidget.filterApplication.checked: self.filterInputCT() inputNode = self.CTNode self.applyButton.text = "Creating Label Map..." # TODO: why doesn't processEvents alone make the label text change? self.applyButton.repaint() slicer.app.processEvents() self.labelNode = slicer.mrmlScene.AddNode( slicer.vtkMRMLLabelMapVolumeNode()) name = inputNode.GetName() + '_partialLungLabelMap' self.labelNode.SetName(slicer.mrmlScene.GenerateUniqueName(name)) self.preProcessingWidget.createPartialLM(inputNode, self.labelNode) label_image = self.labelNode.GetImageData() shape = list(label_image.GetDimensions()) input_array = vtk.util.numpy_support.vtk_to_numpy( label_image.GetPointData().GetScalars()) original_shape = input_array.shape input_array = input_array.reshape( shape[2], shape[1], shape[0]) # input_array.transpose([2, 1, 0]) would not work! input_image = sitk.GetImageFromArray(input_array) input_image.SetSpacing(self.labelNode.GetSpacing()) input_image.SetOrigin(self.labelNode.GetOrigin()) my_lung_splitter = lung_splitter(split_thirds=True) split_lm = my_lung_splitter.execute(input_image) split = sitk.GetArrayFromImage(split_lm) input_aa = vtk.util.numpy_support.vtk_to_numpy( label_image.GetPointData().GetScalars()) input_aa[:] = split.reshape(original_shape) self.labelNode.StorableModified() self.labelNode.Modified() self.labelNode.InvokeEvent( slicer.vtkMRMLVolumeNode.ImageDataModifiedEvent, self.labelNode) SlicerUtil.changeLabelmapOpacity(0.5) def onApply(self): """Calculate the parenchyma analysis """ if not self.inputVolumesAreValid(): return self.applyButton.enabled = False if self.preProcessingWidget.filterOnRadioButton.checked and self.preProcessingWidget.filterApplication.checked: self.filterInputCT() self.applyButton.text = "Analysing..." # TODO: why doesn't processEvents alone make the label text change? self.applyButton.repaint() slicer.app.processEvents() self.logic = CIP_ParenchymaAnalysisLogic(self.CTNode, self.labelNode) self.populateStats() self.logic.createHistogram() for i in xrange(len(self.histogramCheckBoxes)): self.histogramCheckBoxes[i].setChecked(0) self.histogramCheckBoxes[i].hide() for tag in self.rTags: if tag in self.logic.regionTags: self.histogramCheckBoxes[self.rTags.index(tag)].show() self.HistSection.enabled = True self.chartBox.enabled = True # self.reportsWidget.saveButton.enabled = True # self.reportsWidget.openButton.enabled = True # self.reportsWidget.exportButton.enabled = True # self.reportsWidget.removeButton.enabled = True self.applyButton.enabled = True self.applyButton.text = "Apply" for color in ['Red', 'Yellow', 'Green']: slicer.app.layoutManager().sliceWidget(color).sliceLogic( ).GetSliceCompositeNode().SetBackgroundVolumeID( self.CTNode.GetID()) self.labelSelector.setCurrentNode(self.labelNode) def onHistogram(self): """Histogram of the selected region """ self.histList = [] for i in xrange(len(self.histogramCheckBoxes)): if self.histogramCheckBoxes[i].checked == True: self.histList.append(self.rTags[i]) self.logic.AddSelectedHistograms(self.histList) def onChart(self): """chart the parenchyma analysis """ valueToPlot = self.chartOptions[self.chartOption.currentIndex] self.logic.createStatsChart(self.labelNode, valueToPlot) def onSaveReport(self): """ Save the current values in a persistent csv file """ self.logic.statsAsCSV(self.reportsWidget, self.CTNode) def onPrintReport(self): """ Print a pdf report """ emphysema_image_path, ct_slice_path = self.logic.computeEmphysemaOnSlice( self.CTNode, self.labelNode, op=0.5) pdfReporter = PdfReporter() # Get the values that are going to be inserted in the html template caseName = self.CTNode.GetName() values = dict() values["@@PATH_TO_STATIC@@"] = os.path.join( os.path.dirname(os.path.realpath(__file__)), "Resources/") values["@@SUBJECT@@"] = "Subject: " + str(caseName) values["@@GLOBAL_LEVEL@@"] = "{:.2f}%".format( self.logic.labelStats['LAA%-950', 'WholeLung']) values["@@SUMMARY@@"] = "Emphysema per region: " pdfRows = """""" for tag in self.logic.regionTags: pdfRows += """<tr> <td align="center">{} </td> <td align="center">{:.2f} </td> </tr>""".format(tag, self.logic.labelStats['LAA%-950', tag]) values["@@TABLE_ROWS@@"] = pdfRows # Get the path to the html template htmlTemplatePath = os.path.join( os.path.dirname(os.path.realpath(__file__)), "Resources/CIP_ParenchymaAnalysisReport.html") # Get a list of image absolute paths that may be needed for the report. In this case, we get the ACIL logo imagesFileList = [SlicerUtil.ACIL_LOGO_PATH] values["@@EMPHYSEMA_IMAGE@@"] = emphysema_image_path values["@@CT_IMAGE@@"] = ct_slice_path # Print the report. Remember that we can optionally specify the absolute path where the report is going to # be stored pdfReporter.printPdf(htmlTemplatePath, values, self.reportPrinted, imagesFileList=imagesFileList) def reportPrinted(self, reportPath): Util.openFile(reportPath) def onFileSelected(self, fileName): self.logic.saveStats(fileName) def populateStats(self): if not self.logic: return displayNode = self.labelNode.GetDisplayNode() colorNode = displayNode.GetColorNode() lut = colorNode.GetLookupTable() self.items = [] self.model = qt.QStandardItemModel() self.view.setModel(self.model) self.view.verticalHeader().visible = False row = 0 for regionTag, regionValue in zip(self.logic.regionTags, self.logic.regionValues): color = qt.QColor() rgb = lut.GetTableValue(regionValue[0]) color.setRgb(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255) item = qt.QStandardItem() item.setData(color, 1) item.setText(str(regionTag)) item.setData(regionTag, 1) item.setToolTip(regionTag) item.setTextAlignment(1) self.model.setItem(row, 0, item) self.items.append(item) col = 1 for k in self.logic.keys: item = qt.QStandardItem() item.setText("%.3f" % self.logic.labelStats[k, regionTag]) item.setTextAlignment(4) self.view.setColumnWidth(col, 15 * len(item.text())) self.model.setItem(row, col, item) self.items.append(item) col += 1 row += 1 self.view.setColumnWidth(0, 15 * len('Region')) self.model.setHeaderData(0, 1, "Region") col = 1 for k in self.logic.keys: # self.view.setColumnWidth(col,15*len(k)) self.model.setHeaderData(col, 1, k) col += 1
class CIP_CalciumScoringWidget(ScriptedLoadableModuleWidget): def __init__(self, parent=None): print "init" ScriptedLoadableModuleWidget.__init__(self, parent) # Default variables # ----------------- self.calcificationType = 0 self.ThresholdMin = 130.0 self.ThresholdMax = 1000.0 self.MinimumLesionSize = 1 self.MaximumLesionSize = 500 self.selectedLabelList = [] self.selectedLabels = {} self.modelNodes = [] self.selectedRGB = [1, 0, 0] self.summary_reports = [ "Agatston Score 2D", "Agatston Score 3D", "Mass Score", "Volume" ] self.labelScores = dict() self.totalScores = dict() for sr in self.summary_reports: self.labelScores[sr] = [] self.totalScores[sr] = 0 self.columnsDict = OrderedDict() self.columnsDict["CaseID"] = "CaseID" for sr in self.summary_reports: self.columnsDict[sr.replace(" ", "")] = sr def setup(self): print "setup()" self.setInteractor() # Instantiate and connect widgets # ------------------------------- ScriptedLoadableModuleWidget.setup(self) # Parameters Area # --------------- parametersCollapsibleButton = ctk.ctkCollapsibleButton() parametersCollapsibleButton.text = "Parameters" self.layout.addWidget(parametersCollapsibleButton) parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton) # Target volume selector # ---------------------- self.inputSelector = slicer.qMRMLNodeComboBox() self.inputSelector.nodeTypes = (("vtkMRMLScalarVolumeNode"), "") self.inputSelector.addEnabled = False self.inputSelector.removeEnabled = False self.inputSelector.noneEnabled = False self.inputSelector.showHidden = False self.inputSelector.showChildNodeTypes = False self.inputSelector.setMRMLScene(slicer.mrmlScene) self.inputSelector.setToolTip("Pick the input to the algorithm.") parametersFormLayout.addRow("Target Volume: ", self.inputSelector) self.inputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onVolumeChanged) self.volumeNode = self.inputSelector.currentNode() # Thresholds selectors # -------------------- self.ThresholdRange = ctk.ctkRangeWidget() self.ThresholdRange.minimum = 0 self.ThresholdRange.maximum = 2000 self.ThresholdRange.setMinimumValue(self.ThresholdMin) self.ThresholdRange.setMaximumValue(self.ThresholdMax) self.ThresholdRange.connect("minimumValueChanged(double)", self.onThresholdMinChanged) self.ThresholdRange.connect("maximumValueChanged(double)", self.onThresholdMaxChanged) parametersFormLayout.addRow("Threshold Value", self.ThresholdRange) self.ThresholdRange.setMinimumValue(self.ThresholdMin) self.ThresholdRange.setMaximumValue(self.ThresholdMax) self.LesionSizeRange = ctk.ctkRangeWidget() self.LesionSizeRange.minimum = 0.5 self.LesionSizeRange.maximum = 2000 # 1000 self.LesionSizeRange.setMinimumValue(self.MinimumLesionSize) self.LesionSizeRange.setMaximumValue(self.MaximumLesionSize) self.LesionSizeRange.connect("minimumValueChanged(double)", self.onMinSizeChanged) self.LesionSizeRange.connect("maximumValueChanged(double)", self.onMaxSizeChanged) parametersFormLayout.addRow("Lesion Size (mm^3)", self.LesionSizeRange) self.LesionSizeRange.setMinimumValue(self.MinimumLesionSize) self.LesionSizeRange.setMaximumValue(self.MaximumLesionSize) # Summary Fields # -------------------------- self.scoreField = dict() for sr in self.summary_reports: self.scoreField[sr] = qt.QLineEdit() self.scoreField[sr].setText(0) parametersFormLayout.addRow("Total " + sr, self.scoreField[sr]) # Update button # ------------- self.updateButton = qt.QPushButton("Update") self.updateButton.toolTip = "Update calcium score computation" self.updateButton.enabled = True self.updateButton.setFixedSize(100, 50) self.updateButton.connect('clicked()', self.onUpdate) # Select table # ------------ self.selectLabels = qt.QTableWidget() self.selectLabels.verticalHeader().hide() self.selectLabels.setColumnCount(6) self.selectLabels.itemClicked.connect(self.handleItemClicked) col_names = [ "", "Agatston Score 2D", "Agatston Score 3D", "Mass Score", "Volume (mm^3)", "Mean HU", "Max HU" ] self.selectLabels.setHorizontalHeaderLabels(col_names) parametersFormLayout.addRow(self.updateButton, self.selectLabels) # Save button # ----------- self.reportsWidget = CaseReportsWidget(self.moduleName, self.columnsDict, parentWidget=self.parent) self.reportsWidget.setup() self.reportsWidget.showPrintButton(False) self.reportsWidget.addObservable( self.reportsWidget.EVENT_SAVE_BUTTON_CLICKED, self.onSaveReport) # ROI Area # -------- self.roiCollapsibleButton = ctk.ctkCollapsibleButton() self.roiCollapsibleButton.text = "ROI" self.roiCollapsibleButton.setChecked(False) self.layout.addWidget(self.roiCollapsibleButton) # Layout within the dummy collapsible button roiFormLayout = qt.QFormLayout(self.roiCollapsibleButton) # ROI # --- self.ROIWidget = slicer.qMRMLAnnotationROIWidget() self.roiNode = slicer.vtkMRMLAnnotationROINode() slicer.mrmlScene.AddNode(self.roiNode) self.ROIWidget.setMRMLAnnotationROINode(self.roiNode) roiFormLayout.addRow("", self.ROIWidget) # Add vertical spacer self.layout.addStretch(1) # Add temp nodes self.croppedNode = slicer.vtkMRMLScalarVolumeNode() self.croppedNode.SetHideFromEditors(1) slicer.mrmlScene.AddNode(self.croppedNode) self.labelsNode = slicer.vtkMRMLLabelMapVolumeNode() slicer.mrmlScene.AddNode(self.labelsNode) if self.inputSelector.currentNode(): self.onVolumeChanged(self.inputSelector.currentNode()) def setInteractor(self): slice_views = ["Red", "Yellow", "Green"] for sv in slice_views: slice_view_interactor = slicer.app.layoutManager().sliceWidget( sv).sliceView().renderWindow().GetInteractor() slice_view_interactor.AddObserver("LeftButtonReleaseEvent", self.processEvent) slice_view_interactor.TAG = "three views: %s" % sv slice_view_interactor.Node = slicer.app.layoutManager( ).sliceWidget(sv).sliceLogic().GetLabelLayer().GetSliceNode() def processEvent(self, observee, event): xy = observee.GetEventPosition() transformationMatrix = observee.Node.GetXYToRAS() xyRAS = np.array( transformationMatrix.MultiplyPoint([xy[0], xy[1], 0, 1])) transformationMatrix = vtk.vtkMatrix4x4() self.labelsNode.GetRASToIJKMatrix(transformationMatrix) numpy_data = slicer.util.array(self.labelsNode.GetName()) ijk_coords = transformationMatrix.MultiplyPoint(xyRAS) ijk_coords = np.array(np.rint(ijk_coords), dtype=np.int16) value = numpy_data[ijk_coords[2], ijk_coords[1], ijk_coords[0]] table_item = self.selectLabels.takeItem(value - 1, 0) if table_item.checkState() == qt.Qt.Checked: table_item.setCheckState(qt.Qt.Unchecked) else: table_item.setCheckState(qt.Qt.Checked) self.selectLabels.setItem(value - 1, 0, table_item) self.handleItemClicked(table_item) def computeTotalScore(self): for sr in self.summary_reports: self.totalScores[sr] = 0 for n in range(0, len(self.selectedLabelList)): if self.selectedLabelList[n] == 1: for sr in self.summary_reports: self.totalScores[ sr] = self.totalScores[sr] + self.labelScores[sr][n] for sr in self.summary_reports: self.scoreField[sr].setText(self.totalScores[sr]) def updateModels(self): for n in range(0, len(self.selectedLabelList)): model = self.modelNodes[n] dnode = model.GetDisplayNode() rgb = [1, 0, 0] if self.selectedLabelList[n] == 1: rgb = self.selectedRGB else: ct = slicer.mrmlScene.GetNodeByID( 'vtkMRMLColorTableNodeLabels') ct.GetLookupTable().GetColor(n + 1, rgb) dnode.SetColor(rgb) def deleteModels(self): for m in self.modelNodes: m.SetAndObservePolyData(None) slicer.mrmlScene.RemoveNode(m.GetDisplayNode()) slicer.mrmlScene.RemoveNode(m) self.modelNodes = [] self.selectedLabels = {} def handleItemClicked(self, item): """Select a candidate from list and re-compute the total score""" if item.checkState() == qt.Qt.Checked: self.selectedLabelList[item.row()] = 1 else: self.selectedLabelList[item.row()] = 0 self.computeTotalScore() self.updateModels() def addLabel(self, row, rgb, values): self.selectLabels.setRowCount(row + 1) item0 = qt.QTableWidgetItem('') item0.setFlags(qt.Qt.ItemIsUserCheckable | qt.Qt.ItemIsEnabled) item0.setCheckState(qt.Qt.Unchecked) self.selectLabels.setItem(row, 0, item0) for ii, val in enumerate(values): item1 = qt.QTableWidgetItem('') color = qt.QColor() color.setRgbF(rgb[0], rgb[1], rgb[2]) item1.setData(qt.Qt.BackgroundRole, color) item1.setText("%.02f" % val) self.selectLabels.setItem(row, 1 + ii, item1) def computeDensityScore(self, d): score = 0 if d > 129 and d < 200: score = 1 elif d < 300: score = 2 elif d < 400: score = 3 else: score = 4 return score def agatston_computation(self, n, relabelImage, croppedImage, prod_spacing): croppedImage_arr = sitk.GetArrayFromImage(croppedImage) relabelImage_arr = sitk.GetArrayFromImage(relabelImage) ii = np.where(relabelImage_arr == n) min_coord = np.min(ii, axis=1) max_coord = np.max(ii, axis=1) max_coord += 1 # crop_label = (np.array(relabelImage_arr[min_coord[0]:max_coord[0], min_coord[1]:max_coord[1], min_coord[2]:max_coord[2]]) == n) crop_label = relabelImage_arr[min_coord[0]:max_coord[0], min_coord[1]:max_coord[1], min_coord[2]:max_coord[2]] == n crop_img = croppedImage_arr[min_coord[0]:max_coord[0], min_coord[1]:max_coord[1], min_coord[2]:max_coord[2]] crop_img *= (crop_label == 1) agatston = 0 volume = 0 mass_score = 0 for sl in crop_img: max_HU = np.max(sl) size = np.count_nonzero(sl) layer_volume = size * prod_spacing volume += layer_volume agatston += layer_volume * self.computeDensityScore(max_HU) mass_score += layer_volume * np.mean(sl) return agatston, volume, mass_score def statsAsCSV(self, repWidget, volumeNode): if self.totalScores is None: qt.QMessageBox.warning(slicer.util.mainWindow(), "Data not existing", "No statistics calculated") return row = {} row['CaseID'] = volumeNode.GetName() for sr in self.summary_reports: row[sr.replace(" ", "")] = self.totalScores[sr] print row storageNode = self.volumeNode.GetStorageNode() filepath = str(storageNode.GetFullNameFromFileName()) split_path = filepath.split('.') output_path = split_path[0] with open("%s_cac.%s" % (output_path, "json"), 'w') as f: json.dump(row, f) # repWidget.insertRow(**row) # qt.QMessageBox.information(slicer.util.mainWindow(), 'Data saved', 'The data were saved successfully') # def saveSegmentationAsNrrd(self): def saveSegmentation(self): roiCenter = [0, 0, 0] self.roiNode.GetXYZ(roiCenter) transformationMatrix = vtk.vtkMatrix4x4() self.volumeNode.GetRASToIJKMatrix(transformationMatrix) roiCenter_ijk = transformationMatrix.MultiplyPoint( [roiCenter[0], roiCenter[1], roiCenter[2], 1]) roiCenter_ijk = np.array(np.rint(roiCenter_ijk), dtype=np.int16) numpy_data = np.array(slicer.util.array(self.labelsNode.GetName())) lbs = np.argwhere(np.array(self.selectedLabelList) == 1).flatten() + 1 numpy_data[np.logical_not(np.isin(numpy_data, lbs))] = 0 saved_array = np.array(slicer.util.array(self.volumeNode.GetName())) slicer.util.array(self.volumeNode.GetName()).fill(0) slicer.util.array(self.volumeNode.GetName())[ roiCenter_ijk[2] - numpy_data.shape[0] / 2:roiCenter_ijk[2] + (numpy_data.shape[0] + 1) / 2, roiCenter_ijk[1] - numpy_data.shape[1] / 2:roiCenter_ijk[1] + (numpy_data.shape[1] + 1) / 2, roiCenter_ijk[0] - numpy_data.shape[2] / 2:roiCenter_ijk[0] + (numpy_data.shape[2] + 1) / 2] = numpy_data storageNode = self.volumeNode.GetStorageNode() filepath = str(storageNode.GetFullNameFromFileName()) split_path = filepath.split('.') output_path = split_path[0] extension = split_path[1] result = slicer.util.saveNode(self.volumeNode, "%s_cac.%s" % (output_path, extension)) slicer.util.array(self.volumeNode.GetName())[:, :, :] = saved_array def onVolumeChanged(self, value): self.volumeNode = self.inputSelector.currentNode() if self.volumeNode != None: xyz = [0, 0, 0] c = [0, 0, 0] slicer.vtkMRMLSliceLogic.GetVolumeRASBox(self.volumeNode, xyz, c) xyz[:] = [x * 0.2 for x in xyz] self.roiNode.SetXYZ(c) self.roiNode.SetRadiusXYZ(xyz) sp = self.volumeNode.GetSpacing() self.voxelVolume = sp[0] * sp[1] * sp[2] self.sx = sp[0] self.sy = sp[1] self.sz = sp[2] def onMinSizeChanged(self, value): self.MinimumLesionSize = value #self.createModels() def onMaxSizeChanged(self, value): self.MaximumLesionSize = value #self.createModels() def onThresholdMinChanged(self, value): self.ThresholdMin = value #self.createModels() def onThresholdMaxChanged(self, value): self.ThresholdMax = value #self.createModels() def onUpdate(self): self.createModels() def onSaveReport(self): """Save the current values in a persistent csv file""" self.statsAsCSV(self.reportsWidget, self.volumeNode) self.saveSegmentation() def createModels(self): # Reset previous model and labels self.deleteModels() for sr in self.summary_reports: self.labelScores[sr] = [] self.selectedLabelList = [] if self.calcificationType == 0 and self.volumeNode and self.roiNode: slicer.vtkSlicerCropVolumeLogic().CropVoxelBased( self.roiNode, self.volumeNode, self.croppedNode) croppedImage = sitk.ReadImage( sitkUtils.GetSlicerITKReadWriteAddress( self.croppedNode.GetName())) thresholdImage = sitk.BinaryThreshold(croppedImage, self.ThresholdMin, self.ThresholdMax, 1, 0) connectedCompImage = sitk.ConnectedComponent(thresholdImage, True) relabelImage = sitk.RelabelComponent(connectedCompImage) labelStatFilter = sitk.LabelStatisticsImageFilter() labelStatFilter.Execute(croppedImage, relabelImage) if relabelImage.GetPixelID() != sitk.sitkInt16: relabelImage = sitk.Cast(relabelImage, sitk.sitkInt16) sitk.WriteImage( relabelImage, sitkUtils.GetSlicerITKReadWriteAddress( self.labelsNode.GetName())) prod_spacing = np.prod(croppedImage.GetSpacing()) nLabels = labelStatFilter.GetNumberOfLabels() self.totalScore = 0 count = 0 for n in range(0, nLabels): max = labelStatFilter.GetMaximum(n) mean = labelStatFilter.GetMean(n) size = labelStatFilter.GetCount(n) volume = size * self.voxelVolume # current label is discarted if volume not meet the maximum allowed threshold if volume > self.MaximumLesionSize: continue # As ordered, we stop here if the volume of the current label is less than threshold if volume < self.MinimumLesionSize: nLabels = n + 1 break score2d, volume, mass_score = self.agatston_computation( n, relabelImage, croppedImage, prod_spacing) # Agatston 3d: # ----------- density_score = self.computeDensityScore(max) score3d = size * (self.sx * self.sy) * density_score mass_score = mean * volume # self.labelScores["Agatston Score"].append(score) self.labelScores["Agatston Score 3D"].append(score3d) self.labelScores["Agatston Score 2D"].append(score2d) self.labelScores["Mass Score"].append(mass_score) self.labelScores["Volume"].append(volume) self.selectedLabelList.append(0) # generate the contour marchingCubes = vtk.vtkDiscreteMarchingCubes() marchingCubes.SetInputData(self.labelsNode.GetImageData()) marchingCubes.SetValue(0, count + 1) marchingCubes.Update() transformPolyData = vtk.vtkTransformPolyDataFilter() transformPolyData.SetInputData(marchingCubes.GetOutput()) mat = vtk.vtkMatrix4x4() self.labelsNode.GetIJKToRASMatrix(mat) trans = vtk.vtkTransform() trans.SetMatrix(mat) transformPolyData.SetTransform(trans) transformPolyData.Update() poly = vtk.vtkPolyData() poly.DeepCopy(transformPolyData.GetOutput()) modelNode = slicer.vtkMRMLModelNode() slicer.mrmlScene.AddNode(modelNode) dnode = slicer.vtkMRMLModelDisplayNode() slicer.mrmlScene.AddNode(dnode) modelNode.AddAndObserveDisplayNodeID(dnode.GetID()) modelNode.SetAndObservePolyData(poly) ct = slicer.mrmlScene.GetNodeByID( 'vtkMRMLColorTableNodeLabels') rgb = [0, 0, 0] ct.GetLookupTable().GetColor(count + 1, rgb) dnode.SetColor(rgb) # Enable Slice intersection dnode.SetSliceDisplayMode(0) dnode.SetSliceIntersectionVisibility(1) # self.addLabel(count, rgb, [score, mass_score, volume, mean, max]) self.addLabel( count, rgb, [score2d, score3d, mass_score, volume, mean, max]) count = count + 1 self.modelNodes.append(modelNode) self.selectedLabels[poly] = n for sr in self.summary_reports: self.scoreField[sr].setText(self.totalScores[sr])