diff --git a/AutoscoperM/AutoscoperM.py b/AutoscoperM/AutoscoperM.py index 80ca66e..4da7c31 100644 --- a/AutoscoperM/AutoscoperM.py +++ b/AutoscoperM/AutoscoperM.py @@ -211,11 +211,21 @@ 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) + # segmentation and PV generation 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.segGen_segmentationButton.connect("clicked(bool)", self.onSegmentation) + self.ui.segSTL_importModelsButton.connect("clicked(bool)", self.onImportModels) self.ui.loadPVButton.connect("clicked(bool)", self.onLoadPV) + # config generation + self.ui.populateCameraCalListButton.connect("clicked(bool)", self.onPopulateCameraCalList) + self.ui.stageCameraCalFileButton.setIcon(qt.QApplication.style().standardIcon(qt.QStyle.SP_ArrowRight)) + self.ui.stageCameraCalFileButton.connect("clicked(bool)", self.onStageCameraCalFile) + self.ui.populateTrialNameListButton.connect("clicked(bool)", self.onPopulateTrialNameList) + self.ui.stageTrialDirButton.setIcon(qt.QApplication.style().standardIcon(qt.QStyle.SP_ArrowRight)) + self.ui.stageTrialDirButton.connect("clicked(bool)", self.onStageTrialDir) + self.ui.populatePartialVolumeListButton.connect("clicked(bool)", self.onPopulatePartialVolumeList) + self.ui.configGenButton.connect("clicked(bool)", self.onGenerateConfig) # Default output directory self.ui.mainOutputSelector.setCurrentPath( @@ -225,6 +235,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 +423,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. @@ -465,36 +490,90 @@ def onGenerateConfig(self): with slicer.util.tryWithErrorDisplay("Failed to compute results", waitCursor=True): volumeNode = self.ui.volumeSelector.currentNode() mainOutputDir = self.ui.mainOutputSelector.currentPath - trialName = self.ui.trialName.text - width = self.ui.vrgRes_width.value - height = self.ui.vrgRes_height.value + configFileName = self.ui.configFileName.text + + configPath = os.path.join(mainOutputDir, f"{configFileName}.cfg") tiffSubDir = self.ui.tiffSubDir.text - vrgSubDir = self.ui.vrgSubDir.text + radiographSubDir = self.ui.radiographSubDir.text calibrationSubDir = self.ui.cameraSubDir.text + trialList = self.ui.trialList + partialVolumeList = self.ui.partialVolumeList + camCalList = self.ui.camCalList + # Validate the inputs if not self.logic.validateInputs( volumeNode=volumeNode, mainOutputDir=mainOutputDir, - trialName=trialName, - width=width, - height=height, - volumeSubDir=tiffSubDir, - vrgSubDir=vrgSubDir, + configFileName=configFileName, + tiffSubDir=tiffSubDir, + radiographSubDir=radiographSubDir, calibrationSubDir=calibrationSubDir, + trialList=trialList, + partialVolumeList=partialVolumeList, + camCalList=camCalList, ): raise ValueError("Invalid inputs") return if not self.logic.validatePaths( mainOutputDir=mainOutputDir, tiffDir=os.path.join(mainOutputDir, tiffSubDir), - vrgDir=os.path.join(mainOutputDir, vrgSubDir), + radiographSubDir=os.path.join(mainOutputDir, radiographSubDir), calibDir=os.path.join(mainOutputDir, calibrationSubDir), ): raise ValueError("Invalid paths") return + def get_staged_items(listWidget): + staged_items = [] + for row in range(listWidget.count): + item = listWidget.item(row) + widget = listWidget.itemWidget(item) + + # try to find the label of this item + label = widget.findChild(qt.QLabel) if widget else None + if not label: + raise ValueError(f"Could not extract item label from list at index {row}") + staged_items.append(label.text) + + return staged_items + + # extract filenames from UI lists, and use them to construct the paths relative to mainOutputDir. + # NOTE: We rely here on the order of the files as constructed by the user in the UI. The order of items + # in the staged camera files list and the radiograph root dirs list are expected to match. + camCalFiles = [os.path.join(calibrationSubDir, item) for item in get_staged_items(camCalList)] + trialDirs = [os.path.join(radiographSubDir, item) for item in get_staged_items(trialList)] + + if len(camCalFiles) == 0: + raise ValueError( + "Invalid inputs: must select at least one camera calibration file, but zero were provided." + ) + + if len(trialDirs) == 0: + raise ValueError( + "Invalid inputs: must select at least one radiograph subdirectory, but zero were provided." + ) + + if len(camCalFiles) != len(trialDirs): + raise ValueError( + "Invalid inputs: number of selected trial directories must match the number " + f"of camera calibration files: {len(camCalFiles)} != {len(trialDirs)}" + ) + + def get_checked_items(listWidget): + checked_items = [] + for idx in range(listWidget.count): + item = listWidget.item(idx) + if item.checkState() == qt.Qt.Checked: + checked_items.append(item.text()) + return checked_items + + partialVolumeFiles = [os.path.join(tiffSubDir, item) for item in get_checked_items(partialVolumeList)] + + if len(partialVolumeFiles) == 0: + raise ValueError("Invalid inputs: at least one volume file must be selected!") + optimizationOffsets = [ self.ui.optOffX.value, self.ui.optOffY.value, @@ -503,30 +582,91 @@ 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) + renderResolution = [ + self.ui.configRes_width.value, + self.ui.configRes_height.value, + ] + + voxel_spacing = [ + self.ui.voxelSizeX.value, + self.ui.voxelSizeY.value, + self.ui.voxelSizeZ.value, + ] + + # Validate the inputs + if not self.logic.validateInputs( + *trialDirs, + *partialVolumeFiles, + *camCalFiles, + *optimizationOffsets, + *volumeFlip, + *renderResolution, + *voxel_spacing, + ): + raise ValueError("Invalid inputs") + # generate the config file - configFilePath = IO.generateConfigFile( - mainOutputDir, - [tiffSubDir, vrgSubDir, calibrationSubDir], - trialName, + IO.generateConfigFile( + outputConfigPath=configPath, + trialName=configFileName, + camCalFiles=camCalFiles, + camRootDirs=trialDirs, + volumeFiles=partialVolumeFiles, volumeFlip=volumeFlip, voxelSize=voxel_spacing, - renderResolution=[int(width / 2), int(height / 2)], + renderResolution=renderResolution, optimizationOffsets=optimizationOffsets, ) - self.ui.configSelector.setCurrentPath(configFilePath) + # Set the path to this newly created config file in the "Config File" field in the Autoscoper Control UI + self.ui.configSelector.setCurrentPath(configPath) + 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.segSTL_loadRadioButton.isChecked(): + segmentationFileDir = self.ui.segSTL_modelsDir.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) @@ -551,7 +691,7 @@ def onSegmentation(self): self.logic.cleanFilename(currentVolumeNode.GetName(), i) segmentationNode = SubVolumeExtraction.automaticSegmentation( currentVolumeNode, - self.ui.segGen_ThresholdSpinBox.value, + self.ui.segGen_thresholdSpinBox.value, self.ui.segGen_marginSizeSpin.value, progressCallback=self.updateProgressBar, ) @@ -561,24 +701,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 +773,136 @@ 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.trialCandidateList, self.ui.radiographSubDir.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.camCalCandidateList, 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("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) + + def onStageCameraCalFile(self): + """ + Adds selected items from the camera calibration list to the staged files list + """ + with slicer.util.tryWithErrorDisplay("Failed to compute results.", waitCursor=True): + self.stageSelectedFiles(self.ui.camCalCandidateList, self.ui.camCalList) + + def onStageTrialDir(self): + """ + Adds selected items from the radiograph subdirectories list to the staged subdirs list + """ + with slicer.util.tryWithErrorDisplay("Failed to compute results.", waitCursor=True): + self.stageSelectedFiles(self.ui.trialCandidateList, self.ui.trialList) + + def stageSelectedFiles(self, candidateListWidget, listWidget): + """ + Stages chosen files into listWidget based on the selected items + in candidateListWidget which contains all candidate file names + """ + # gether checked items from the input candidate list + checked_items = [] + for idx in range(candidateListWidget.count): + item = candidateListWidget.item(idx) + if item.checkState() == qt.Qt.Checked: + checked_items.append(item.text()) + item.setCheckState(qt.Qt.Unchecked) + + if len(checked_items) == 0: + raise ValueError("No items were selected.") + + def stagedItemExists(itemText): + # iterate over the list items and see if item with the given label already exists + for row in range(listWidget.count): + item = listWidget.item(row) + widget = listWidget.itemWidget(item) + if widget: + # extract label to compare the text in the item + label = widget.findChild(qt.QLabel) + if label and label.text == itemText: + return True + return False + + # stage all selected items if they're not already in the target list + for file in checked_items: + if not stagedItemExists(file): + # create item widget with text and a delete button + itemBaseWidget = qt.QWidget() + itemLayout = qt.QHBoxLayout() + itemLabel = qt.QLabel(file) + itemDeleteButton = qt.QPushButton("Delete") + + # set styling attributes to make it look nice in the UI + itemLayout.setContentsMargins(3, 1, 3, 1) + itemLayout.setSpacing(3) + itemDeleteButton.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Fixed)) + + itemLayout.addWidget(itemLabel) + # add spacing so that the delete button is always aligned to the right + itemLayout.addItem(qt.QSpacerItem(0, 0, qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum)) + itemLayout.addWidget(itemDeleteButton) + itemBaseWidget.setLayout(itemLayout) + itemWidget = qt.QListWidgetItem(listWidget) + itemWidget.setFlags(itemWidget.flags() & ~qt.Qt.ItemIsSelectable) + + # finally, add the composite widget as an item to the list + listWidget.setItemWidget(itemWidget, itemBaseWidget) + + # add delete functionality to the button + itemDeleteButton.clicked.connect(lambda _, item=itemWidget: listWidget.takeItem(listWidget.row(item))) + else: + logging.info(f"Skipped adding the item '{file}' as it already exists in the target list.") + # # AutoscoperMLogic diff --git a/AutoscoperM/AutoscoperMLib/IO.py b/AutoscoperM/AutoscoperMLib/IO.py index 01bf5c5..890e7ba 100644 --- a/AutoscoperM/AutoscoperMLib/IO.py +++ b/AutoscoperM/AutoscoperMLib/IO.py @@ -1,4 +1,3 @@ -import glob import logging import os from itertools import product @@ -34,53 +33,34 @@ def loadSegmentation(segmentationNode: slicer.vtkMRMLSegmentationNode, filename: def generateConfigFile( - mainDirectory: str, - subDirectories: list[str], + outputConfigPath: str, trialName: str, + camCalFiles: list[str], + camRootDirs: list[str], + volumeFiles: list[str], volumeFlip: list[int], voxelSize: list[float], renderResolution: list[int], optimizationOffsets: list[float], -) -> str: +) -> None: """ Generates the v1.1 config file for the trial - :param mainDirectory: Main directory - :param subDirectories: Sub directories + :param outputConfigPath: The absolute path to the config file to be generated :param trialName: Trial name - :param volumeFlip: Volume flip - :param voxelSize: Voxel size - :param renderResolution: Render resolution - :param optimizationOffsets: Optimization offsets + :param camCalFiles: The list of camera calibration file paths, relative to the main output dir + :param camRootDirs: The list of the radiograph directory paths, relative to the main output dir + :param volumeFiles: The list of tiff volume files, relative to the main output dir + :param volumeFlip: The flip settings for each of the volumes + :param voxelSize: The voxel size of each of the the volumes + :param renderResolution: The resolution of the 2D rendering of each of the volumes + :param optimizationOffsets: The offsets for the optimization :return: Path to the config file """ import datetime - # Get the camera calibration files, camera root directories, and volumes - volumes = glob.glob(os.path.join(mainDirectory, subDirectories[0], "*.tif")) - cameraRootDirs = glob.glob(os.path.join(mainDirectory, subDirectories[1], "*")) - calibrationFiles = glob.glob(os.path.join(mainDirectory, subDirectories[2], "*.json")) - - # Check that we have the same number of camera calibration files and camera root directories - if len(calibrationFiles) != len(cameraRootDirs): - logging.error( - "Number of camera calibration files and camera root directories do not match: " - " {len(calibrationFiles)} != {len(cameraRootDirs)}" - ) - return None - - # Check that we have at least one volume - if len(volumes) == 0: - logging.error("No volumes found!") - return None - - # Transform the paths to be relative to the main directory - calibrationFiles = [os.path.relpath(calibrationFile, mainDirectory) for calibrationFile in calibrationFiles] - cameraRootDirs = [os.path.relpath(cameraRootDir, mainDirectory) for cameraRootDir in cameraRootDirs] - volumes = [os.path.relpath(volume, mainDirectory) for volume in volumes] - - with open(os.path.join(mainDirectory, trialName + ".cfg"), "w+") as f: + with open(outputConfigPath, "w+") as f: # Trial Name as comment f.write(f"# {trialName} configuration file\n") f.write( @@ -94,19 +74,19 @@ def generateConfigFile( # Camera Calibration Files f.write("# Camera Calibration Files\n") - for calibrationFile in calibrationFiles: + for calibrationFile in camCalFiles: f.write("mayaCam_csv " + calibrationFile + "\n") f.write("\n") # Camera Root Directories f.write("# Camera Root Directories\n") - for cameraRootDir in cameraRootDirs: + for cameraRootDir in camRootDirs: f.write("CameraRootDir " + cameraRootDir + "\n") f.write("\n") # Volumes f.write("# Volumes\n") - for volume in volumes: + for volume in volumeFiles: f.write("VolumeFile " + volume + "\n") f.write("VolumeFlip " + " ".join([str(x) for x in volumeFlip]) + "\n") f.write("VoxelSize " + " ".join([str(x) for x in voxelSize]) + "\n") @@ -122,8 +102,6 @@ def generateConfigFile( f.write("OptimizationOffsets " + " ".join([str(x) for x in optimizationOffsets]) + "\n") f.write("\n") - return os.path.join(mainDirectory, trialName + ".cfg") - def writeVolume(volumeNode: slicer.vtkMRMLVolumeNode, filename: str): """ diff --git a/AutoscoperM/Resources/UI/AutoscoperM.ui b/AutoscoperM/Resources/UI/AutoscoperM.ui index 09d347a..6d8625a 100644 --- a/AutoscoperM/Resources/UI/AutoscoperM.ui +++ b/AutoscoperM/Resources/UI/AutoscoperM.ui @@ -9,8 +9,8 @@ 0 0 - 1018 - 860 + 659 + 1566 @@ -120,6 +120,13 @@ + + + + Qt::Vertical + + + @@ -128,254 +135,145 @@ - + + + 0 + + + false + + + + + + + + + Output Directory: + + + + + + + ctkPathLineEdit::Dirs|ctkPathLineEdit::Executable|ctkPathLineEdit::NoDot|ctkPathLineEdit::NoDotDot|ctkPathLineEdit::Writable + + + + + + + Volume Node: + + + + + + + true + + + + vtkMRMLScalarVolumeNode + vtkMRMLSequenceNode + + + + + + + + + + + + + + - General Input + Segmentation Generation true - + + false + + - + - Volume Node: - - - - - - - false - - - - vtkMRMLScalarVolumeNode - vtkMRMLSequenceNode - - - - + Automatic Segmentation - - + + true - - + + - Output Directory: - - - - - - - ctkPathLineEdit::Dirs|ctkPathLineEdit::Executable|ctkPathLineEdit::NoDot|ctkPathLineEdit::NoDotDot|ctkPathLineEdit::Writable + Threshold Value: - - - - true + + + + 10000 - - Trial Name: + + 700 - - - - true + + + + Generate Segmentations - - - - true - - - Advanced Options - - - true - - - true - - - false + + + + Margin Size: - - - - - 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 + 2.000000000000000 - - - - - - - Segmentation Generation - - - false - - - + - Threshold Value: + OR - - + + - Automatic Segmentation - - - true + Segmentation from Model - - + + + + false + - Batch Load from File + STL Models Directory: - - + + false @@ -384,41 +282,13 @@ - - - - 10000 - - - 700 - - - - - - - Segmentation File Directory: - - - - - - - Generate Segmentations + + + + false - - - - - Margin Size: - - - - - - - 2.000000000000000 + Import Models @@ -431,10 +301,20 @@ Partial Volume Generation + true + + false - + + + + Segmentation Node: + + + + true @@ -452,21 +332,14 @@ - - - - Segmentation Node: - - - - + Generate Partial Volumes - + Load Partial Volumes @@ -477,148 +350,557 @@ - + 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 + + + + + + + + Config Trial Name: + + + + + + + true + + + + + + + .cfg + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 10 + + + + + + + + Select Paired Camera Calibrations: + + + + + + + + QListWidget::indicator:unchecked { + background-color: palette(alternate-base); + } + + + + + + + + + + + Add selected camera calibration file as next in order + + + + 40 + 16777215 + + + + + + + + + + + Populate From Camera Subdirectory + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 10 + + + + + + + + Select Paired Radiograph Subirectories: + + + + + + + + QListWidget::indicator:unchecked { + background-color: palette(alternate-base); + } + + + + + + + + + + + Add selected radiograph subdirectory as next in order + + + + 40 + 16777215 + + + + + + + + + + + Populate From Radiographs Subdirectory + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 10 + + + + + + + + Select Partial Volumes: + + + + + + + + QListWidget::indicator:unchecked { + background-color: palette(alternate-base); + } + + + + + + + + Populate From Volume Subdirectory + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 10 + + + + + + + + + + + + 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 +921,6 @@
ctkCollapsibleButton.h
1 - - ctkCollapsibleGroupBox - QGroupBox -
ctkCollapsibleGroupBox.h
- 1 -
- - ctkDoubleRangeSlider - QWidget -
ctkDoubleRangeSlider.h
-
ctkPathLineEdit QWidget @@ -677,7 +948,7 @@ segGen_autoRadioButton toggled(bool) - segGen_ThresholdSpinBox + segGen_thresholdSpinBox setEnabled(bool) @@ -691,9 +962,9 @@ - segGen_fileRadioButton + segSTL_loadRadioButton toggled(bool) - segGen_lineEdit + segSTL_modelsDir setEnabled(bool) @@ -738,5 +1009,85 @@ + + segSTL_loadRadioButton + toggled(bool) + segSTL_importModelsButton + setEnabled(bool) + + + 122 + 276 + + + 698 + 204 + + + + + segGen_autoRadioButton + toggled(bool) + segGen_segmentationButton + setEnabled(bool) + + + 122 + 189 + + + 698 + 276 + + + + + segSTL_loadRadioButton + toggled(bool) + segSTL_modelsDirLabel + setEnabled(bool) + + + 122 + 276 + + + 279 + 276 + + + + + segGen_autoRadioButton + toggled(bool) + segGen_thresholdLabel + setEnabled(bool) + + + 122 + 189 + + + 279 + 189 + + + + + segGen_autoRadioButton + toggled(bool) + segGen_marginSizeLabel + setEnabled(bool) + + + 122 + 189 + + + 279 + 220 + + +