diff --git a/AutoscoperM/AutoscoperM.py b/AutoscoperM/AutoscoperM.py index ae5d577..2942c26 100644 --- a/AutoscoperM/AutoscoperM.py +++ b/AutoscoperM/AutoscoperM.py @@ -484,24 +484,29 @@ 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 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, + configFileName=configFileName, + tiffSubDir=tiffSubDir, vrgSubDir=vrgSubDir, calibrationSubDir=calibrationSubDir, + trialList=trialList, + partialVolumeList=partialVolumeList, + camCalList=camCalList, ): raise ValueError("Invalid inputs") return @@ -514,6 +519,32 @@ def onGenerateConfig(self): raise ValueError("Invalid paths") return + 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 + + # extract filenames from UI lists, and use them to construct the paths relative to mainOutputDir + # FIXME: don't assume the list of camera files is given in the same order as the list of radiograph root dir! + camCalFiles = [os.path.join(calibrationSubDir, item) for item in get_checked_items(camCalList)] + trialDirs = [os.path.join(vrgSubDir, item) for item in get_checked_items(trialList)] + + if not 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)}" + ) + return + + 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!") + return + optimizationOffsets = [ self.ui.optOffX.value, self.ui.optOffY.value, @@ -529,24 +560,45 @@ def onGenerateConfig(self): int(self.ui.flipZ.isChecked()), ] + 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") + return + # 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): diff --git a/AutoscoperM/AutoscoperMLib/IO.py b/AutoscoperM/AutoscoperMLib/IO.py index 01bf5c5..f440c77 100644 --- a/AutoscoperM/AutoscoperMLib/IO.py +++ b/AutoscoperM/AutoscoperMLib/IO.py @@ -34,53 +34,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 +75,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 +103,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): """