Skip to content

Commit

Permalink
binding
Browse files Browse the repository at this point in the history
  • Loading branch information
c-h-benedetti committed Dec 9, 2024
1 parent 78da655 commit 47f743c
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/microglia_analyzer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.1.0"
__version__ = "0.2.0"

import re

Expand All @@ -10,4 +10,4 @@
The networks used in this package (A 2D UNet and a YOLOv5) rely on images that have a pixel size of 0.325 µm.
Images with a different pixel size will be resized to artificially have a pixel size of 0.325 µm.
It is from these resized images that we will extract the patches.
"""
"""
20 changes: 16 additions & 4 deletions src/microglia_analyzer/_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from qtpy.QtCore import QThread, Qt

from PyQt5.QtGui import QFont, QDoubleValidator, QColor
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtCore import pyqtSignal, Qt, QLocale

import napari
from napari.utils.notifications import show_info
Expand Down Expand Up @@ -91,6 +91,7 @@ def calibration_panel(self):
# Create QLineEdit for float input
self.calibration_input = QLineEdit()
float_validator = QDoubleValidator()
float_validator.setLocale(QLocale(QLocale.English))
float_validator.setNotation(QDoubleValidator.StandardNotation)
self.calibration_input.setValidator(float_validator)
nav_layout.addWidget(self.calibration_input)
Expand Down Expand Up @@ -146,12 +147,12 @@ def segment_microglia_panel(self):
h_layout.addWidget(self.probability_threshold_label)
self.probability_threshold_slider = QSlider(Qt.Horizontal)
self.probability_threshold_slider.setRange(0, 100)
self.probability_threshold_slider.setValue(5)
self.probability_threshold_slider.setValue(40)
self.probability_threshold_slider.setTickInterval(1)
self.probability_threshold_slider.setTickPosition(QSlider.TicksBelow)
self.probability_threshold_slider.valueChanged.connect(self.proba_threshold_update)
h_layout.addWidget(self.probability_threshold_slider)
self.proba_value_label = QLabel("5%")
self.proba_value_label = QLabel("40%")
h_layout.addWidget(self.proba_value_label)
layout.addLayout(h_layout)

Expand Down Expand Up @@ -400,7 +401,7 @@ def show_classification(self):
tps = [(c, None, b) for c, b in zip(classification['classes'], classification['boxes'])]
boxes, colors = boxes_as_napari_shapes(tps, True)
if _YOLO_LAYER_NAME not in self.viewer.layers:
layer = self.viewer.add_shapes(boxes, name=_YOLO_LAYER_NAME, edge_color=colors, face_color='#00000000', edge_width=4)
layer = self.viewer.add_shapes(boxes, name=_YOLO_LAYER_NAME, edge_color=colors, face_color='#00000000', edge_width=4, visible=False)
else:
layer = self.viewer.layers[_YOLO_LAYER_NAME]
layer.data = boxes
Expand Down Expand Up @@ -493,6 +494,16 @@ def set_sources_folder(self, folder_path):
self.images_combo.clear()
self.images_combo.addItems(self.get_all_tiff_files(folder_path))

def reset_layers(self):
if _SEGMENTATION_LAYER_NAME in self.viewer.layers:
self.viewer.layers[_SEGMENTATION_LAYER_NAME].data = np.zeros_like(self.viewer.layers[_SEGMENTATION_LAYER_NAME].data)
if _CLASSIFICATION_LAYER_NAME in self.viewer.layers:
self.viewer.layers[_CLASSIFICATION_LAYER_NAME].data = []
if _YOLO_LAYER_NAME in self.viewer.layers:
self.viewer.layers[_YOLO_LAYER_NAME].data = []
if _SKELETON_LAYER_NAME in self.viewer.layers:
self.viewer.layers[_SKELETON_LAYER_NAME].data = np.zeros_like(self.viewer.layers[_SKELETON_LAYER_NAME].data)

