diff --git a/AutoscoperM/AutoscoperM.py b/AutoscoperM/AutoscoperM.py
index 461a57f..2860b91 100644
--- a/AutoscoperM/AutoscoperM.py
+++ b/AutoscoperM/AutoscoperM.py
@@ -468,24 +468,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, 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
@@ -498,6 +503,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,
@@ -512,19 +543,42 @@ def onGenerateConfig(self):
int(self.ui.flipZ.isChecked()),
]
+ renderResolution = [
+ self.ui.configRes_width.value,
+ self.ui.configRes_height.value,
+ ]
+
+ # TODO: automatically populate UI fields when input volume is selected
voxel_spacing = self.logic.GetVolumeSpacing(volumeNode)
+
+ # 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)], # why divide by 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):
@@ -676,31 +730,40 @@ def onPopulateTrialNameList(self):
Populates trial name UI list using files from the selected radiograph directory
"""
radiographDir = os.path.join(self.ui.mainOutputSelector.currentPath, self.ui.vrgSubDir.text)
- self.populateFileList(self.ui.trialList, radiographDir, itemType="dir")
+ try:
+ self.populateFileList(self.ui.trialList, radiographDir, itemType="dir")
+ except ValueError as errMsg:
+ slicer.util.errorDisplay(errMsg)
def onPopulatePartialVolumeList(self):
"""
Populates partial volumes UI list using files from the selected PV directory
"""
- radiographDir = os.path.join(self.ui.mainOutputSelector.currentPath, self.ui.tiffSubDir.text)
- self.populateFileList(self.ui.partialVolumeList, radiographDir)
+ partialVolumeshDir = os.path.join(self.ui.mainOutputSelector.currentPath, self.ui.tiffSubDir.text)
+ try:
+ self.populateFileList(self.ui.partialVolumeList, partialVolumeshDir)
+ except ValueError as errMsg:
+ slicer.util.errorDisplay(errMsg)
def onPopulateCameraCalList(self):
"""
Populates camera calibration UI list using files from the selected camera directory
"""
- radiographDir = os.path.join(self.ui.mainOutputSelector.currentPath, self.ui.cameraSubDir.text)
- self.populateFileList(self.ui.camCalList, radiographDir)
+ cameraDir = os.path.join(self.ui.mainOutputSelector.currentPath, self.ui.cameraSubDir.text)
+ try:
+ self.populateFileList(self.ui.camCalList, cameraDir)
+ except ValueError as errMsg:
+ slicer.util.errorDisplay(errMsg)
def populateFileList(self, listWidget, fileDir, itemType="file"):
"""
- Populates trial name UI list using files from the selected radiograph directory
+ Populates input UI list with files/directories that exist in the given input directory
"""
listWidget.clear()
if not self.logic.validatePaths(
fileDir=fileDir,
):
- raise ValueError("Invalid input: subdirectory does not exist")
+ raise ValueError(f"Invalid input: subdirectory '{fileDir}' does not exist.")
return
if itemType == "file":
@@ -708,11 +771,12 @@ def populateFileList(self, listWidget, fileDir, itemType="file"):
elif itemType == "dir":
listFiles = [f.name for f in os.scandir(fileDir) if os.path.isdir(f)]
else:
- raise ValueError(f"Invalid input: can only search for either files or directories in specified path, but given itemType={itemType}")
+ raise ValueError(f"Invalid input: can either search for type 'file' or 'dir' in specified path, but given itemType='{itemType}'")
return
- for file in listFiles:
+ 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)
diff --git a/AutoscoperM/AutoscoperMLib/IO.py b/AutoscoperM/AutoscoperMLib/IO.py
index 01bf5c5..6cceef0 100644
--- a/AutoscoperM/AutoscoperMLib/IO.py
+++ b/AutoscoperM/AutoscoperMLib/IO.py
@@ -34,53 +34,35 @@ 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:
+ logging.info(f"generateConfigFile: writing to '{outputConfigPath}'")
+ with open(outputConfigPath, "w+") as f:
# Trial Name as comment
f.write(f"# {trialName} configuration file\n")
f.write(
@@ -94,19 +76,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,7 +104,7 @@ def generateConfigFile(
f.write("OptimizationOffsets " + " ".join([str(x) for x in optimizationOffsets]) + "\n")
f.write("\n")
- return os.path.join(mainDirectory, trialName + ".cfg")
+ return True
def writeVolume(volumeNode: slicer.vtkMRMLVolumeNode, filename: str):
diff --git a/AutoscoperM/Resources/UI/AutoscoperM.ui b/AutoscoperM/Resources/UI/AutoscoperM.ui
index c825079..1bfdf2b 100644
--- a/AutoscoperM/Resources/UI/AutoscoperM.ui
+++ b/AutoscoperM/Resources/UI/AutoscoperM.ui
@@ -364,7 +364,7 @@
true
- false
+ true
-
@@ -526,7 +526,7 @@
-
-
+
true
@@ -686,7 +686,7 @@
-
-
+
999999999
@@ -696,7 +696,7 @@
-
-
+
999999999