Skip to content

Commit

Permalink
ENH: Improve UI to assign camera and radiograph file orders
Browse files Browse the repository at this point in the history
Changes include some reordering of the UI to better match the order of
the config v1.1 file format.

Co-authored-by: Amy M Morton <[email protected]>
  • Loading branch information
sbelsk and amymmorton committed Dec 2, 2024
1 parent 469c550 commit d8045fd
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 49 deletions.
138 changes: 123 additions & 15 deletions AutoscoperM/AutoscoperM.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,20 @@ def setup(self):

# 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.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.populateCameraCalListButton.connect("clicked(bool)", self.onPopulateCameraCalList)
self.ui.configGenButton.connect("clicked(bool)", self.onGenerateConfig)

# Default output directory
self.ui.mainOutputSelector.setCurrentPath(
Expand Down Expand Up @@ -519,18 +525,38 @@ 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
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)

# 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(radiographSubDir, item) for item in get_checked_items(trialList)]
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."
)
return

if len(trialDirs) == 0:
raise ValueError(
"Invalid inputs: must select at least one radiograph subdirectory, but zero were provided."
)
return

if len(camCalFiles) != len(trialDirs):
raise ValueError(
Expand All @@ -539,6 +565,14 @@ def get_checked_items(listWidget):
)
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

partialVolumeFiles = [os.path.join(tiffSubDir, item) for item in get_checked_items(partialVolumeList)]

if len(partialVolumeFiles) == 0:
Expand Down Expand Up @@ -750,7 +784,7 @@ 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.radiographSubDir.text, itemType="dir")
self.populateListFromOutputSubDir(self.ui.trialCandidateList, self.ui.radiographSubDir.text, itemType="dir")

def onPopulatePartialVolumeList(self):
"""
Expand All @@ -764,7 +798,7 @@ 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)
self.populateListFromOutputSubDir(self.ui.camCalCandidateList, self.ui.cameraSubDir.text)

def populateListFromOutputSubDir(self, listWidget, fileSubDir, itemType="file"):
"""
Expand Down Expand Up @@ -803,6 +837,80 @@ def populateListFromOutputSubDir(self, listWidget, fileSubDir, itemType="file"):
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 contrains 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 checked, 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
Expand Down
Loading

0 comments on commit d8045fd

Please sign in to comment.