def open_image(self, image_path):
data = tifffile.imread(image_path)
layer = None
Expand All @@ -501,6 +512,7 @@ def open_image(self, image_path):
layer.data = data
else:
layer = self.viewer.add_image(data, name=_IMAGE_LAYER_NAME, colormap='gray')
self.reset_layers()
self.mam.set_input_image(data.copy())
if self.mam.calibration is not None:
self.set_calibration(*self.mam.calibration)
Expand Down
13 changes: 8 additions & 5 deletions src/microglia_analyzer/dl/unet2d_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@
batch_size = 8
epochs = 500
unet_depth = 2
num_filters_start = 24
dropout_rate = 0.2
num_filters_start = 32
dropout_rate = 0.25
optimizer = 'Adam'
learning_rate = 0.001
skeleton_coef = 0.2
bce_coef = 0.25
bce_coef = 0.5
early_stop_patience = 50
dilation_kernel = diamond(1)
loss = dice_skeleton_loss(skeleton_coef, bce_coef)
Expand Down Expand Up @@ -658,7 +658,8 @@ def open_pair(input_path, mask_path, training, img_only):
def pairs_generator(src, training, img_only):
source = src.decode('utf-8')
_, l_files = get_data_pools(os.path.join(working_directory, source), [inputs_name], True)
l_files = sorted(list(l_files))
l_files = list(l_files)
random.shuffle(l_files)
i = 0
while i < len(l_files):
input_path = os.path.join(working_directory, source, inputs_name, l_files[i])
Expand Down Expand Up @@ -825,6 +826,8 @@ def create_unet2d_model(input_shape):
x = Conv2D(num_filters, 3, activation='relu', padding='same', kernel_initializer='he_normal')(x)
# x = BatchNormalization()(x)
x = Conv2D(num_filters, 3, activation='relu', padding='same', kernel_initializer='he_normal')(x)
if i > 0:
x = BatchNormalization()(x)
# x = BatchNormalization()(x)

outputs = Conv2D(1, 1, activation='sigmoid')(x)
Expand Down Expand Up @@ -923,7 +926,7 @@ def cosine_annealing(epoch, _):
return float(learning_rate * decayed)

def train_model(model, train_dataset, val_dataset, output_path):
plot_model(model, to_file=os.path.join(output_path, 'architecture.png'), show_shapes=True)
#plot_model(model, to_file=os.path.join(output_path, 'architecture.png'), show_shapes=True)
print(f"💾 Exporting model to: {output_path}")

checkpoint = ModelCheckpoint(os.path.join(output_path, 'best.keras'), save_best_only=True, monitor='val_loss', mode='min')
Expand Down
10 changes: 6 additions & 4 deletions src/microglia_analyzer/experimental/segment_microglia.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def normalize_batch(batch):
bce_coef = 0.7

class MicrogliaSegmenter(object):
def __init__(self, model_path, image_path, tile_size=512, overlap=128):
def __init__(self, model_path, image_path, tile_size=512, overlap=256):
if not os.path.isfile(model_path):
raise FileNotFoundError(f"Model file {model_path} not found")
if not model_path.endswith(".keras"):
Expand All @@ -27,7 +27,7 @@ def __init__(self, model_path, image_path, tile_size=512, overlap=128):
model_path,
custom_objects={
"bcl": bce_dice_loss(bce_coef),
"dsl": dice_skeleton_loss(skeleton_coef, bce_coef)
"_dice_skeleton_loss": dice_skeleton_loss(skeleton_coef, bce_coef)
}
)
if not os.path.isfile(image_path):
Expand All @@ -48,14 +48,16 @@ def inference(self):
tiles = np.array(tiles_manager.image_to_tiles(self.image))
predictions = np.squeeze(self.model.predict(tiles, batch_size=8))
tifffile.imwrite("/tmp/tiles.tif", predictions)
normalize_batch(predictions)
tifffile.imwrite("/tmp/coefs.tif", tiles_manager.blending_coefs)
# normalize_batch(predictions)
probabilities = tiles_manager.tiles_to_image(predictions)
return probabilities


if __name__ == "__main__":
output_path = "/home/benedetti/Downloads/training-audrey/output/"
model_path = "/home/benedetti/Downloads/training-audrey/models/unet-V007/best.keras"
folder_path = "/home/benedetti/Documents/projects/2060-microglia/data/raw-data/tiff-data"
folder_path = "/home/benedetti/Downloads/training-audrey/raw/"
content = [f for f in os.listdir(folder_path) if f.endswith(".tif")]
for i, image_name in enumerate(content):
print(f"{i+1}/{len(content)}: {image_name}")
Expand Down
43 changes: 33 additions & 10 deletions src/microglia_analyzer/experimental/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,37 @@ def generate_checkerboard(width, height, num_squares_x, num_squares_y):
img = Image.fromarray(checkerboard)
return img

# Générer une image de 2048x2048 avec des cases de 128x128
checkerboard_img = np.squeeze(np.array(generate_checkerboard(2048, 2048, 16, 16)))
tifffile.imwrite("/tmp/original.tif", checkerboard_img)
tiles_manager = ImageTiler2D(512, 128, checkerboard_img.shape)
tiles = tiles_manager.image_to_tiles(checkerboard_img)
tifffile.imwrite("/tmp/checkerboard.tif", tiles)
merged = tiles_manager.tiles_to_image(tiles)
tifffile.imwrite("/tmp/merged.tif", merged)
if __name__ == "__main__":
import os
import tifffile
import numpy as np
output_path = "/tmp/dump/"
os.makedirs(output_path, exist_ok=True)

