Slicer 5.13
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
Loading...
Searching...
No Matches
qSlicerMultiVolumeExplorerModuleWidget.py
Go to the documentation of this file.
1from __main__ import ctk, slicer
2import logging
3import PythonQt
4from qt import QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout, QSizePolicy, QDialog, QSize, QPoint
5from qt import QWidget, QLabel, QPushButton, QCheckBox, QRadioButton, QSpinBox, QTimer, QButtonGroup, QGroupBox
6from qSlicerMultiVolumeExplorerModuleHelper import qSlicerMultiVolumeExplorerModuleHelper as Helper
7from qSlicerMultiVolumeExplorerCharts import LabeledImageChartView, MultiVolumeIntensityChartView
8
9
11
12 def __init__(self, parent=None):
13 logging.debug("qSlicerMultiVolumeExplorerSimplifiedModuleWidget:init() called")
14 if not parent or not hasattr(parent, "layout"):
15 self.parent = slicer.qMRMLWidget()
16 self.parent.setLayout(QVBoxLayout())
17 else:
18 self.parent = parent
19
20 self.layout = self.parent.layout()
21
24
27
28 self.chartPopupWindow = None
29 self.chartPopupSize = QSize(600, 300)
30 self.chartPopupPosition = QPoint(0,0)
31
32 def hide(self):
33 self.widget.hide()
34
35 def show(self):
36 self.widget.show()
37
38 def setup(self):
39 self.widget = QWidget()
40 layout = QGridLayout()
41 self.widget.setLayout(layout)
42 self.layout.addWidget(self.widget)
43 self.widget.show()
44 self.layout = layout
45
46 self.setupInputFrame()
50
51 self.setFramesEnabled(False)
52
53 self.timer = QTimer()
54 self.timer.setInterval(50)
55
56 self.setupConnections()
57
58 # initialize slice observers (from DataProbe.py)
59 # keep list of pairs: [observee,tag] so they can be removed easily
60 self.styleObserverTags = []
61 # keep a map of interactor styles to sliceWidgets so we can easily get sliceLogic
62 self.sliceWidgetsPerStyle = {}
63 self.refreshObservers()
64
65 def setupInputFrame(self, parent=None):
66 if not parent:
67 parent = self.layout
68 self.bgMultiVolumeSelector = slicer.qMRMLNodeComboBox()
69 self.bgMultiVolumeSelector.nodeTypes = ["vtkMRMLMultiVolumeNode"]
70 self.bgMultiVolumeSelector.setMRMLScene(slicer.mrmlScene)
71 self.bgMultiVolumeSelector.addEnabled = 0
72 self._bgMultiVolumeSelectorLabel = QLabel("Input multivolume")
73 inputFrameWidget = QWidget()
74 self.inputFrameLayout = QFormLayout()
75 inputFrameWidget.setLayout(self.inputFrameLayout)
77 parent.addWidget(inputFrameWidget)
78
80 # TODO: initialize the slider based on the contents of the labels array
81 self.frameSlider = ctk.ctkSliderWidget()
82 self.frameSlider.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
83 self.frameLabel = QLabel("Current frame number")
84 self.playButton = QPushButton("Play")
85 self.playButton.toolTip = "Iterate over multivolume frames"
86 self.playButton.checkable = True
87 frameControlHBox = QHBoxLayout()
88 frameControlHBox.addWidget(self.frameLabel)
89 frameControlHBox.addWidget(self.frameSlider)
90 frameControlHBox.addWidget(self.playButton)
91 self.inputFrameLayout.addRow(frameControlHBox)
92
94 pass
95
96 def setupPlottingFrame(self, parent=None):
97 if not parent:
98 parent = self.layout
99 self.plottingFrameWidget = QWidget()
100 self.plottingFrameLayout = QGridLayout()
101 self.plottingFrameWidget.setLayout(self.plottingFrameLayout)
103 self.popupChartButton = QPushButton("Undock chart")
104 self.popupChartButton.setCheckable(True)
105 self.plottingFrameLayout.addWidget(self._multiVolumeIntensityChart.chartView)
106 self.plottingFrameLayout.addWidget(self.popupChartButton)
107 parent.addWidget(self.plottingFrameWidget)
108
110 self.parent.connect("mrmlSceneChanged(vtkMRMLScene*)", self.onVCMRMLSceneChanged)
111 self.bgMultiVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onBackgroundInputChanged)
112 self.playButton.connect("toggled(bool)", self.onPlayButtonToggled)
113 self.frameSlider.connect("valueChanged(double)", self.onSliderChanged)
114 self.timer.connect("timeout()", self.goToNext)
115 self.popupChartButton.connect("toggled(bool)", self.onDockChartViewToggled)
116
117 def onDockChartViewToggled(self, checked):
118 if checked:
119 self.chartPopupWindow = QDialog()
120 self.chartPopupWindow.setWindowFlags(PythonQt.QtCore.Qt.WindowStaysOnTopHint)
121 layout = QGridLayout()
122 self.chartPopupWindow.setLayout(layout)
123 layout.addWidget(self._multiVolumeIntensityChart.chartView)
124 layout.addWidget(self.popupChartButton)
125 self.chartPopupWindow.finished.connect(self.dockChartView)
126 self.chartPopupWindow.resize(self.chartPopupSize)
129 self.popupChartButton.setText("Dock chart")
130 self._multiVolumeIntensityChart.chartView.show()
131 else:
132 self.chartPopupWindow.close()
133
134 def dockChartView(self):
135 self.chartPopupSize = self.chartPopupWindow.size
137 self.plottingFrameLayout.addWidget(self._multiVolumeIntensityChart.chartView)
138 self.plottingFrameLayout.addWidget(self.popupChartButton)
139 self.popupChartButton.setText("Undock chart")
140 self.popupChartButton.disconnect("toggled(bool)", self.onDockChartViewToggled)
141 self.popupChartButton.checked = False
142 self.popupChartButton.connect("toggled(bool)", self.onDockChartViewToggled)
143
144 def onSliderChanged(self, frameId):
145 if self._bgMultiVolumeNode is None:
146 return
147 newValue = int(frameId)
148 self.setCurrentFrameNumber(newValue)
149
150 def onVCMRMLSceneChanged(self, mrmlScene):
151 logging.debug("qSlicerMultiVolumeExplorerSimplifiedModuleWidget:onVCMRMLSceneChanged")
152 self.bgMultiVolumeSelector.setMRMLScene(slicer.mrmlScene)
154
156 self._multiVolumeIntensityChart.reset()
157 self.setFramesEnabled(True)
158 if self._fgMultiVolumeNode and self._bgMultiVolumeNode:
159 Helper.SetBgFgVolumes(self._bgMultiVolumeNode.GetID(), self._fgMultiVolumeNode.GetID())
160 else:
161 Helper.SetBgVolume(self._bgMultiVolumeNode.GetID())
162 self.refreshFrameSlider()
163 self._multiVolumeIntensityChart.bgMultiVolumeNode = self._bgMultiVolumeNode
164 self.refreshObservers()
165
167 return self.bgMultiVolumeSelector.currentNode()
168
171
172 if self._bgMultiVolumeNode is not None:
174 else:
175 self.setFramesEnabled(False)
176
177 def onPlayButtonToggled(self, checked):
178 if self._bgMultiVolumeNode is None:
179 return
180 if checked:
181 self.timer.start()
182 self.playButton.text = "Stop"
183 else:
184 self.timer.stop()
185 self.playButton.text = "Play"
186
187 def processEvent(self, observee, event):
188 # logging.debug("processing event %s" % event)
189 if self._bgMultiVolumeNode is None:
190 return
191
192 # TODO: use a timer to delay calculation and compress events
193 if event == "LeaveEvent":
194 # reset all the readouts
195 # TODO: reset the label text
196 return
197
198 if observee not in self.sliceWidgetsPerStyle:
199 return
200
201 interactor = observee.GetInteractor()
202 self.createChart(self.sliceWidgetsPerStyle[observee], interactor.GetEventPosition())
203
204 def createChart(self, sliceWidget, position):
205 self._multiVolumeIntensityChart.createChart(sliceWidget, position)
206
207 def setCurrentFrameNumber(self, frameNumber):
208 mvDisplayNode = self._bgMultiVolumeNode.GetDisplayNode()
209 mvDisplayNode.SetFrameComponent(frameNumber)
210
211 def setFramesEnabled(self, enabled):
212 pass
213
215 """When the layout changes, drop the observers from
216 all the old widgets and create new observers for the
217 newly created widgets
218 """
219 self.removeObservers()
220 # get new slice nodes
221 layoutManager = slicer.app.layoutManager()
222 sliceNodeCount = slicer.mrmlScene.GetNumberOfNodesByClass("vtkMRMLSliceNode")
223 for nodeIndex in range(sliceNodeCount):
224 # find the widget for each node in scene
225 sliceNode = slicer.mrmlScene.GetNthNodeByClass(nodeIndex, "vtkMRMLSliceNode")
226 sliceWidget = layoutManager.sliceWidget(sliceNode.GetLayoutName())
227 if sliceWidget:
228 # add observers and keep track of tags
229 style = sliceWidget.sliceView().interactorStyle()
230 self.sliceWidgetsPerStyle[style] = sliceWidget
231 events = ("MouseMoveEvent", "EnterEvent", "LeaveEvent")
232 for event in events:
233 tag = style.AddObserver(event, self.processEvent)
234 self.styleObserverTags.append([style,tag])
235
237 for observee,tag in self.styleObserverTags:
238 observee.RemoveObserver(tag)
239 self.styleObserverTags = []
240 self.sliceWidgetsPerStyle = {}
241
243 self.frameSlider.minimum = 0
244 if not self._bgMultiVolumeNode:
245 self.frameSlider.maximum = 0
246 return
247 nFrames = self._bgMultiVolumeNode.GetNumberOfFrames()
248 self.frameSlider.maximum = nFrames - 1
249
250 def goToNext(self):
251 currentElement = self.frameSlider.value
252 currentElement += 1
253 if currentElement > self.frameSlider.maximum:
254 currentElement = 0
255 self.frameSlider.value = currentElement
256
257
259
260 def __init__(self, parent=None):
261 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.__init__(self, parent)
262
266 def setupInputFrame(self, parent=None):
267 self.inputFrame = ctk.ctkCollapsibleButton()
268 self.inputFrame.text = "Input"
269 self.inputFrame.collapsed = 0
270 inputFrameCollapsibleLayout = QFormLayout(self.inputFrame)
271 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.setupInputFrame(self, parent=inputFrameCollapsibleLayout)
272 self.layout.addWidget(self.inputFrame)
273
274 self.fgMultiVolumeSelector = slicer.qMRMLNodeComboBox()
275 self.fgMultiVolumeSelector.nodeTypes = ["vtkMRMLMultiVolumeNode"]
276 self.fgMultiVolumeSelector.setMRMLScene(slicer.mrmlScene)
277 self.fgMultiVolumeSelector.addEnabled = 0
278 self.fgMultiVolumeSelector.noneEnabled = 1
279 self.fgMultiVolumeSelector.toolTip = "Secondary multivolume will be used for the secondary \
280 plot in interactive charting. As an example, this can be used to overlay the \
281 curve obtained by fitting a model to the data"
282 self.inputFrameLayout.addRow(QLabel("Input secondary multivolume"), self.fgMultiVolumeSelector)
283
285 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.setupFrameControlFrame(self)
286
287 self.frameCopySelector = slicer.qMRMLNodeComboBox()
288 self.frameCopySelector.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
289 self.frameCopySelector.nodeTypes = ["vtkMRMLScalarVolumeNode"]
290 self.frameCopySelector.setMRMLScene(slicer.mrmlScene)
291 self.frameCopySelector.addEnabled = 1
292 self.frameCopySelector.enabled = 0
293 # do not show "children" of vtkMRMLScalarVolumeNode
294 self.frameCopySelector.hideChildNodeTypes = ["vtkMRMLDiffusionWeightedVolumeNode",
295 "vtkMRMLDiffusionTensorVolumeNode",
296 "vtkMRMLVectorVolumeNode"]
297 self.extractFrameCopy = False
298 self.extractFrameCheckBox = QCheckBox("Enable copying while sliding")
299 hbox = QHBoxLayout()
300 hbox.addWidget(QLabel("Current frame copy"))
301 hbox.addWidget(self.frameCopySelector)
302 hbox.addWidget(self.extractFrameCheckBox)
303 self.inputFrameLayout.addRow(hbox)
304
305 self.currentFrameCopySelector = slicer.qMRMLNodeComboBox()
306 self.currentFrameCopySelector.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
307 self.currentFrameCopySelector.nodeTypes = ["vtkMRMLScalarVolumeNode"]
308 self.currentFrameCopySelector.setMRMLScene(slicer.mrmlScene)
309 self.currentFrameCopySelector.addEnabled = 0
310 self.currentFrameCopySelector.enabled = 0
311
312 self.currentFrameCopyButton = QPushButton("Copy frame")
313 self.currentFrameCopyButton.toolTip = "Copy currently selected frame"
314
315 hbox2 = QHBoxLayout()
316 hbox2.addWidget(QLabel("Current frame click-to-copy"))
317 hbox2.addWidget(self.currentFrameCopySelector)
318 hbox2.addWidget(self.currentFrameCopyButton)
319 self.inputFrameLayout.addRow(hbox2)
320
322 self.plotSettingsFrame = ctk.ctkCollapsibleButton()
323 self.plotSettingsFrame.text = "Plotting Settings"
324 self.plotSettingsFrame.collapsed = 1
325 plotSettingsFrameLayout = QFormLayout(self.plotSettingsFrame)
326 self.layout.addWidget(self.plotSettingsFrame)
327
328 # label map for probing
329 self.labelMapSelector = slicer.qMRMLNodeComboBox()
330 self.labelMapSelector.nodeTypes = ["vtkMRMLLabelMapVolumeNode"]
331 self.labelMapSelector.toolTip = "Label map to be probed"
332 self.labelMapSelector.setMRMLScene(slicer.mrmlScene)
333 self.labelMapSelector.addEnabled = 0
334 self.chartButton = QPushButton("Chart")
335 self.chartButton.setEnabled(False)
336
337 hbox = QHBoxLayout()
338 hbox.addWidget(QLabel("Probed label volume"))
339 hbox.addWidget(self.labelMapSelector)
340 hbox.addWidget(self.chartButton)
341 plotSettingsFrameLayout.addRow(hbox)
342
343 self.iCharting = QCheckBox("Interactive charting")
344 self.iCharting.setChecked(True)
345 plotSettingsFrameLayout.addRow(self.iCharting)
346
347 self.iChartingMode = QButtonGroup()
348 self.iChartingIntensity = QRadioButton("Signal intensity")
349 self.iChartingIntensityFixedAxes = QRadioButton("Fixed range intensity")
350 self.iChartingPercent = QRadioButton("Percentage change")
351 self.iChartingIntensity.setChecked(1)
352 self.iChartingMode.addButton(self.iChartingIntensity)
353 self.iChartingMode.addButton(self.iChartingIntensityFixedAxes)
354 self.iChartingMode.addButton(self.iChartingPercent)
355
356 hbox = QHBoxLayout()
357 self.plottingModeGroupBox = QGroupBox("Plotting mode:")
358 plottingModeLayout = QVBoxLayout()
359 self.plottingModeGroupBox.setLayout(plottingModeLayout)
360 plottingModeLayout.addWidget(self.iChartingIntensity)
361 plottingModeLayout.addWidget(self.iChartingIntensityFixedAxes)
362 plottingModeLayout.addWidget(self.iChartingPercent)
363 hbox.addWidget(self.plottingModeGroupBox)
364
365 self.showLegendCheckBox = QCheckBox("Display legend")
366 self.showLegendCheckBox.setChecked(0)
367 self.xLogScaleCheckBox = QCheckBox("Use log scale for X axis")
368 self.xLogScaleCheckBox.setChecked(0)
369 self.yLogScaleCheckBox = QCheckBox("Use log scale for Y axis")
370 self.yLogScaleCheckBox.setChecked(0)
371
372 self.plotGeneralSettingsGroupBox = QGroupBox("General Plot options:")
373 plotGeneralSettingsLayout = QVBoxLayout()
374 self.plotGeneralSettingsGroupBox.setLayout(plotGeneralSettingsLayout)
375 plotGeneralSettingsLayout.addWidget(self.showLegendCheckBox)
376 plotGeneralSettingsLayout.addWidget(self.xLogScaleCheckBox)
377 plotGeneralSettingsLayout.addWidget(self.yLogScaleCheckBox)
378 hbox.addWidget(self.plotGeneralSettingsGroupBox)
379 plotSettingsFrameLayout.addRow(hbox)
380
382 self.nFramesBaselineCalculation.minimum = 1
383 hbox = QHBoxLayout()
384 hbox.addWidget(QLabel("Frame count(baseline calculation):"))
385 hbox.addWidget(self.nFramesBaselineCalculation)
386 plotSettingsFrameLayout.addRow(hbox)
387
388 def setupPlottingFrame(self, parent=None):
389 self.plotFrame = ctk.ctkCollapsibleButton()
390 self.plotFrame.text = "Plotting"
391 self.plotFrame.collapsed = 0
392 plotFrameLayout = QGridLayout(self.plotFrame)
393 self.layout.addWidget(self.plotFrame)
394 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.setupPlottingFrame(self, parent=plotFrameLayout)
395
396 def onDockChartViewToggled(self, checked):
397 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.onDockChartViewToggled(self, checked)
398 if checked:
399 self.layout.removeWidget(self.plotFrame)
400 self.plotFrame.hide()
401
402 def dockChartView(self):
403 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.dockChartView(self)
404 self.layout.addWidget(self.plotFrame)
405 self.plotFrame.show()
406
407 def setFramesEnabled(self, enabled):
408 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.setFramesEnabled(self, enabled)
409 self.plotSettingsFrame.setEnabled(enabled)
410 self.plotFrame.setEnabled(enabled)
411 self.plotFrame.collapsed = 0 if enabled else 1
412
414 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.setupConnections(self)
415 self.labelMapSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onLabelNodeChanged)
416 self.chartButton.connect("clicked()", self.onLabeledChartRequested)
417 self.xLogScaleCheckBox.connect("stateChanged(int)", self.onXLogScaleRequested)
418 self.yLogScaleCheckBox.connect("stateChanged(int)", self.onYLogScaleRequested)
420 self.iChartingMode.buttonClicked.connect(self.onChartingModeChanged)
421 self.showLegendCheckBox.connect("stateChanged(int)", self.onShowLegendChanged)
422 self.fgMultiVolumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onForegroundInputChanged)
423 self.extractFrameCheckBox.connect("stateChanged(int)", self.onExtractFrameChanged)
424 self.frameCopySelector.connect("mrmlSceneChanged(vtkMRMLScene*)", self.onVFMRMLSceneChanged)
425 self.currentFrameCopyButton.connect("clicked()", self.onCopyButtonClicked)
426
428 self._multiVolumeIntensityChart.nFramesForBaselineCalculation = value
429
430 def onChartingModeChanged(self, button):
431 if button is self.iChartingIntensity:
432 self._multiVolumeIntensityChart.activateSignalIntensityMode()
433 elif button is self.iChartingIntensityFixedAxes:
434 self._multiVolumeIntensityChart.activateFixedRangeIntensityMode()
435 elif button is self.iChartingPercent:
436 self._multiVolumeIntensityChart.activatePercentageChangeMode()
437
438 def onShowLegendChanged(self, checked):
439 self._multiVolumeIntensityChart.showLegend = True if checked == 2 else False
440
441 def onXLogScaleRequested(self, checked):
442 self._multiVolumeIntensityChart.showXLogScale = True if checked == 2 else False
443
444 def onYLogScaleRequested(self, checked):
445 self._multiVolumeIntensityChart.showYLogScale = True if checked == 2 else False
446
447 def onLVMRMLSceneChanged(self, mrmlScene):
448 self.labelMapSelector.setMRMLScene(slicer.mrmlScene)
449
450 def onVFMRMLSceneChanged(self, mrmlScene):
451 self.frameCopySelector.setMRMLScene(slicer.mrmlScene)
452
454 labelNode = self.labelMapSelector.currentNode()
455 self.chartButton.setEnabled(labelNode is not None and self._bgMultiVolumeNode is not None)
456
458 logging.info("qSlicerMultiVolumeExplorerModuleWidget:ForegroundInputChanged")
460 self._multiVolumeIntensityChart.fgMultiVolumeNode = self.fgMultiVolumeSelector.currentNode()
462
464 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.onBackgroundInputChanged(self)
465
466 if self._bgMultiVolumeNode is not None:
467 self.frameCopySelector.setCurrentNode(None)
468 self.currentFrameCopySelector.setCurrentNode(None)
469
470 self.nFramesBaselineCalculation.maximum = self._bgMultiVolumeNode.GetNumberOfFrames()
471 self.onLabelNodeChanged()
472
473 """
474 If extract button is checked, will copy the current frame to the
475 selected volume node on each event from frame slider
476 """
477 def onExtractFrameChanged(self, checked):
478 if checked:
479 self.extractFrameCopy = True
480 self.onSliderChanged(self.frameSlider.value)
481 else:
482 self.extractFrameCopy = False
483
484 def onSliderChanged(self, frameId):
485 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.onSliderChanged(self, frameId)
486 frameId = int(frameId)
487
488 if self.extractFrameCopy:
489 frameVolume = self.frameCopySelector.currentNode()
490 frameVolumeCopy = Helper.extractFrame(frameVolume, self._bgMultiVolumeNode, frameId)
491 if not frameVolume:
492 self.frameCopySelector.setCurrentNode(frameVolumeCopy)
493 frameName = "%s frame %d" % (self._bgMultiVolumeNode.GetName(), frameId)
494 frameVolumeCopy.SetName(frameName)
495
497 if self._bgMultiVolumeNode is None:
498 return
499
500 frameId = int(self.frameSlider.value)
501
502 frameName = "%s copied frame %d" % (self._bgMultiVolumeNode.GetName(), frameId)
503 nodeVolume = slicer.mrmlScene.GetNodesByName(frameName).GetNumberOfItems()
504 if (nodeVolume == 0):
505 frameVolume = self.currentFrameCopySelector.addNode()
506 frameVolumeCopy = Helper.extractFrame(frameVolume, self._bgMultiVolumeNode, frameId)
507 frameVolumeCopy.SetName(frameName)
508 self.currentFrameCopySelector.setCurrentNode(frameVolumeCopy)
509
511 labelNode = self.labelMapSelector.currentNode()
512 mvNode = self._bgMultiVolumeNode
513
514 mvLabels = MultiVolumeIntensityChartView.getMultiVolumeLabels(self._bgMultiVolumeNode)
515
516 chartViewNode = LabeledImageChartView(labelNode=labelNode,
517 multiVolumeNode=mvNode,
518 multiVolumeLabels=mvLabels,
519 baselineFrames=self.nFramesBaselineCalculation,
520 displayPercentageChange=self.iChartingPercent.checked)
521 chartViewNode.requestChartCreation()
522
523 def processEvent(self, observee, event):
524 if not self.iCharting.checked:
525 return
526 qSlicerMultiVolumeExplorerSimplifiedModuleWidget.processEvent(self, observee, event)