From 85a8da9cd24b82c18fb9b0bb2a15342a007066ed Mon Sep 17 00:00:00 2001 From: Stephen Aylward Date: Sat, 3 Aug 2024 21:12:18 -0400 Subject: [PATCH] ENH: Update image table - allow for labels for images/scenes --- README.md | 2 +- src/minder3d/lib/sovImageTablePanelWidget.py | 70 +++++++++++++++----- src/minder3d/lib/sovImageTableSettings.py | 25 ++++++- src/minder3d/lib/sovImportDICOMSettings.py | 15 ++--- src/minder3d/minder3DState.py | 2 + src/minder3d/minder3DWindow.py | 20 ++++++ 6 files changed, 105 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index db36d32..6bc068a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ For windows running gitbash Install the test-build of ITK, ITKMinimalPathExtraction ITKTubetk: - python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ itk itk-minimalpathextraction itk-tubetk + python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ --upgrade itk itk-minimalpathextraction itk-tubetk Get the Minder3D source code diff --git a/src/minder3d/lib/sovImageTablePanelWidget.py b/src/minder3d/lib/sovImageTablePanelWidget.py index 4bda368..a1e975b 100644 --- a/src/minder3d/lib/sovImageTablePanelWidget.py +++ b/src/minder3d/lib/sovImageTablePanelWidget.py @@ -42,20 +42,21 @@ def __init__(self, gui, state, parent=None): self.selected_icon = self.style().standardIcon(pixmapi) self.imageTableWidget.setRowCount(0) - self.imageTableWidget.setColumnCount(7) + self.imageTableWidget.setColumnCount(8) self.imageTableWidget.setHorizontalHeaderLabels( [ 'Selected', 'Loaded', 'Type', 'Thumbnail', + 'Label', 'Size', 'Spacing', 'Filename', ] ) self.col_filetype = 2 - self.col_filename = 6 + self.col_filename = 7 self.imageTableWidget.setEditTriggers(QTableWidget.NoEditTriggers) self.imageTableWidget.setSelectionBehavior(QTableWidget.SelectRows) self.imageTableWidget.setSelectionMode(QTableWidget.SingleSelection) @@ -96,40 +97,46 @@ def unload_selected(self): self.selected = [] self.fill_table() + @time_and_log def remove_selected(self): for row in self.selected: if row < len(self.state.image_filename): self.gui.unload_image(row, False) self.settings.remove_data(self.state.image_filename[row]) - elif row == len(self.state.image_filename): + elif ( + row == len(self.state.image_filename) + and self.state.scene_filename + != self.imageTableWidget.item(row, self.col_filename).text() + ): self.gui.unload_scene() - self.settings.remove_data(self.state.scene_filename) + self.settings.remove_data(self.state.image_filename[row]) else: - filename = str( - self.imageTableWidget.getItem( - row, self.col_filename - ).values[0] + self.settings.remove_data( + self.imageTableWidget.item(row, self.col_filename).text() ) - self.settings.remove_data(filename) if len(self.selected) > 0: self.selected = [] self.fill_table() + @time_and_log def remove_all(self): num_images = len(self.state.image_filename) - for row in range(num_images - 1, 0, -1): - self.gui.unload_image(row, False) - self.settings.remove_data(self.state.image_filename[row]) - self.gui.unload_scene() - self.settings.remove_data(self.state.scene_filename) + for row in range(self.imageTableWidget.getRowCount()): + if row < num_images: + self.gui.unload_image(row, False) + self.settings.remove_data( + self.imageTableWidget.item(row, self.col_filename).text() + ) self.selected = [] self.settings.clear_data() self.fill_table() + @time_and_log def close_expanded_table(self): self.enlarged_table.close() self.enlarged_table = None + @time_and_log def expand_table(self): if self.enlarged_table is None: self.enlarged_table = ImageTablePanelWidget(self.gui, self.state) @@ -204,6 +211,12 @@ def redraw_image_row(self, row_num): QTableWidgetItem(QIcon(self.state.image_thumbnail[img_num]), ''), ) col_num += 1 + self.imageTableWidget.setItem( + row_num, + col_num, + QTableWidgetItem(QIcon(self.state.image_label[img_num]), ''), + ) + col_num += 1 size_str = [ str(i) for i in self.state.image[img_num] @@ -261,6 +274,10 @@ def redraw_scene_row(self, row_num): QTableWidgetItem(QIcon(self.state.scene_thumbnail), ''), ) col_num += 1 + self.imageTableWidget.setItem( + row_num, col_num, QTableWidgetItem(self.state.scene_label) + ) + col_num += 1 size = self.state.scene.GetNumberOfChildren() self.imageTableWidget.setItem( row_num, col_num, QTableWidgetItem(str(size)) @@ -296,6 +313,7 @@ def fill_table(self): 'Loaded', 'Type', 'Thumbnail', + 'Label', 'Size', 'Spacing', 'Filename', @@ -349,6 +367,10 @@ def fill_table(self): row_num, col_num, QTableWidgetItem(' ') ) col_num += 1 + self.imageTableWidget.setItem( + row_num, col_num, QTableWidgetItem(str(file.file_label)) + ) + col_num += 1 self.imageTableWidget.setItem( row_num, col_num, QTableWidgetItem(str(file.file_size)) ) @@ -392,6 +414,10 @@ def fill_table(self): row_num, col_num, QTableWidgetItem(QIcon(qthumb), '') ) col_num += 1 + self.imageTableWidget.setItem( + row_num, col_num, QTableWidgetItem(file.file_label) + ) + col_num += 1 self.imageTableWidget.setItem( row_num, col_num, QTableWidgetItem(file.file_size) ) @@ -407,6 +433,9 @@ def fill_table(self): @time_and_log def create_new_image(self): + self.state.image_label.append( + os.path.basename(self.state.image_filename[-1]) + ) self.state.image_thumbnail.append( self.settings.get_thumbnail( self.state.image[-1], self.state.image_filename[-1], 'image' @@ -416,6 +445,7 @@ def create_new_image(self): self.state.image[-1], self.state.image_filename[-1], 'image', + self.state.image_label[-1], self.state.image_thumbnail[-1], ) self.selected = [] @@ -427,6 +457,7 @@ def save_image(self, filename): self.state.image[self.state.current_image_num], filename, 'image', + self.state.image_label[self.state.current_image_num], self.state.image_thumbnail[self.state.current_image_num], ) self.selected = [] @@ -437,10 +468,12 @@ def load_scene(self): self.state.scene_thumbnail = self.settings.get_thumbnail( self.state.scene, self.state.scene_filename, 'scene' ) + self.state.scene_label = os.path.basename(self.state.scene_filename) self.settings.add_data( self.state.scene, self.state.scene_filename, 'scene', + self.state.scene_label, self.state.scene_thumbnail, ) self.selected = [] @@ -449,13 +482,18 @@ def load_scene(self): @time_and_log def save_scene(self, filename): self.settings.add_data( - self.state.scene, filename, 'scene', self.state.scene_thumbnail + self.state.scene, + filename, + 'scene', + self.state.scene_label, + self.state.scene_thumbnail, ) @time_and_log def replace_image(self, img_num): self.redraw_image_row(img_num) + @time_and_log def rename_selected(self): pass @@ -521,12 +559,14 @@ def register_images(self, dir, first=True): img = imread(filename) except Exception: continue + label = os.path.basename(filename) thumbnail = self.settings.get_thumbnail(img, filename, 'image') print(f'Adding {filename} to settings') self.settings.add_data( img, filename, 'image', + label, thumbnail, ) diff --git a/src/minder3d/lib/sovImageTableSettings.py b/src/minder3d/lib/sovImageTableSettings.py index 17cd5cf..586eaae 100644 --- a/src/minder3d/lib/sovImageTableSettings.py +++ b/src/minder3d/lib/sovImageTableSettings.py @@ -17,6 +17,7 @@ def __init__( file_spacing='', file_size='', file_thumbnail='', + file_label='', ): """Initialize the object with the provided file details. @@ -26,6 +27,7 @@ def __init__( file_spacing (str): A list of spacing details. Defaults to ''. file_size (str): A list of size details. Defaults to ''. file_thumbnail (str): The thumbnail of the file. Defaults to ''. + file_label (str): The short diaplay-name of the file. Defaults to ''. """ self.filename = filename @@ -33,6 +35,7 @@ def __init__( self.file_spacing = file_spacing self.file_size = file_size self.file_thumbnail = file_thumbnail + self.file_label = file_label class ImageTableSettings(QSettings): @@ -69,15 +72,23 @@ def get_file_records(self): file_spacing = self.value('file_spacing', '') file_size = self.value('file_size', '') file_thumbnail = self.value('file_thumbnail', '') + file_label = self.value('file_label', '') file = ImageTableSettingsFileRecord( - filename, file_type, file_spacing, file_size, file_thumbnail + filename, + file_type, + file_spacing, + file_size, + file_thumbnail, + file_label, ) file_records.append(file) self.endArray() return file_records @time_and_log - def add_data(self, obj, filename, file_type, thumbnail_pixmap=None): + def add_data( + self, obj, filename, file_type, file_label=None, thumbnail_pixmap=None + ): """Add a file to the settings. This function adds a file to the settings, including its filename, type, @@ -87,6 +98,7 @@ def add_data(self, obj, filename, file_type, thumbnail_pixmap=None): obj: The object representing the file. filename (str): The name of the file. file_type (str): The type of the file. + file_label (Optional[str]): The custom label of file, defaults to basename of filename thumbnail_pixmap (Optional[QPixmap]): The thumbnail of the file. @@ -115,6 +127,8 @@ def add_data(self, obj, filename, file_type, thumbnail_pixmap=None): file_thumbnail = os.path.join(data_dir, file_thumbnail) if not thumbnail_pixmap.save(file_thumbnail): file_thumbnail = '' + if file_label is None or file_label == '': + file_label = os.path.basename(filename) self.beginWriteArray('files') for i, file in enumerate(file_records): @@ -128,6 +142,7 @@ def add_data(self, obj, filename, file_type, thumbnail_pixmap=None): ): os.remove(file.file_thumbnail) self.setValue('file_thumbnail', file_thumbnail) + self.setValue('file_label', file_label) self.setArrayIndex(len(file_records) - 1) self.endArray() self.sync() @@ -145,12 +160,14 @@ def add_data(self, obj, filename, file_type, thumbnail_pixmap=None): self.setValue('file_spacing', file.file_spacing) self.setValue('file_size', file.file_size) self.setValue('file_thumbnail', file.file_thumbnail) + self.setValue('file_label', file.file_label) self.setArrayIndex(len(file_records)) self.setValue('filename', filename) self.setValue('file_type', file_type) self.setValue('file_spacing', file_spacing) self.setValue('file_size', file_size) self.setValue('file_thumbnail', file_thumbnail) + self.setValue('file_label', file_label) self.setArrayIndex(len(file_records) - 1) self.endArray() self.sync() @@ -182,11 +199,12 @@ def remove_data(self, filename): for j in range(i + 1, len(file_records)): next_file = file_records[j] self.setArrayIndex(j - 1) - self.setValue('file_filename', next_file.file_filename) + self.setValue('file_filename', next_file.filename) self.setValue('file_type', next_file.file_type) self.setValue('file_spacing', next_file.file_spacing) self.setValue('file_size', next_file.file_size) self.setValue('file_thumbnail', next_file.file_thumbnail) + self.setValue('file_label', next_file.file_label) self.sync() return else: @@ -196,6 +214,7 @@ def remove_data(self, filename): self.setValue('file_spacing', next_file.file_spacing) self.setValue('file_size', next_file.file_size) self.setValue('file_thumbnail', next_file.file_thumbnail) + self.setValue('file_label', next_file.file_label) self.sync() return diff --git a/src/minder3d/lib/sovImportDICOMSettings.py b/src/minder3d/lib/sovImportDICOMSettings.py index 01e15c4..84f63e4 100644 --- a/src/minder3d/lib/sovImportDICOMSettings.py +++ b/src/minder3d/lib/sovImportDICOMSettings.py @@ -18,18 +18,13 @@ def __init__(self): def add_data(self, input_directory, output_directory, auto_register): """Add a data settings. - This function adds a file to the settings, including its filename, type, - spacing, size, and thumbnail. + This function converts a DICOM directory of files to .nii.gz files. + Optionally adds them to the settings database. Args: - obj: The object representing the file. - filename (str): The name of the file. - file_type (str): The type of the file. - thumbnail_pixmap (Optional[QPixmap]): The thumbnail of the file. - - - Raises: - IndexError: If the input list is empty. + input_directory (str): The directory containing DICOM data. + output_directory (str): The directory for storing the nii.gz files. + auto_register (Optional[bool]): Should the created nii.gz files be stored in the settings database. """ self.setValue('input_directory', input_directory) self.setValue('output_directory', output_directory) diff --git a/src/minder3d/minder3DState.py b/src/minder3d/minder3DState.py index 4c0f287..be0480d 100644 --- a/src/minder3d/minder3DState.py +++ b/src/minder3d/minder3DState.py @@ -28,6 +28,7 @@ def __init__(self): self.image_max = [] self.image_filename = [] self.image_thumbnail = [] + self.image_label = [] self.csa_to_image_axis = [] # Overlay @@ -63,6 +64,7 @@ def __init__(self): self.scene_list_properties = dict() self.scene_filename = './scene.tre' self.scene_thumbnail = None + self.scene_label = None # Selected Spatial Objects self.multiple_selections_enabled = False diff --git a/src/minder3d/minder3DWindow.py b/src/minder3d/minder3DWindow.py index 1b3b421..c18ed7c 100644 --- a/src/minder3d/minder3DWindow.py +++ b/src/minder3d/minder3DWindow.py @@ -589,6 +589,7 @@ def unload_image(self, img_num, update_image_table=True): self.state.image_max.pop(img_num) self.state.image_filename.pop(img_num) self.state.image_thumbnail.pop(img_num) + self.state.image_label.pop(img_num) self.state.csa_to_image_axis.pop(img_num) self.state.overlay.pop(img_num) @@ -610,3 +611,22 @@ def unload_image(self, img_num, update_image_table=True): self.imageTablePanel.unload_image() self.view2DPanel.update_view_image_num(self.state.current_image_num) + + @time_and_log + def unload_scene(self): + """ + This function deletes the all objects from the scene + """ + for scene_idx in range(len(self.state.scene_list)): + so_id = self.state.scene_list_ids[scene_idx] + if so_id == -1: + continue + so = self.state.scene_list[scene_idx] + so_parent = so.GetParent() + so_parent.RemoveChild(so) + self.state.scene_list.pop(scene_idx) + self.state.scene_list_properties.pop(scene_idx) + self.state.selected_ids = [] + self.state.selected_point_ids = [] + + self.update_scene()