Slicer  4.8
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
SegmentEditorLogicalEffect.py
Go to the documentation of this file.
1 import os
2 import vtk, qt, ctk, slicer
3 import logging
4 from SegmentEditorEffects import *
5 
7  """ LogicalEffect is an MorphologyEffect to erode a layer of pixels from a segment
8  """
9 
10  def __init__(self, scriptedEffect):
11  scriptedEffect.name = 'Logical operators'
12  self.operationsRequireModifierSegment = [LOGICAL_COPY, LOGICAL_UNION, LOGICAL_SUBTRACT, LOGICAL_INTERSECT]
13  AbstractScriptedSegmentEditorEffect.__init__(self, scriptedEffect)
14 
15  def clone(self):
16  import qSlicerSegmentationsEditorEffectsPythonQt as effects
17  clonedEffect = effects.qSlicerSegmentEditorScriptedEffect(None)
18  clonedEffect.setPythonSource(__file__.replace('\\','/'))
19  return clonedEffect
20 
21  def icon(self):
22  iconPath = os.path.join(os.path.dirname(__file__), 'Resources/Icons/Logical.png')
23  if os.path.exists(iconPath):
24  return qt.QIcon(iconPath)
25  return qt.QIcon()
26 
27  def helpText(self):
28  return """<html>Apply logical operators or combine segments<br>. Available operations:<p>
29 <ul style="margin: 0">
30 <li><b>Copy:</b> replace the selected segment by the modifier segment.</li>
31 <li><b>Add:</b> add modifier segment to current segment.</li>
32 <li><b>Subtract:</b> subtract region of modifier segment from the selected segment.</li>
33 <li><b>Intersect:</b> only keeps those regions in the select segment that are common with the modifier segment.</li>
34 <li><b>Invert:</b> inverts selected segment.</li>
35 <li><b>Clear:</b> clears selected segment.</li>
36 <li><b>Fill:</b> completely fills selected segment.</li>
37 </ul><p>
38 <b>Selected segment:</b> segment selected in the segment list - above. <b>Modifier segment:</b> segment chosen in segment list in effect options - below.
39 <p></html>"""
40 
41  def setupOptionsFrame(self):
42 
43  self.methodSelectorComboBox = qt.QComboBox()
44  self.methodSelectorComboBox.addItem("Copy", LOGICAL_COPY)
45  self.methodSelectorComboBox.addItem("Add", LOGICAL_UNION)
46  self.methodSelectorComboBox.addItem("Subtract", LOGICAL_SUBTRACT)
47  self.methodSelectorComboBox.addItem("Intersect", LOGICAL_INTERSECT)
48  self.methodSelectorComboBox.addItem("Invert", LOGICAL_INVERT)
49  self.methodSelectorComboBox.addItem("Clear", LOGICAL_CLEAR)
50  self.methodSelectorComboBox.addItem("Fill", LOGICAL_FILL)
51  self.methodSelectorComboBox.setToolTip('Click <dfn>Show details</dfn> link above for description of operations.')
52 
53  self.bypassMaskingCheckBox = qt.QCheckBox("Bypass masking")
54  self.bypassMaskingCheckBox.setToolTip("Ignore all masking options and only modify the selected segment.")
55  self.bypassMaskingCheckBox.objectName = self.__class__.__name__ + 'BypassMasking'
56 
57  self.applyButton = qt.QPushButton("Apply")
58  self.applyButton.objectName = self.__class__.__name__ + 'Apply'
59 
60  operationFrame = qt.QHBoxLayout()
61  operationFrame.addWidget(self.methodSelectorComboBox)
62  operationFrame.addWidget(self.applyButton)
63  operationFrame.addWidget(self.bypassMaskingCheckBox)
64  self.marginSizeMmLabel = self.scriptedEffect.addLabeledOptionsWidget("Operation:", operationFrame)
65 
66  self.modifierSegmentSelectorLabel = qt.QLabel("Modifier segment:")
67  self.scriptedEffect.addOptionsWidget(self.modifierSegmentSelectorLabel)
68 
69  self.modifierSegmentSelector = slicer.qMRMLSegmentsTableView()
70  self.modifierSegmentSelector.selectionMode = qt.QAbstractItemView.SingleSelection
71  self.modifierSegmentSelector.headerVisible = False
72  self.modifierSegmentSelector.visibilityColumnVisible = False
73  self.modifierSegmentSelector.opacityColumnVisible = False
74 
75  self.modifierSegmentSelector.setMRMLScene(slicer.mrmlScene)
76  self.modifierSegmentSelector.setToolTip('Contents of this segment will be used for modifying the selected segment. This segment itself will not be changed.')
77  self.scriptedEffect.addOptionsWidget(self.modifierSegmentSelector)
78 
79  self.applyButton.connect('clicked()', self.onApply)
80  self.methodSelectorComboBox.connect("currentIndexChanged(int)", self.updateMRMLFromGUI)
81  self.modifierSegmentSelector.connect("selectionChanged(QItemSelection, QItemSelection)", self.updateMRMLFromGUI)
82  self.bypassMaskingCheckBox.connect("stateChanged(int)", self.updateMRMLFromGUI)
83 
84  def createCursor(self, widget):
85  # Turn off effect-specific cursor for this effect
86  return slicer.util.mainWindow().cursor
87 
88  def setMRMLDefaults(self):
89  self.scriptedEffect.setParameterDefault("Operation", LOGICAL_COPY)
90  self.scriptedEffect.setParameterDefault("ModifierSegmentID", "")
91  self.scriptedEffect.setParameterDefault("BypassMasking", 1)
92 
93  def modifierSegmentID(self):
94  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
95  if not segmentationNode:
96  return ""
97  if not self.scriptedEffect.parameterDefined("ModifierSegmentID"):
98  # Avoid logging warning
99  return ""
100  modifierSegmentIDs = self.scriptedEffect.parameter("ModifierSegmentID").split(';')
101  if not modifierSegmentIDs:
102  return ""
103  return modifierSegmentIDs[0]
104 
105  def updateGUIFromMRML(self):
106  operation = self.scriptedEffect.parameter("Operation")
107  operationIndex = self.methodSelectorComboBox.findData(operation)
108  wasBlocked = self.methodSelectorComboBox.blockSignals(True)
109  self.methodSelectorComboBox.setCurrentIndex(operationIndex)
110  self.methodSelectorComboBox.blockSignals(wasBlocked)
111 
112  modifierSegmentID = self.modifierSegmentID()
113  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
114  wasBlocked = self.modifierSegmentSelector.blockSignals(True)
115  self.modifierSegmentSelector.setSegmentationNode(segmentationNode)
116  self.modifierSegmentSelector.setSelectedSegmentIDs([modifierSegmentID])
117  self.modifierSegmentSelector.blockSignals(wasBlocked)
118 
119  modifierSegmentRequired = (operation in self.operationsRequireModifierSegment)
120  self.modifierSegmentSelectorLabel.setVisible(modifierSegmentRequired)
121  self.modifierSegmentSelector.setVisible(modifierSegmentRequired)
122 
123  if operation == LOGICAL_COPY:
124  self.modifierSegmentSelectorLabel.text = "Copy from segment:"
125  elif operation == LOGICAL_UNION:
126  self.modifierSegmentSelectorLabel.text = "Add segment:"
127  elif operation == LOGICAL_SUBTRACT:
128  self.modifierSegmentSelectorLabel.text = "Subtract segment:"
129  elif operation == LOGICAL_INTERSECT:
130  self.modifierSegmentSelectorLabel.text = "Intersect with segment:"
131  else:
132  self.modifierSegmentSelectorLabel.text = "Modifier segment:"
133 
134  if modifierSegmentRequired and not modifierSegmentID:
135  self.applyButton.setToolTip("Please select a modifier segment in the list below.")
136  self.applyButton.enabled = False
137  else:
138  self.applyButton.setToolTip("")
139  self.applyButton.enabled = True
140 
141  bypassMasking = qt.Qt.Unchecked if self.scriptedEffect.integerParameter("BypassMasking") == 0 else qt.Qt.Checked
142  wasBlocked = self.bypassMaskingCheckBox.blockSignals(True)
143  self.bypassMaskingCheckBox.setCheckState(bypassMasking)
144  self.bypassMaskingCheckBox.blockSignals(wasBlocked)
145 
146  def updateMRMLFromGUI(self):
147  operationIndex = self.methodSelectorComboBox.currentIndex
148  operation = self.methodSelectorComboBox.itemData(operationIndex)
149  self.scriptedEffect.setParameter("Operation", operation)
150 
151  bypassMasking = 1 if self.bypassMaskingCheckBox.isChecked() else 0
152  self.scriptedEffect.setParameter("BypassMasking", bypassMasking)
153 
154  modifierSegmentIDs = ';'.join(self.modifierSegmentSelector.selectedSegmentIDs()) # semicolon-separated list of segment IDs
155  self.scriptedEffect.setParameter("ModifierSegmentID", modifierSegmentIDs)
156 
157  def getInvertedBinaryLabelmap(self, modifierLabelmap):
158  import vtkSegmentationCorePython as vtkSegmentationCore
159 
160  fillValue = 1
161  eraseValue = 0
162  inverter = vtk.vtkImageThreshold()
163  inverter.SetInputData(modifierLabelmap)
164  inverter.SetInValue(fillValue)
165  inverter.SetOutValue(eraseValue)
166  inverter.ReplaceInOn()
167  inverter.ThresholdByLower(0)
168  inverter.SetOutputScalarType(vtk.VTK_UNSIGNED_CHAR)
169  inverter.Update()
170 
171  invertedModifierLabelmap = vtkSegmentationCore.vtkOrientedImageData()
172  invertedModifierLabelmap.ShallowCopy(inverter.GetOutput())
173  imageToWorldMatrix = vtk.vtkMatrix4x4()
174  modifierLabelmap.GetImageToWorldMatrix(imageToWorldMatrix)
175  invertedModifierLabelmap.SetGeometryFromImageToWorldMatrix(imageToWorldMatrix)
176  return invertedModifierLabelmap
177 
178  def onApply(self):
179 
180  self.scriptedEffect.saveStateForUndo()
181 
182  import vtkSegmentationCorePython as vtkSegmentationCore
183 
184  # Get modifier labelmap and parameters
185 
186  operation = self.scriptedEffect.parameter("Operation")
187  bypassMasking = (self.scriptedEffect.integerParameter("BypassMasking") != 0)
188 
189  selectedSegmentID = self.scriptedEffect.parameterSetNode().GetSelectedSegmentID()
190 
191  segmentationNode = self.scriptedEffect.parameterSetNode().GetSegmentationNode()
192  segmentation = segmentationNode.GetSegmentation()
193 
194  if operation in self.operationsRequireModifierSegment:
195 
196  # Get modifier segment
197  modifierSegmentID = self.modifierSegmentID()
198  if not modifierSegmentID:
199  logging.error("Operation {0} requires a selected modifier segment".format(operation))
200  return
201  modifierSegment = segmentation.GetSegment(modifierSegmentID)
202  modifierSegmentLabelmap = modifierSegment.GetRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationBinaryLabelmapRepresentationName())
203 
204  if operation == LOGICAL_COPY:
205  if bypassMasking:
206  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(modifierSegmentLabelmap,
207  segmentationNode, selectedSegmentID, slicer.vtkSlicerSegmentationsModuleLogic.MODE_REPLACE, modifierSegmentLabelmap.GetExtent())
208  else:
209  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
210  elif operation == LOGICAL_UNION:
211  if bypassMasking:
212  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(modifierSegmentLabelmap,
213  segmentationNode, selectedSegmentID, slicer.vtkSlicerSegmentationsModuleLogic.MODE_MERGE_MAX, modifierSegmentLabelmap.GetExtent())
214  else:
215  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeAdd)
216  elif operation == LOGICAL_SUBTRACT:
217  if bypassMasking:
218  invertedModifierSegmentLabelmap = self.getInvertedBinaryLabelmap(modifierSegmentLabelmap)
219  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(invertedModifierSegmentLabelmap, segmentationNode, selectedSegmentID,
220  slicer.vtkSlicerSegmentationsModuleLogic.MODE_MERGE_MIN, modifierSegmentLabelmap.GetExtent())
221  else:
222  self.scriptedEffect.modifySelectedSegmentByLabelmap(modifierSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeRemove)
223  elif operation == LOGICAL_INTERSECT:
224  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
225  intersectionLabelmap = vtkSegmentationCore.vtkOrientedImageData()
226  vtkSegmentationCore.vtkOrientedImageDataResample.MergeImage(selectedSegmentLabelmap, modifierSegmentLabelmap, intersectionLabelmap, vtkSegmentationCore.vtkOrientedImageDataResample.OPERATION_MINIMUM, selectedSegmentLabelmap.GetExtent())
227  selectedSegmentLabelmapExtent = selectedSegmentLabelmap.GetExtent()
228  modifierSegmentLabelmapExtent = modifierSegmentLabelmap.GetExtent()
229  commonExtent = [max(selectedSegmentLabelmapExtent[0], modifierSegmentLabelmapExtent[0]),
230  min(selectedSegmentLabelmapExtent[1], modifierSegmentLabelmapExtent[1]),
231  max(selectedSegmentLabelmapExtent[2], modifierSegmentLabelmapExtent[2]),
232  min(selectedSegmentLabelmapExtent[3], modifierSegmentLabelmapExtent[3]),
233  max(selectedSegmentLabelmapExtent[4], modifierSegmentLabelmapExtent[4]),
234  min(selectedSegmentLabelmapExtent[5], modifierSegmentLabelmapExtent[5])]
235  if bypassMasking:
236  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(intersectionLabelmap, segmentationNode, selectedSegmentID,
237  slicer.vtkSlicerSegmentationsModuleLogic.MODE_REPLACE, commonExtent)
238  else:
239  self.scriptedEffect.modifySelectedSegmentByLabelmap(intersectionLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet, commonExtent)
240 
241  elif operation == LOGICAL_INVERT:
242  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
243  invertedSelectedSegmentLabelmap = self.getInvertedBinaryLabelmap(selectedSegmentLabelmap)
244  if bypassMasking:
245  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(
246  invertedSelectedSegmentLabelmap, segmentationNode, selectedSegmentID, slicer.vtkSlicerSegmentationsModuleLogic.MODE_REPLACE)
247  else:
248  self.scriptedEffect.modifySelectedSegmentByLabelmap(invertedSelectedSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
249 
250  elif operation == LOGICAL_CLEAR or operation == LOGICAL_FILL:
251  selectedSegmentLabelmap = self.scriptedEffect.selectedSegmentLabelmap()
252  vtkSegmentationCore.vtkOrientedImageDataResample.FillImage(selectedSegmentLabelmap, 1 if operation == LOGICAL_FILL else 0, selectedSegmentLabelmap.GetExtent())
253  if bypassMasking:
254  slicer.vtkSlicerSegmentationsModuleLogic.SetBinaryLabelmapToSegment(
255  selectedSegmentLabelmap, segmentationNode, selectedSegmentID, slicer.vtkSlicerSegmentationsModuleLogic.MODE_REPLACE)
256  else:
257  self.scriptedEffect.modifySelectedSegmentByLabelmap(selectedSegmentLabelmap, slicer.qSlicerSegmentEditorAbstractEffect.ModificationModeSet)
258 
259  else:
260  logging.error("Uknown operation: {0}".format(operation))
261 
262 LOGICAL_COPY = 'COPY'
263 LOGICAL_UNION = 'UNION'
264 LOGICAL_INTERSECT = 'INTERSECT'
265 LOGICAL_SUBTRACT = 'SUBTRACT'
266 LOGICAL_INVERT = 'INVERT'
267 LOGICAL_CLEAR = 'CLEAR'
268 LOGICAL_FILL = 'FILL'