Example #1
0
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")
Example #2
0
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
Example #3
0
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])