Giter VIP home page Giter VIP logo

napari-segment-blobs-and-things-with-membranes's Introduction

napari-segment-blobs-and-things-with-membranes (nsbatwm)

License PyPI Python Version tests codecov Development Status napari hub DOI

This napari-plugin is based on scikit-image and allows segmenting nuclei and cells based on fluorescence microscopy images with high intensity in nuclei and/or membranes.

Usage

This plugin populates image processing operations to the Tools menu in napari. You can recognize them with their suffix (nsbatwm) in brackets. Furthermore, it can be used from the napari-assistant graphical user interface. Therefore, just click the menu Tools > Utilities > Assistant (na) or run naparia from the command line.

img.png

You can also call these functions as shown in the demo notebook.

Voronoi-Otsu-Labeling

This algorithm uses Otsu's thresholding method in combination with Gaussian blur and a Voronoi-Tesselation approach to label bright objects such as nuclei in an intensity image. The alogrithm has two sigma parameters which allow you to fine-tune where objects should be cut (spot_sigma) and how smooth outlines should be (outline_sigma). This implementation aims to be similar to Voronoi-Otsu-Labeling in clesperanto.

img.png

Seeded Watershed

Starting from an image showing high-intensity membranes and a seed-image where objects have been labeled (e.g. using Voronoi-Otsu-Labeling), objects are labeled that are constrained by the membranes.

img.png

Seeded Watershed with mask

If there is additionally a mask image available, one can use the Seeded Watershed with mask, to constraint the flooding on a membrane image (1), starting from nuclei (2), limited by a mask image (3) to produce a cell segmentation within the mask (4).

img.png

Seeded Watershed using local minima as starting points

Similar to the Seeded Watershed and Voronoi-Otsu-Labeling explained above, you can use this tool to segment an image showing membranes without an additional image showing nuclei. The two sigma parameters allow to fine tune how close objects can be and how precise their boundaries are detected.

img.png

Gaussian blur

Applies a Gaussian blur to an image. This might be useful for denoising, e.g. before applying the Threshold-Otsu method.

img.png

Subtract background

Subtracts background using scikit-image's rolling-ball algorithm. This might be useful, for example to make intensity of membranes more similar in different regions of an image.

img.png

Threshold Otsu

Binarizes an image using scikit-image's threshold Otsu algorithm, also known as Otsu's method.

img.png

Split touching objects (formerly known as binary watershed).

In case objects stick together after thresholding, this tool might help. It aims to deliver similar results as ImageJ's watershed implementation.

img.png

Connected component labeling

Takes a binary image and produces a label image with all separated objects labeled differently. Under the hood, it uses scikit-image's label function.

img.png

Manual split and merge labels

Split and merge labels in napari manually via the Tools > Utilities menu:


This napari plugin was generated with Cookiecutter using with @napari's cookiecutter-napari-plugin template.

Installation

This plugin is part of devbio-napari. To install it, please follow its installation instructions.

Contributing

Contributions are very welcome. Tests can be run with tox, please ensure the coverage at least stays the same before you submit a pull request.

License

Distributed under the terms of the BSD-3 license, "napari-segment-blobs-and-things-with-membranes" is free and open source software

Issues

If you encounter any problems, please create a thread on image.sc along with a detailed description and tag @haesleinhuepf.

napari-segment-blobs-and-things-with-membranes's People

Contributors

dragadoncila avatar haesleinhuepf avatar k-meech avatar lazigu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

napari-segment-blobs-and-things-with-membranes's Issues

cannot import on a headless system

importing napari_segment_blobs_and_things_with_membranes on a headless system (the TUD cluster)
fails with errors about missing GL libraries. I assume this means that this plugin needs a working display?

is there a workaround to make theis plugins work without gl support?

Cheers,

Till

`napari_segment_blobs_and_things_with_membranes` import errors ---------------------------------------------------------------------------

OSError Traceback (most recent call last)
Input In [5], in <cell line: 6>()
4 import matplotlib.pyplot as plt
5 import numpy as np
----> 6 import napari_segment_blobs_and_things_with_membranes as nsbatwm

File /app/env/lib/python3.9/site-packages/napari_segment_blobs_and_things_with_membranes/init.py:5, in
2 version = "0.3.1"
3 common_alias = "nsbatwm"
----> 5 from napari.types import ImageData, LabelsData
7 from napari_plugin_engine import napari_hook_implementation
9 import numpy as np

File /app/env/lib/python3.9/site-packages/napari/types.py:186, in
176 if sys.version_info >= (3, 9):
177 register_type(
178 Future[data_type], # type: ignore
179 choices=_mgui.get_layers_data,
(...)
182 ),
183 )
--> 186 _register_types_with_magicgui()

