Skip to content

Commit

Permalink
ENH: Implement config generation from updated UI
Browse files Browse the repository at this point in the history
TODO: decide and implement correct pairing of radiograph subfolders with
their camera calibration files
  • Loading branch information
sbelsk committed Nov 24, 2024
1 parent 34c64ca commit df78491
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 52 deletions.
78 changes: 65 additions & 13 deletions AutoscoperM/AutoscoperM.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 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 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,
Expand All @@ -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):
Expand Down
56 changes: 17 additions & 39 deletions AutoscoperM/AutoscoperMLib/IO.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import glob
import logging
import os
from itertools import product
Expand Down Expand Up @@ -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(
Expand All @@ -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")
Expand All @@ -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):
"""
Expand Down

0 comments on commit df78491

Please sign in to comment.