Skip to content

Commit 6528ca6

Browse files
committed
from dev repo; merge into Main
1 parent f42e207 commit 6528ca6

File tree

12 files changed

+229
-118
lines changed

12 files changed

+229
-118
lines changed
-1.36 KB
Binary file not shown.

napari_cellseg3d/interface.py

Lines changed: 128 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Union
2+
from typing import Optional
23

34
from qtpy.QtCore import Qt
45
from qtpy.QtCore import QUrl
@@ -312,6 +313,32 @@ def make_button(
312313
return btn
313314

314315

316+
class DropdownMenu(QComboBox):
317+
"""Creates a dropdown menu with a title and adds specified entries to it"""
318+
319+
def __init__(
320+
self,
321+
entries: Optional[list] = None,
322+
parent: Optional[QWidget] = None,
323+
label: Optional[str] = None,
324+
fixed: Optional[bool] = True,
325+
):
326+
"""Args:
327+
entries (array(str)): Entries to add to the dropdown menu. Defaults to None, no entries if None
328+
parent (QWidget): parent QWidget to add dropdown menu to. Defaults to None, no parent is set if None
329+
label (str) : if not None, creates a QLabel with the contents of 'label', and returns the label as well
330+
fixed (bool): if True, will set the size policy of the dropdown menu to Fixed in h and w. Defaults to True.
331+
"""
332+
super().__init__(parent)
333+
self.label = None
334+
if entries is not None:
335+
self.addItems(entries)
336+
if label is not None:
337+
self.label = QLabel(label)
338+
if fixed:
339+
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
340+
341+
315342
def make_combobox(
316343
entries=None,
317344
parent: QWidget = None,
@@ -329,22 +356,13 @@ def make_combobox(
329356
Returns:
330357
QComboBox : created dropdown menu
331358
"""
332-
if parent is None:
333-
menu = QComboBox()
334-
else:
335-
menu = QComboBox(parent)
336-
337-
if entries is not None:
338-
menu.addItems(entries)
339-
340-
if fixed:
341-
menu.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
342-
343359
if label is not None:
344-
label = QLabel(label)
360+
menu = DropdownMenu(entries, parent, label, fixed)
361+
label = menu.label
345362
return menu, label
346-
347-
return menu
363+
else:
364+
menu = DropdownMenu(entries, parent, fixed=fixed)
365+
return menu
348366

349367

350368
def add_widgets(layout, widgets, alignment=LEFT_AL):
@@ -363,6 +381,30 @@ def add_widgets(layout, widgets, alignment=LEFT_AL):
363381
layout.addWidget(w, alignment=alignment)
364382

365383

384+
class CheckBox(QCheckBox):
385+
"""Shortcut for creating QCheckBox with a title and a function"""
386+
387+
def __init__(
388+
self,
389+
title: Optional[str] = None,
390+
func: Optional[callable] = None,
391+
parent: Optional[QWidget] = None,
392+
fixed: Optional[bool] = True,
393+
):
394+
"""
395+
Args:
396+
title (str-like): title of the checkbox. Defaults to None, if None no title is set
397+
func (callable): function to execute when checkbox is toggled. Defaults to None, no binding is made if None
398+
parent (QWidget): parent QWidget to add checkbox to. Defaults to None, no parent is set if None
399+
fixed (bool): if True, will set the size policy of the checkbox to Fixed in h and w. Defaults to True.
400+
"""
401+
super().__init__(title, parent)
402+
if fixed:
403+
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
404+
if func is not None:
405+
self.toggled.connect(func)
406+
407+
366408
def make_checkbox(
367409
title: str = None,
368410
func: callable = None,
@@ -378,26 +420,10 @@ def make_checkbox(
378420
fixed (bool): if True, will set the size policy of the checkbox to Fixed in h and w. Defaults to True.
379421
380422
Returns:
381-
QCheckBox : created button
423+
QCheckBox : created widget
382424
"""
383-
if parent is not None:
384-
if title is not None:
385-
box = QCheckBox(title, parent)
386-
else:
387-
box = QCheckBox(parent)
388-
else:
389-
if title is not None:
390-
box = QCheckBox(title, parent)
391-
else:
392-
box = QCheckBox(parent)
393425

394-
if fixed:
395-
box.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
396-
397-
if func is not None:
398-
box.toggled.connect(func)
399-
400-
return box
426+
return CheckBox(title, func, parent, fixed)
401427

402428

403429
def combine_blocks(
@@ -456,6 +482,76 @@ def combine_blocks(
456482
return temp_widget
457483

458484

485+
def toggle_visibility(checkbox, widget):
486+
"""Toggles the visibility of a widget based on the status of a checkbox.
487+
488+
Args:
489+
checkbox: The QCheckbox that determines whether to show or not
490+
widget: The widget to hide or show
491+
"""
492+
widget.setVisible(checkbox.isChecked())
493+
494+
495+
class AnisotropyWidgets(QWidget):
496+
def __init__(self, parent, default_x=1, default_y=1, default_z=1):
497+
super().__init__(parent)
498+
499+
self._layout = QVBoxLayout()
500+
self._layout.setSpacing(0)
501+
self._layout.setContentsMargins(0, 0, 0, 0)
502+
503+
self.container, self._boxes_layout = make_container(T=7, parent=parent)
504+
self.checkbox = make_checkbox(
505+
"Anisotropic data", self.toggle_display_aniso, parent
506+
)
507+
508+
self.box_widgets = make_n_spinboxes(
509+
n=3, min=1.0, max=1000, default=1, step=0.5, double=True
510+
)
511+
self.box_widgets[0].setValue(default_x) # TODO change default
512+
self.box_widgets[1].setValue(default_y) # TODO change default
513+
self.box_widgets[2].setValue(default_z) # TODO change default
514+
515+
for w in self.box_widgets:
516+
w.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
517+
518+
self.box_widgets_lbl = [
519+
make_label("Resolution in " + axis + " (microns) :", parent=parent)
520+
for axis in "xyz"
521+
]
522+
523+
##################
524+
# tooltips
525+
self.checkbox.setToolTip(
526+
"If you have anisotropic data, you can scale data using your resolution in microns"
527+
)
528+
[w.setToolTip("Resolution in microns") for w in self.box_widgets]
529+
##################
530+
531+
self.build()
532+
533+
def toggle_display_aniso(self):
534+
"""Shows the choices for correcting anisotropy when viewing results depending on whether :py:attr:`self.checkbox` is checked"""
535+
toggle_visibility(self.checkbox, self.container)
536+
537+
def build(self):
538+
[
539+
self._boxes_layout.addWidget(widget, alignment=LEFT_AL)
540+
for widgets in zip(self.box_widgets_lbl, self.box_widgets)
541+
for widget in widgets
542+
]
543+
# anisotropy
544+
self.container.setLayout(self._boxes_layout)
545+
self.container.setVisible(False)
546+
547+
add_widgets(self._layout, [self.checkbox, self.container])
548+
self.setLayout(self._layout)
549+
550+
def get_anisotropy_factors(self):
551+
"""Returns : the resolution in microns for each of the three dimensions"""
552+
return [w.value() for w in self.box_widgets]
553+
554+
459555
def open_url(url):
460556
"""Opens the url given as a string in OS default browser using :py:func:`QDesktopServices.openUrl`.
461557

napari_cellseg3d/launch_review.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from matplotlib.figure import Figure
1010
from qtpy.QtWidgets import QSizePolicy
1111
from scipy import ndimage
12+
from monai.transforms import Zoom
1213
from tifffile import imwrite
1314

1415
from napari_cellseg3d import utils
@@ -25,6 +26,7 @@ def launch_review(
2526
checkbox,
2627
filetype,
2728
as_folder,
29+
zoom_factor,
2830
):
2931
"""Launch the review process, loading the original image, the labels & the raw labels (from prediction)
3032
in the viewer.
@@ -64,12 +66,13 @@ def launch_review(
6466
6567
as_folder (bool): Whether to load as folder or single file
6668
69+
zoom_factor (array(int)): zoom factors for each axis
70+
6771
6872
"""
6973
images_original = original
7074
base_label = base
7175

72-
7376
view1 = viewer
7477

7578
view1.scale_bar.visible = True
@@ -79,14 +82,17 @@ def launch_review(
7982
name="volume",
8083
colormap="inferno",
8184
contrast_limits=[200, 1000],
85+
scale=zoom_factor,
8286
) # anything bigger than 255 will get mapped to 255... they did it like this because it must have rgb images
83-
view1.add_labels(base_label, name="labels", seed=0.6)
87+
view1.add_labels(base_label, name="labels", seed=0.6, scale=zoom_factor)
88+
8489
if raw is not None: # raw labels is from the prediction
8590
view1.add_image(
8691
ndimage.gaussian_filter(raw, sigma=3),
8792
colormap="magenta",
8893
name="low_confident",
8994
blending="additive",
95+
scale=zoom_factor,
9096
)
9197
else:
9298
pass
@@ -199,17 +205,17 @@ def quicksave():
199205

200206
xy_axes = canvas.figure.add_subplot(3, 1, 1)
201207
canvas.figure.suptitle("Shift-click on image for plot \n", fontsize=8)
202-
xy_axes.imshow(np.zeros((100, 100), np.uint8))
208+
xy_axes.imshow(np.zeros((100, 100), np.int16))
203209
xy_axes.scatter(50, 50, s=10, c="red", alpha=0.25)
204210
xy_axes.set_xlabel("x axis")
205211
xy_axes.set_ylabel("y axis")
206212
yz_axes = canvas.figure.add_subplot(3, 1, 2)
207-
yz_axes.imshow(np.zeros((100, 100), np.uint8))
213+
yz_axes.imshow(np.zeros((100, 100), np.int16))
208214
yz_axes.scatter(50, 50, s=10, c="red", alpha=0.25)
209215
yz_axes.set_xlabel("y axis")
210216
yz_axes.set_ylabel("z axis")
211217
zx_axes = canvas.figure.add_subplot(3, 1, 3)
212-
zx_axes.imshow(np.zeros((100, 100), np.uint8))
218+
zx_axes.imshow(np.zeros((100, 100), np.int16))
213219
zx_axes.scatter(50, 50, s=10, c="red", alpha=0.25)
214220
zx_axes.set_xlabel("x axis")
215221
zx_axes.set_ylabel("z axis")
@@ -232,12 +238,13 @@ def update_canvas_canvas(viewer, event):
232238
print(m_point)
233239

234240
crop_big = crop_img(
235-
[m_point[0], m_point[1], m_point[2]], viewer.layers[0]
241+
[m_point[0], m_point[1], m_point[2]],
242+
viewer.layers["volume"],
236243
)
237244

238-
xy_axes.imshow(crop_big[50], "gray")
239-
yz_axes.imshow(crop_big.transpose(1, 0, 2)[50], "gray")
240-
zx_axes.imshow(crop_big.transpose(2, 0, 1)[50], "gray")
245+
xy_axes.imshow(crop_big[50], cmap="inferno", vmin=200, vmax=2000)
246+
yz_axes.imshow(crop_big.transpose(1, 0, 2)[50], cmap="inferno", vmin=200, vmax=2000)
247+
zx_axes.imshow(crop_big.transpose(2, 0, 1)[50], cmap="inferno", vmin=200, vmax=2000)
241248
canvas.draw_idle()
242249
except Exception as e:
243250
print(e)
@@ -256,21 +263,36 @@ def update_button(axis_event):
256263
view1.dims.events.current_step.connect(update_button)
257264

258265
def crop_img(points, layer):
266+
267+
if zoom_factor != [1,1,1]:
268+
im = np.array(layer.data, dtype=np.int16)
269+
image = Zoom(
270+
zoom_factor,
271+
keep_size=False,
272+
padding_mode="empty",
273+
)(np.expand_dims(im, axis=0))
274+
image = image[0]
275+
# image = ndimage.zoom(layer.data, zoom_factor, mode="nearest") # cleaner but much slower...
276+
else :
277+
image = layer.data
278+
259279
min_vals = [x - 50 for x in points]
260280
max_vals = [x + 50 for x in points]
261281
yohaku_minus = [n if n < 0 else 0 for n in min_vals]
262282
yohaku_plus = [
263-
x - layer.data.shape[i] if layer.data.shape[i] < x else 0
283+
x - image.shape[i] if image.shape[i] < x else 0
264284
for i, x in enumerate(max_vals)
265285
]
266286
crop_slice = tuple(
267287
slice(np.maximum(0, n), x) for n, x in zip(min_vals, max_vals)
268288
)
289+
269290
if as_folder:
270-
crop_temp = layer.data[crop_slice].persist().compute()
291+
crop_temp = image[crop_slice].persist().compute()
271292
else:
293+
272294
crop_temp = layer.data[crop_slice]
273-
cropped_img = np.zeros((100, 100, 100), np.uint8)
295+
cropped_img = np.zeros((100, 100, 100), np.int16)
274296
cropped_img[
275297
-yohaku_minus[0] : 100 - yohaku_plus[0],
276298
-yohaku_minus[1] : 100 - yohaku_plus[1],

napari_cellseg3d/model_framework.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,6 @@ def __init__(self, viewer: "napari.viewer.Viewer"):
130130
self.btn_save_log.setVisible(False)
131131
#####################################################
132132

133-
def toggle_visibility(self, checkbox, widget):
134-
"""Toggles the visibility of a widget based on the status of a checkbox.
135-
136-
Args:
137-
checkbox: The QCheckbox that determines whether to show or not
138-
widget: The widget to hide or show
139-
"""
140-
widget.setVisible(checkbox.isChecked())
141-
142133
def send_log(self, text):
143134
"""Emit a signal to print in a Log"""
144135
self.log.print_and_log(text)
@@ -231,7 +222,7 @@ def display_status_report(self):
231222

232223
def toggle_weights_path(self):
233224
"""Toggle visibility of weight path"""
234-
self.toggle_visibility(
225+
ui.toggle_visibility(
235226
self.custom_weights_choice, self.weights_path_container
236227
)
237228

napari_cellseg3d/model_instance_seg.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,11 @@ def volume_stats(volume_image):
218218
def fill(lst, n=len(properties) - 1):
219219
return fill_list_in_between(lst, n, "")
220220

221+
if len(volume_image.flatten()) != 0:
222+
ratio = fill([np.sum(volume) / len(volume_image.flatten())])
223+
else:
224+
ratio = 0
225+
221226
return {
222227
"Volume": volume,
223228
"Centroid x": [region.centroid[0] for region in properties],
@@ -228,6 +233,6 @@ def fill(lst, n=len(properties) - 1):
228233
"Image size": fill([volume_image.shape]),
229234
"Total image volume": fill([len(volume_image.flatten())]),
230235
"Total object volume (pixels)": fill([np.sum(volume)]),
231-
"Filling ratio": fill([np.sum(volume) / len(volume_image.flatten())]),
236+
"Filling ratio": ratio,
232237
"Number objects": fill([len(properties)]),
233238
}

0 commit comments

Comments
 (0)