Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ __pycache__/
# unwanted results files
*.tif
napari_cellseg3d/_tests/res/*.csv
*.pth

# Distribution / packaging
.Python
Expand Down
12 changes: 11 additions & 1 deletion .napari/DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,19 @@ make sure to mention this.
If you know of researchers, groups or labs using your plugin, or if it has been cited
anywhere, feel free to also include this information here.
-->
<!--
## Quickstart

Install from pip with `pip install napari-cellseg3d`

OR

- Install napari from pip with `pip install "napari[all]"`,
then from the “Plugins” menu within the napari application, select “Install/Uninstall Package(s)...”
- Copy `napari-cellseg3d` and paste it where it says “Install by name/url…”
- Click “Install”
<!--


This section should go through step-by-step examples of how your plugin should be used.
Where your plugin provides multiple dock widgets or functions, you should split these
out into separate subsections for easy browsing. Include screenshots and videos
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ If you encounter any problems, please [file an issue] along with a detailed desc
To run tests locally:

- Locally : run ``pytest`` in the plugin folder
- Locally with coverage : In the plugin folder, run ``coverage run --source=src -m pytest`` then ``coverage.xml`` to generate a .xml coverage file.
- Locally with coverage : In the plugin folder, run ``coverage run --source=napari_cellseg3d -m pytest`` then ``coverage xml`` to generate a .xml coverage file.
- With tox : run ``tox`` in the plugin folder (will simulate tests with several python and OS configs, requires substantial storage space)

## Contributing
Expand Down
Binary file removed docs/res/logo/logo_alpha.png
Binary file not shown.
Binary file removed docs/res/logo/logo_background.png
Binary file not shown.
27 changes: 15 additions & 12 deletions napari_cellseg3d/_tests/test_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@

def test_launch_review(make_napari_viewer):

view = make_napari_viewer()
widget = rev.Reviewer(view)
view = make_napari_viewer()
widget = rev.Reviewer(view)

# widget.filetype_choice.setCurrentIndex(0)
# widget.filetype_choice.setCurrentIndex(0)

im_path = os.path.dirname(os.path.realpath(__file__)) + "/res/test.tif"
im_path = os.path.dirname(os.path.realpath(__file__)) + "/res/test.tif"

widget.image_path = im_path
widget.label_path = im_path
widget.image_path = im_path
widget.label_path = im_path

print(widget.image_path)
print(widget.label_path)
print(widget.as_folder)
print(widget.filetype)
widget.run_review()
widget._viewer.close()

assert widget._viewer is not None

print(widget.image_path)
print(widget.label_path)
print(widget.as_folder)
print(widget.filetype)
widget.run_review()

assert widget._viewer is not None
71 changes: 56 additions & 15 deletions napari_cellseg3d/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
BOTT_AL = Qt.AlignmentFlag.AlignBottom
"""Alias for Qt.AlignmentFlag.AlignBottom, to use in addWidget"""
###############

# colors
dark_red = "#72071d" # crimson red
default_cyan = "#8dd3c7" # turquoise cyan (default matplotlib line color under dark background context)
napari_grey = "#262930" # napari background color (grey)
Expand Down Expand Up @@ -95,7 +95,7 @@ def open_file_dialog(
return filenames


def make_label(name, parent=None):
def make_label(name, parent=None): # TODO update to child class
"""Creates a QLabel

Args:
Expand All @@ -113,7 +113,7 @@ def make_label(name, parent=None):

def make_scrollable(
contained_layout, containing_widget, min_wh=None, max_wh=None, base_wh=None
):
): # TODO convert to child class
"""Creates a QScrollArea and sets it up, then adds the contained_widget to it,
and finally adds the scroll area in a layout and sets it to the contaning_widget

Expand Down Expand Up @@ -163,7 +163,7 @@ def make_n_spinboxes(
parent=None,
double=False,
fixed=True,
) -> Union[list, QWidget]:
) -> Union[list, QWidget]: # TODO: child class if possible ?
"""

Args:
Expand Down Expand Up @@ -222,7 +222,7 @@ def add_to_group(title, widget, layout, L=7, T=20, R=7, B=11):
layout.addWidget(group)


def make_group(title, L=7, T=20, R=7, B=11, parent=None):
def make_group(title, L=7, T=20, R=7, B=11, parent=None): # TODO : child class
"""Creates a group widget and layout, with a header (`title`) and content margins for top/left/right/bottom `L, T, R, B` (in pixels)
Group widget and layout returned will have a Fixed size policy.

Expand All @@ -246,7 +246,9 @@ def make_group(title, L=7, T=20, R=7, B=11, parent=None):
return group, layout


def make_container(L=0, T=0, R=1, B=11, vertical=True, parent=None):
def make_container(
L=0, T=0, R=1, B=11, vertical=True, parent=None
): # TODO child class?
"""Creates a QWidget and a layout for the purpose of containing other modules, with a Fixed layout.

Args:
Expand Down Expand Up @@ -276,7 +278,7 @@ def make_container(L=0, T=0, R=1, B=11, vertical=True, parent=None):
return container_widget, container_layout


def make_button(
def make_button( # TODO child class
title: str = None,
func: callable = None,
parent: QWidget = None,
Expand Down Expand Up @@ -339,7 +341,7 @@ def __init__(
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)


def make_combobox(
def make_combobox( # TODO child class
entries=None,
parent: QWidget = None,
label: str = None,
Expand Down Expand Up @@ -405,7 +407,7 @@ def __init__(
self.toggled.connect(func)


def make_checkbox(
def make_checkbox( # TODO update calls to class
title: str = None,
func: callable = None,
parent: QWidget = None,
Expand Down Expand Up @@ -508,9 +510,9 @@ def __init__(self, parent, default_x=1, default_y=1, default_z=1):
self.box_widgets = make_n_spinboxes(
n=3, min=1.0, max=1000, default=1, step=0.5, double=True
)
self.box_widgets[0].setValue(default_x) # TODO change default
self.box_widgets[1].setValue(default_y) # TODO change default
self.box_widgets[2].setValue(default_z) # TODO change default
self.box_widgets[0].setValue(default_x)
self.box_widgets[1].setValue(default_y)
self.box_widgets[2].setValue(default_z)

for w in self.box_widgets:
w.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
Expand Down Expand Up @@ -547,9 +549,48 @@ def build(self):
add_widgets(self._layout, [self.checkbox, self.container])
self.setLayout(self._layout)

def get_anisotropy_factors(self):
"""Returns : the resolution in microns for each of the three dimensions"""
return [w.value() for w in self.box_widgets]
def get_anisotropy_resolution_xyz(self, as_factors=True):
"""
Args :
as_factors: if True, returns zoom factors, otherwise returns the input resolution

Returns : the resolution in microns for each of the three dimensions. ZYX order suitable for napari scale"""

resolution = [w.value() for w in self.box_widgets]
if as_factors:
return self.anisotropy_zoom_factor(resolution)

return resolution

def get_anisotropy_resolution_zyx(self, as_factors=True):
"""
Args :
as_factors: if True, returns zoom factors, otherwise returns the input resolution

Returns : the resolution in microns for each of the three dimensions. XYZ order suitable for MONAI"""
resolution = [w.value() for w in self.box_widgets]
if as_factors:
resolution = self.anisotropy_zoom_factor(resolution)

return [resolution[2], resolution[1], resolution[0]]

def anisotropy_zoom_factor(self, aniso_res):
"""Computes a zoom factor to correct anisotropy, based on anisotropy resolutions

Args:
resolutions: array for resolution (float) in microns for each axis

Returns: an array with the corresponding zoom factors for each axis (all values divided by min)

"""

base = min(aniso_res)
zoom_factors = [base / res for res in aniso_res]
return zoom_factors

def is_enabled(self):
"""Returns : whether anisotropy correction has been enabled or not"""
return self.checkbox.isChecked()


def open_url(url):
Expand Down
19 changes: 9 additions & 10 deletions napari_cellseg3d/launch_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from pathlib import Path

import matplotlib.pyplot as plt
import napari
import numpy as np
from magicgui import magicgui
from matplotlib.backends.backend_qt5agg import \
FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import (
FigureCanvasQTAgg as FigureCanvas,
)
from matplotlib.figure import Figure
from monai.transforms import Zoom
from qtpy.QtWidgets import QSizePolicy
Expand All @@ -17,7 +19,6 @@


def launch_review(
viewer,
original,
base,
raw,
Expand Down Expand Up @@ -48,7 +49,6 @@ def launch_review(
and determine whether it should be labeled or not.

Args:
viewer (napari.viewer.Viewer): The viewer the widgets are to be displayed in

original (dask.array.Array): The original images/volumes that have been labeled

Expand All @@ -68,12 +68,13 @@ def launch_review(

zoom_factor (array(int)): zoom factors for each axis


Returns : list of all docked widgets
"""
images_original = original
base_label = base

view1 = viewer
view1 = napari.Viewer()
viewer = view1 #TODO fix duplicate name

view1.scale_bar.visible = True

Expand Down Expand Up @@ -144,10 +145,6 @@ def launch_review(
# return labeled_c, labeled_sorted, nums
#
# worker = create_label()

layer = view1.layers[0]
layer1 = view1.layers[1]

# if not as_folder:
# r_path = os.path.dirname(r_path)

Expand Down Expand Up @@ -324,3 +321,5 @@ def crop_volume_around_point(points, layer):
-inferior_bound[2] : 100 - superior_bound[2],
] = crop_temp
return cropped_volume

return view1, [file_widget, canvas, dmg]
1 change: 1 addition & 0 deletions napari_cellseg3d/model_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import napari
import torch

# Qt
from qtpy.QtWidgets import QLineEdit
from qtpy.QtWidgets import QProgressBar
Expand Down
1 change: 1 addition & 0 deletions napari_cellseg3d/model_instance_seg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import print_function

import numpy as np

# from skimage.measure import marching_cubes
# from skimage.measure import mesh_surface_area
from skimage.measure import label
Expand Down
4 changes: 4 additions & 0 deletions napari_cellseg3d/model_workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import numpy as np
import torch

# MONAI
from monai.data import CacheDataset
from monai.data import DataLoader
Expand All @@ -29,14 +30,17 @@
from monai.transforms import SpatialPadd
from monai.transforms import Zoom
from monai.utils import set_determinism

# threads
from napari.qt.threading import GeneratorWorker
from napari.qt.threading import WorkerBaseSignals

# Qt
from qtpy.QtCore import Signal
from tifffile import imwrite

from napari_cellseg3d import utils

# local
from napari_cellseg3d.model_instance_seg import binary_connected
from napari_cellseg3d.model_instance_seg import binary_watershed
Expand Down
23 changes: 16 additions & 7 deletions napari_cellseg3d/plugin_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,27 @@ def update_default(self):
def remove_from_viewer(self):
"""Removes the widget from the napari window.
Can be re-implemented in children classes if needed"""
if len(self.docked_widgets) != 0:
[
self._viewer.window.remove_dock_widget(w)
for w in self.docked_widgets
if w is not None
]

self.remove_docked_widgets()

if self.parent is not None:
self.parent.remove_from_viewer()
self.parent.remove_from_viewer() # TODO keep this way ?
return
self._viewer.window.remove_dock_widget(self)

def remove_docked_widgets(self):
"""Removes all docked widgets from napari window"""
try:
if len(self.docked_widgets) != 0:
[
self._viewer.window.remove_dock_widget(w)
for w in self.docked_widgets
if w is not None
]
return True
except LookupError:
return False


class BasePluginFolder(QTabWidget):
"""A basic plugin template for working with **folders of images**"""
Expand Down
Loading