Slicer  4.10
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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("Unknown 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'