Skip to content

Commit

Permalink
Minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
c-h-benedetti committed Oct 1, 2024
1 parent a25b928 commit bff9165
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 97 deletions.
186 changes: 94 additions & 92 deletions src/microglia_analyzer/_widget_yolo_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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.
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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):
"""
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -559,7 +561,7 @@ def update_count_display(self, counts):
</td>
<td style="padding: 6px;">
{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)}%)
</td>
</tr>
'''
Expand Down
11 changes: 6 additions & 5 deletions src/microglia_analyzer/microglia_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit bff9165

Please sign in to comment.