tifffile.imwrite("/tmp/coefs.tif", tiles_manager.blending_coefs)
tifffile.imwrite("/tmp/gradient.tif", tiles_manager.tiles_to_image(tiles_manager.blending_coefs))
shapes = [
(2048, 2048),
(1024, 1024)
]
for shape in shapes:
print("-----------")
image = np.ones(shape, dtype=np.float32)
tiles_manager = ImageTiler2D(512, 128, shape)
for t in tiles_manager.layout:
print(t)
tiles = tiles_manager.image_to_tiles(image)
merged = tiles_manager.tiles_to_image(tiles)
for i in range(len(tiles)):
tifffile.imwrite(os.path.join(output_path, f"{shape[0]}_{str(i).zfill(2)}.tif"), tiles_manager.blending_coefs[i])
tifffile.imwrite(os.path.join(output_path, f"{shape[0]}_merged.tif"), merged)

if __name__ == "":
# Générer une image de 2048x2048 avec des cases de 128x128
checkerboard_img = np.squeeze(np.array(generate_checkerboard(2048, 2048, 16, 16)))
tifffile.imwrite("/tmp/original.tif", checkerboard_img)
tiles_manager = ImageTiler2D(512, 128, checkerboard_img.shape)
tiles = tiles_manager.image_to_tiles(checkerboard_img)
tifffile.imwrite("/tmp/checkerboard.tif", tiles)
merged = tiles_manager.tiles_to_image(tiles)
tifffile.imwrite("/tmp/merged.tif", merged)
tifffile.imwrite("/tmp/coefs.tif", tiles_manager.blending_coefs)
tifffile.imwrite("/tmp/gradient.tif", tiles_manager.tiles_to_image(tiles_manager.blending_coefs))
36 changes: 33 additions & 3 deletions src/microglia_analyzer/ma_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import shutil
import pint
import json
import pathlib
import platform

import tifffile
import numpy as np
Expand Down Expand Up @@ -165,6 +167,9 @@ def set_classification_model(self, path, use="best", reload=False):
if not os.path.isfile(confusion_matrix_path):
raise ValueError("The training of this model is not complete.")
self.classification_model_path = weights_path
plt = platform.system()
if plt == "Windows":
pathlib.PosixPath = pathlib.WindowsPath
self.classification_model = torch.hub.load(
'ultralytics/yolov5',
'custom',
Expand Down Expand Up @@ -287,7 +292,7 @@ def classification_postprocessing(self):
used.add(i)
self.classifications = clean_boxes

def bind_classifications(self):
def _bind_classifications(self):
labeled = self.mask
regions = regionprops(labeled)
bindings = {int(l): (None, 0.0, None) for l in np.unique(labeled) if l != 0} # label: (class, IoU)
Expand All @@ -298,10 +303,35 @@ def bind_classifications(self):
x1, y1, x2, y2 = list(map(int, box))
detect_bbox = [y1, x1, y2, x2]
iou = calculate_iou(seg_bbox, detect_bbox)
if iou > 0.8 and iou > bindings[region.label][1]:
if iou > bindings[region.label][1]: # iou > 0.2 and
bindings[region.label] = (cls, iou, seg_bbox)
self.bindings = bindings

def bind_classifications(self):
labeled = self.mask
regions = regionprops(labeled)
# box_index: (class, IoU, label, seg_bbox)
bindings = {b: (None, 0.0, None, None) for b in range(len(self.classifications['boxes']))}

for b_index, (box, cls) in enumerate(zip(self.classifications['boxes'], self.classifications['classes'])):
x1, y1, x2, y2 = list(map(int, box))
detect_bbox = [y1, x1, y2, x2]
for region in regions:
seg_bbox = list(map(int, region.bbox))
iou = calculate_iou(seg_bbox, detect_bbox)
if iou > bindings[b_index][1]:
bindings[b_index] = (cls, iou, region.label, seg_bbox)

self.bindings = self.flip_dict(bindings)

def flip_dict(self, d):
flipped = {}
for _, (cls, iou, label, seg_bbox) in d.items():
if seg_bbox is None:
continue
flipped[label] = (cls, iou, seg_bbox)
return flipped

def analyze_skeleton(self, mask):
skeleton = skeletonize(mask)
skel = Skeleton(skeleton)
Expand Down Expand Up @@ -360,7 +390,7 @@ def as_csv(self, identifier):
if i == 0:
values[0] = identifier
graph_measures = self.graph_metrics[label]
iou, class_value = self.bindings[label][:2]
class_value, iou = self.bindings[label][:2]
class_value = self.classes[int(class_value)] if class_value is not None else ""
values += [graph_measures[key] for key in graph_measure_keys] + [iou, class_value]
line = ", ".join([str(v) for v in values])
Expand Down

0 comments on commit 47f743c

Please sign in to comment.