From bff916529334129862fe3d311b113c9c08108ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20H=2E=20Benedetti?= Date: Tue, 1 Oct 2024 14:41:23 +0200 Subject: [PATCH] Minor fixes --- .../_widget_yolo_annotations.py | 186 +++++++++--------- src/microglia_analyzer/microglia_analyzer.py | 11 +- 2 files changed, 100 insertions(+), 97 deletions(-) diff --git a/src/microglia_analyzer/_widget_yolo_annotations.py b/src/microglia_analyzer/_widget_yolo_annotations.py index b3f799d..3cf66a6 100644 --- a/src/microglia_analyzer/_widget_yolo_annotations.py +++ b/src/microglia_analyzer/_widget_yolo_annotations.py @@ -76,7 +76,7 @@ def add_media_management_group_ui(self): # Label + button to select the source directory: self.select_root_directory_button = QPushButton("📂 Root directory") - self.select_root_directory_button.clicked.connect(self.select_sources_directory) + self.select_root_directory_button.clicked.connect(self.select_root_directory) layout.addWidget(self.select_root_directory_button) # Label + text box for the inputs sub-folder's name: @@ -149,7 +149,7 @@ def init_ui(self): # ----------------- CALLBACKS ------------------------------------------- - def select_sources_directory(self): + def select_root_directory(self): """ Select the folder containing the "images" and "labels" sub-folders. """ @@ -159,6 +159,96 @@ def select_sources_directory(self): return self.set_root_directory(directory) + def set_sources_directory(self): + """ + Whenever the user selects a new source directory, the content of the 'sources' folder is probed. + This function also checks if the 'annotations' folder exists, and creates it if not. + """ + source_folder = self.inputs_name.currentText() + annotations_folder = source_folder + "-labels" + if (source_folder is None) or (source_folder == "---") or (source_folder == ""): + return + inputs_path = os.path.join(self.root_directory, source_folder) + annotations_path = os.path.join(self.root_directory, annotations_folder) + if not os.path.isdir(inputs_path): + return False + if not os.path.isdir(annotations_path): + os.makedirs(annotations_path) + self.sources_directory = source_folder + self.annotations_directory = annotations_folder + self.annotations_name.setText(annotations_folder) + self.open_sources_directory() + + def add_yolo_class(self): + """ + Adds a new layer representing a YOLO class. + """ + class_name = self.get_new_class_name() + if _IMAGE_LAYER not in self.viewer.layers: + show_info("No image loaded.") + return + n_classes = len(self.get_classes()) + color = _COLORS[n_classes % len(_COLORS)] + l = self.viewer.layers.selection.active + if class_name is None: + return + # Clears the selection of the current layer. + if (l is not None) and (l.name.startswith(_CLASS_PREFIX)): + l.selected_data = set() + self.viewer.add_shapes( + name=class_name, + edge_color=color, + face_color="transparent", + opacity=0.8, + edge_width=3 + ) + + def open_image(self): + """ + Uses the value contained in the `self.image_selector` to find and open an image. + Reloads the annotations if some were already made for this image. + """ + current_image = self.image_selector.currentText() + if (self.root_directory is None) or (current_image is None) or (current_image == "---") or (current_image == ""): + return + image_path = os.path.join(self.root_directory, self.sources_directory, current_image) + name, _ = os.path.splitext(current_image) + current_as_txt = name + ".txt" + labels_path = os.path.join(self.root_directory, self.annotations_directory, current_as_txt) + if not os.path.isfile(image_path): + print(f"The image: '{current_image}' doesn't exist.") + return + data = imread(image_path, **ARGS) + if _IMAGE_LAYER in self.viewer.layers: + self.viewer.layers[_IMAGE_LAYER].data = data + self.viewer.layers[_IMAGE_LAYER].contrast_limits = (np.min(data), np.max(data)) + self.viewer.layers[_IMAGE_LAYER].reset_contrast_limits_range() + else: + self.viewer.add_image(data, name=_IMAGE_LAYER) + self.deselect_all() + self.restore_classes_layers() + self.clear_classes_layers() + if os.path.isfile(labels_path): # If some annotations already exist for this image. + self.load_annotations(labels_path) + self.count_boxes() + + def save_state(self): + """ + Saves the current annotations (bounding-boxes) in a '.txt' file. + The class index corresponds to the rank of the layers in the Napari stack. + """ + count = 0 + lines = [] + for l in self.viewer.layers: + if not l.name.startswith(_CLASS_PREFIX): + continue + lines += self.layer2yolo(l.name, count) + count += 1 + self.write_annotations(lines) + self.count_boxes() + + # ----------------- METHODS ------------------------------------------- + def upper_corner(self, box): """ Locates the upper-right corner of a bounding-box having the Napari format. @@ -237,21 +327,6 @@ def write_annotations(self, tuples): for c in self.get_classes(): f.write(c + "\n") show_info("Annotations saved.") - - def save_state(self): - """ - Saves the current annotations (bounding-boxes) in a '.txt' file. - The class index corresponds to the rank of the layers in the Napari stack. - """ - count = 0 - lines = [] - for l in self.viewer.layers: - if not l.name.startswith(_CLASS_PREFIX): - continue - lines += self.layer2yolo(l.name, count) - count += 1 - self.write_annotations(lines) - self.count_boxes() def get_new_class_name(self): """ @@ -267,30 +342,6 @@ def get_new_class_name(self): full_name = _CLASS_PREFIX + name_candidate self.new_name.setText("") return full_name - - def add_yolo_class(self): - """ - Adds a new layer representing a YOLO class. - """ - class_name = self.get_new_class_name() - if _IMAGE_LAYER not in self.viewer.layers: - show_info("No image loaded.") - return - n_classes = len(self.get_classes()) - color = _COLORS[n_classes % len(_COLORS)] - l = self.viewer.layers.selection.active - if class_name is None: - return - # Clears the selection of the current layer. - if (l is not None) and (l.name.startswith(_CLASS_PREFIX)): - l.selected_data = set() - self.viewer.add_shapes( - name=class_name, - edge_color=color, - face_color="transparent", - opacity=0.8, - edge_width=3 - ) def set_root_directory(self, directory): """ @@ -301,31 +352,11 @@ def set_root_directory(self, directory): Args: - directory (str): The absolute path to the root directory. """ - folders = sorted([f for f in os.listdir(directory) if os.path.isdir(os.path.join(directory, f))]) + folders = sorted([f for f in os.listdir(directory) if (not f.endswith('-labels')) and os.path.isdir(os.path.join(directory, f))]) folders = ["---"] + folders self.inputs_name.clear() self.inputs_name.addItems(folders) self.root_directory = directory - - def set_sources_directory(self): - """ - Whenever the user selects a new source directory, the content of the 'sources' folder is probed. - This function also checks if the 'annotations' folder exists, and creates it if not. - """ - source_folder = self.inputs_name.currentText() - annotations_folder = source_folder + "-labels" - if (source_folder is None) or (source_folder == "---") or (source_folder == ""): - return - inputs_path = os.path.join(self.root_directory, source_folder) - annotations_path = os.path.join(self.root_directory, annotations_folder) - if not os.path.isdir(inputs_path): - return False - if not os.path.isdir(annotations_path): - os.makedirs(annotations_path) - self.sources_directory = source_folder - self.annotations_directory = annotations_folder - self.annotations_name.setText(annotations_folder) - self.open_sources_directory() def update_reader_fx(self): global imread @@ -488,35 +519,6 @@ def deselect_all(self): l.mode = 'pan_zoom' l.selected_data = set() - def open_image(self): - """ - Uses the value contained in the `self.image_selector` to find and open an image. - Reloads the annotations if some were already made for this image. - """ - current_image = self.image_selector.currentText() - if (self.root_directory is None) or (current_image is None) or (current_image == "---") or (current_image == ""): - return - image_path = os.path.join(self.root_directory, self.sources_directory, current_image) - name, _ = os.path.splitext(current_image) - current_as_txt = name + ".txt" - labels_path = os.path.join(self.root_directory, self.annotations_directory, current_as_txt) - if not os.path.isfile(image_path): - print(f"The image: '{current_image}' doesn't exist.") - return - data = imread(image_path, **ARGS) - if _IMAGE_LAYER in self.viewer.layers: - self.viewer.layers[_IMAGE_LAYER].data = data - self.viewer.layers[_IMAGE_LAYER].contrast_limits = (np.min(data), np.max(data)) - self.viewer.layers[_IMAGE_LAYER].reset_contrast_limits_range() - else: - self.viewer.add_image(data, name=_IMAGE_LAYER) - self.deselect_all() - self.restore_classes_layers() - self.clear_classes_layers() - if os.path.isfile(labels_path): # If some annotations already exist for this image. - self.load_annotations(labels_path) - self.count_boxes() - def count_boxes(self): """ Counts the number of boxes in each class to make sure annotations are balanced. @@ -559,7 +561,7 @@ def update_count_display(self, counts): - {count} ({round((count/total_count*100) if total_count > 0 else 0, 2)}%) + {count} ({round((count/total_count*100) if total_count > 0 else 0, 1)}%) ''' diff --git a/src/microglia_analyzer/microglia_analyzer.py b/src/microglia_analyzer/microglia_analyzer.py index fc18e40..41e1387 100644 --- a/src/microglia_analyzer/microglia_analyzer.py +++ b/src/microglia_analyzer/microglia_analyzer.py @@ -18,9 +18,9 @@ def __init__(self, logging_f=None): self.input_image = None # Directory in which we export stuff relative to the `image_path`. self.working_directory = None - # Name of the YOLO model that we use to classify microglia. + # Path of the YOLO model that we use to classify microglia. self.classification_model = None - # Name of the model that we use to segment microglia on YOLO patches. + # Path of the model that we use to segment microglia on YOLO patches. self.segmentation_model = None # Pixel size => tuple (pixel size, unit). self.calibration = None @@ -63,10 +63,11 @@ def get_image_shape(self): def set_calibration(self, pixel_size, unit): self.calibration = (pixel_size, unit) - def set_segmentation_model(self, model_name): - pass + def set_segmentation_model(self, model_path): + best_path = os.path.join(model_path, ) + self.segmentation_model - def set_classification_model(self, model_name): + def set_classification_model(self, model_path): pass def export_patches(self):