From 5e353158a0867b04014d211aafcf8b3824cb9641 Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Wed, 17 Dec 2025 16:47:43 -0500 Subject: [PATCH 1/3] Fix Qt6 incompatibility QApplication.flush() is no longer available in Qt6. --- SimpleFilters/Testing/Python/SimpleFiltersModuleTest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SimpleFilters/Testing/Python/SimpleFiltersModuleTest.py b/SimpleFilters/Testing/Python/SimpleFiltersModuleTest.py index 387f9f8..a645dbf 100644 --- a/SimpleFilters/Testing/Python/SimpleFiltersModuleTest.py +++ b/SimpleFilters/Testing/Python/SimpleFiltersModuleTest.py @@ -25,7 +25,7 @@ def delayDisplay(self,message,msec=1000): self.info.exec_() # make sure all events are processed before moving on - qt.QApplication.flush() + slicer.app.processEvents() def setUp(self): """ Do whatever is needed to reset the state - typically a scene clear will be enough. From 8295775c7c81524facc10f70b50e89512ebe0210 Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Wed, 17 Dec 2025 16:52:37 -0500 Subject: [PATCH 2/3] Make selection and showing of output node optional If user does not select output volume then a new one is created automatically. If the user does not want to show the output volume automatically (thereby resetting field of view, etc.) then auto-show can be turned off in the new "Advanced" section. --- SimpleFilters/SimpleFilters.py | 51 ++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/SimpleFilters/SimpleFilters.py b/SimpleFilters/SimpleFilters.py index 20df6ca..e788f1d 100644 --- a/SimpleFilters/SimpleFilters.py +++ b/SimpleFilters/SimpleFilters.py @@ -206,6 +206,23 @@ def setup(self): hlayout.addWidget(self.applyButton) self.layout.addLayout(hlayout) + # + # Advanced Area + # + advancedCollapsibleButton = ctk.ctkCollapsibleButton() + advancedCollapsibleButton.text = "Advanced" + advancedCollapsibleButton.collapsed = True + self.layout.addWidget(advancedCollapsibleButton) + + # Layout within the advanced collapsible button + advancedFormLayout = qt.QFormLayout(advancedCollapsibleButton) + + self.showOutputCheckbox = qt.QCheckBox() + self.showOutputCheckbox.checked = True + self.showOutputCheckbox.toolTip = "Automatically show output volume in slice views and fit slices to volume after execution is completed." + self.showOutputCheckbox.connect('toggled(bool)', self.onShowOutputCheckboxToggled) + advancedFormLayout.addRow("Auto-show output:", self.showOutputCheckbox) + # connections self.restoreDefaultsButton.connect('clicked(bool)', self.onRestoreDefaultsButton) self.applyButton.connect('clicked(bool)', self.onApplyButton) @@ -290,6 +307,14 @@ def onApplyButton(self): self.filterParameters.prerun() self.logic = SimpleFiltersLogic() + self.logic.showOutput = self.showOutputCheckbox.checked + + if self.filterParameters.outputSelector.currentNode() is None: + # create a new output volume + if self.filterParameters.outputLabelMap: + self.filterParameters.outputSelector.addNode("vtkMRMLLabelMapVolumeNode") + else: + self.filterParameters.outputSelector.addNode("vtkMRMLScalarVolumeNode") self.printPythonCommand() @@ -320,6 +345,11 @@ def onCancelButton(self): self.logic.abort = True + def onShowOutputCheckboxToggled(self, checked): + if self.logic: + self.logic.showOutput = checked + + def onLogicEventStart(self): self.filterStartTime = time.time() self.currentStatusLabel.text = "Running" @@ -365,6 +395,7 @@ def __init__(self): self.main_queue_running = False self.thread = threading.Thread() self.abort = False + self.showOutput = True def __del__(self): @@ -507,16 +538,17 @@ def updateOutput(self,img): with slicer.util.RenderBlocker(): sitkUtils.PushVolumeToSlicer(img, node) - applicationLogic = slicer.app.applicationLogic() - selectionNode = applicationLogic.GetSelectionNode() + if self.showOutput: + applicationLogic = slicer.app.applicationLogic() + selectionNode = applicationLogic.GetSelectionNode() - if self.outputLabelMap: - selectionNode.SetReferenceActiveLabelVolumeID(node.GetID()) - else: - selectionNode.SetReferenceActiveVolumeID(node.GetID()) + if self.outputLabelMap: + selectionNode.SetReferenceActiveLabelVolumeID(node.GetID()) + else: + selectionNode.SetReferenceActiveVolumeID(node.GetID()) - applicationLogic.PropagateVolumeSelection(0) - applicationLogic.FitSliceToAll() + applicationLogic.PropagateVolumeSelection(0) + applicationLogic.FitSliceToAll() def run(self, filter, outputMRMLNode, outputLabelMap, *inputs): """ @@ -864,8 +896,9 @@ def create(self, json): self.outputSelector.showHidden = False self.outputSelector.showChildNodeTypes = False self.outputSelector.baseName = json["name"]+" Output" - self.outputSelector.setMRMLScene( slicer.mrmlScene ) self.outputSelector.setToolTip( "Pick the output to the algorithm." ) + self.outputSelector.noneDisplay = "(Create New Volume)" + self.outputSelector.setMRMLScene( slicer.mrmlScene ) self.outputSelector.connect("nodeActivated(vtkMRMLNode*)", lambda node:self.onOutputSelect(node)) self.widgetConnections.append((self.outputSelector, "nodeActivated(vtkMRMLNode*)")) From 55a56cc7802672348deeed5b665aa925e31adafe Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Wed, 17 Dec 2025 18:41:29 -0500 Subject: [PATCH 3/3] Retry filter with floating-point input image Many filters require float images. If the input image is integer then the filter fails and the user has to convert the image manually to float. This commit detects if the filter failed due to unsupported pixel type and retries execution with all input images converted to float type. --- SimpleFilters/SimpleFilters.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/SimpleFilters/SimpleFilters.py b/SimpleFilters/SimpleFilters.py index e788f1d..09849be 100644 --- a/SimpleFilters/SimpleFilters.py +++ b/SimpleFilters/SimpleFilters.py @@ -469,13 +469,37 @@ def thread_doit(self,sitkFilter,*inputImages): self.main_queue.put(lambda img=img:self.updateOutput(img)) except Exception as e: - import traceback - traceback.print_exc() if hasattr(e, 'message'): msg = e.message else: msg = str(e) + + # Check if this is a pixel type error and retry with float cast + if re.search(r'Pixel type:.*is not supported', msg): + try: + print("This filter is not compatible with the pixel type of the input images. Attempting to retry filter after casting all input images to float.") + + # Cast all input images to float + floatImages = [sitk.Cast(img, sitk.sitkFloat32) for img in inputImages] + img = sitkFilter.Execute(*floatImages) + + if not self.abort: + self.main_queue.put(lambda img=img:self.updateOutput(img)) + return + except Exception as e2: + import traceback + traceback.print_exc() + if hasattr(e2, 'message'): + msg = e2.message + else: + msg = str(e2) + + else: + # It is some other error, just log it + import traceback + traceback.print_exc() + self.abort = True self.yieldPythonGIL() @@ -533,7 +557,7 @@ def main_queue_process(self): def updateOutput(self,img): node = slicer.mrmlScene.GetNodeByID(self.outputNodeID) - + # Volume is temporarily set to empty during reading from file, pause rendering to avoid warnings with slicer.util.RenderBlocker(): sitkUtils.PushVolumeToSlicer(img, node)