File /app/env/lib/python3.9/site-packages/napari/types.py:157, in _register_types_with_magicgui()
153 from concurrent.futures import Future
155 from magicgui import register_type
--> 157 from . import layers
158 from .utils import _magicgui as _mgui
160 for _type in (LayerDataTuple, List[LayerDataTuple]):

File :1055, in handle_fromlist(module, fromlist, import, recursive)

File /app/env/lib/python3.9/site-packages/napari/_lazy.py:45, in install_lazy..getattr(name)
42 from scipy import stats # noqa: F401
44 if name in submodules:
---> 45 return import_module(f'{module_name}.{name}')
46 elif name in attr_to_modules:
47 submod = import_module(f'{module_name}.{attr_to_modules[name]}')

File /app/env/lib/python3.9/importlib/init.py:127, in import_module(name, package)
125 break
126 level += 1
--> 127 return _bootstrap._gcd_import(name[level:], package, level)

File /app/env/lib/python3.9/site-packages/napari/layers/init.py:11, in
8 from importlib import import_module as _imp
10 from ..utils.misc import all_subclasses as _all_subcls
---> 11 from .base import Layer
12 from .image import Image
13 from .labels import Labels

File /app/env/lib/python3.9/site-packages/napari/layers/base/init.py:1, in
----> 1 from .base import Layer, no_op

File /app/env/lib/python3.9/site-packages/napari/layers/base/base.py:15, in
12 import numpy as np
14 from ...utils._dask_utils import configure_dask
---> 15 from ...utils._magicgui import add_layer_to_viewer, get_layers
16 from ...utils.events import EmitterGroup, Event
17 from ...utils.events.event import WarningEmitter

File /app/env/lib/python3.9/site-packages/napari/utils/init.py:2, in
1 from ._dask_utils import resize_dask_cache
----> 2 from .colormaps import Colormap
3 from .info import citation_text, sys_info
4 from .notebook_display import nbscreenshot

File /app/env/lib/python3.9/site-packages/napari/utils/colormaps/init.py:2, in
1 from .colorbars import make_colorbar
----> 2 from .colormap import Colormap
3 from .colormap_utils import (
4 ALL_COLORMAPS,
5 AVAILABLE_COLORMAPS,
(...)
16 matplotlib_colormaps,
17 )

File /app/env/lib/python3.9/site-packages/napari/utils/colormaps/colormap.py:11, in
9 from ..translations import trans
10 from .colorbars import make_colorbar
---> 11 from .standardize_color import transform_color
14 class ColormapInterpolationMode(str, Enum):
15 """INTERPOLATION: Interpolation mode for colormaps.
16
17 Selects an interpolation mode for the colormap.
(...)
21 bin between by neighboring controls points.
22 """

File /app/env/lib/python3.9/site-packages/napari/utils/colormaps/standardize_color.py:27, in
24 from typing import Any, Callable, Dict, Sequence
26 import numpy as np
---> 27 from vispy.color import ColorArray, get_color_dict, get_color_names
28 from vispy.color.color_array import _string_to_rgb
30 from ..translations import trans

File /app/env/lib/python3.9/site-packages/vispy/color/init.py:12, in
10 from ._color_dict import get_color_names, get_color_dict # noqa
11 from .color_array import Color, ColorArray
---> 12 from .colormap import (Colormap, BaseColormap, # noqa
13 get_colormap, get_colormaps) # noqa
15 all = ['Color', 'ColorArray', 'Colormap', 'BaseColormap',
16 'get_colormap', 'get_colormaps',
17 'get_color_names', 'get_color_dict']

File /app/env/lib/python3.9/site-packages/vispy/color/colormap.py:14, in
12 from hsluv import hsluv_to_rgb
13 from ..util.check_environment import has_matplotlib
---> 14 import vispy.gloo
16 ###############################################################################
17 # Color maps
18
19 # Length of the texture map used for luminance to RGBA conversion
20 LUT_len = 1024

File /app/env/lib/python3.9/site-packages/vispy/gloo/init.py:47, in
5 """
6 Object oriented interface to OpenGL.
7
(...)
42
43 """
45 from future import division
---> 47 from . import gl # noqa
48 from .wrappers import * # noqa
49 from .context import (GLContext, get_default_config, # noqa
50 get_current_canvas) # noqa

File /app/env/lib/python3.9/site-packages/vispy/gloo/gl/init.py:230, in
228 from . import gl2 as default_backend # noqa
229 if default_backend._lib is None: # Probably Android or RPi
--> 230 from . import es2 as default_backend # noqa
233 # Call use to start using our default backend
234 use_gl()

