diff --git a/AutoscoperM/AutoscoperM.py b/AutoscoperM/AutoscoperM.py
index 80ca66e..ae5d577 100644
--- a/AutoscoperM/AutoscoperM.py
+++ b/AutoscoperM/AutoscoperM.py
@@ -211,11 +211,15 @@ def setup(self):
self.ui.ankleSampleButton.connect("clicked(bool)", lambda: self.onSampleDataButtonClicked("2023-08-01-Ankle"))
# Pre-processing Library Buttons
+ self.ui.volumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onCurrentNodeChanged)
self.ui.tiffGenButton.connect("clicked(bool)", self.onGeneratePartialVolumes)
self.ui.configGenButton.connect("clicked(bool)", self.onGenerateConfig)
self.ui.segmentationButton.connect("clicked(bool)", self.onSegmentation)
-
+ self.ui.importModelsButton.connect("clicked(bool)", self.onImportModels)
self.ui.loadPVButton.connect("clicked(bool)", self.onLoadPV)
+ self.ui.populateTrialNameListButton.connect("clicked(bool)", self.onPopulateTrialNameList)
+ self.ui.populatePartialVolumeListButton.connect("clicked(bool)", self.onPopulatePartialVolumeList)
+ self.ui.populateCameraCalListButton.connect("clicked(bool)", self.onPopulateCameraCalList)
# Default output directory
self.ui.mainOutputSelector.setCurrentPath(
@@ -225,6 +229,9 @@ def setup(self):
# Make sure parameter node is initialized (needed for module reload)
self.initializeParameterNode()
+ # Trigger any required UI updates based on the volume node selected by default
+ self.onCurrentNodeChanged()
+
def cleanup(self):
"""
Called when the application closes and the module widget is destroyed.
@@ -410,6 +417,18 @@ def onSampleDataButtonClicked(self, dataType):
for cam in range(numCams):
self.logic.AutoscoperSocket.loadFilters(cam, filterSettings)
+ def onCurrentNodeChanged(self):
+ """
+ Updates and UI components that correspond to the selected input volume node
+ """
+ volumeNode = self.ui.volumeSelector.currentNode()
+ if volumeNode:
+ with slicer.util.tryWithErrorDisplay("Failed to grab volume node information", waitCursor=True):
+ vSizeX, vSizeY, vSizeZ = self.logic.GetVolumeSpacing(volumeNode)
+ self.ui.voxelSizeX.value = vSizeX
+ self.ui.voxelSizeY.value = vSizeY
+ self.ui.voxelSizeZ.value = vSizeZ
+
def onGeneratePartialVolumes(self):
"""
This function creates partial volumes for each segment in the segmentation node for the selected volume node.
@@ -503,13 +522,19 @@ def onGenerateConfig(self):
self.ui.optOffPitch.value,
self.ui.optOffRoll.value,
]
+
volumeFlip = [
int(self.ui.flipX.isChecked()),
int(self.ui.flipY.isChecked()),
int(self.ui.flipZ.isChecked()),
]
- voxel_spacing = self.logic.GetVolumeSpacing(volumeNode)
+ voxel_spacing = [
+ self.ui.voxelSizeX.value,
+ self.ui.voxelSizeY.value,
+ self.ui.voxelSizeZ.value,
+ ]
+
# generate the config file
configFilePath = IO.generateConfigFile(
mainOutputDir,
@@ -524,9 +549,44 @@ def onGenerateConfig(self):
self.ui.configSelector.setCurrentPath(configFilePath)
slicer.util.messageBox("Success!")
+ def onImportModels(self):
+ """
+ Imports Models from a directory- converts to Segmentation Nodes
+ """
+ with slicer.util.tryWithErrorDisplay("Failed to compute results", waitCursor=True):
+ self.ui.progressBar.setValue(0)
+ self.ui.progressBar.setMaximum(100)
+
+ volumeNode = self.ui.volumeSelector.currentNode()
+
+ if not self.logic.validateInputs(voluemNode=volumeNode):
+ raise ValueError("Invalid inputs")
+ return
+
+ if self.ui.segGen_fileRadioButton.isChecked():
+ segmentationFileDir = self.ui.segGen_lineEdit.currentPath
+ if not self.logic.validatePaths(segmentationFileDir=segmentationFileDir):
+ raise ValueError("Invalid paths")
+ return
+ segmentationFiles = glob.glob(os.path.join(segmentationFileDir, "*.*"))
+ segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
+ segmentationNode.CreateDefaultDisplayNodes()
+ for idx, file in enumerate(segmentationFiles):
+ returnedNode = IO.loadSegmentation(segmentationNode, file)
+ if returnedNode:
+ # get the segment from the returned node and add it to the segmentation node
+ segment = returnedNode.GetSegmentation().GetNthSegment(0)
+ segmentationNode.GetSegmentation().AddSegment(segment)
+ slicer.mrmlScene.RemoveNode(returnedNode)
+ self.ui.progressBar.setValue((idx + 1) / len(segmentationFiles) * 100)
+ else: # Should never happen but just in case
+ raise Exception("Please select the 'Segmentation From Model' option in order to import models")
+ return
+ slicer.util.messageBox("Success!")
+
def onSegmentation(self):
"""
- Either launches the automatic segmentation process or loads in a set of segmentations from a directory
+ Launches the automatic segmentation process
"""
with slicer.util.tryWithErrorDisplay("Failed to compute results", waitCursor=True):
self.ui.progressBar.setValue(0)
@@ -561,24 +621,8 @@ def onSegmentation(self):
segmentationSequenceNode.SetDataNodeAtValue(segmentationNode, str(i))
slicer.mrmlScene.RemoveNode(segmentationNode)
currentVolumeNode = self.logic.getNextItemInSequence(volumeNode)
- elif self.ui.segGen_fileRadioButton.isChecked():
- segmentationFileDir = self.ui.segGen_lineEdit.currentPath
- if not self.logic.validatePaths(segmentationFileDir=segmentationFileDir):
- raise ValueError("Invalid paths")
- return
- segmentationFiles = glob.glob(os.path.join(segmentationFileDir, "*.*"))
- segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
- segmentationNode.CreateDefaultDisplayNodes()
- for idx, file in enumerate(segmentationFiles):
- returnedNode = IO.loadSegmentation(segmentationNode, file)
- if returnedNode:
- # get the segment from the returned node and add it to the segmentation node
- segment = returnedNode.GetSegmentation().GetNthSegment(0)
- segmentationNode.GetSegmentation().AddSegment(segment)
- slicer.mrmlScene.RemoveNode(returnedNode)
- self.ui.progressBar.setValue((idx + 1) / len(segmentationFiles) * 100)
else: # Should never happen but just in case
- raise Exception("No segmentation method selected")
+ raise Exception("Please select the 'Automatic Segmentation' option in order to generate segmentations")
return
slicer.util.messageBox("Success!")
@@ -649,6 +693,63 @@ def onLoadPV(self):
slicer.util.messageBox("Success!")
+ def onPopulateTrialNameList(self):
+ """
+ Populates trial name UI list using files from the selected radiograph directory
+ """
+ with slicer.util.tryWithErrorDisplay("Failed to compute results.", waitCursor=True):
+ self.populateListFromOutputSubDir(self.ui.trialList, self.ui.vrgSubDir.text, itemType="dir")
+
+ def onPopulatePartialVolumeList(self):
+ """
+ Populates partial volumes UI list using files from the selected PV directory
+ """
+ with slicer.util.tryWithErrorDisplay("Failed to compute results.", waitCursor=True):
+ self.populateListFromOutputSubDir(self.ui.partialVolumeList, self.ui.tiffSubDir.text)
+
+ def onPopulateCameraCalList(self):
+ """
+ Populates camera calibration UI list using files from the selected camera directory
+ """
+ with slicer.util.tryWithErrorDisplay("Failed to compute results.", waitCursor=True):
+ self.populateListFromOutputSubDir(self.ui.camCalList, self.ui.cameraSubDir.text)
+
+ def populateListFromOutputSubDir(self, listWidget, fileSubDir, itemType="file"):
+ """
+ Populates input UI list with files/directories that exist in the given input directory
+ """
+ listWidget.clear()
+
+ mainOutputDir = self.ui.mainOutputSelector.currentPath
+ if not self.logic.validateInputs(
+ listWidget=listWidget,
+ mainOutputDir=mainOutputDir,
+ fileSubDir=fileSubDir,
+ ):
+ raise ValueError(f"Invalid inputs")
+ return
+
+ fileDir = os.path.join(mainOutputDir, fileSubDir)
+ if not self.logic.validatePaths(fileDir=fileDir):
+ raise ValueError(f"Invalid input: subdirectory '{fileDir}' does not exist.")
+ return
+
+ if itemType == "file":
+ listFiles = [f.name for f in os.scandir(fileDir) if os.path.isfile(f)]
+ elif itemType == "dir":
+ listFiles = [f.name for f in os.scandir(fileDir) if os.path.isdir(f)]
+ else:
+ raise ValueError(
+ "Invalid input: can either search for type 'file' or 'dir' "
+ f"in specified path, but given itemType='{itemType}'"
+ )
+ return
+
+ for file in sorted(listFiles):
+ fileItem = qt.QListWidgetItem(file)
+ fileItem.setFlags(fileItem.flags() & ~qt.Qt.ItemIsSelectable) # Remove the selectable flag
+ fileItem.setCheckState(qt.Qt.Unchecked)
+ listWidget.addItem(fileItem)
#
# AutoscoperMLogic
diff --git a/AutoscoperM/Resources/UI/AutoscoperM.ui b/AutoscoperM/Resources/UI/AutoscoperM.ui
index 09d347a..dd0eca6 100644
--- a/AutoscoperM/Resources/UI/AutoscoperM.ui
+++ b/AutoscoperM/Resources/UI/AutoscoperM.ui
@@ -9,8 +9,8 @@
0
0
- 1018
- 860
+ 811
+ 1566
@@ -120,6 +120,13 @@
+ -
+
+
+ Qt::Vertical
+
+
+
@@ -128,235 +135,71 @@
-
-
-
- General Input
+
+
+ 0
-
- true
+
+ false
-
-
-
-
-
- Volume Node:
-
-
-
- -
-
-
- false
-
-
-
- vtkMRMLScalarVolumeNode
- vtkMRMLSequenceNode
-
-
-
-
-
-
-
-
-
-
- -
-
-
- Output Directory:
-
-
-
- -
-
-
- ctkPathLineEdit::Dirs|ctkPathLineEdit::Executable|ctkPathLineEdit::NoDot|ctkPathLineEdit::NoDotDot|ctkPathLineEdit::Writable
-
-
-
- -
-
-
- true
-
-
- Trial Name:
-
-
-
- -
-
-
- true
-
-
-
- -
-
-
- true
-
-
- Advanced Options
-
-
- true
-
-
- true
-
-
- false
-
-
-
-
-
-
- Camera Subdirectory:
-
-
-
- -
-
-
- Radiograph Subdirectory:
-
-
-
- -
-
-
- true
-
-
- Calibration
-
-
-
- -
-
-
- Tracking Subdirectory:
-
-
-
- -
-
-
- VRG Resolution: (width,height)
-
-
-
- -
-
-
- Transforms
-
-
-
- -
-
-
- Volumes
-
-
-
- -
-
-
- 999999999
-
-
- 1760
-
-
-
- -
-
-
- Partial Volume Transforms Subdirectory:
-
-
-
- -
-
-
- RadiographImages
-
-
-
- -
-
-
- 999999999
-
-
- 1760
-
-
-
- -
-
-
- Partial Volume Subdirectory:
-
-
-
- -
-
-
- Model Subdirectory:
-
-
-
- -
-
-
- Tracking
-
-
-
- -
-
-
- Models
-
-
-
-
-
-
- -
-
-
- 0
-
-
- false
-
-
-
-
+ -
+
+
-
+
+
+ Output Directory:
+
+
+
+ -
+
+
+ ctkPathLineEdit::Dirs|ctkPathLineEdit::Executable|ctkPathLineEdit::NoDot|ctkPathLineEdit::NoDotDot|ctkPathLineEdit::Writable
+
+
+
+ -
+
+
+ Volume Node:
+
+
+
+ -
+
+
+ true
+
+
+
+ vtkMRMLScalarVolumeNode
+ vtkMRMLSequenceNode
+
+
+
+
+
+
+
+
+
+
+
+
-
Segmentation Generation
+ true
+
+
false
-
-
-
-
- Threshold Value:
-
-
-
-
@@ -368,23 +211,13 @@
-
-
+
- Batch Load from File
-
-
-
- -
-
-
- false
-
-
- ctkPathLineEdit::Dirs|ctkPathLineEdit::Drives|ctkPathLineEdit::Executable|ctkPathLineEdit::NoDot|ctkPathLineEdit::NoDotDot|ctkPathLineEdit::Readable
+ Threshold Value:
- -
+
-
10000
@@ -394,31 +227,68 @@
- -
-
+
-
+
- Segmentation File Directory:
+ Margin Size:
- -
+
-
+
+
+ 2.000000000000000
+
+
+
+ -
Generate Segmentations
+ -
+
+
+ OR
+
+
+
-
-
+
- Margin Size:
+ Segmentation from Model
-
-
-
- 2.000000000000000
+
+
+ false
+
+
+ STL Models Directory:
+
+
+
+ -
+
+
+ false
+
+
+ ctkPathLineEdit::Dirs|ctkPathLineEdit::Drives|ctkPathLineEdit::Executable|ctkPathLineEdit::NoDot|ctkPathLineEdit::NoDotDot|ctkPathLineEdit::Readable
+
+
+
+ -
+
+
+ false
+
+
+ Import Models
@@ -431,9 +301,19 @@
Partial Volume Generation
+ true
+
+
false
+ -
+
+
+ Segmentation Node:
+
+
+
-
@@ -452,13 +332,6 @@
- -
-
-
- Segmentation Node:
-
-
-
-
@@ -477,148 +350,430 @@
-
-
+
true
- Generate Config
+ Default Subdirectories
+
+
+ true
- false
+ true
true
-
-
-
-
+
+
-
+
- Flip Y
-
-
- false
+ Partial Volume Subdirectory:
- -
-
-
- 0.100000000000000
-
-
- 0.100000000000000
+
-
+
+
+ Volumes
- -
-
+
-
+
- Generate Config File
+ Partial Volume Transforms Subdirectory:
- -
-
-
- 0.100000000000000
-
-
- 0.100000000000000
+
-
+
+
+ Transforms
- -
-
-
- Flip X
+
-
+
+
+
+ 75
+ true
+
-
- false
+
+ Radiograph Subdirectory:
- -
-
-
- 0.100000000000000
+
-
+
+
+
+ 75
+ true
+
-
- 0.100000000000000
+
+ RadiographImages
- -
-
-
- 0.100000000000000
+
-
+
+
+
+ 75
+ true
+
-
- 0.100000000000000
+
+ Camera Subdirectory:
- -
-
+
-
+
+
+ true
+
+
+
+ 75
+ true
+
+
- Flip Z
+ Calibration
- -
-
-
- 0.100000000000000
-
-
- 0.100000000000000
+
-
+
+
+ Tracking Subdirectory:
- -
-
-
- 0.100000000000000
-
-
- 0.100000000000000
+
-
+
+
+ Tracking
- -
-
+
-
+
- Optimization Offsets:
+ Model Subdirectory:
- -
-
+
-
+
- Volume Flip:
+ Models
+ -
+
+
+ true
+
+
+ Generate Config
+
+
+ true
+
+
+ false
+
+
+
-
+
+
-
+
+
+ true
+
+
+ Select Trial Names:
+
+
+
+ -
+
+
+ Populate From Radiographs Subdirectory
+
+
+
+ -
+
+
+ -
+
+
+ and enter :
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ .cfg
+
+
+
+ -
+
+
+ Select Partial Volumes:
+
+
+
+ -
+
+
+ Populate From Volume Subdirectory
+
+
+
+ -
+
+
+ -
+
+
+ Select Camera Calibrations:
+
+
+
+ -
+
+
+ Populate From Camera Subdirectory
+
+
+
+ -
+
+
+
+
+ -
+
+
-
+
+
+ Optimization Offsets:
+
+
+
+ -
+
+
+ 0.100000000000000
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ 0.100000000000000
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ 0.100000000000000
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ 0.100000000000000
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ 0.100000000000000
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ 0.100000000000000
+
+
+ 0.100000000000000
+
+
+
+ -
+
+
+ Volume Flip:
+
+
+
+ -
+
+
+ Flip X
+
+
+ false
+
+
+
+ -
+
+
+ Flip Y
+
+
+ false
+
+
+
+ -
+
+
+ Flip Z
+
+
+
+ -
+
+
+ Render Resolution: (width,height)
+
+
+
+ -
+
+
+ 999999999
+
+
+ 1760
+
+
+
+ -
+
+
+ 999999999
+
+
+ 1760
+
+
+
+ -
+
+
+ Voxel Size:
+
+
+
+ -
+
+
+ true
+
+
+ 3
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ true
+
+
+ 3
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ true
+
+
+ 3
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ Generate Config File
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
@@ -639,17 +794,6 @@
1
-
- ctkCollapsibleGroupBox
- QGroupBox
-
- 1
-
-
- ctkDoubleRangeSlider
- QWidget
-
-
ctkPathLineEdit
QWidget
@@ -738,5 +882,85 @@
+
+ segGen_fileRadioButton
+ toggled(bool)
+ importModelsButton
+ setEnabled(bool)
+
+
+ 122
+ 276
+
+
+ 698
+ 204
+
+
+
+
+ segGen_autoRadioButton
+ toggled(bool)
+ segmentationButton
+ setEnabled(bool)
+
+
+ 122
+ 189
+
+
+ 698
+ 276
+
+
+
+
+ segGen_fileRadioButton
+ toggled(bool)
+ segSTLModelsDirLabel
+ setEnabled(bool)
+
+
+ 122
+ 276
+
+
+ 279
+ 276
+
+
+
+
+ segGen_autoRadioButton
+ toggled(bool)
+ segThresholdLabel
+ setEnabled(bool)
+
+
+ 122
+ 189
+
+
+ 279
+ 189
+
+
+
+
+ segGen_autoRadioButton
+ toggled(bool)
+ segMarginSizeLabel
+ setEnabled(bool)
+
+
+ 122
+ 189
+
+
+ 279
+ 220
+
+
+