├── .github └── workflows │ ├── git-bob.yml │ └── test_and_deploy.yml ├── .gitignore ├── .napari └── config.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── binary_watershed.png ├── connected_component_labeling.png ├── demo.ipynb ├── gaussian_blur.png ├── labeling.ipynb ├── local_minima_seeded_watershed.png ├── napari_download.png ├── napari_plugin_installer.png ├── seeded_watershed.png ├── seeded_watershed_with_mask.png ├── skeletonize_labels.ipynb ├── split_and_merge_demo.gif ├── subtract_background.png ├── threshold_otsu.png ├── tools_menu_screenshot.png └── voronoi_otsu_labeling.png ├── napari_segment_blobs_and_things_with_membranes ├── __init__.py ├── _bia_bob_plugins.py └── _tests │ ├── __init__.py │ └── test_function.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tox.ini /.github/workflows/git-bob.yml: -------------------------------------------------------------------------------- 1 | name: git-bob acting 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | issue_comment: 7 | types: 8 | - created 9 | pull_request: 10 | types: [opened, synchronize] 11 | pull_request_review_comment: 12 | types: [ created ] 13 | 14 | jobs: 15 | respond: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v2 21 | 22 | - name: Print pull request number 23 | run: | 24 | echo "Pull Request Number - ${{ github.event.pull_request.number }}" 25 | echo "Organization - ${{ github.repository_owner }}" 26 | echo "Repository Name - ${{ github.repository }}" 27 | 28 | - name: Print Job details 29 | run: | 30 | echo "Run ID - ${{ github.run_id }}" 31 | echo "Run No - ${{ github.run_number }}" 32 | echo "Job - ${{ github.job }}" 33 | echo "Job ID - ${{ github.job_id }}" 34 | 35 | - name: Set up Python 36 | uses: actions/setup-python@v2 37 | with: 38 | python-version: 3.x 39 | 40 | - name: Install dependencies 41 | run: | 42 | python -m pip install --upgrade pip 43 | pip install git-bob 44 | pip install -e . 45 | pip install -r requirements.txt 46 | 47 | 48 | - name: Run git-bob 49 | env: 50 | GIT_BOB_AGENT_NAME: "git-bob" 51 | GIT_BOB_LLM_NAME: "${{ secrets.GIT_BOB_LLM_NAME }}" 52 | ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}" 53 | GOOGLE_API_KEY: "${{ secrets.GOOGLE_API_KEY }}" 54 | OPENAI_API_KEY: "${{ secrets.OPENAI_API_KEY }}" 55 | GH_MODELS_API_KEY: "${{ secrets.GH_MODELS_API_KEY }}" 56 | KISSKI_API_KEY: "${{ secrets.KISSKI_API_KEY }}" 57 | BLABLADOR_API_KEY: "${{ secrets.BLABLADOR_API_KEY }}" 58 | GITHUB_API_KEY: "${{ secrets.GITHUB_TOKEN }}" 59 | GITHUB_RUN_ID: "${{ github.run_id }}" 60 | TWINE_USERNAME: "${{ secrets.TWINE_USERNAME }}" 61 | TWINE_PASSWORD: "${{ secrets.TWINE_PASSWORD }}" 62 | SYSTEM_MESSAGE: | 63 | You are an extremely skilled python developer. Your name is git-bob. You are sometimes called github-actions bot. 64 | You can solve programming tasks and review code. 65 | When asked to solve a specific problem, you keep your code changes minimal and only solve the problem at hand. 66 | You cannot execute code. 67 | 68 | You are an export when it comes to using the napari-segment-blobs-and-things-with-membranes (nsbatwm) Python library. 69 | When you use it, you always start by importing the library: `import napari_segment_blobs_and_things_with_membranes as nsbatwm`. 70 | When asked for how to use nsbatwm, you can adapt one of the following code snippets: 71 | 72 | * Splits touching objects in a binary image using an algorithm similar to the ImageJ watershed. 73 | nsbatwm.split_touching_objects(binary_image) 74 | 75 | * Applies Otsu's method to binarize an intensity image (also works with yen, isodata, li, mean, minimum, triangle instead of otsu). 76 | nsbatwm.threshold_otsu(image) 77 | 78 | * Labels connected components in a binary image. 79 | nsbatwm.connected_component_labeling(binary_image) 80 | 81 | * Applies seeded watershed segmentation using labeled objects, e.g. nuclei, and an image showing bright borders between objects such as cell membranes. 82 | nsbatwm.seeded_watershed(image, labeled_objects) 83 | 84 | * Segments blob-like structures using Voronoi-Otsu labeling. 85 | nsbatwm.voronoi_otsu_labeling(image, spot_sigma=4, outline_sigma=1) 86 | 87 | * Applies a Gaussian blur for noise reduction. 88 | nsbatwm.gaussian_blur(image, sigma=5) 89 | 90 | * Applies median filter to reduce noise while preserving edges. 91 | nsbatwm.median_filter(image, radius=5) 92 | 93 | * Smooth a label image using a local most popular intensity (mode) filter. 94 | nsbatwm.mode_filter(labels) 95 | 96 | * Applies a percentile filter. 97 | nsbatwm.percentile_filter(image) 98 | 99 | * Removes background in an image using the top-hat filter. 100 | nsbatwm.white_tophat(image) 101 | 102 | * Applies local minimum filtering to an image (also works with maximum, and mean). 103 | nsbatwm.minimum_filter(image) 104 | 105 | * Subtracts background in an image using the rolling ball algorithm. 106 | nsbatwm.subtract_background(image) 107 | 108 | * Removes labeled objects touching image borders. 109 | nsbatwm.remove_labels_on_edges(label_image) 110 | 111 | * Expands labels by a specified distance. 112 | nsbatwm.expand_labels(label_image, distance=2) 113 | 114 | * Segments using seeded watershed with local minima as seeds. 115 | nsbatwm.local_minima_seeded_watershed(image, spot_sigma=10, outline_sigma=2) 116 | 117 | * Skeletonizes labeled objects. 118 | nsbatwm.skeletonize(image) 119 | 120 | You cannot retrieve information from other sources but from github.com. 121 | Do not claim anything that you don't know. 122 | If you do not know the answer to a question, just say that you don't know and tag @haesleinhuepf so that he can answer the question. 123 | In case you are asked to review code, you focus on the quality of the code. 124 | VISION_SYSTEM_MESSAGE: | 125 | You are an AI-based vision model with excellent skills when it comes to describing image. When describing an image, you typically explain: 126 | * What is shown in the image. 127 | * If the image shows clearly distinct objects in its channels, these structures are listed for each channel individually. 128 | * You speculate how the image was acquired. 129 | run: | 130 | git-bob github-action ${{ github.repository }} ${{ github.event.pull_request.number }} ${{ github.event.issue.number }} 131 | -------------------------------------------------------------------------------- /.github/workflows/test_and_deploy.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: tests 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | - main 11 | tags: 12 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 13 | pull_request: 14 | branches: 15 | - master 16 | - main 17 | workflow_dispatch: 18 | 19 | jobs: 20 | test: 21 | name: ${{ matrix.platform }} py${{ matrix.python-version }} 22 | runs-on: ${{ matrix.platform }} 23 | strategy: 24 | matrix: 25 | platform: [ubuntu-latest, windows-latest, macos-latest] 26 | python-version: [3.8, 3.9, '3.10'] 27 | 28 | steps: 29 | - uses: actions/checkout@v2 30 | 31 | - name: Set up Python ${{ matrix.python-version }} 32 | uses: actions/setup-python@v2 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | 36 | # these libraries, along with pytest-xvfb (added in the `deps` in tox.ini), 37 | # enable testing on Qt on linux 38 | - name: Install Linux libraries 39 | if: runner.os == 'Linux' 40 | run: | 41 | sudo apt-get install -y libdbus-1-3 libxkbcommon-x11-0 libxcb-icccm4 \ 42 | libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \ 43 | libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 44 | 45 | # strategy borrowed from vispy for installing opengl libs on windows 46 | - name: Install Windows OpenGL 47 | if: runner.os == 'Windows' 48 | run: | 49 | git clone --depth 1 https://github.com/pyvista/gl-ci-helpers.git 50 | powershell gl-ci-helpers/appveyor/install_opengl.ps1 51 | 52 | # note: if you need dependencies from conda, considering using 53 | # setup-miniconda: https://github.com/conda-incubator/setup-miniconda 54 | # and 55 | # tox-conda: https://github.com/tox-dev/tox-conda 56 | - name: Install dependencies 57 | run: | 58 | python -m pip install --upgrade pip 59 | pip install setuptools tox tox-gh-actions 60 | 61 | # this runs the platform-specific tests declared in tox.ini 62 | - name: Test with tox 63 | run: tox 64 | env: 65 | PLATFORM: ${{ matrix.platform }} 66 | 67 | - name: Coverage 68 | uses: codecov/codecov-action@v1 69 | 70 | deploy: 71 | # this will run when you have tagged a commit, starting with "v*" 72 | # and requires that you have put your twine API key in your 73 | # github secrets (see readme for details) 74 | needs: [test] 75 | runs-on: ubuntu-latest 76 | if: contains(github.ref, 'tags') 77 | steps: 78 | - uses: actions/checkout@v2 79 | - name: Set up Python 80 | uses: actions/setup-python@v2 81 | with: 82 | python-version: "3.x" 83 | - name: Install dependencies 84 | run: | 85 | python -m pip install --upgrade pip 86 | pip install -U setuptools setuptools_scm wheel twine 87 | - name: Build and publish 88 | env: 89 | TWINE_USERNAME: __token__ 90 | TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }} 91 | run: | 92 | git tag 93 | python setup.py sdist bdist_wheel 94 | twine upload dist/* 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | .napari_cache 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask instance folder 58 | instance/ 59 | 60 | # Sphinx documentation 61 | docs/_build/ 62 | 63 | # MkDocs documentation 64 | /site/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # OS 76 | .DS_Store 77 | 78 | # written by setuptools_scm 79 | */_version.py 80 | 81 | .idea/* 82 | venv/* 83 | 84 | deploy.bat 85 | -------------------------------------------------------------------------------- /.napari/config.yml: -------------------------------------------------------------------------------- 1 | 2 | # Add labels the plugin from the EDAM Bioimaging ontology 3 | labels: 4 | ontology: EDAM-BIOIMAGING:alpha06 5 | terms: 6 | - Cell segmentation 7 | - Smoothing 8 | - Image thresholding 9 | - Watershed segmentation 10 | - Image denoising 11 | - Fluorescence microscopy 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2021, Robert Haase 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of napari-segment-blobs-and-things-with-membranes nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include requirements.txt 4 | 5 | recursive-exclude * __pycache__ 6 | recursive-exclude * *.py[co] 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # napari-segment-blobs-and-things-with-membranes (nsbatwm) 2 | 3 | [![License](https://img.shields.io/pypi/l/napari-segment-blobs-and-things-with-membranes.svg?color=green)](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/master/LICENSE) 4 | [![PyPI](https://img.shields.io/pypi/v/napari-segment-blobs-and-things-with-membranes.svg?color=green)](https://pypi.org/project/napari-segment-blobs-and-things-with-membranes) 5 | [![Python Version](https://img.shields.io/pypi/pyversions/napari-segment-blobs-and-things-with-membranes.svg?color=green)](https://python.org) 6 | [![tests](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/workflows/tests/badge.svg)](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/actions) 7 | [![codecov](https://codecov.io/gh/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/branch/master/graph/badge.svg)](https://codecov.io/gh/haesleinhuepf/napari-segment-blobs-and-things-with-membranes) 8 | [![Development Status](https://img.shields.io/pypi/status/napari-segment-blobs-and-things-with-membranes.svg)](https://en.wikipedia.org/wiki/Software_release_life_cycle#Alpha) 9 | [![napari hub](https://img.shields.io/endpoint?url=https://api.napari-hub.org/shields/napari-segment-blobs-and-things-with-membranes)](https://napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes) 10 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7027634.svg)](https://doi.org/10.5281/zenodo.7027634) 11 | 12 | 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. 13 | 14 | ## Usage 15 | 16 | This plugin populates image processing operations to the `Tools` menu in napari. 17 | You can recognize them with their suffix `(nsbatwm)` in brackets. 18 | Furthermore, it can be used from the [napari-assistant](https://www.napari-hub.org/plugins/napari-assistant) graphical user interface. 19 | Therefore, just click the menu `Tools > Utilities > Assistant (na)` or run `naparia` from the command line. 20 | 21 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/tools_menu_screenshot.png) 22 | 23 | You can also call these functions as shown in [the demo notebook](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/blob/main/docs/demo.ipynb). 24 | 25 | ### Voronoi-Otsu-Labeling 26 | 27 | This algorithm uses [Otsu's thresholding method](https://ieeexplore.ieee.org/document/4310076) in combination with 28 | [Gaussian blur](https://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.gaussian) and a 29 | [Voronoi-Tesselation](https://en.wikipedia.org/wiki/Voronoi_diagram) 30 | approach to label bright objects such as nuclei in an intensity image. The alogrithm has two sigma parameters which allow 31 | you to fine-tune where objects should be cut (`spot_sigma`) and how smooth outlines should be (`outline_sigma`). 32 | This implementation aims to be similar to [Voronoi-Otsu-Labeling in clesperanto](https://github.com/clEsperanto/pyclesperanto_prototype/blob/master/demo/segmentation/voronoi_otsu_labeling.ipynb). 33 | 34 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/voronoi_otsu_labeling.png) 35 | 36 | ### Seeded Watershed 37 | 38 | Starting from an image showing high-intensity membranes and a seed-image where objects have been labeled (e.g. using Voronoi-Otsu-Labeling), 39 | objects are labeled that are constrained by the membranes. 40 | 41 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/seeded_watershed.png) 42 | 43 | ### Seeded Watershed with mask 44 | 45 | If there is additionally a mask image available, one can use the `Seeded Watershed with mask`, to constraint the flooding 46 | on a membrane image (1), starting from nuclei (2), limited by a mask image (3) to produce a cell segmentation within the mask (4). 47 | 48 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/seeded_watershed_with_mask.png) 49 | 50 | ### Seeded Watershed using local minima as starting points 51 | 52 | Similar to the Seeded Watershed and Voronoi-Otsu-Labeling explained above, you can use this tool to segment an image 53 | showing membranes without an additional image showing nuclei. The two sigma parameters allow to fine tune how close 54 | objects can be and how precise their boundaries are detected. 55 | 56 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/local_minima_seeded_watershed.png) 57 | 58 | ### Gaussian blur 59 | 60 | Applies a [Gaussian blur](https://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.gaussian) to an 61 | image. This might be useful for denoising, e.g. before applying the Threshold-Otsu method. 62 | 63 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/gaussian_blur.png) 64 | 65 | ### Subtract background 66 | 67 | Subtracts background using [scikit-image's rolling-ball algorithm](https://scikit-image.org/docs/stable/auto_examples/segmentation/plot_rolling_ball.html). 68 | This might be useful, for example to make intensity of membranes more similar in different regions of an image. 69 | 70 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/subtract_background.png) 71 | 72 | ### Threshold Otsu 73 | 74 | Binarizes an image using [scikit-image's threshold Otsu algorithm](https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_thresholding.html), also known as 75 | [Otsu's method](https://ieeexplore.ieee.org/document/4310076). 76 | 77 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/threshold_otsu.png) 78 | 79 | ### Split touching objects (formerly known as binary watershed). 80 | 81 | In case objects stick together after thresholding, this tool might help. 82 | It aims to deliver similar results as [ImageJ's watershed implementation](https://imagej.nih.gov/ij/docs/menus/process.html#watershed). 83 | 84 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/binary_watershed.png) 85 | 86 | ### Connected component labeling 87 | 88 | Takes a binary image and produces a label image with all separated objects labeled differently. Under the hood, it uses 89 | [scikit-image's label function](https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_label.html). 90 | 91 | ![img.png](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/connected_component_labeling.png) 92 | 93 | ### Manual split and merge labels 94 | 95 | Split and merge labels in napari manually via the `Tools > Utilities menu`: 96 | 97 | ![](https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/raw/main/docs/split_and_merge_demo.gif) 98 | 99 | ---------------------------------- 100 | 101 | This [napari] plugin was generated with [Cookiecutter] using with [@napari]'s [cookiecutter-napari-plugin] template. 102 | 103 | ## Installation 104 | 105 | This plugin is part of devbio-napari. To install it, please follow its [installation instructions](https://github.com/haesleinhuepf/devbio-napari#installation). Note: This plugin is not compatible with napari 0.6.0 or later. 106 | 107 | ## Contributing 108 | 109 | Contributions are very welcome. Tests can be run with [tox], please ensure 110 | the coverage at least stays the same before you submit a pull request. 111 | 112 | ## License 113 | 114 | Distributed under the terms of the [BSD-3] license, 115 | "napari-segment-blobs-and-things-with-membranes" is free and open source software 116 | 117 | ## Issues 118 | 119 | If you encounter any problems, please create a thread on [image.sc] along with a detailed description and tag [@haesleinhuepf]. 120 | 121 | [napari]: https://github.com/napari/napari 122 | [Cookiecutter]: https://github.com/audreyr/cookiecutter 123 | [@napari]: https://github.com/napari 124 | [MIT]: http://opensource.org/licenses/MIT 125 | [BSD-3]: http://opensource.org/licenses/BSD-3-Clause 126 | [GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt 127 | [GNU LGPL v3.0]: http://www.gnu.org/licenses/lgpl-3.0.txt 128 | [Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0 129 | [Mozilla Public License 2.0]: https://www.mozilla.org/media/MPL/2.0/index.txt 130 | [cookiecutter-napari-plugin]: https://github.com/napari/cookiecutter-napari-plugin 131 | 132 | [file an issue]: https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/issues 133 | 134 | [napari]: https://github.com/napari/napari 135 | [tox]: https://tox.readthedocs.io/en/latest/ 136 | [pip]: https://pypi.org/project/pip/ 137 | [PyPI]: https://pypi.org/ 138 | 139 | [image.sc]: https://image.sc 140 | [@haesleinhuepf]: https://twitter.com/haesleinhuepf 141 | -------------------------------------------------------------------------------- /docs/binary_watershed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/binary_watershed.png -------------------------------------------------------------------------------- /docs/connected_component_labeling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/connected_component_labeling.png -------------------------------------------------------------------------------- /docs/gaussian_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/gaussian_blur.png -------------------------------------------------------------------------------- /docs/labeling.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "f946ba84-3a9b-403b-9746-e2b425cb2636", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from napari_segment_blobs_and_things_with_membranes import local_minima_seeded_watershed\n", 11 | "from skimage.data import cells3d\n", 12 | "from pyclesperanto_prototype import imshow" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 2, 18 | "id": "3ed84961-bff5-4c02-b8fd-1953373dac39", 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "data": { 23 | "image/png": "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\n", 24 | "text/plain": [ 25 | "
" 26 | ] 27 | }, 28 | "metadata": { 29 | "needs_background": "light" 30 | }, 31 | "output_type": "display_data" 32 | } 33 | ], 34 | "source": [ 35 | "membranes = cells3d()[30,0]\n", 36 | "\n", 37 | "imshow(membranes)" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 3, 43 | "id": "c1e7dac7-7675-401c-ae73-fd0fc45820de", 44 | "metadata": {}, 45 | "outputs": [ 46 | { 47 | "data": { 48 | "text/html": [ 49 | "\n", 50 | "\n", 51 | "\n", 54 | "\n", 64 | "\n", 65 | "
\n", 52 | "\n", 53 | "\n", 55 | "nsbatwm made image
\n", 56 | "\n", 57 | "\n", 58 | "\n", 59 | "\n", 60 | "\n", 61 | "
shape(256, 256)
dtypeint32
size256.0 kB
min1
max27
\n", 62 | "\n", 63 | "
" 66 | ], 67 | "text/plain": [ 68 | "StackViewNDArray([[ 5, 5, 5, ..., 3, 3, 3],\n", 69 | " [ 5, 5, 5, ..., 3, 3, 3],\n", 70 | " [ 5, 5, 5, ..., 3, 3, 3],\n", 71 | " ...,\n", 72 | " [24, 24, 24, ..., 27, 27, 27],\n", 73 | " [24, 24, 24, ..., 27, 27, 27],\n", 74 | " [24, 24, 24, ..., 27, 27, 27]])" 75 | ] 76 | }, 77 | "execution_count": 3, 78 | "metadata": {}, 79 | "output_type": "execute_result" 80 | } 81 | ], 82 | "source": [ 83 | "label_image = local_minima_seeded_watershed(membranes)\n", 84 | "\n", 85 | "label_image" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 4, 91 | "id": "52ab8bfa-26d3-49b6-bdca-7da01b80b375", 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "data": { 96 | "text/plain": [ 97 | "dtype('int32')" 98 | ] 99 | }, 100 | "execution_count": 4, 101 | "metadata": {}, 102 | "output_type": "execute_result" 103 | } 104 | ], 105 | "source": [ 106 | "label_image.dtype" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 5, 112 | "id": "5d95ebff-39a5-4821-81fa-d801709d2a6a", 113 | "metadata": {}, 114 | "outputs": [ 115 | { 116 | "data": { 117 | "text/html": [ 118 | "
numpy.ndarray([ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24\n",
119 |        " 25 26 27], dtype=int32)
" 120 | ], 121 | "text/plain": [ 122 | "StackViewNDArray([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,\n", 123 | " 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27])" 124 | ] 125 | }, 126 | "execution_count": 5, 127 | "metadata": {}, 128 | "output_type": "execute_result" 129 | } 130 | ], 131 | "source": [ 132 | "import numpy as np\n", 133 | "np.unique(label_image)" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": 6, 139 | "id": "a85fc8b6-bad0-486b-a0ba-e8581ce2daaa", 140 | "metadata": {}, 141 | "outputs": [ 142 | { 143 | "data": { 144 | "image/png": "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\n", 145 | "text/plain": [ 146 | "
" 147 | ] 148 | }, 149 | "metadata": { 150 | "needs_background": "light" 151 | }, 152 | "output_type": "display_data" 153 | } 154 | ], 155 | "source": [ 156 | "from skimage.measure import label\n", 157 | "\n", 158 | "result = label(label_image)\n", 159 | "\n", 160 | "imshow(result, labels=True)" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 7, 166 | "id": "9eacd86f-509f-4a4f-8b9a-f42eaaa9e9ec", 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "data": { 171 | "text/plain": [ 172 | "array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,\n", 173 | " 18, 19, 20, 21, 22, 23, 24, 25, 26, 27], dtype=int64)" 174 | ] 175 | }, 176 | "execution_count": 7, 177 | "metadata": {}, 178 | "output_type": "execute_result" 179 | } 180 | ], 181 | "source": [ 182 | "import numpy as np\n", 183 | "np.unique(result)" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "id": "4024629b-32cd-4d72-a5e3-d0d2b2aab935", 190 | "metadata": {}, 191 | "outputs": [], 192 | "source": [] 193 | } 194 | ], 195 | "metadata": { 196 | "kernelspec": { 197 | "display_name": "Python 3 (ipykernel)", 198 | "language": "python", 199 | "name": "python3" 200 | }, 201 | "language_info": { 202 | "codemirror_mode": { 203 | "name": "ipython", 204 | "version": 3 205 | }, 206 | "file_extension": ".py", 207 | "mimetype": "text/x-python", 208 | "name": "python", 209 | "nbconvert_exporter": "python", 210 | "pygments_lexer": "ipython3", 211 | "version": "3.9.13" 212 | } 213 | }, 214 | "nbformat": 4, 215 | "nbformat_minor": 5 216 | } 217 | -------------------------------------------------------------------------------- /docs/local_minima_seeded_watershed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/local_minima_seeded_watershed.png -------------------------------------------------------------------------------- /docs/napari_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/napari_download.png -------------------------------------------------------------------------------- /docs/napari_plugin_installer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/napari_plugin_installer.png -------------------------------------------------------------------------------- /docs/seeded_watershed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/seeded_watershed.png -------------------------------------------------------------------------------- /docs/seeded_watershed_with_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/seeded_watershed_with_mask.png -------------------------------------------------------------------------------- /docs/skeletonize_labels.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "id": "9825bf41-3510-4f31-9037-e3069e976467", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "from napari_segment_blobs_and_things_with_membranes import skeletonize\n", 12 | "from skimage.io import imread, imshow\n", 13 | "from skimage.filters import gaussian, threshold_otsu\n", 14 | "from skimage.measure import label" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 2, 20 | "id": "690b7548-def0-40bd-b9d1-00e7896efa09", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "image = imread('https://fiji.sc/samples/blobs.png')" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "id": "7d121d17-ec28-4da9-98f5-c0806b93dd1c", 31 | "metadata": {}, 32 | "outputs": [ 33 | { 34 | "name": "stderr", 35 | "output_type": "stream", 36 | "text": [ 37 | "C:\\Users\\rober\\miniconda3\\envs\\bio_39\\lib\\site-packages\\skimage\\io\\_plugins\\matplotlib_plugin.py:150: UserWarning: Low image data range; displaying image with stretched contrast.\n", 38 | " lo, hi, cmap = _get_display_range(image)\n" 39 | ] 40 | }, 41 | { 42 | "data": { 43 | "text/plain": [ 44 | "" 45 | ] 46 | }, 47 | "execution_count": 3, 48 | "metadata": {}, 49 | "output_type": "execute_result" 50 | }, 51 | { 52 | "data": { 53 | "image/png": "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\n", 54 | "text/plain": [ 55 | "
" 56 | ] 57 | }, 58 | "metadata": { 59 | "needs_background": "light" 60 | }, 61 | "output_type": "display_data" 62 | } 63 | ], 64 | "source": [ 65 | "blurred = gaussian(image, sigma=10)\n", 66 | "binary = blurred > threshold_otsu(blurred)\n", 67 | "labels = label(binary)\n", 68 | "imshow(labels, cmap=\"jet\")" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 4, 74 | "id": "81517731-10e7-47c7-9148-101bd2a631ae", 75 | "metadata": {}, 76 | "outputs": [ 77 | { 78 | "data": { 79 | "text/plain": [ 80 | "" 81 | ] 82 | }, 83 | "execution_count": 4, 84 | "metadata": {}, 85 | "output_type": "execute_result" 86 | }, 87 | { 88 | "data": { 89 | "image/png": "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\n", 90 | "text/plain": [ 91 | "
" 92 | ] 93 | }, 94 | "metadata": { 95 | "needs_background": "light" 96 | }, 97 | "output_type": "display_data" 98 | } 99 | ], 100 | "source": [ 101 | "skel = skeletonize(labels)\n", 102 | "imshow(skel, cmap=\"jet\")" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 5, 108 | "id": "b6e6fbd9-3197-4185-aebe-539c7bfcf9c6", 109 | "metadata": {}, 110 | "outputs": [ 111 | { 112 | "data": { 113 | "text/plain": [ 114 | "" 115 | ] 116 | }, 117 | "execution_count": 5, 118 | "metadata": {}, 119 | "output_type": "execute_result" 120 | }, 121 | { 122 | "data": { 123 | "image/png": "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\n", 124 | "text/plain": [ 125 | "
" 126 | ] 127 | }, 128 | "metadata": { 129 | "needs_background": "light" 130 | }, 131 | "output_type": "display_data" 132 | } 133 | ], 134 | "source": [ 135 | "imshow(skel[180:250, 100:150], cmap=\"jet\")" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "id": "a99ae17e-f648-47ba-8b09-b120a6b77285", 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [] 145 | } 146 | ], 147 | "metadata": { 148 | "kernelspec": { 149 | "display_name": "Python 3 (ipykernel)", 150 | "language": "python", 151 | "name": "python3" 152 | }, 153 | "language_info": { 154 | "codemirror_mode": { 155 | "name": "ipython", 156 | "version": 3 157 | }, 158 | "file_extension": ".py", 159 | "mimetype": "text/x-python", 160 | "name": "python", 161 | "nbconvert_exporter": "python", 162 | "pygments_lexer": "ipython3", 163 | "version": "3.9.7" 164 | } 165 | }, 166 | "nbformat": 4, 167 | "nbformat_minor": 5 168 | } 169 | -------------------------------------------------------------------------------- /docs/split_and_merge_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/split_and_merge_demo.gif -------------------------------------------------------------------------------- /docs/subtract_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/subtract_background.png -------------------------------------------------------------------------------- /docs/threshold_otsu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/threshold_otsu.png -------------------------------------------------------------------------------- /docs/tools_menu_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/tools_menu_screenshot.png -------------------------------------------------------------------------------- /docs/voronoi_otsu_labeling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/docs/voronoi_otsu_labeling.png -------------------------------------------------------------------------------- /napari_segment_blobs_and_things_with_membranes/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = "0.3.12" 3 | __common_alias__ = "nsbatwm" 4 | 5 | from napari_plugin_engine import napari_hook_implementation 6 | import numpy as np 7 | from scipy import ndimage as ndi 8 | from skimage.filters import threshold_otsu as sk_threshold_otsu, gaussian, sobel 9 | from skimage.segmentation import watershed 10 | from skimage.feature import peak_local_max 11 | from skimage.measure import label 12 | from skimage.morphology import local_maxima, local_minima, opening, closing, erosion, dilation, disk, ball 13 | from skimage.morphology import binary_opening as sk_binary_opening 14 | from skimage.morphology import binary_closing as sk_binary_closing 15 | from skimage.morphology import binary_erosion as sk_binary_erosion 16 | from skimage.morphology import binary_dilation as sk_binary_dilation 17 | from skimage.restoration import rolling_ball 18 | from napari_tools_menu import register_function 19 | from skimage.measure import regionprops 20 | from skimage.segmentation import relabel_sequential as sk_relabel_sequential 21 | from skimage.segmentation import clear_border 22 | from skimage.segmentation import expand_labels as sk_expand_labels 23 | from skimage import filters 24 | import scipy 25 | from scipy import ndimage 26 | from napari_time_slicer import time_slicer 27 | from stackview import jupyter_displayable_output 28 | 29 | @napari_hook_implementation 30 | def napari_experimental_provide_function(): 31 | return [ 32 | gaussian_blur, 33 | subtract_background, 34 | threshold_otsu, 35 | threshold_yen, 36 | threshold_isodata, 37 | threshold_li, 38 | threshold_mean, 39 | threshold_minimum, 40 | threshold_triangle, 41 | binary_invert, 42 | split_touching_objects, 43 | connected_component_labeling, 44 | seeded_watershed, 45 | voronoi_otsu_labeling, 46 | gauss_otsu_labeling, 47 | gaussian_laplace, 48 | median_filter, 49 | mode_filter, 50 | maximum_filter, 51 | minimum_filter, 52 | percentile_filter, 53 | black_tophat, 54 | white_tophat, 55 | morphological_gradient, 56 | local_minima_seeded_watershed, 57 | thresholded_local_minima_seeded_watershed, 58 | sum_images, 59 | multiply_images, 60 | divide_images, 61 | invert_image, 62 | skeletonize, 63 | Manually_merge_labels, 64 | Manually_split_labels, 65 | rescale, 66 | resize, 67 | extract_slice, 68 | grayscale_erosion, 69 | binary_erosion, 70 | grayscale_dilation, 71 | binary_dilation, 72 | grayscale_closing, 73 | binary_closing, 74 | grayscale_opening, 75 | binary_opening 76 | ] 77 | 78 | 79 | 80 | 81 | 82 | 83 | def _sobel_3d(image): 84 | kernel = np.asarray([ 85 | [ 86 | [0, 0, 0], 87 | [0, 1, 0], 88 | [0, 0, 0] 89 | ], [ 90 | [0, 1, 0], 91 | [1, -6, 1], 92 | [0, 1, 0] 93 | ], [ 94 | [0, 0, 0], 95 | [0, 1, 0], 96 | [0, 0, 0] 97 | ] 98 | ]) 99 | return ndi.convolve(image, kernel) 100 | 101 | 102 | def _generate_disk_footprint(radius, ndim): 103 | """Generate a disk/sphere footprint (for 2D or 3D) with the given radius.""" 104 | if ndim == 2: 105 | return disk(radius) 106 | elif ndim == 3: 107 | return ball(radius) 108 | else: 109 | raise ValueError("Disk footprints are only implemented for 2D or 3D images") 110 | 111 | 112 | @register_function(menu="Segmentation post-processing > Split touching objects (nsbatwm)") 113 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 114 | @time_slicer 115 | def split_touching_objects(binary:"napari.types.LabelsData", sigma: float = 3.5) -> "napari.types.LabelsData": 116 | """ 117 | Takes a binary image and draws cuts in the objects similar to the ImageJ watershed algorithm [1]. 118 | 119 | This allows cutting connected objects such as not to dense nuclei. If the nuclei are too dense, 120 | consider using stardist [2] or cellpose [3]. 121 | 122 | See also 123 | -------- 124 | .. [1] https://imagej.nih.gov/ij/docs/menus/process.html#watershed 125 | .. [2] https://www.napari-hub.org/plugins/stardist-napari 126 | .. [3] https://www.napari-hub.org/plugins/cellpose-napari 127 | """ 128 | binary = np.asarray(binary) 129 | 130 | # typical way of using scikit-image watershed 131 | distance = ndi.distance_transform_edt(binary) 132 | blurred_distance = gaussian(distance, sigma=sigma) 133 | fp = np.ones((3,) * binary.ndim) 134 | coords = peak_local_max(blurred_distance, footprint=fp, labels=binary) 135 | mask = np.zeros(distance.shape, dtype=bool) 136 | mask[tuple(coords.T)] = True 137 | markers = label(mask) 138 | labels = watershed(-blurred_distance, markers, mask=binary) 139 | 140 | # identify label-cutting edges 141 | if len(binary.shape) == 2: 142 | edges = sobel(labels) 143 | edges2 = sobel(binary) 144 | else: # assuming 3D 145 | edges = _sobel_3d(labels) 146 | edges2 = _sobel_3d(binary) 147 | 148 | almost = np.logical_not(np.logical_xor(edges != 0, edges2 != 0)) * binary 149 | return sk_binary_opening(almost) 150 | 151 | 152 | @register_function(menu="Segmentation / binarization > Threshold (Otsu et al 1979, scikit-image, nsbatwm)") 153 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 154 | @time_slicer 155 | def threshold_otsu(image:"napari.types.ImageData") -> "napari.types.LabelsData": 156 | """ 157 | Applies Otsu's threshold selection method to an intensity image and returns a binary image with pixels==1 where 158 | intensity is above the determined threshold. 159 | 160 | See also 161 | -------- 162 | .. [0] https://en.wikipedia.org/wiki/Otsu%27s_method 163 | .. [1] https://ieeexplore.ieee.org/document/4310076 164 | """ 165 | threshold = sk_threshold_otsu(np.asarray(image)) 166 | binary_otsu = image > threshold 167 | 168 | return binary_otsu * 1 169 | 170 | 171 | @register_function(menu="Segmentation / binarization > Threshold (Yen et al 1995, scikit-image, nsbatwm)") 172 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 173 | @time_slicer 174 | def threshold_yen(image :"napari.types.ImageData") -> "napari.types.LabelsData": 175 | """ 176 | Binarize an image using Yen's method. 177 | 178 | Parameters 179 | ---------- 180 | image: Image 181 | 182 | See Also 183 | -------- 184 | ..[0] https://imagej.net/plugins/auto-threshold#yen 185 | 186 | Returns 187 | ------- 188 | binary_image: napari.types.LabelsData 189 | """ 190 | return image > filters.threshold_yen(image) 191 | 192 | 193 | @register_function(menu="Segmentation / binarization > Threshold (Isodata, Ridler et al 1978, scikit-image, nsbatwm)") 194 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 195 | @time_slicer 196 | def threshold_isodata(image :"napari.types.ImageData") -> "napari.types.LabelsData": 197 | """ 198 | Binarize an image using the IsoData / Ridler's method. 199 | The method is similar to ImageJ's "default" threshold. 200 | 201 | Parameters 202 | ---------- 203 | image: Image 204 | 205 | See Also 206 | -------- 207 | ..[0] https://imagej.net/plugins/auto-threshold#isodata 208 | ..[1] https://ieeexplore.ieee.org/document/4310039 209 | 210 | Returns 211 | ------- 212 | binary_image: napari.types.LabelsData 213 | """ 214 | return image > filters.threshold_isodata(image) 215 | 216 | 217 | @register_function(menu="Segmentation / binarization > Threshold (Li et al 1993, scikit-image, nsbatwm)") 218 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 219 | @time_slicer 220 | def threshold_li(image:"napari.types.ImageData") -> "napari.types.LabelsData": 221 | """ 222 | Binarize an image using Li's method method. 223 | 224 | Parameters 225 | ---------- 226 | image: Image 227 | 228 | See Also 229 | -------- 230 | ..[0] https://imagej.net/plugins/auto-threshold#li 231 | 232 | Returns 233 | ------- 234 | binary_image: napari.types.LabelsData 235 | """ 236 | return image > filters.threshold_li(image) 237 | 238 | 239 | @register_function(menu="Segmentation / binarization > Threshold (Ridler et al 1978, scikit-image, nsbatwm)") 240 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 241 | @time_slicer 242 | def threshold_mean(image :"napari.types.ImageData") -> "napari.types.LabelsData": 243 | """ 244 | Binarize an image using the Mean method. 245 | 246 | Parameters 247 | ---------- 248 | image: Image 249 | 250 | See Also 251 | -------- 252 | ..[0] https://imagej.net/plugins/auto-threshold#mean 253 | 254 | Returns 255 | ------- 256 | binary_image: napari.types.LabelsData 257 | """ 258 | return image > filters.threshold_mean(image) 259 | 260 | 261 | @register_function(menu="Segmentation / binarization > Threshold (Mean, scikit-image, nsbatwm)") 262 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 263 | @time_slicer 264 | def threshold_minimum(image :"napari.types.ImageData") -> "napari.types.LabelsData": 265 | """ 266 | Binarize an image using the Minimum method. 267 | 268 | Parameters 269 | ---------- 270 | image: Image 271 | 272 | See Also 273 | -------- 274 | ..[0] https://imagej.net/plugins/auto-threshold#minimum 275 | 276 | Returns 277 | ------- 278 | binary_image: napari.types.LabelsData 279 | """ 280 | return image > filters.threshold_minimum(image) 281 | 282 | 283 | @register_function(menu="Segmentation / binarization > Threshold (Triangle method, Zack et al 1977, scikit-image, nsbatwm)") 284 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 285 | @time_slicer 286 | def threshold_triangle(image:"napari.types.ImageData") -> "napari.types.LabelsData": 287 | """ 288 | Binarize an image using the Triangle method. 289 | 290 | Parameters 291 | ---------- 292 | image: Image 293 | 294 | See Also 295 | -------- 296 | ..[0] https://imagej.net/plugins/auto-threshold#triangle 297 | 298 | Returns 299 | ------- 300 | binary_image: napari.types.LabelsData 301 | """ 302 | return image > filters.threshold_triangle(image) 303 | 304 | 305 | @register_function(menu="Filtering / noise removal > Gaussian (scikit-image, nsbatwm)") 306 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 307 | @time_slicer 308 | def gaussian_blur(image:"napari.types.ImageData", sigma: float = 1) -> "napari.types.ImageData": 309 | """ 310 | Applies a Gaussian blur to an image with a defined sigma. Useful for denoising. 311 | """ 312 | return gaussian(image, sigma) 313 | 314 | 315 | @register_function(menu="Filtering / edge enhancement > Gaussian Laplace (scipy, nsbatwm)") 316 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 317 | @time_slicer 318 | def gaussian_laplace(image:"napari.types.ImageData", sigma: float = 2)-> "napari.types.ImageData": 319 | """ 320 | Apply Laplace filter for edge detection / edge enhancement after applying a Gaussian-blur 321 | 322 | Parameters 323 | ---------- 324 | image: array-like 325 | Image to detect edges in 326 | sigma: float 327 | The filter will be applied with this specified Gaussian-blur sigma 328 | 329 | Returns 330 | ------- 331 | array-like 332 | 333 | See also 334 | -------- 335 | .. [1] https://en.wikipedia.org/wiki/Laplace_operator 336 | """ 337 | return scipy.ndimage.gaussian_laplace(image.astype(float), sigma) 338 | 339 | 340 | @register_function(menu="Filtering / noise removal > Median (scipy, nsbatwm)") 341 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 342 | @time_slicer 343 | def median_filter(image:"napari.types.ImageData", radius: float = 2)-> "napari.types.ImageData": 344 | """ 345 | The median-filter allows removing noise from images. While locally averaging intensity, it 346 | is an edge-preserving filter. 347 | 348 | It is equal to a percentile-filter with percentile==50. 349 | In case applying the filter takes to much time, consider using a Gaussian blur instead. 350 | """ 351 | return scipy.ndimage.median_filter(image.astype(float), size=int(radius * 2 + 1)) 352 | 353 | 354 | @register_function(menu="Segmentation post-processing > Mode filter (scikit-image, nsbatwm)") 355 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 356 | @time_slicer 357 | def mode_filter(labels: "napari.types.LabelsData", radius: int = 2)-> "napari.types.LabelsData": 358 | """ 359 | The mode-filter allows correcting label images in a way that every pixel receives the most popular pixel value 360 | in its neighborhood. 361 | 362 | Note: This operation is limited to images with less than 256 labels. 363 | """ 364 | if labels.max() > 255: 365 | raise ValueError("The mode filter only works on label images with less than 256 labels.") 366 | 367 | from skimage.filters.rank import majority 368 | footprint = np.ones((int(radius * 2 + 1),) * labels.ndim) 369 | return majority(labels.astype(np.uint8), footprint=footprint).astype(labels.dtype) 370 | 371 | @register_function(menu="Filtering / noise removal > Percentile (scipy, nsbatwm)") 372 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 373 | @time_slicer 374 | def percentile_filter(image:"napari.types.ImageData", percentile : float = 50, radius: float = 2)-> "napari.types.ImageData": 375 | """The percentile filter is similar to the median-filter but it allows specifying the percentile. 376 | The percentile-filter with percentile==50 is equal to the median-filter. 377 | """ 378 | return scipy.ndimage.percentile_filter(image.astype(float), percentile=percentile, size=int(radius * 2 + 1)) 379 | 380 | 381 | @register_function(menu="Filtering / background removal > White top-hat (scipy, nsbatwm)") 382 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 383 | @time_slicer 384 | def white_tophat(image:"napari.types.ImageData", radius: float = 2)-> "napari.types.ImageData": 385 | """ 386 | The white top-hat filter removes bright regions from an image showing black islands. 387 | 388 | In the context of fluorescence microscopy, it allows removing intensity resulting from out-of-focus light. 389 | """ 390 | return scipy.ndimage.white_tophat(image.astype(float), size=int(radius * 2 + 1)) 391 | 392 | 393 | @register_function(menu="Filtering / background removal > Black top-hat (scipy, nsbatwm)") 394 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 395 | @time_slicer 396 | def black_tophat(image:"napari.types.ImageData", radius: float = 2)-> "napari.types.ImageData": 397 | """ 398 | The black top-hat filter removes bright regions from an image showing black islands. 399 | """ 400 | return scipy.ndimage.black_tophat(image.astype(float), size=int(radius * 2 + 1)) 401 | 402 | 403 | @register_function(menu="Filtering / background removal > Minimum (scipy, nsbatwm)") 404 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 405 | @time_slicer 406 | def minimum_filter(image:"napari.types.ImageData", radius: float = 2)-> "napari.types.ImageData": 407 | """ 408 | Local minimum filter 409 | 410 | Can be used for noise and background removal. 411 | """ 412 | return scipy.ndimage.minimum_filter(image.astype(float), size=radius * 2 + 1) 413 | 414 | 415 | @register_function(menu="Filtering / background removal > Maximum (scipy, nsbatwm)") 416 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 417 | @time_slicer 418 | def maximum_filter(image:"napari.types.ImageData", radius: float = 2)-> "napari.types.ImageData": 419 | """ 420 | Local maximum filter 421 | 422 | In the context of cell segmentation it can be used to make membranes wider 423 | and close small gaps of insufficient staining. 424 | """ 425 | return scipy.ndimage.maximum_filter(image.astype(float), size=radius * 2 + 1) 426 | 427 | 428 | @register_function(menu="Filtering / edge enhancement > Morphological Gradient (scipy, nsbatwm)") 429 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 430 | @time_slicer 431 | def morphological_gradient(image:"napari.types.ImageData", radius: float = 2)-> "napari.types.ImageData": 432 | """ 433 | Apply gradient filter (similar to the Sobel operator) for edge detection / edge enhancement. 434 | This is similar to applying a Gaussian-blur to an image and afterwards the gradient operator 435 | 436 | Parameters 437 | ---------- 438 | image: array-like 439 | Image to detect edges in 440 | radius: float 441 | The filter will be applied with a kernel size of (radius * 2 + 1) 442 | 443 | Returns 444 | ------- 445 | array-like 446 | 447 | See also 448 | -------- 449 | .. [1] https://en.wikipedia.org/wiki/Morphological_gradient 450 | """ 451 | return scipy.ndimage.morphological_gradient(image.astype(float), size=int(radius * 2 + 1)) 452 | 453 | 454 | @register_function(menu="Filtering / background removal > Rolling ball (scikit-image, nsbatwm)") 455 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 456 | @time_slicer 457 | def subtract_background(image:"napari.types.ImageData", rolling_ball_radius: float = 5) -> "napari.types.ImageData": 458 | """ 459 | Subtract background in an image using the rolling-ball algorithm. 460 | 461 | See also 462 | -------- 463 | ..[0] https://scikit-image.org/docs/stable/auto_examples/segmentation/plot_rolling_ball.html 464 | """ 465 | background = rolling_ball(image, radius = rolling_ball_radius) 466 | return image - background 467 | 468 | 469 | @register_function(menu="Segmentation post-processing > Invert binary image (nsbatwm)") 470 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 471 | @time_slicer 472 | def binary_invert(binary_image:"napari.types.LabelsData") -> "napari.types.LabelsData": 473 | """ 474 | Inverts a binary image. 475 | """ 476 | return (np.asarray(binary_image) == 0) * 1 477 | 478 | 479 | @register_function(menu="Segmentation / labeling > Connected component labeling (scikit-image, nsbatwm)") 480 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 481 | @time_slicer 482 | def connected_component_labeling(binary_image: "napari.types.LabelsData", exclude_on_edges: bool = False) -> "napari.types.LabelsData": 483 | """ 484 | Takes a binary image and produces a label image with all separated objects labeled with 485 | different integer numbers. 486 | 487 | Parameters 488 | ---------- 489 | exclude_on_edges : bool, optional 490 | Whether or not to clear objects connected to the label image border/planes (either xy, xz or yz). 491 | """ 492 | if exclude_on_edges: 493 | # processing the image, which is not a timelapse 494 | return remove_labels_on_edges(label(np.asarray(binary_image))) 495 | 496 | else: 497 | return label(np.asarray(binary_image)) 498 | 499 | 500 | @register_function(menu="Segmentation post-processing > Remove labeled objects at the image border (scikit-image, nsbatwm)") 501 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 502 | @time_slicer 503 | def remove_labels_on_edges(label_image: "napari.types.LabelsData") -> "napari.types.LabelsData": 504 | """ 505 | Takes a label image and removes objects that touch the image border. 506 | The remaining labels are relabeled sequentially. 507 | 508 | See also 509 | -------- 510 | ..[0] https://scikit-image.org/docs/stable/api/skimage.segmentation.html#skimage.segmentation.clear_border 511 | """ 512 | 513 | result = clear_border(np.asarray(label_image)) 514 | relabeled_result, _, _ = sk_relabel_sequential(result) 515 | return relabeled_result 516 | 517 | 518 | @register_function(menu="Segmentation post-processing > Expand labels (scikit-image, nsbatwm)") 519 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 520 | @time_slicer 521 | def expand_labels(label_image: "napari.types.LabelsData", distance: float = 1) -> "napari.types.LabelsData": 522 | """ 523 | Takes a label image and makes labels larger up to a given radius (distance). 524 | Labels will not overwrite each other while expanding. This operation is also known as label dilation. 525 | 526 | See also 527 | -------- 528 | ..[0] https://scikit-image.org/docs/stable/api/skimage.segmentation.html#skimage.segmentation.expand_labels 529 | """ 530 | 531 | return sk_expand_labels(np.asarray(label_image), distance=distance) 532 | 533 | 534 | @register_function(menu="Segmentation / labeling > Voronoi-Otsu-labeling (nsbatwm)") 535 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 536 | @time_slicer 537 | def voronoi_otsu_labeling(image:"napari.types.ImageData", spot_sigma: float = 2, outline_sigma: float = 2) -> "napari.types.LabelsData": 538 | """Voronoi-Otsu-Labeling is a segmentation algorithm for blob-like structures such as nuclei and 539 | granules with high signal intensity on low-intensity background. 540 | 541 | The two sigma parameters allow tuning the segmentation result. The first sigma controls how close detected cells 542 | can be (spot_sigma) and the second controls how precise segmented objects are outlined (outline_sigma). Under the 543 | hood, this filter applies two Gaussian blurs, spot detection, Otsu-thresholding and Voronoi-labeling. The 544 | thresholded binary image is flooded using the Voronoi approach starting from the found local maxima. Noise-removal 545 | sigma for spot detection and thresholding can be configured separately. 546 | 547 | This allows segmenting connected objects such as not to dense nuclei. 548 | If the nuclei are too dense, consider using stardist [1] or cellpose [2]. 549 | 550 | See also 551 | -------- 552 | .. [0] https://github.com/clEsperanto/pyclesperanto_prototype/blob/master/demo/segmentation/voronoi_otsu_labeling.ipynb 553 | .. [1] https://www.napari-hub.org/plugins/stardist-napari 554 | .. [2] https://www.napari-hub.org/plugins/cellpose-napari 555 | """ 556 | image = np.asarray(image) 557 | 558 | # blur and detect local maxima 559 | blurred_spots = gaussian(image, spot_sigma) 560 | spot_centroids = local_maxima(blurred_spots) 561 | 562 | # blur and threshold 563 | blurred_outline = gaussian(image, outline_sigma) 564 | threshold = sk_threshold_otsu(blurred_outline) 565 | binary_otsu = blurred_outline > threshold 566 | 567 | # determine local maxima within the thresholded area 568 | remaining_spots = spot_centroids * binary_otsu 569 | 570 | # start from remaining spots and flood binary image with labels 571 | labeled_spots = label(remaining_spots) 572 | labels = watershed(binary_otsu, labeled_spots, mask=binary_otsu) 573 | 574 | return labels 575 | 576 | 577 | @register_function(menu="Segmentation / labeling > Gauss-Otsu-labeling (nsbatwm)") 578 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 579 | @time_slicer 580 | def gauss_otsu_labeling(image:"napari.types.ImageData", outline_sigma: float = 2) -> "napari.types.LabelsData": 581 | """Gauss-Otsu-Labeling can be used to segment objects such as nuclei with bright intensity on 582 | low intensity background images. 583 | 584 | The outline_sigma parameter allows tuning how precise segmented objects are outlined. Under the 585 | hood, this filter applies a Gaussian blur, Otsu-thresholding and connected component labeling. 586 | 587 | See also 588 | -------- 589 | .. [0] https://github.com/clEsperanto/pyclesperanto_prototype/blob/master/demo/segmentation/gauss_otsu_labeling.ipynb 590 | """ 591 | image = np.asarray(image) 592 | 593 | # blur 594 | blurred_outline = gaussian(image, outline_sigma) 595 | 596 | # threshold 597 | threshold = sk_threshold_otsu(blurred_outline) 598 | binary_otsu = blurred_outline > threshold 599 | 600 | # connected component labeling 601 | labels = label(binary_otsu) 602 | 603 | return labels 604 | 605 | 606 | @register_function(menu="Segmentation / labeling > Seeded watershed (scikit-image, nsbatwm)") 607 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 608 | @time_slicer 609 | def seeded_watershed(membranes:"napari.types.ImageData", labeled_nuclei:"napari.types.LabelsData") -> "napari.types.LabelsData": 610 | """ 611 | Takes a image with bright (high intensity) membranes and an image with labeled objects such as nuclei. 612 | The latter serves as seeds image for a watershed labeling. 613 | 614 | See also 615 | -------- 616 | .. [1] https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_watershed.html 617 | """ 618 | cells = watershed( 619 | np.asarray(membranes), 620 | np.asarray(labeled_nuclei) 621 | ) 622 | return cells 623 | 624 | @register_function(menu="Segmentation / labeling > Seeded watershed with mask (scikit-image, nsbatwm)") 625 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 626 | @time_slicer 627 | def seeded_watershed_with_mask(membranes:"napari.types.ImageData", labeled_nuclei:"napari.types.LabelsData", mask:"napari.types.LabelsData") -> "napari.types.LabelsData": 628 | """ 629 | Takes a image with bright (high intensity) membranes, an image with labeled objects such as nuclei and a mask imge, e.g. a binary image of the entire tissue of interest. 630 | The labeled nuclei serve as seeds image for a watershed labeling and the mask for constrainting the flooding. 631 | 632 | See also 633 | -------- 634 | .. [1] https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_watershed.html 635 | """ 636 | cells = watershed( 637 | np.asarray(membranes), 638 | np.asarray(labeled_nuclei), 639 | mask=mask 640 | ) 641 | return cells 642 | 643 | 644 | @register_function(menu="Segmentation / labeling > Seeded watershed using local minima as seeds (nsbatwm)") 645 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 646 | @time_slicer 647 | def local_minima_seeded_watershed(image:"napari.types.ImageData", spot_sigma: float = 10, outline_sigma: float = 0) -> "napari.types.LabelsData": 648 | """ 649 | Segment cells in images with fluorescently marked membranes. 650 | 651 | The two sigma parameters allow tuning the segmentation result. The first sigma controls how close detected cells 652 | can be (spot_sigma) and the second controls how precise segmented objects are outlined (outline_sigma). Under the 653 | hood, this filter applies two Gaussian blurs, local minima detection and a seeded watershed. 654 | 655 | See also 656 | -------- 657 | .. [1] https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_watershed.html 658 | """ 659 | 660 | image = np.asarray(image) 661 | 662 | spot_blurred = gaussian(image, sigma=spot_sigma) 663 | 664 | spots = label(local_minima(spot_blurred)) 665 | 666 | if outline_sigma == spot_sigma: 667 | outline_blurred = spot_blurred 668 | else: 669 | outline_blurred = gaussian(image, sigma=outline_sigma) 670 | 671 | return watershed(outline_blurred, spots) 672 | 673 | 674 | @register_function(menu="Segmentation / labeling > Seeded watershed using local minima as seeds and an intensity threshold (nsbatwm)") 675 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 676 | @time_slicer 677 | def thresholded_local_minima_seeded_watershed(image:"napari.types.ImageData", spot_sigma: float = 3, outline_sigma: float = 0, minimum_intensity: float = 500) -> "napari.types.LabelsData": 678 | """ 679 | Segment cells in images with marked membranes that have a high signal intensity. 680 | 681 | The two sigma parameters allow tuning the segmentation result. The first sigma controls how close detected cells 682 | can be (spot_sigma) and the second controls how precise segmented objects are outlined (outline_sigma). Under the 683 | hood, this filter applies two Gaussian blurs, local minima detection and a seeded watershed. 684 | 685 | Afterwards, all objects are removed that have an average intensity below a given minimum_intensity 686 | """ 687 | labels = local_minima_seeded_watershed(image, spot_sigma=spot_sigma, outline_sigma=outline_sigma) 688 | 689 | # measure intensities 690 | stats = regionprops(labels, image) 691 | intensities = [r.mean_intensity for r in stats] 692 | 693 | # filter labels with low intensity 694 | new_label_indices, _, _ = sk_relabel_sequential((np.asarray(intensities) > minimum_intensity) * np.arange(labels.max())) 695 | new_label_indices = np.insert(new_label_indices, 0, 0) 696 | new_labels = np.take(np.asarray(new_label_indices, np.uint32), labels) 697 | 698 | return new_labels 699 | 700 | @register_function(menu="Image math > Sum images (numpy, nsbatwm)", factor1={'min': -1000000, 'max': 1000000}, factor2={'min': -1000000, 'max': 1000000}) 701 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 702 | @time_slicer 703 | def sum_images(image1: "napari.types.ImageData", image2: "napari.types.ImageData", factor1: float = 1, factor2: float = 1) -> "napari.types.ImageData": 704 | """Add two images""" 705 | return image1 * factor1 + image2 * factor2 706 | 707 | 708 | @register_function(menu="Image math > Multiply images (numpy, nsbatwm)") 709 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 710 | @time_slicer 711 | def multiply_images(image1: "napari.types.ImageData", image2: "napari.types.ImageData") -> "napari.types.ImageData": 712 | """Multiply two images""" 713 | return image1 * image2 714 | 715 | 716 | @register_function(menu="Image math > Divide images (numpy, nsbatwm)") 717 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 718 | @time_slicer 719 | def divide_images(image1: "napari.types.ImageData", image2: "napari.types.ImageData") -> "napari.types.ImageData": 720 | """Divide one image by another""" 721 | return image1 / image2 722 | 723 | 724 | @register_function(menu="Image math > Invert image (scikit-image, nsbatwm)") 725 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 726 | @time_slicer 727 | def invert_image(image: "napari.types.ImageData") -> "napari.types.ImageData": 728 | """Invert an image. The exact math behind depends on the image type. 729 | 730 | See also 731 | -------- 732 | .. [0] https://scikit-image.org/docs/dev/api/skimage.util.html?highlight=invert#skimage.util.invert 733 | """ 734 | from skimage import util 735 | return util.invert(image) 736 | 737 | 738 | @register_function(menu="Segmentation post-processing > Skeletonize (scikit-image, nsbatwm)") 739 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 740 | @time_slicer 741 | def skeletonize(image: "napari.types.LabelsData") -> "napari.types.LabelsData": 742 | """ 743 | Skeletonize labeled objects in an image. This can be useful to reduce objects such as neurons, roots and vessels 744 | with variable thickness to single pixel lines for further analysis. 745 | 746 | See also 747 | -------- 748 | .. [0] https://en.wikipedia.org/wiki/Topological_skeleton 749 | """ 750 | from skimage import morphology 751 | if image.max() == 1: 752 | return morphology.skeletonize(image) 753 | else: 754 | result = np.zeros(image.shape) 755 | for i in range(1, image.max() + 1): 756 | skeleton = morphology.skeletonize(image == i) 757 | result = skeleton * i + result 758 | return result.astype(int) 759 | 760 | 761 | @register_function(menu="Transforms > Rescale (scikit-image, nsbatwm)") 762 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 763 | @time_slicer 764 | def rescale(image: "napari.types.ImageData", 765 | scale_x: float = 1.0, 766 | scale_y: float = 1.0, 767 | scale_z: float = 1.0) -> "napari.types.ImageData": 768 | """ 769 | Rescale an image by a given set of scale factors. 770 | 771 | Parameters 772 | ---------- 773 | image : ImageData 774 | scale_x : float, optional 775 | factor by which to scale the image along the x axis. The default is 1.0. 776 | scale_y : float, optional 777 | factor by which to scale the image along the y dimension. The default is 1.0. 778 | scale_z : float, optional 779 | factor by which to scale the image along the z dimension. The default is 1.0. 780 | 781 | Returns 782 | ------- 783 | ImageData 784 | 785 | See Also 786 | -------- 787 | https://scikit-image.org/docs/stable/api/skimage.transform.html#rescale 788 | """ 789 | from skimage import transform 790 | 791 | if len(image.shape) == 3: 792 | scale_factors = np.asarray([scale_z, scale_y, scale_x]) 793 | elif len(image.shape) == 2: 794 | scale_factors = np.asarray([scale_y, scale_x]) 795 | else: 796 | raise ValueError("Rescaling only supported for 2D and 3D images") 797 | 798 | return transform.rescale(image, scale=scale_factors, preserve_range=True) 799 | 800 | 801 | @register_function(menu="Transforms > Resize (scikit-image, nsbatwm)") 802 | @time_slicer 803 | def resize(image: "napari.types.ImageData", 804 | new_width: int = 10.0, 805 | new_height: int = 10.0, 806 | new_depth: int = 10.0) -> "napari.types.ImageData": 807 | """ 808 | Rescale an image to fit in a given new size. 809 | 810 | Parameters 811 | ---------- 812 | image : ImageData 813 | new_width : int, optional 814 | factor by which to scale the image along the x axis. The default is 1.0. 815 | new_height : int, optional 816 | factor by which to scale the image along the y dimension. The default is 1.0. 817 | new_depth : int, optional 818 | factor by which to scale the image along the z dimension. The default is 1.0. 819 | 820 | Returns 821 | ------- 822 | ImageData 823 | 824 | See Also 825 | -------- 826 | https://scikit-image.org/docs/stable/api/skimage.transform.html#resize 827 | """ 828 | from skimage import transform 829 | 830 | if len(image.shape) == 3: 831 | output_shape = np.asarray([new_depth, new_height, new_width]) 832 | elif len(image.shape) == 2: 833 | output_shape = np.asarray([new_height, new_width]) 834 | else: 835 | raise ValueError("Resizing only supported for 2D and 3D images") 836 | 837 | return transform.resize(image, output_shape, preserve_range=True) 838 | 839 | 840 | @register_function(menu="Utilities > Manually merge labels (nsbatwm)") 841 | def Manually_merge_labels(labels_layer: "napari.layers.Labels", points_layer: "napari.layers.Points", viewer : "napari.Viewer"): 842 | if points_layer is None: 843 | points_layer = viewer.add_points([]) 844 | points_layer.mode = 'ADD' 845 | return 846 | labels = np.asarray(labels_layer.data) 847 | points = points_layer.data 848 | 849 | label_ids = [labels.item(tuple([int(j) for j in i])) for i in points] 850 | 851 | # replace labels with minimum of the selected labels 852 | new_label_id = min(label_ids) 853 | for l in label_ids: 854 | if l != new_label_id: 855 | labels[labels == l] = new_label_id 856 | 857 | labels_layer.data = labels 858 | points_layer.data = [] 859 | 860 | @register_function(menu="Utilities > Manually split labels (nsbatwm)") 861 | def Manually_split_labels(labels_layer: "napari.layers.Labels", points_layer: "napari.layers.Points", viewer: "napari.Viewer"): 862 | if points_layer is None: 863 | points_layer = viewer.add_points([]) 864 | points_layer.mode = 'ADD' 865 | return 866 | 867 | labels = np.asarray(labels_layer.data) 868 | points = points_layer.data 869 | 870 | label_ids = [labels.item(tuple([int(j) for j in i])) for i in points] 871 | 872 | # make a binary image first 873 | binary = np.zeros(labels.shape, dtype=bool) 874 | new_label_id = min(label_ids) 875 | for l in label_ids: 876 | binary[labels == l] = True 877 | 878 | # origin: https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_watershed.html 879 | from scipy import ndimage as ndi 880 | from skimage.segmentation import watershed 881 | #from skimage.feature import peak_local_max 882 | 883 | #distance = ndi.distance_transform_edt(binary) 884 | #coords = peak_local_max(distance, footprint=np.ones((3, 3)), labels=binary) 885 | mask = np.zeros(labels.shape, dtype=bool) 886 | for i in points: 887 | #mask[tuple(points)] = True 888 | mask[tuple([int(j) for j in i])] = True 889 | 890 | markers, _ = ndi.label(mask) 891 | new_labels = watershed(binary, markers, mask=binary) 892 | labels[binary] = new_labels[binary] + labels.max() 893 | 894 | labels_layer.data = labels 895 | points_layer.data = [] 896 | 897 | 898 | @register_function(menu="Filtering / noise removal > Butterworth (scikit-image, nsbatwm)") 899 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 900 | @time_slicer 901 | def butterworth(image: "napari.types.ImageData", cutoff_frequency_ratio: float = 0.005, high_pass: bool = False, 902 | order: float = 2) -> "napari.types.ImageData": 903 | """Apply a Butterworth filter to enhance high or low frequency features. 904 | 905 | This filter is defined in the Fourier domain. 906 | 907 | Parameters 908 | ---------- 909 | image : (M[, N[, ..., P]][, C]) ndarray 910 | Input image. 911 | cutoff_frequency_ratio : float, optional 912 | Determines the position of the cut-off relative to the shape of the 913 | FFT. 914 | high_pass : bool, optional 915 | Whether to perform a high pass filter. If False, a low pass filter is 916 | performed. 917 | order : float, optional 918 | Order of the filter which affects the slope near the cut-off. Higher 919 | order means steeper slope in frequency space. 920 | 921 | See also 922 | -------- 923 | ..[0] https://scikit-image.org/docs/stable/api/skimage.filters.html#skimage.filters.butterworth 924 | """ 925 | from skimage.filters import butterworth as skimage_butterworth 926 | return skimage_butterworth(image, cutoff_frequency_ratio, high_pass, order) 927 | 928 | @register_function(menu="Segmentation post-processing > Relabel sequential (scikit-image, nsbatwm)") 929 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 930 | @time_slicer 931 | def relabel_sequential(label_image:"napari.types.LabelsData") -> "napari.types.LabelsData": 932 | """Relabel a label image sequentially""" 933 | return sk_relabel_sequential(label_image)[0] 934 | 935 | 936 | @register_function(menu="Utilities > Extract slice (nsbatwm)") 937 | @register_function(menu="Transforms > Extract slice (nsbatwm)") 938 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 939 | @time_slicer 940 | def extract_slice(image:"napari.types.ImageData", slice_index:int = 0, axis:int = 0) -> "napari.types.ImageData": 941 | """Extract (take) a slice from a stack. 942 | 943 | See also 944 | -------- 945 | ..[0] https://numpy.org/doc/stable/reference/generated/numpy.take.html 946 | """ 947 | return np.take(image, slice_index, axis=axis) 948 | 949 | 950 | @register_function(menu="Transforms > Sub-sample (nsbatwm)") 951 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 952 | @time_slicer 953 | def sub_sample(image:"napari.types.ImageData", sample_x: int = 1, sample_y: int = 1, sample_z: int = 1) -> "napari.types.ImageData": 954 | """Sample every nth pixel in x, y and z.""" 955 | if len(image.shape) == 2: 956 | return image[::sample_y, ::sample_x] 957 | elif len(image.shape) == 3: 958 | return image[::sample_z, ::sample_y, ::sample_x] 959 | else: 960 | raise ValueError("Sub-sampling only supported for 2D and 3D images") 961 | 962 | 963 | @register_function(menu="Transforms > Remove axes of length 1 (numpy.squeeze, nsbatwm)") 964 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 965 | @time_slicer 966 | def squeeze(image:"napari.types.ImageData") -> "napari.types.ImageData": 967 | return np.squeeze(image) 968 | 969 | 970 | @register_function(menu="Filtering / background removal > Grayscale erosion (scikit-image, nsbatwm)") 971 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 972 | @time_slicer 973 | def grayscale_erosion(image: "napari.types.ImageData", radius: int = 1) -> "napari.types.ImageData": 974 | """ 975 | Applies grayscale erosion to an image, using a disk/sphere footprint with the given radius. 976 | 977 | See also 978 | -------- 979 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.erosion 980 | """ 981 | 982 | footprint = _generate_disk_footprint(radius, image.ndim) 983 | return erosion(image, footprint=footprint) 984 | 985 | 986 | @register_function(menu="Segmentation post-processing > Binary erosion (scikit-image, nsbatwm)") 987 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 988 | @time_slicer 989 | def binary_erosion(binary_image: "napari.types.LabelsData", radius: int = 1) -> "napari.types.LabelsData": 990 | """ 991 | Applies binary erosion to a binary image, using a disk/sphere footprint with the given radius. 992 | 993 | See also 994 | -------- 995 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.binary_erosion 996 | """ 997 | 998 | footprint = _generate_disk_footprint(radius, binary_image.ndim) 999 | return sk_binary_erosion(binary_image, footprint=footprint) 1000 | 1001 | 1002 | @register_function(menu="Filtering / background removal > Grayscale dilation (scikit-image, nsbatwm)") 1003 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 1004 | @time_slicer 1005 | def grayscale_dilation(image: "napari.types.ImageData", radius: int = 1) -> "napari.types.ImageData": 1006 | """ 1007 | Applies grayscale dilation to an image, using a disk/sphere footprint with the given radius. 1008 | 1009 | See also 1010 | -------- 1011 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.dilation 1012 | """ 1013 | 1014 | footprint = _generate_disk_footprint(radius, image.ndim) 1015 | return dilation(image, footprint=footprint) 1016 | 1017 | 1018 | @register_function(menu="Segmentation post-processing > Binary dilation (scikit-image, nsbatwm)") 1019 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 1020 | @time_slicer 1021 | def binary_dilation(binary_image: "napari.types.LabelsData", radius: int = 1) -> "napari.types.LabelsData": 1022 | """ 1023 | Applies binary dilation to a binary image, using a disk/sphere footprint with the given radius. 1024 | 1025 | See also 1026 | -------- 1027 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.binary_dilation 1028 | """ 1029 | 1030 | footprint = _generate_disk_footprint(radius, binary_image.ndim) 1031 | return sk_binary_dilation(binary_image, footprint=footprint) 1032 | 1033 | 1034 | @register_function(menu="Filtering / background removal > Grayscale opening (scikit-image, nsbatwm)") 1035 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 1036 | @time_slicer 1037 | def grayscale_opening(image: "napari.types.ImageData", radius: int = 1) -> "napari.types.ImageData": 1038 | """ 1039 | Applies grayscale opening to an image, using a disk/sphere footprint with the given radius. 1040 | 1041 | See also 1042 | -------- 1043 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.opening 1044 | """ 1045 | 1046 | footprint = _generate_disk_footprint(radius, image.ndim) 1047 | return opening(image, footprint=footprint) 1048 | 1049 | 1050 | @register_function(menu="Segmentation post-processing > Binary opening (scikit-image, nsbatwm)") 1051 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 1052 | @time_slicer 1053 | def binary_opening(binary_image: "napari.types.LabelsData", radius: int = 1) -> "napari.types.LabelsData": 1054 | """ 1055 | Applies binary opening to a binary image, using a disk/sphere footprint with the given radius. 1056 | 1057 | See also 1058 | -------- 1059 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.binary_opening 1060 | """ 1061 | 1062 | footprint = _generate_disk_footprint(radius, binary_image.ndim) 1063 | return sk_binary_opening(binary_image, footprint=footprint) 1064 | 1065 | 1066 | @register_function(menu="Filtering / background removal > Grayscale closing (scikit-image, nsbatwm)") 1067 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 1068 | @time_slicer 1069 | def grayscale_closing(image: "napari.types.ImageData", radius: int = 1) -> "napari.types.ImageData": 1070 | """ 1071 | Applies grayscale closing to an image, using a disk/sphere footprint with the given radius. 1072 | 1073 | See also 1074 | -------- 1075 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.closing 1076 | """ 1077 | 1078 | footprint = _generate_disk_footprint(radius, image.ndim) 1079 | return closing(image, footprint=footprint) 1080 | 1081 | 1082 | @register_function(menu="Segmentation post-processing > Binary closing (scikit-image, nsbatwm)") 1083 | @jupyter_displayable_output(library_name='nsbatwm', help_url='https://www.napari-hub.org/plugins/napari-segment-blobs-and-things-with-membranes') 1084 | @time_slicer 1085 | def binary_closing(binary_image: "napari.types.LabelsData", radius: int = 1) -> "napari.types.LabelsData": 1086 | """ 1087 | Applies binary opening to a binary image, using a disk/sphere footprint with the given radius. 1088 | 1089 | See also 1090 | -------- 1091 | ..[0] https://scikit-image.org/docs/stable/api/skimage.morphology.html#skimage.morphology.binary_closing 1092 | """ 1093 | 1094 | footprint = _generate_disk_footprint(radius, binary_image.ndim) 1095 | return sk_binary_closing(binary_image, footprint=footprint) 1096 | -------------------------------------------------------------------------------- /napari_segment_blobs_and_things_with_membranes/_bia_bob_plugins.py: -------------------------------------------------------------------------------- 1 | def list_bia_bob_plugins(): 2 | """List of function hints for bia_bob""" 3 | good_alternative_installed = False 4 | 5 | try: 6 | import pyclesperanto 7 | good_alternative_installed = True 8 | except: 9 | pass 10 | try: 11 | import pyclesperanto_prototype 12 | good_alternative_installed = True 13 | except: 14 | pass 15 | 16 | basic_hints = "" 17 | if not good_alternative_installed: 18 | basic_hints = """ 19 | 20 | * Applies Otsu's method to binarize an intensity image (also works with yen, isodata, li, mean, minimum, triangle instead of otsu). 21 | nsbatwm.threshold_otsu(image) 22 | 23 | * Segments blob-like structures using Voronoi-Otsu labeling. 24 | nsbatwm.voronoi_otsu_labeling(image, spot_sigma=4, outline_sigma=1) 25 | 26 | * Applies a Gaussian blur for noise reduction. 27 | nsbatwm.gaussian_blur(image, sigma=5) 28 | 29 | * Applies median filter to reduce noise while preserving edges. 30 | nsbatwm.median_filter(image, radius=5) 31 | 32 | * Smooth a label image using a local most popular intensity (mode) filter. 33 | nsbatwm.mode_filter(labels) 34 | 35 | * Removes background in an image using the top-hat filter. 36 | nsbatwm.white_tophat(image) 37 | 38 | * Applies local minimum filtering to an image (also works with maximum, and mean). 39 | nsbatwm.minimum_filter(image) 40 | 41 | * Subtracts background in an image using the rolling ball algorithm. 42 | nsbatwm.subtract_background(image) 43 | 44 | * Removes labeled objects touching image borders. 45 | nsbatwm.remove_labels_on_edges(label_image) 46 | 47 | * Expands labels by a specified distance. 48 | nsbatwm.expand_labels(label_image, distance=2) 49 | """ 50 | 51 | return f""" ## napari-segment-blobs-and-things-with-membranes (nsbatwm) 52 | nsbatwm is a Python library that processes images, mostly using the scikit-image library, but with simpler access. 53 | When you use it, you always start by importing the library: `import napari_segment_blobs_and_things_with_membranes as nsbatwm`. 54 | When asked for how to use nsbatwm, you can adapt one of the following code snippets: 55 | 56 | {basic_hints} 57 | 58 | * Splits touching objects in a binary image using an algorithm similar to the ImageJ watershed. 59 | nsbatwm.split_touching_objects(binary_image) 60 | 61 | * Labels connected components in a binary image. 62 | nsbatwm.connected_component_labeling(binary_image) 63 | 64 | * Applies seeded watershed segmentation using labeled objects, e.g. nuclei, and an image showing bright borders between objects such as cell membranes. 65 | nsbatwm.seeded_watershed(image, labeled_objects) 66 | 67 | * Applies a percentile filter. 68 | nsbatwm.percentile_filter(image) 69 | 70 | * Segments using seeded watershed with local minima as seeds. 71 | nsbatwm.local_minima_seeded_watershed(image, spot_sigma=10, outline_sigma=2) 72 | 73 | * Skeletonizes labeled objects. 74 | nsbatwm.skeletonize(image) 75 | """ -------------------------------------------------------------------------------- /napari_segment_blobs_and_things_with_membranes/_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/f0a392a079f45ea002d57ca25ee930848092046a/napari_segment_blobs_and_things_with_membranes/_tests/__init__.py -------------------------------------------------------------------------------- /napari_segment_blobs_and_things_with_membranes/_tests/test_function.py: -------------------------------------------------------------------------------- 1 | # from napari_segment_blobs_and_things_with_membranes import threshold, image_arithmetic 2 | 3 | # add your tests here... 4 | import numpy as np 5 | 6 | 7 | def test_something(): 8 | from napari_segment_blobs_and_things_with_membranes import gaussian_blur, \ 9 | subtract_background,\ 10 | threshold_otsu,\ 11 | threshold_yen,\ 12 | threshold_isodata,\ 13 | threshold_li,\ 14 | threshold_mean,\ 15 | threshold_minimum,\ 16 | threshold_triangle,\ 17 | binary_invert,\ 18 | split_touching_objects,\ 19 | connected_component_labeling,\ 20 | seeded_watershed, \ 21 | seeded_watershed_with_mask, \ 22 | voronoi_otsu_labeling, \ 23 | gauss_otsu_labeling,\ 24 | gaussian_laplace,\ 25 | median_filter, \ 26 | mode_filter, \ 27 | maximum_filter,\ 28 | minimum_filter,\ 29 | percentile_filter,\ 30 | black_tophat,\ 31 | white_tophat,\ 32 | morphological_gradient,\ 33 | local_minima_seeded_watershed,\ 34 | thresholded_local_minima_seeded_watershed,\ 35 | sum_images,\ 36 | multiply_images,\ 37 | divide_images,\ 38 | invert_image, \ 39 | skeletonize, \ 40 | rescale, \ 41 | resize, \ 42 | butterworth, \ 43 | extract_slice, \ 44 | sub_sample, \ 45 | squeeze, \ 46 | grayscale_erosion, \ 47 | binary_erosion, \ 48 | grayscale_dilation, \ 49 | binary_dilation, \ 50 | grayscale_opening, \ 51 | binary_opening, \ 52 | grayscale_closing, \ 53 | binary_closing 54 | 55 | import numpy as np 56 | 57 | image_2d = np.asarray([[0, 1, 2, 3], 58 | [2, 0, 1, 3], 59 | [2, 253, 1, 3], 60 | [255, 253, 1, 3]]) 61 | 62 | image_3d = np.ones((4, 4, 4)) 63 | 64 | for operation in [gaussian_blur, 65 | subtract_background, 66 | threshold_otsu, 67 | threshold_yen, 68 | threshold_isodata, 69 | threshold_li, 70 | threshold_mean, 71 | threshold_minimum, 72 | threshold_triangle, 73 | binary_invert, 74 | split_touching_objects, 75 | connected_component_labeling, 76 | voronoi_otsu_labeling, 77 | gauss_otsu_labeling, 78 | gaussian_laplace, 79 | median_filter, 80 | maximum_filter, 81 | minimum_filter, 82 | percentile_filter, 83 | black_tophat, 84 | white_tophat, 85 | morphological_gradient, 86 | local_minima_seeded_watershed, 87 | invert_image, 88 | rescale, 89 | resize, 90 | butterworth, 91 | extract_slice, 92 | sub_sample, 93 | squeeze]: 94 | 95 | print(operation) 96 | 97 | operation(image_2d) 98 | 99 | for operation in [ 100 | seeded_watershed, 101 | sum_images, 102 | multiply_images, 103 | divide_images]: 104 | 105 | print(operation) 106 | 107 | operation(image_2d, image_2d) 108 | 109 | for operation in [ 110 | grayscale_erosion, 111 | binary_erosion, 112 | grayscale_dilation, 113 | binary_dilation, 114 | grayscale_opening, 115 | binary_opening, 116 | grayscale_closing, 117 | binary_closing 118 | ]: 119 | for image in (image_2d, image_3d): 120 | labels = image > 0 121 | print(f"{operation} with {labels.ndim}d image") 122 | operation(labels) 123 | 124 | skeletonize(image_2d > 0) 125 | 126 | seeded_watershed_with_mask(image_2d, image_2d, image_2d) 127 | 128 | mode_filter(image_2d.astype(int)) 129 | 130 | def test_remove_labels_on_edges_sequential_labeling(): 131 | image = np.asarray([ 132 | [1,2,3], 133 | [4,5,6], 134 | [7,7,7], 135 | ]) 136 | 137 | reference = np.asarray([ 138 | [0,0,0], 139 | [0,1,0], 140 | [0,0,0], 141 | ]) 142 | 143 | from napari_segment_blobs_and_things_with_membranes import remove_labels_on_edges 144 | result = remove_labels_on_edges(image) 145 | 146 | print(result) 147 | print(reference) 148 | 149 | assert np.array_equal(result, reference) 150 | 151 | def test_connected_component_labeling_sequential_labeling(): 152 | image = np.asarray([ 153 | [1, 0, 1, 0, 1], 154 | [0, 0, 0, 0, 0], 155 | [1, 0, 1, 0, 1], 156 | [0, 0, 0, 0, 0], 157 | [1, 0, 1, 0, 1], 158 | ]) 159 | 160 | reference = np.asarray([ 161 | [0,0,0,0,0], 162 | [0,0,0,0,0], 163 | [0,0,1,0,0], 164 | [0,0,0,0,0], 165 | [0,0,0,0,0], 166 | ]) 167 | 168 | from napari_segment_blobs_and_things_with_membranes import connected_component_labeling 169 | result = connected_component_labeling(image, exclude_on_edges=True) 170 | 171 | print(result) 172 | print(reference) 173 | 174 | assert np.array_equal(result, reference) 175 | 176 | 177 | def test_relabel_sequential(): 178 | image = np.asarray([[0, 0, 0, 1, 3, 4]]) 179 | reference = np.asarray([[0, 0, 0, 1, 2, 3]]) 180 | 181 | from napari_segment_blobs_and_things_with_membranes import relabel_sequential 182 | result = relabel_sequential(image) 183 | 184 | print(result) 185 | print(reference) 186 | 187 | assert np.array_equal(result, reference) 188 | 189 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # The requirements listed here are for development. 2 | # Requirements of your napari plugin should be listed in setup.cfg 3 | # See also: https://caremad.io/posts/2013/07/setup-vs-requirement/ 4 | 5 | scikit-image 6 | scipy 7 | napari-tools-menu>=0.1.17 8 | napari-time-slicer 9 | napari-assistant 10 | stackview>=0.3.2 11 | -e . 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = napari-segment-blobs-and-things-with-membranes 3 | version = 0.3.12 4 | author = Robert Haase 5 | author_email = robert.haase@tu-dresden.de 6 | url = https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes 7 | license = BSD-3-Clause 8 | description = A plugin based on scikit-image for segmenting nuclei and cells based on fluorescent microscopy images with high intensity in nuclei and/or membranes 9 | long_description = file: README.md 10 | long_description_content_type = text/markdown 11 | classifiers = 12 | Development Status :: 3 - Alpha 13 | Intended Audience :: Science/Research 14 | Framework :: napari 15 | Topic :: Scientific/Engineering :: Image Processing 16 | Topic :: Scientific/Engineering :: Information Analysis 17 | Programming Language :: Python 18 | Programming Language :: Python :: 3 19 | Programming Language :: Python :: 3.8 20 | Programming Language :: Python :: 3.9 21 | Programming Language :: Python :: 3.10 22 | Operating System :: OS Independent 23 | License :: OSI Approved :: BSD License 24 | project_urls = 25 | Bug Tracker = https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/issues 26 | Documentation = https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes#README.md 27 | Source Code = https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes 28 | User Support = https://github.com/haesleinhuepf/napari-segment-blobs-and-things-with-membranes/issues 29 | 30 | [options] 31 | packages = find: 32 | python_requires = >=3.8 33 | 34 | # add your package requirements here 35 | install_requires = 36 | napari-plugin-engine>=0.1.4 37 | numpy 38 | scikit-image 39 | scipy 40 | napari-tools-menu>=0.1.17 41 | napari-time-slicer>=0.4.8 42 | napari-assistant 43 | stackview>=0.9.1 44 | 45 | [options.entry_points] 46 | napari.plugin = 47 | napari-segment-blobs-and-things-with-membranes = napari_segment_blobs_and_things_with_membranes 48 | bia_bob_plugins = 49 | nsbatwm_plugin = napari_segment_blobs_and_things_with_membranes._bia_bob_plugins:list_bia_bob_plugins -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import setup 4 | 5 | setup() 6 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # For more information about tox, see https://tox.readthedocs.io/en/latest/ 2 | [tox] 3 | envlist = py{38,39,310}-{linux,macos,windows} 4 | 5 | [gh-actions] 6 | python = 7 | 3.8: py38 8 | 3.9: py39 9 | 3.10: py310 10 | 11 | [gh-actions:env] 12 | PLATFORM = 13 | ubuntu-latest: linux 14 | macos-latest: macos 15 | windows-latest: windows 16 | 17 | [testenv] 18 | platform = 19 | macos: darwin 20 | linux: linux 21 | windows: win32 22 | passenv = 23 | CI 24 | GITHUB_ACTIONS 25 | DISPLAY 26 | XAUTHORITY 27 | NUMPY_EXPERIMENTAL_ARRAY_FUNCTION 28 | PYVISTA_OFF_SCREEN 29 | deps = 30 | pytest # https://docs.pytest.org/en/latest/contents.html 31 | pytest-cov # https://pytest-cov.readthedocs.io/en/latest/ 32 | pytest-xvfb ; sys_platform == 'linux' 33 | # you can remove these if you don't use them 34 | napari 35 | magicgui 36 | pytest-qt 37 | qtpy 38 | pyqt5 39 | commands = pytest -v --color=yes --cov=napari_segment_blobs_and_things_with_membranes --cov-report=xml 40 | --------------------------------------------------------------------------------