File /app/env/lib/python3.9/site-packages/vispy/gloo/gl/es2.py:48, in
46 # Else, we failed and exit
47 if es2_file is None:
---> 48 raise OSError('GL ES 2.0 library not found')
49 # Load it
50 _lib = ctypes.CDLL(es2_file)

OSError: GL ES 2.0 library not found

[headless mode]: permission issue in `register_theme` upon import

Hey Robert,

[This issue may actually belong to a different package, but I couldn't trace it back and it triggers for us when using nsbatwm. It does not trigger when we use e.g. napari-skimage-regionprops as part of a napari workflow]

We're using nsbatwm as part of the Fractal workflows on a slurm cluster. As part of this setup, the python environments are owned by a service user, but used by the actual users running some jobs. The way we set it up, users can read the python environment, but they don't have write access to it.

That can become an issue when napari needs to use cache files it would normally put in e.g. a hidden folder in the python virtualenv directory. We can mostly work around this by setting things like NAPARI_CONFIG=${HOME}/.cache/NAPARI_CACHE_DIR for each user (=> napari would use that cache for some things, not something in the virtual environment).

This had worked last summer, but stopped working now for the nsbatwm plugin. It's not a version question of nsbatwm, as it fails with version 0.3.3 and 0.3.4. We rarely use nsbatwm in our napari workflows at the moment, so I haven't manage to trace back the time it stopped working yet.

Some theme imports appear to get triggered and they aren't collected from the $NAPARI_CONFIG folder, but instead in the home folder of the user that created the virtual environment (e.g. /data/homes/fractal/.cache/napari/). Our users don't have access there, thus importing a workflow with nsbatwm now fails.

Here's the full traceback:
Traceback (most recent call last):
  File "/net/nfs4/pelkmanslab-fileserver-common/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/fractal_tasks_core/napari_workflows_wrapper.py", line 604, in <module>
    run_fractal_task(
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/fractal_tasks_core/_utils.py", line 104, in run_fractal_task
    metadata_update = task_function(**task_args.dict())
  File "/net/nfs4/pelkmanslab-fileserver-common/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/fractal_tasks_core/napari_workflows_wrapper.py", line 115, in napari_workflows_wrapper
    wf: napari_workflows.Worfklow = load_workflow(workflow_file)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari_workflows/_io_yaml_v1.py", line 36, in load_workflow
    return unsafe_load(stream)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/__init__.py", line 145, in unsafe_load
    return load(stream, UnsafeLoader)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/__init__.py", line 81, in load
    return loader.get_single_data()
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 51, in get_single_data
    return self.construct_document(node)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 60, in construct_document
    for dummy in generator:
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 413, in construct_yaml_map
    value = self.construct_mapping(node)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 218, in construct_mapping
    return super().construct_mapping(node, deep=deep)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 143, in construct_mapping
    value = self.construct_object(value_node, deep=deep)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 100, in construct_object
    data = constructor(self, node)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 523, in construct_python_tuple
    return tuple(self.construct_sequence(node))
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 129, in construct_sequence
    return [self.construct_object(child, deep=deep)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 129, in <listcomp>
    return [self.construct_object(child, deep=deep)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 102, in construct_object
    data = constructor(self, tag_suffix, node)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 570, in construct_python_name
    return self.find_python_name(suffix, node.start_mark)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 719, in find_python_name
    return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/yaml/constructor.py", line 551, in find_python_name
    __import__(module_name)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari_segment_blobs_and_things_with_membranes/__init__.py", line 23, in <module>
    from napari_time_slicer import time_slicer
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari_time_slicer/__init__.py", line 4, in <module>
    from ._function import napari_experimental_provide_function
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari_time_slicer/_function.py", line 7, in <module>
    from napari.layers import Image, Labels, Layer
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/layers/__init__.py", line 11, in <module>
    from .base import Layer
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/layers/base/__init__.py", line 1, in <module>
    from .base import Layer, no_op
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/layers/base/base.py", line 28, in <module>
    from ...utils.key_bindings import KeymapProvider
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/utils/key_bindings.py", line 46, in <module>
    from ..settings import get_settings
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/settings/__init__.py", line 5, in <module>
    from ._base import _NOT_SET
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/settings/_base.py", line 18, in <module>
    from ._yaml import PydanticYamlMixin
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/settings/_yaml.py", line 9, in <module>
    from ._fields import Version
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/settings/_fields.py", line 6, in <module>
    from ..utils.theme import available_themes
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/utils/theme.py", line 320, in <module>
    register_theme('dark', DARK)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/utils/theme.py", line 252, in register_theme
    build_theme_svgs(name)
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/resources/_icons.py", line 163, in build_theme_svgs
    write_colorized_svgs(
  File "/data/homes/fractal/joel/fractal_v1/fractal-demos/examples/server/FRACTAL_TASKS_DIR/.fractal/fractal-tasks-core0.7.2/venv/lib/python3.8/site-packages/napari/resources/_icons.py", line 145, in write_colorized_svgs
    dest.mkdir(parents=True, exist_ok=True)
  File "/data/homes/fractal/.conda/envs/fractal-server-1.0.7/lib/python3.8/pathlib.py", line 1297, in mkdir
    if not exist_ok or not self.is_dir():
  File "/data/homes/fractal/.conda/envs/fractal-server-1.0.7/lib/python3.8/pathlib.py", line 1422, in is_dir
    return S_ISDIR(self.stat().st_mode)
  File "/data/homes/fractal/.conda/envs/fractal-server-1.0.7/lib/python3.8/pathlib.py", line 1198, in stat
    return self._accessor.stat(self)
PermissionError: [Errno 13] Permission denied: '/data/homes/fractal/.cache/napari/0.4.17/_themes/dark'

Do you know whether the way this cache folder for the theme gets picked is something set by nsbatwm (unlikely, given that it worked with version 0.3.3 before and now breaks), something in the napari workflows import mode of nsbatwm or by core napari? Are you maybe aware of an environment variable that can be set to determine which cache folder is used to store themes? If we can set it similar to the NAPARI_CONFIG variable, we can avoid this issue.
I looked around a bit in the napari code, but the place where NAPARI_CONFIG comes up (e.g. here, no other path configs are set.

I suspect there is a combination of the main napari version and the versions of napari workflows and its dependencies that would make this work again. But would be great to get this working again in the current release version :)

The relevant versions of packages are:

napari==0.4.17
napari-assistant==0.4.4
napari-console==0.0.7
napari-plugin-engine==0.2.0
napari-segment-blobs-and-things-with-membranes==0.3.3
napari-skimage-regionprops==0.8.1
napari-svg==0.1.6
napari-time-slicer==0.4.9
napari-tools-menu==0.1.19
napari-workflows==0.2.8

Also tested with napari-segment-blobs-and-things-with-membranes==0.3.4, same issue there.
As mentioned above, it's not a general things with using napari workflows. We use napari-skimage-regionprops quite heavily in some workflows and have not had this issue with it.

Use disc/sphere footprint in all filters

As discovered in #33 our filters (maximum, median, etc) currently use a square-footprint (np.ones), even though a disc/sphere would be biologically more useful. We should update all our filters.

Backwards compatibility

To avoid breaking backwards-compatibility on code-level, we could introduce new functions, e.g. median_filter2, which uses the disc-footprint. The old median_filter function will become deprecated. We add the new function to the menu, and remove the old from the menu. In this way, formerly written workflows produce consistent results, while newly setup workflows will use the new filters. A deprecation-warning can warn users of the old workflows. I also tend to never remove deprecated functions, unless absolutely necessary, aiming for long-term backwards compatibility.

`StackViewNDArray` breaks downstream functions

Hi @haesleinhuepf ,

I just wanted to report an issue I ran into earlier.

This does not work:

from skimage import data, measure
import napari_segment_blobs_and_things_with_membranes as nsbatwm
import pandas as pd

image = data.human_mitosis()
labels = nsbatwm.voronoi_otsu_labeling(image, spot_sigma=5)
measurements = pd.DataFrame(measure.regionprops_table(labels, intensity_image=image, properties=['label', 'area']))

---------------------------------------------------------------------------
{
	"name": "IndexError",
	"message": "tuple index out of range",
	"stack": "---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[2], line 8
      6 image = data.human_mitosis()
      7 labels = nsbatwm.voronoi_otsu_labeling(image, spot_sigma=5)
----> 8 measurements = pd.DataFrame(measure.regionprops_table(labels, intensity_image=image, properties=['label', 'area']))

File c:\\Users\\johamuel\\AppData\\Local\\mambaforge\\envs\\clusters-plotter-dev\\lib\\site-packages\\skimage\\measure\\_regionprops.py:1043, in regionprops_table(label_image, intensity_image, properties, cache, separator, extra_properties, spacing)
   1039     out_d = _props_to_dict(regions, properties=properties,
   1040                            separator=separator)
   1041     return {k: v[:0] for k, v in out_d.items()}
-> 1043 return _props_to_dict(
   1044     regions, properties=properties, separator=separator
   1045 )

File c:\\Users\\johamuel\\AppData\\Local\\mambaforge\\envs\\clusters-plotter-dev\\lib\\site-packages\\skimage\\measure\\_regionprops.py:865, in _props_to_dict(regions, properties, separator)
    861 for ind in np.ndindex(np.shape(rp)):
    862     modified_props.append(
    863         separator.join(map(str, (orig_prop,) + ind))
    864     )
--> 865     locs.append(ind if len(ind) > 1 else ind[0])
    867 # fill temporary column data_array
    868 n_columns = len(locs)

IndexError: tuple index out of range"
}

This works:

from skimage import data, measure
import napari_segment_blobs_and_things_with_membranes as nsbatwm
import pandas as pd

image = data.human_mitosis()
labels = np.asarray(nsbatwm.voronoi_otsu_labeling(image, spot_sigma=5))
measurements = pd.DataFrame(measure.regionprops_table(labels, intensity_image=image, properties=['label', 'area']))

The only difference is probably just that in the above case, nsbatwm returns a StackViewNDArray, which is probably inherited from a regular numpy array. Still, it seems to miss some properties for regionprops to throw an error.

I'm not sure whether it's a bug or a feature or whether you think that this would belong here or in the stackview repo. I can totally move it to stackview if you feel that's where it belongs.

Versions:

import skimage
skimage.__version__

>>> 0.24.0

nsbatwm.__version__
>>> 0.3.8

edit: Added traceback

napari_time_slicer not listed in dependecies in setup.cfg


~/.pyenv/versions/3.8.3/envs/napari/lib/python3.8/site-packages/napari_segment_blobs_and_things_with_membranes/_function.py in 
     22 import napari
---> 23 from napari_time_slicer import time_slicer
        global napari_time_slicer = undefined
        global time_slicer = undefined
     24 

ModuleNotFoundError: No module named 'napari_time_slicer'

mode-filter

would be cool to have the mode-filter (a.k.a. most-popular), for label images

Support for MultScalaData (e.g. OME-Zarr)

Hey @haesleinhuepf

Very useful collection of image processing functions in this plugin!

I've been trying to use it with OME-Zarr files (using the awesome napari-ome-zarr plugin to read them lazily into napari). This creates a MultiScaleData image object:

In [1]: viewer.layers[0].data
Out[1]: <MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>

When I try to run different functions of the napari-segment-blobs-and-things-with-membranes on this, I run into some issues:

1) Functions that don't support MultiScaleData

For example, running a gaussian blur returns an AttributeError: 'MultiScaleData' object has no attribute 'ndim'

Full stack trace
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/magicgui/widgets/_bases/value_widget.py:57, in ValueWidget._on_value_change(self=PushButton(value=False, annotation=None, name='call_button'), value=False)
     55 if value is self.null_value and not self._nullable:
     56     return
---> 57 self.changed.emit(value)
        value = False
        self.changed = <SignalInstance 'changed' on PushButton(value=False, annotation=None, name='call_button')>
        self = PushButton(value=False, annotation=None, name='call_button')

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/psygnal/_signal.py:725, in psygnal._signal.SignalInstance.emit()

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/psygnal/_signal.py:767, in psygnal._signal.SignalInstance._run_emit_loop()

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/psygnal/_signal.py:768, in psygnal._signal.SignalInstance._run_emit_loop()

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/psygnal/_signal.py:788, in psygnal._signal.SignalInstance._run_emit_loop()

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/magicgui/widgets/_function_gui.py:207, in FunctionGui.__init__.<locals>._disable_button_and_call()
    205 self._call_button.enabled = False
    206 try:
--> 207     self.__call__()
        self = <FunctionGui gaussian_blur(image: <function NewType.<locals>.new_type at 0x1528baf70> = <MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, sigma: float = 2.0, *, viewer: napari.viewer.Viewer = Viewer(axes=Axes(visible=False, labels=True, colored=True, dashed=False, arrows=True), camera=Camera(center=(0.0, 2159.5, 2559.5), zoom=0.154453432405846, angles=(0.0, 0.0, 90.0), perspective=0.0, interactive=True), cursor=Cursor(position=(0.0, -815.5067337861703, 4537.442632718694), scaled=True, size=40, style=<CursorStyle.STANDARD: 'standard'>), dims=Dims(ndim=3, ndisplay=2, last_used=0, range=((0.0, 1.0, 1.0), (-1.5, 4320.0, 1.0), (-1.5, 5120.0, 1.0)), current_step=(0, 2160, 2560), order=(0, 1, 2), axis_labels=('0', '1', '2')), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[<Image layer 'DAPI' at 0x29fef9d30>, <Image layer 'nanog' at 0x29ff45100>, <Image layer 'Lamin B1' at 0x2a99c8220>, <Labels layer 'label_DAPI' at 0x29fef9f70>], scale_bar=ScaleBar(visible=False, colored=False, ticks=True, position=<Position.BOTTOM_RIGHT: 'bottom_right'>, font_size=10, unit=None), text_overlay=TextOverlay(visible=False, color=(0.5, 0.5, 0.5, 1.0), font_size=10, position=<TextOverlayPosition.TOP_LEFT: 'top_left'>, text=''), overlays=Overlays(interaction_box=InteractionBox(points=None, show=False, show_handle=False, show_vertices=False, selection_box_drag=None, selection_box_final=None, transform_start=<napari.utils.transforms.transforms.Affine object at 0x15519d970>, transform_drag=<napari.utils.transforms.transforms.Affine object at 0x15519d9d0>, transform_final=<napari.utils.transforms.transforms.Affine object at 0x15519da30>, transform=<napari.utils.transforms.transforms.Affine object at 0x15519da90>, allow_new_selection=True, selected_vertex=None)), help='enter paint or fill mode to edit labels', status='label_DAPI [   0 -816 4537]', tooltip=Tooltip(visible=False, text=''), theme='dark', title='napari', mouse_move_callbacks=[<function InteractionBoxMouseBindings.initialize_mouse_events.<locals>.mouse_move at 0x28cce8430>], mouse_drag_callbacks=[<function InteractionBoxMouseBindings.initialize_mouse_events.<locals>.mouse_drag at 0x28ccd38b0>], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[<function dims_scroll at 0x152b61040>], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, keymap={'Shift': <function InteractionBoxMouseBindings.initialize_key_events.<locals>.hold_to_lock_aspect_ratio at 0x28ccd3700>, 'Control-Shift-R': <function InteractionBoxMouseBindings._reset_active_layer_affine at 0x28cd111f0>, 'Control-Shift-A': <function InteractionBoxMouseBindings._transform_active_layer at 0x28cd114c0>})) -> <function NewType.<locals>.new_type at 0x1528baf70>>
    208 finally:
    209     self._call_button.enabled = True

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/magicgui/widgets/_function_gui.py:318, in FunctionGui.__call__(self=<FunctionGui gaussian_blur(image: <function NewT...14c0>})) -> <function NewType.<locals>.new_type>>, update_widget=False, *args=(), **kwargs={})
    316 self._tqdm_depth = 0  # reset the tqdm stack count
    317 with _function_name_pointing_to_widget(self):
--> 318     value = self._function(*bound.args, **bound.kwargs)
        self = <FunctionGui gaussian_blur(image: <function NewType.<locals>.new_type at 0x1528baf70> = <MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, sigma: float = 2.0, *, viewer: napari.viewer.Viewer = Viewer(axes=Axes(visible=False, labels=True, colored=True, dashed=False, arrows=True), camera=Camera(center=(0.0, 2159.5, 2559.5), zoom=0.154453432405846, angles=(0.0, 0.0, 90.0), perspective=0.0, interactive=True), cursor=Cursor(position=(0.0, -815.5067337861703, 4537.442632718694), scaled=True, size=40, style=<CursorStyle.STANDARD: 'standard'>), dims=Dims(ndim=3, ndisplay=2, last_used=0, range=((0.0, 1.0, 1.0), (-1.5, 4320.0, 1.0), (-1.5, 5120.0, 1.0)), current_step=(0, 2160, 2560), order=(0, 1, 2), axis_labels=('0', '1', '2')), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[<Image layer 'DAPI' at 0x29fef9d30>, <Image layer 'nanog' at 0x29ff45100>, <Image layer 'Lamin B1' at 0x2a99c8220>, <Labels layer 'label_DAPI' at 0x29fef9f70>], scale_bar=ScaleBar(visible=False, colored=False, ticks=True, position=<Position.BOTTOM_RIGHT: 'bottom_right'>, font_size=10, unit=None), text_overlay=TextOverlay(visible=False, color=(0.5, 0.5, 0.5, 1.0), font_size=10, position=<TextOverlayPosition.TOP_LEFT: 'top_left'>, text=''), overlays=Overlays(interaction_box=InteractionBox(points=None, show=False, show_handle=False, show_vertices=False, selection_box_drag=None, selection_box_final=None, transform_start=<napari.utils.transforms.transforms.Affine object at 0x15519d970>, transform_drag=<napari.utils.transforms.transforms.Affine object at 0x15519d9d0>, transform_final=<napari.utils.transforms.transforms.Affine object at 0x15519da30>, transform=<napari.utils.transforms.transforms.Affine object at 0x15519da90>, allow_new_selection=True, selected_vertex=None)), help='enter paint or fill mode to edit labels', status='label_DAPI [   0 -816 4537]', tooltip=Tooltip(visible=False, text=''), theme='dark', title='napari', mouse_move_callbacks=[<function InteractionBoxMouseBindings.initialize_mouse_events.<locals>.mouse_move at 0x28cce8430>], mouse_drag_callbacks=[<function InteractionBoxMouseBindings.initialize_mouse_events.<locals>.mouse_drag at 0x28ccd38b0>], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[<function dims_scroll at 0x152b61040>], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, keymap={'Shift': <function InteractionBoxMouseBindings.initialize_key_events.<locals>.hold_to_lock_aspect_ratio at 0x28ccd3700>, 'Control-Shift-R': <function InteractionBoxMouseBindings._reset_active_layer_affine at 0x28cd111f0>, 'Control-Shift-A': <function InteractionBoxMouseBindings._transform_active_layer at 0x28cd114c0>})) -> <function NewType.<locals>.new_type at 0x1528baf70>>
        bound = <BoundArguments (image=<MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, sigma=2.0, viewer=Viewer(axes=Axes(visible=False, labels=True, colored=True, dashed=False, arrows=True), camera=Camera(center=(0.0, 2159.5, 2559.5), zoom=0.154453432405846, angles=(0.0, 0.0, 90.0), perspective=0.0, interactive=True), cursor=Cursor(position=(0.0, -815.5067337861703, 4537.442632718694), scaled=True, size=40, style=<CursorStyle.STANDARD: 'standard'>), dims=Dims(ndim=3, ndisplay=2, last_used=0, range=((0.0, 1.0, 1.0), (-1.5, 4320.0, 1.0), (-1.5, 5120.0, 1.0)), current_step=(0, 2160, 2560), order=(0, 1, 2), axis_labels=('0', '1', '2')), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[<Image layer 'DAPI' at 0x29fef9d30>, <Image layer 'nanog' at 0x29ff45100>, <Image layer 'Lamin B1' at 0x2a99c8220>, <Labels layer 'label_DAPI' at 0x29fef9f70>], scale_bar=ScaleBar(visible=False, colored=False, ticks=True, position=<Position.BOTTOM_RIGHT: 'bottom_right'>, font_size=10, unit=None), text_overlay=TextOverlay(visible=False, color=(0.5, 0.5, 0.5, 1.0), font_size=10, position=<TextOverlayPosition.TOP_LEFT: 'top_left'>, text=''), overlays=Overlays(interaction_box=InteractionBox(points=None, show=False, show_handle=False, show_vertices=False, selection_box_drag=None, selection_box_final=None, transform_start=<napari.utils.transforms.transforms.Affine object at 0x15519d970>, transform_drag=<napari.utils.transforms.transforms.Affine object at 0x15519d9d0>, transform_final=<napari.utils.transforms.transforms.Affine object at 0x15519da30>, transform=<napari.utils.transforms.transforms.Affine object at 0x15519da90>, allow_new_selection=True, selected_vertex=None)), help='enter paint or fill mode to edit labels', status='label_DAPI [   0 -816 4537]', tooltip=Tooltip(visible=False, text=''), theme='dark', title='napari', mouse_move_callbacks=[<function InteractionBoxMouseBindings.initialize_mouse_events.<locals>.mouse_move at 0x28cce8430>], mouse_drag_callbacks=[<function InteractionBoxMouseBindings.initialize_mouse_events.<locals>.mouse_drag at 0x28ccd38b0>], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[<function dims_scroll at 0x152b61040>], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, keymap={'Shift': <function InteractionBoxMouseBindings.initialize_key_events.<locals>.hold_to_lock_aspect_ratio at 0x28ccd3700>, 'Control-Shift-R': <function InteractionBoxMouseBindings._reset_active_layer_affine at 0x28cd111f0>, 'Control-Shift-A': <function InteractionBoxMouseBindings._transform_active_layer at 0x28cd114c0>}))>
        self._function = <function gaussian_blur at 0x28ff945e0>
    320 self._call_count += 1
    321 if self._result_widget is not None:

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/napari_time_slicer/__init__.py:61, in time_slicer.<locals>.worker_function(*args=[<MultiScaleData at 0x29ff03d00. 5 levels, 'uint1..., (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, 2.0], **kwargs={})
     58         _break_down_4d_to_2d_kwargs(bound.arguments, current_timepoint, viewer)
     60 # call the decorated function
---> 61 result = function(*bound.args, **bound.kwargs)
        bound = <BoundArguments (image=<MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, sigma=2.0)>
        function = <function gaussian_blur at 0x28ff94550>
     62 return result

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/napari_segment_blobs_and_things_with_membranes/__init__.py:283, in gaussian_blur(image=<MultiScaleData at 0x29ff03d00. 5 levels, 'uint1..., (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, sigma=2.0)
    277 @register_function(menu="Filtering / noise removal > Gaussian (scikit-image, nsbatwm)")
    278 @time_slicer
    279 def gaussian_blur(image:ImageData, sigma: float = 1) -> ImageData:
    280     """
    281     Applies a Gaussian blur to an image with a defined sigma. Useful for denoising.
    282     """
--> 283     return gaussian(image, sigma)
        image = <MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>
        sigma = 2.0

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/skimage/_shared/utils.py:348, in deprecate_multichannel_kwarg.__call__.<locals>.fixed_func(*args=(<MultiScaleData at 0x29ff03d00. 5 levels, 'uint1..., (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, 2.0), **kwargs={})
    345     kwargs['channel_axis'] = convert[kwargs.pop('multichannel')]
    347 # Call the function with the fixed arguments
--> 348 return func(*args, **kwargs)
        func = <function gaussian at 0x28f092b80>
        kwargs = {}
        args = (<MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, 2.0)

File ~/opt/miniconda3/envs/napari-assistant/lib/python3.9/site-packages/skimage/_shared/filters.py:116, in gaussian(image=<MultiScaleData at 0x29ff03d00. 5 levels, 'uint1..., (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>, sigma=2.0, output=None, mode='nearest', cval=0, multichannel=None, preserve_range=False, truncate=4.0, channel_axis=None)
     16 @utils.deprecate_multichannel_kwarg(multichannel_position=5)
     17 def gaussian(image, sigma=1, output=None, mode='nearest', cval=0,
     18              multichannel=None, preserve_range=False, truncate=4.0, *,
     19              channel_axis=None):
     20     """Multi-dimensional Gaussian filter.
     21 
     22     Parameters
   (...)
    114 
    115     """
--> 116     if image.ndim == 3 and image.shape[-1] == 3 and channel_axis is None:
        image = <MultiScaleData at 0x29ff03d00. 5 levels, 'uint16', shapes: ((1, 4320, 5120), (1, 2160, 2560), (1, 1080, 1280), (1, 540, 640), (1, 270, 320))>
        channel_axis is None = True
        channel_axis = None
    117         msg = ("Images with dimensions (M, N, 3) are interpreted as 2D+RGB "
    118                "by default. Use `multichannel=False` to interpret as "
    119                "3D image with last dimension of length 3.")
    120         warn(RuntimeWarning(msg))

AttributeError: 'MultiScaleData' object has no attribute 'ndim'

2) Functions that run, but ignore the scales

For example, applying a threshold_mean runs, but returns a tiny output image in the top left corner. It looks like it just ran on the top level, because when zooming in, the thresholding is a very coarse threshold of the full image.

Example images

Overview (with threshold result in top left corner)
Screenshot 2022-07-26 at 16 33 41

Zoom in to top left corner:
Screenshot 2022-07-26 at 16 33 50

Thus my broader question: Is support for MultiScaleData in scope for this plugin? May require extra work for different functions and, especially when looking at large example like whole plates, it wouldn't be feasible to always run at full resolution. So could add complexity as in "What resolution is run?" and "What region do I want to run?"
There are interesting approaches to this, e.g. in StarDist: https://github.com/stardist/stardist-napari. StarDist has the ability to run on current field of view only ("Predict on field of view (only for 2D models in 2D view)"). But even that is limited to 2D data processing only. So I'm not sure whether the complexity of handling what part of the image needs to be processed should be handled by the processing plugins (would be neat, but potentially also quite complex) or whether we should just create a plugin for loading regions of interest from an OME-Zarr into memory (as a numpy array, not a MultiScaleData pyramid). Curious about your opinion on the topic @haesleinhuepf

error running Manually merge labels/split labels

Environment:
napari 0.4.15
Plugins:

  • clEsperanto: 0.16.0
  • console: 0.0.4
  • napari-assistant: 0.1.6
  • napari-segment-blobs-and-things-with-membranes: 0.2.20
  • napari-time-slicer: 0.4.3
  • napari_skimage_regionprops1: 0.3.2
  • napari_skimage_regionprops2: 0.3.2
  • scikit-image: 0.4.15
  • svg: 0.1.6

Steps to reproduce:
(1) File > Open Sample > napari > Cell
(2) pyClEsperanto > Label > use default
(3.1) create a points layer, manually place two points on different labels
(4.1) nsbatwm > manually merge labels --> AttributeError: 'OCLArray' object has no attribute 'item'

(3.2) create a points layer, manually place two points within the same label
(4.2) nsbatwm > manually split labels --> AttributeError: 'OCLArray' object has no attribute 'item'

Adding erosion / dilation / opening / closing

It would be useful to add scikit-image's erosion / dilation / opening / closing functions from skimage.morphology

I'd be happy to put together a PR adding this - if you think it's a useful addition?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.