├── .github └── workflows │ └── build.yml ├── .gitignore ├── .mailmap ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── bin ├── check.sh ├── clean.sh ├── fmt.sh ├── lint.sh ├── setup.sh └── test.sh ├── codecov.yml ├── conftest.py ├── doc ├── 01-Starting-PyImageJ.ipynb ├── 02-Working-with-Java-classes-and-Python.ipynb ├── 03-Sending-Data-to-Java.ipynb ├── 04-Retrieving-Data-from-Java.ipynb ├── 05-Convenience-methods-of-PyImageJ.ipynb ├── 06-Working-with-Images.ipynb ├── 07-Running-Macros-Scripts-and-Plugins.ipynb ├── 08-Discover-and-run-ImageJ-commands.ipynb ├── 09-Working-with-Large-Images.ipynb ├── 10-Using-ImageJ-Ops.ipynb ├── 11-Working-with-the-original-ImageJ.ipynb ├── 12-Troubleshooting.ipynb ├── Cellpose-StarDist-Segmentation.ipynb ├── Classic-Segmentation.ipynb ├── Deconvolution.ipynb ├── Development.md ├── GLCM.ipynb ├── Headless.md ├── Initialization.md ├── Install.md ├── Makefile ├── Puncta-Segmentation.ipynb ├── README.md ├── Troubleshooting.md ├── api.rst ├── cellprofiler │ ├── MakeBBBC030GT_ManuallyFix.cppipe │ ├── README.md │ ├── RunImageJMacroWorkflow │ │ ├── RunImageJBallWithMacro.cppipe │ │ └── test_ball_macro.py │ ├── RunImageJScriptRollingBall │ │ ├── RunImageJBall.cppipe │ │ └── test_ball.bsh │ ├── RunImageJScriptSegmentation │ │ ├── RunImageJScriptSegmentation_withGT.cppipe │ │ ├── classifier.model │ │ └── test.bsh │ └── figure.svg ├── conf.py ├── doc-images │ ├── imagej_ui_macro_recorder.png │ ├── logo.svg │ ├── macro_recorder_find_maxima.png │ ├── test_still_analyze_particles.png │ └── test_still_find_maxima.png ├── examples │ ├── README.md │ ├── blob_detection_interactive.py │ ├── blob_detection_xvfb.py │ ├── blob_interactive.rst │ └── blob_xvfb.rst ├── imagej-ops-use-cases.rst ├── index.rst ├── integration-use-cases.rst ├── make.bat ├── notebooks.rst ├── other_use_cases.rst ├── requirements.txt ├── sample-data │ ├── test_image.tif │ ├── test_still.tif │ ├── test_still_stack.tif │ └── test_timeseries.tif └── segmentation-use-cases.rst ├── pyproject.toml ├── src └── imagej │ ├── __init__.py │ ├── _java.py │ ├── convert.py │ ├── dims.py │ ├── doctor.py │ ├── images.py │ └── stack.py └── tests ├── test_callbacks.py ├── test_ctypes.py ├── test_doctor.py ├── test_fiji.py ├── test_image_conversion.py ├── test_labeling.py ├── test_legacy.py ├── test_ops.py └── test_rai_arraylike.py /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "*-[0-9]+.*" 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | build: 15 | name: ${{matrix.os}} - ${{matrix.python-version}} 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, windows-latest, macos-latest] 20 | python-version: ["3.9", "3.13"] 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{matrix.python-version}} 27 | 28 | - name: Install uv 29 | run: | 30 | python -m pip install --upgrade pip 31 | python -m pip install uv 32 | 33 | - name: Test PyImageJ 34 | shell: bash 35 | run: | 36 | bin/test.sh 37 | 38 | ensure-clean-code: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions/setup-python@v3 43 | 44 | - name: Install uv 45 | run: | 46 | python -m pip install --upgrade pip 47 | python -m pip install uv 48 | 49 | - name: Lint code 50 | run: | 51 | uv run ruff check 52 | uv run ruff format --check 53 | 54 | - name: Validate pyproject.toml 55 | run: | 56 | uv run validate-pyproject pyproject.toml 57 | 58 | - name: Analyze code coverage 59 | run: | 60 | bin/test.sh tests --cov-report=xml --cov=. 61 | 62 | - name: Upload coverage to Codecov 63 | uses: codecov/codecov-action@v2 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | /.eggs/ 4 | /.idea/ 5 | /.pytest_cache/ 6 | /build/ 7 | /dist/ 8 | /doc/_build/ 9 | /src/pyimagej.egg-info/ 10 | /Fiji 11 | /Fiji.app 12 | /fiji-*.zip 13 | /uv.lock 14 | MANIFEST 15 | .vscode/settings.json 16 | .vscode/launch.json 17 | .gitignore 18 | 19 | # Jupyter # 20 | *.ipynb_checkpoints* 21 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Bin Li 2 | Claire McQuin 3 | Edward Evans 4 | Edward Evans 5 | Jan Eglinger 6 | Mark Hiner 7 | Michael Pinkert 8 | Philipp Hanslovsky 9 | Yang Liu 10 | Yang Liu <32599769+kkangle@users.noreply.github.com> 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # ruff version 4 | rev: v0.9.1 5 | hooks: 6 | # run the linter 7 | - id: ruff 8 | # run the formatter 9 | - id: ruff-format 10 | - repo: https://github.com/abravalheri/validate-pyproject 11 | rev: v0.10.1 12 | hooks: 13 | - id: validate-pyproject 14 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | build: 8 | os: ubuntu-22.04 9 | tools: 10 | python: "3.11" 11 | 12 | # Build documentation in the docs/ directory with Sphinx 13 | sphinx: 14 | configuration: doc/conf.py 15 | 16 | # If using Sphinx, optionally build your docs in additional formats such as PDF 17 | # formats: 18 | # - pdf 19 | 20 | # Optionally declare the Python requirements required to build your docs 21 | python: 22 | install: 23 | - requirements: doc/requirements.txt 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 - 2020 Board of Regents of the University of 2 | Wisconsin-Madison. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Metadata 2 | include LICENSE.txt 3 | include MANIFEST.in 4 | include Makefile 5 | include README.md 6 | include conftest.py 7 | include pyproject.toml 8 | 9 | # Conda Environment Files 10 | include environment.yml 11 | include dev-environment.yml 12 | 13 | # Directory inclusion 14 | graft src 15 | graft tests 16 | graft bin 17 | 18 | # Directory exclusion 19 | prune doc 20 | 21 | # File inclusion 22 | include doc/*.md 23 | 24 | # File exclusion 25 | global-exclude __pycache__ 26 | global-exclude *.py[doc] 27 | global-exclude *.ipynb 28 | 29 | exclude doc/**/README.md 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: 2 | @echo "Available targets:\n\ 3 | clean - remove build files and directories\n\ 4 | lint - run code formatters and linters\n\ 5 | test - run automated test suite\n\ 6 | docs - generate documentation site\n\ 7 | dist - generate release archives\n\ 8 | " 9 | 10 | clean: 11 | bin/clean.sh 12 | 13 | lint: 14 | bin/lint.sh 15 | 16 | fmt: 17 | bin/fmt.sh 18 | 19 | test: 20 | bin/test.sh 21 | 22 | docs: 23 | cd doc && $(MAKE) html 24 | 25 | dist: clean 26 | uv run python -m build 27 | 28 | .PHONY: tests 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyImageJ: Python wrapper for ImageJ2 2 | 3 | [![Image.sc Forum](https://img.shields.io/badge/dynamic/json.svg?label=forum&url=https%3A%2F%2Fforum.image.sc%2Ftags%2Fpyimagej.json&query=%24.topic_list.tags.0.topic_count&colorB=brightgreen&suffix=%20topics&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABPklEQVR42m3SyyqFURTA8Y2BER0TDyExZ+aSPIKUlPIITFzKeQWXwhBlQrmFgUzMMFLKZeguBu5y+//17dP3nc5vuPdee6299gohUYYaDGOyyACq4JmQVoFujOMR77hNfOAGM+hBOQqB9TjHD36xhAa04RCuuXeKOvwHVWIKL9jCK2bRiV284QgL8MwEjAneeo9VNOEaBhzALGtoRy02cIcWhE34jj5YxgW+E5Z4iTPkMYpPLCNY3hdOYEfNbKYdmNngZ1jyEzw7h7AIb3fRTQ95OAZ6yQpGYHMMtOTgouktYwxuXsHgWLLl+4x++Kx1FJrjLTagA77bTPvYgw1rRqY56e+w7GNYsqX6JfPwi7aR+Y5SA+BXtKIRfkfJAYgj14tpOF6+I46c4/cAM3UhM3JxyKsxiOIhH0IO6SH/A1Kb1WBeUjbkAAAAAElFTkSuQmCC)](https://forum.image.sc/tag/pyimagej) 4 | [![Build Status](https://github.com/imagej/pyimagej/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/imagej/pyimagej/actions/workflows/build.yml) 5 | [![codecov](https://codecov.io/gh/imagej/pyimagej/branch/main/graph/badge.svg?token=9z6AYgHINK)](https://codecov.io/gh/imagej/pyimagej) 6 | [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/imagej/pyimagej/main) 7 | 8 | PyImageJ provides a set of wrapper functions for integration between [ImageJ2] 9 | and Python. It also supports the original [ImageJ] API and data structures. 10 | 11 | A major advantage of this approach is the ability to combine ImageJ and ImageJ2 12 | with other tools available from the Python software ecosystem, including NumPy, 13 | SciPy, scikit-image, [CellProfiler], [OpenCV], [ITK] and many more. 14 | 15 | ## Quick Start 16 | 17 | Jump into the [documentation and tutorials](https://pyimagej.readthedocs.io/) to get started! 18 | 19 | ## System Requirements 20 | 21 | ### Hardware Requirements 22 | 23 | PyImageJ requires at minimum a standard computer with enough RAM and CPU 24 | performance to support the workflow operations defined by the user. While 25 | PyImageJ will run on a range of hardware, we recommend the following RAM 26 | and CPU specifications: 27 | 28 | - RAM: >= 2 GB (64 MB minimum) 29 | - CPU: >= 1 core 30 | 31 | Notably, PyImageJ can be installed and used on server infrastructure for 32 | large scale image processing. 33 | 34 | ### OS Requirements 35 | 36 | PyImageJ has been tested on the following operating systems: 37 | 38 | - Linux (Ubuntu 20.04 LTS) 39 | - Windows 40 | - macOS 41 | 42 | ### Software Requirements 43 | 44 | PyImageJ requires the following packages: 45 | 46 | * Python >= 3.9 47 | * [imglyb] >= 2.1.0 48 | * [jgo] >= 1.0.3 49 | * [JPype] >= 1.3.0 50 | * [labeling] >= 0.1.14 51 | * [matplotlib] \(optional, for `ij.py.show` function only) 52 | * [NumPy] 53 | * [scyjava] >= 1.12.0 54 | * [xarray] 55 | 56 | PyImageJ will not function properly if dependency versions are too old. 57 | 58 | ## Installation 59 | 60 | On Mac and Linux, PyImageJ can be installed using [Conda]+[Mamba]. Here is 61 | how to create and activate a new conda environment with PyImageJ available: 62 | 63 | ``` 64 | conda install mamba -n base -c conda-forge 65 | mamba create -n pyimagej -c conda-forge pyimagej 66 | conda activate pyimagej 67 | ``` 68 | 69 | Alternately, you can `pip install pyimagej`. 70 | 71 | Installation time takes approximately 20 seconds. Initializing PyImageJ 72 | takes an additional ~30 seconds to ~2-3 minutes (depending on bandwidth) 73 | while it downloads and caches the needed Java libraries. 74 | 75 | For detailed installation instructions and requirements, see 76 | [Installation](https://pyimagej.readthedocs.io/en/latest/Install.html). 77 | 78 | ## Usage 79 | 80 | The first step when using PyImageJ is to create an ImageJ2 gateway. 81 | This gateway can point to any official release of ImageJ2 or to a local 82 | installation. Using the gateway, you have full access to the ImageJ2 API, 83 | plus utility functions for translating between Python (NumPy, xarray, 84 | pandas, etc.) and Java (ImageJ2, ImgLib2, etc.) structures. 85 | 86 | For instructions on how to start up the gateway for various settings, see 87 | [How to initialize PyImageJ](https://pyimagej.readthedocs.io/en/latest/Initialization.html). 88 | 89 | Here is an example of opening an image using ImageJ2 and displaying it: 90 | 91 | ```python 92 | # Create an ImageJ2 gateway with the newest available version of ImageJ2. 93 | import imagej 94 | ij = imagej.init() 95 | 96 | # Load an image. 97 | image_url = 'https://imagej.net/images/clown.jpg' 98 | jimage = ij.io().open(image_url) 99 | 100 | # Convert the image from ImageJ2 to xarray, a package that adds 101 | # labeled datasets to numpy (http://xarray.pydata.org/en/stable/). 102 | image = ij.py.from_java(jimage) 103 | 104 | # Display the image (backed by matplotlib). 105 | ij.py.show(image, cmap='gray') 106 | ``` 107 | 108 | For more, see the 109 | [tutorial notebooks](https://pyimagej.readthedocs.io/en/latest/notebooks.html). 110 | 111 | ## API Reference 112 | 113 | For a complete reference of the PyImageJ API, please see the 114 | [API Reference](https://pyimagej.readthedocs.io/en/latest/api.html). 115 | 116 | ## Getting Help 117 | 118 | [The Scientific Community Image Forum](https://forum.image.sc/tag/pyimagej) 119 | is the best place to get general help on usage of PyImageJ, ImageJ2, and any 120 | other image processing tasks. Bugs can be reported to the PyImageJ GitHub 121 | [issue tracker](https://github.com/imagej/pyimagej/issues). 122 | 123 | ## Contributing 124 | 125 | All contributions, reports, and ideas are welcome. Contribution is done 126 | via pull requests onto the pyimagej repository. 127 | 128 | Most development discussion takes place on the pyimagej 129 | [GitHub repository](https://github.com/imagej/pyimagej). 130 | You can also reach the developers at the 131 | [Image.sc Zulip chat](https://imagesc.zulipchat.com/#narrow/stream/328100-scyjava). 132 | 133 | For details on how to develop the PyImageJ codebase, see 134 | [Development.md](https://pyimagej.readthedocs.io/en/latest/Development.html). 135 | 136 | ------------------------------------------------------------------------------ 137 | 138 | [ImageJ2]: https://imagej.net/software/imagej2 139 | [ImageJ]: https://imagej.net/software/imagej 140 | [CellProfiler]: https://imagej.net/software/cellprofiler 141 | [OpenCV]: https://imagej.net/software/opencv 142 | [ITK]: https://imagej.net/software/itk 143 | [imglyb]: https://github.com/imglib/imglyb 144 | [jgo]: https://github.com/scijava/jgo 145 | [JPype]: https://jpype.readthedocs.io/ 146 | [labeling]: https://github.com/Labelings/Labeling 147 | [matplotlib]: https://matplotlib.org/ 148 | [NumPy]: https://numpy.org/ 149 | [scyjava]: https://github.com/scijava/scyjava 150 | [xarray]: https://docs.xarray.dev/ 151 | [Conda]: https://conda.io/ 152 | [Mamba]: https://mamba.readthedocs.io/ 153 | -------------------------------------------------------------------------------- /bin/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case "$CONDA_PREFIX" in 4 | */pyimagej-dev) 5 | ;; 6 | *) 7 | echo "Please run 'make setup' and then 'mamba activate pyimagej-dev' first." 8 | exit 1 9 | ;; 10 | esac 11 | -------------------------------------------------------------------------------- /bin/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname "$0") 4 | cd "$dir/.." 5 | 6 | find . -name __pycache__ -type d | while read d 7 | do rm -rfv "$d" 8 | done 9 | rm -rfv .pytest_cache build dist doc/_build doc/rtd src/*.egg-info 10 | -------------------------------------------------------------------------------- /bin/fmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname "$0") 4 | cd "$dir/.." 5 | 6 | exitCode=0 7 | uv run ruff check --fix 8 | code=$?; test $code -eq 0 || exitCode=$code 9 | uv run ruff format 10 | code=$?; test $code -eq 0 || exitCode=$code 11 | exit $exitCode 12 | -------------------------------------------------------------------------------- /bin/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname "$0") 4 | cd "$dir/.." 5 | 6 | exitCode=0 7 | uv run ruff check 8 | code=$?; test $code -eq 0 || exitCode=$code 9 | uv run ruff format --check 10 | code=$?; test $code -eq 0 || exitCode=$code 11 | uv run validate-pyproject pyproject.toml 12 | code=$?; test $code -eq 0 || exitCode=$code 13 | exit $exitCode 14 | -------------------------------------------------------------------------------- /bin/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname "$0") 4 | cd "$dir/.." 5 | 6 | mamba env create -f dev-environment.yml 7 | -------------------------------------------------------------------------------- /bin/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname "$0") 4 | cd "$dir/.." 5 | 6 | modes=" 7 | | Testing ImageJ2 + original ImageJ |--legacy=true 8 | | Testing ImageJ2 standalone |--legacy=false 9 | | Testing Fiji Is Just ImageJ(2) |--ij=sc.fiji:fiji 10 | | Testing local Fiji-Stable |--ij=Fiji.app 11 | | Testing local Fiji-Latest |--ij=Fiji 12 | | Testing ImageJ2 version 2.10.0 |--ij=2.10.0 13 | | Testing ImageJ2 version 2.14.0 |--ij=2.14.0 14 | " 15 | 16 | if [ ! -d Fiji.app ] 17 | then 18 | # No locally available Fiji-Stable; download one. 19 | echo "-- Downloading and unpacking Fiji-Stable --" 20 | curl -fsLO https://downloads.imagej.net/fiji/stable/fiji-stable-portable-nojava.zip 21 | unzip fiji-stable-portable-nojava.zip 22 | echo 23 | fi 24 | 25 | if [ ! -d Fiji ] 26 | then 27 | # No locally available Fiji-Latest; download one. 28 | echo "-- Downloading and unpacking Fiji-Latest --" 29 | curl -fsLO https://downloads.imagej.net/fiji/latest/fiji-latest-portable-nojava.zip 30 | unzip fiji-latest-portable-nojava.zip 31 | echo 32 | fi 33 | 34 | echo "$modes" | while read mode 35 | do 36 | test "$mode" || continue 37 | msg="${mode%|*}|" 38 | flag=${mode##*|} 39 | for java in 8 21 40 | do 41 | # Fiji-Latest requires Java 21; skip Fiji-Latest + Java 8. 42 | echo "$msg" | grep -q Fiji-Latest && test "$java" -eq 8 && continue 43 | 44 | echo "-------------------------------------" 45 | echo "$msg" 46 | printf "| < OpenJDK %2s > |\n" "$java" 47 | echo "-------------------------------------" 48 | if [ $# -gt 0 ] 49 | then 50 | uv run python -m pytest -p no:faulthandler $flag --java $java $@ 51 | else 52 | uv run python -m pytest -p no:faulthandler $flag --java $java tests 53 | fi 54 | code=$? 55 | if [ $code -eq 0 ] 56 | then 57 | echo "==> TESTS PASSED with code 0" 58 | else 59 | # HACK: `while read` creates a subshell, which can't modify the parent 60 | # shell's variables. So we save the failure code to a temporary file. 61 | echo 62 | echo "==> TESTS FAILED with code $code" 63 | echo $code >exitCode.tmp 64 | fi 65 | echo 66 | done 67 | done 68 | exitCode=0 69 | if [ -f exitCode.tmp ] 70 | then 71 | exitCode=$(cat exitCode.tmp) 72 | rm -f exitCode.tmp 73 | fi 74 | exit "$exitCode" 75 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "*/test/*" 3 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import subprocess 3 | import pytest 4 | 5 | import imagej 6 | import imagej.doctor 7 | from scyjava import config 8 | 9 | 10 | def pytest_addoption(parser): 11 | """ 12 | Set up the command line parser for ImageJ2 location and headless mode 13 | :param parser: pytest's parser, passed in automatically 14 | :return: None 15 | """ 16 | parser.addoption( 17 | "--ij", 18 | action="store", 19 | default=None, 20 | help="directory or endpoint (see imagej.init)", 21 | ) 22 | parser.addoption( 23 | "--headless", 24 | type=str2bool, 25 | action="store", 26 | default=True, 27 | help="Start in headless mode", 28 | ) 29 | parser.addoption( 30 | "--legacy", 31 | type=str2bool, 32 | action="store", 33 | default=True, 34 | help="Include the original ImageJ", 35 | ) 36 | parser.addoption( 37 | "--java", 38 | action="store", 39 | default=None, 40 | help="version of Java to cache and use (e.g. 8, 11, 17, 21)", 41 | ) 42 | 43 | 44 | @pytest.fixture(scope="session") 45 | def ij(request): 46 | """ 47 | Create an ImageJ2 gateway to be used by the whole testing environment 48 | :param request: Pytest variable passed in to fixtures 49 | """ 50 | # enable debug logging 51 | imagej.doctor.debug_to_stderr() 52 | # get test configuration 53 | ij_dir = request.config.getoption("--ij") 54 | legacy = request.config.getoption("--legacy") 55 | headless = request.config.getoption("--headless") 56 | java_version = request.config.getoption("--java") 57 | config.set_java_constraints(fetch=True, vendor='zulu', version=java_version) 58 | # JavaScript is used in the tests. But the Nashorn engine was 59 | # dropped from the JDK at v15, so we readd it here if needed. 60 | if int(java_version) >= 15: 61 | config.endpoints.append("org.openjdk.nashorn:nashorn-core") 62 | imagej.when_imagej_starts(lambda ij: setattr(ij, "_java_version", java_version)) 63 | # initialize the ImageJ2 gateway 64 | mode = "headless" if headless else "interactive" 65 | ij = imagej.init(ij_dir, mode=mode, add_legacy=legacy) 66 | yield ij 67 | 68 | ij.dispose() 69 | 70 | 71 | def str2bool(v): 72 | """ 73 | Convert string inputs into bool 74 | :param v: A string 75 | :return: Corresponding boolean 76 | """ 77 | if isinstance(v, bool): 78 | return v 79 | if v.lower() in ("yes", "true", "t", "y", "1"): 80 | return True 81 | elif v.lower() in ("no", "false", "f", "n", "0"): 82 | return False 83 | else: 84 | raise argparse.ArgumentTypeError("Boolean value expected") 85 | -------------------------------------------------------------------------------- /doc/01-Starting-PyImageJ.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "This notebook is part of the PyImageJ [Tutorial Series](./notebooks.rst), and assumes familiarity with the ImageJ API. Dedicated tutorials for ImageJ can be found [here](https://imagej.net/tutorials/)." 9 | ] 10 | }, 11 | { 12 | "attachments": {}, 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "# 1 Starting ImageJ from Python\n", 17 | "\n", 18 | "The [pyimagej](https://pypi.org/project/pyimagej/) module enables access to the entire ImageJ API from Python in a natural way.\n", 19 | "\n", 20 | "Let's initialize an ImageJ gateway including Fiji plugins, at a reproducible version:" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 1, 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "name": "stdout", 30 | "output_type": "stream", 31 | "text": [ 32 | "ImageJ version: 2.14.0/1.54f\n" 33 | ] 34 | } 35 | ], 36 | "source": [ 37 | "import imagej\n", 38 | "\n", 39 | "# initialize ImageJ\n", 40 | "ij = imagej.init('sc.fiji:fiji:2.14.0')\n", 41 | "print(f\"ImageJ version: {ij.getVersion()}\")" 42 | ] 43 | }, 44 | { 45 | "attachments": {}, 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "## 1.1 Ways to initialize\n", 50 | "\n", 51 | "| Requirement | Code1 | Reproducible?2 |\n", 52 | "|:-------------------------------------------------------|:------------------------------------------------------------------|:-------------------------:|\n", 53 | "| Newest available version of ImageJ2 w/ ImageJ support | `ij = imagej.init()` | NO |\n", 54 | "| Specific version of ImageJ2 w/ ImageJ support | `ij = imagej.init('2.14.0')` | YES |\n", 55 | "| Interactive (newest version)* | `ij = imagej.init(mode='interactive')` | NO |\n", 56 | "| Interactive (specific version)* | `ij = imagej.init('net.imagej:imagej:2.14.0', mode='interactive')` | YES |\n", 57 | "| Without support for original ImageJ (newest versions) | `ij = imagej.init('net.imagej:imagej', add_legacy=False` | NO |\n", 58 | "| With Fiji plugins (newest version) | `ij = imagej.init('sc.fiji:fiji')` | NO |\n", 59 | "| With Fiji plugins (specific version) | `ij = imagej.init('sc.fiji:fiji:2.14.0')` | YES |\n", 60 | "| From a local installation | `ij = imagej.init('/Applications/Fiji.app')` | DEPENDS |\n", 61 | "\n", 62 | "1 pyimagej uses [`jgo`](https://github.com/scijava/jgo) internally to call up ImageJ, so all of these initializations are tied to the usage of `jgo`. You can read up on the [usage of `jgo`](https://github.com/scijava/jgo#usage) to find out more about this initialization.\n", 63 | "\n", 64 | "2 ___Reproducible___ means code is stable, executing the same today, tomorrow, and in years to come. While it is convenient and elegant to depend on the newest version of a program, behavior may change when new versions are released—for the better if bugs are fixed; for the worse if bugs are introduced—and people executing your notebook at a later time may encounter broken cells, unexpected results, or other more subtle behavioral differences. You can help avoid this pitfall by pinning to a specific version of the software. The British Ecological Society published [Guide to Better Science: Reproducible Code](https://www.britishecologicalsociety.org/wp-content/uploads/2018/12/BES-Reproducible-Code.pdf) diving into the relevant challenges in more detail, including an [R](https://www.r-project.org/)-centric illustration of best practices. A web search for `reproducible python` also yields several detailed articles." 65 | ] 66 | }, 67 | { 68 | "attachments": {}, 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## 1.2 The ImageJ2 gateway\n", 73 | "\n", 74 | "The ImageJ2 gateway is the object interface that lets you use ImageJ-related\n", 75 | "features (see [Initialization.md](Initialization.md)). This gateway contains\n", 76 | "all of the regular ImageJ2 Java functions. PyImageJ also adds a module of\n", 77 | "convenience functions under `ij.py`. For example, converting a numpy array to\n", 78 | "an ImageJ2 dataset:" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 2, 84 | "metadata": {}, 85 | "outputs": [ 86 | { 87 | "name": "stderr", 88 | "output_type": "stream", 89 | "text": [ 90 | "Operating in headless mode - the original ImageJ will have limited functionality.\n" 91 | ] 92 | }, 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "(3, 4, 5)\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "import numpy as np\n", 103 | "\n", 104 | "array = np.random.rand(5, 4, 3)\n", 105 | "dataset = ij.py.to_java(array)\n", 106 | "\n", 107 | "print(dataset.shape)" 108 | ] 109 | }, 110 | { 111 | "attachments": {}, 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "## 1.3 Increasing the memory available to Java\n", 116 | "\n", 117 | "Java's virtual machine (the JVM) has a \"max heap\" value limiting how much\n", 118 | "memory it can use. You can increase it:\n", 119 | "\n", 120 | "```python\n", 121 | "import imagej\n", 122 | "import scyjava\n", 123 | "scyjava.config.add_options('-Xmx6g')\n", 124 | "ij = imagej.init()\n", 125 | "```\n", 126 | "\n", 127 | "Replace `6g` with the amount of memory Java should have. You can also pass\n", 128 | "[other JVM arguments](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html).\n", 129 | "\n", 130 | "Without having specified the max heap value explicitly, here is how much memory this notebook's JVM has available:" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 3, 136 | "metadata": {}, 137 | "outputs": [ 138 | { 139 | "data": { 140 | "text/plain": [ 141 | "'ImageJ2 2.14.0/1.54f; Java 11.0.15-internal [amd64]; 35MB of 7952MB'" 142 | ] 143 | }, 144 | "execution_count": 3, 145 | "metadata": {}, 146 | "output_type": "execute_result" 147 | } 148 | ], 149 | "source": [ 150 | "ij.getApp().getInfo(True)" 151 | ] 152 | } 153 | ], 154 | "metadata": { 155 | "kernelspec": { 156 | "display_name": "Python 3 (ipykernel)", 157 | "language": "python", 158 | "name": "python3" 159 | }, 160 | "language_info": { 161 | "codemirror_mode": { 162 | "name": "ipython", 163 | "version": 3 164 | }, 165 | "file_extension": ".py", 166 | "mimetype": "text/x-python", 167 | "name": "python", 168 | "nbconvert_exporter": "python", 169 | "pygments_lexer": "ipython3", 170 | "version": "3.10.12" 171 | }, 172 | "toc": { 173 | "base_numbering": 1, 174 | "nav_menu": {}, 175 | "number_sections": true, 176 | "sideBar": true, 177 | "skip_h1_title": true, 178 | "title_cell": "Table of Contents", 179 | "title_sidebar": "Contents", 180 | "toc_cell": false, 181 | "toc_position": { 182 | "height": "calc(100% - 180px)", 183 | "left": "10px", 184 | "top": "150px", 185 | "width": "307px" 186 | }, 187 | "toc_section_display": true, 188 | "toc_window_display": true 189 | }, 190 | "vscode": { 191 | "interpreter": { 192 | "hash": "fd4de699765e9fab70e2644720b91b55c1a435ebb41ccdac66a2b7a412168f61" 193 | } 194 | } 195 | }, 196 | "nbformat": 4, 197 | "nbformat_minor": 4 198 | } 199 | -------------------------------------------------------------------------------- /doc/05-Convenience-methods-of-PyImageJ.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "This notebook is part of the PyImageJ [Tutorial Series](./notebooks.rst), and assumes familiarity with the ImageJ API. Dedicated tutorials for ImageJ can be found [here](https://imagej.net/tutorials/)." 9 | ] 10 | }, 11 | { 12 | "attachments": {}, 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "# 5 Convenience methods of PyImageJ\n", 17 | "\n", 18 | "PyImageJ is built to provide easy access to key ImageJ resources. We call these collective methods \"convenience methods\". These methods are attached to`ij.py` after initializing ImageJ. Here's a quick list of some of the more useful methods and additional information." 19 | ] 20 | }, 21 | { 22 | "attachments": {}, 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "| `ij.py.` | function | more information |\n", 27 | "| :---: | :---: | :---: |\n", 28 | "| `show` | Show an image | [06-Working-with-Images](06-Working-with-Images.ipynb)\n", 29 | "| `to_java` | Convert data from Python to Java | [03-Sending-Data-to-Java](03-Sending-Data-to-Java.ipynb) |\n", 30 | "| `from_java` | Convert data from Java to Python | [04-Retrieving-Data-from-Java](04-Retrieving-Data-from-Java.ipynb) |\n", 31 | "| `run_macro` | Run an original ImageJ macro | [07-Running-Macros-Scripts-and-Plugins](07-Running-Macros-Scripts-and-Plugins.ipynb) |\n", 32 | "| `run_script` | Run an ImageJ script (supported languages) | [07-Running-Macros-Scripts-and-Plugins](07-Running-Macros-Scripts-and-Plugins.ipynb) |\n", 33 | "| `run_plugin` | Run a plugin | [07-Running-Macros-Scripts-and-Plugins](07-Running-Macros-Scripts-and-Plugins.ipynb) |\n", 34 | "| `initialize_numpy_image` | Create a new numpy image in the same shape as input image | [06-Working-with-Images](06-Working-with-Images.ipynb) |\n", 35 | "| `sync_image` | Synchronize data between ImageJ and ImageJ2 data structures | -- |\n", 36 | "| `active_dataset` | Get the active image as a `Dataset` | -- |\n", 37 | "| `active_xarray` | Get a copy of the active image as an `xarray.DataArray` | -- |\n", 38 | "| `active_imageplus` | Get the `ImagePlus` from the current window | -- |" 39 | ] 40 | }, 41 | { 42 | "attachments": {}, 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "There are other convenience methods that are attached to `ij.py`. After initializing ImageJ you can explore `ij.py`'s methods with `dir`." 47 | ] 48 | }, 49 | { 50 | "attachments": {}, 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "## 5.1 Other convenient access to ImageJ functions\n", 55 | "\n", 56 | "When the original ImageJ is available (_i.e._ the legacy layer is active) `IJ`, `WindowManager`, `ResultsTable` and `RoiManager` are accessible directly from the initialized `ij` object." 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 1, 62 | "metadata": {}, 63 | "outputs": [ 64 | { 65 | "name": "stderr", 66 | "output_type": "stream", 67 | "text": [ 68 | "Operating in headless mode - the original ImageJ will have limited functionality.\n" 69 | ] 70 | }, 71 | { 72 | "name": "stdout", 73 | "output_type": "stream", 74 | "text": [ 75 | "ImageJ2 version: 2.14.0/1.54f\n", 76 | "Legacy layer active: True\n" 77 | ] 78 | } 79 | ], 80 | "source": [ 81 | "import imagej\n", 82 | "\n", 83 | "# initialize imagej\n", 84 | "ij = imagej.init()\n", 85 | "print(f\"ImageJ2 version: {ij.getVersion()}\")\n", 86 | "\n", 87 | "# first check if the legacy layer is active\n", 88 | "print(f\"Legacy layer active: {ij.legacy.isActive()}\")" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 2, 94 | "metadata": {}, 95 | "outputs": [ 96 | { 97 | "name": "stderr", 98 | "output_type": "stream", 99 | "text": [ 100 | "Operating in headless mode - the IJ class will not be fully functional.\n", 101 | "Operating in headless mode - the ResultsTable class will not be fully functional.\n", 102 | "Operating in headless mode - the RoiManager class will not be fully functional.\n", 103 | "Operating in headless mode - the WindowManager class will not be fully functional.\n" 104 | ] 105 | }, 106 | { 107 | "name": "stdout", 108 | "output_type": "stream", 109 | "text": [ 110 | "\n", 111 | "\n", 112 | "\n", 113 | "\n" 114 | ] 115 | } 116 | ], 117 | "source": [ 118 | "# demonstrate access to classes\n", 119 | "print(ij.IJ)\n", 120 | "print(ij.ResultsTable)\n", 121 | "print(ij.RoiManager)\n", 122 | "print(ij.WindowManager)" 123 | ] 124 | }, 125 | { 126 | "attachments": {}, 127 | "cell_type": "markdown", 128 | "metadata": {}, 129 | "source": [ 130 | "Note the warnings! We're currently in headless mode. The many legacy ImageJ functions operate limitedly or not at all in headless mode. For example the `RoiManager` is not functional in a true headless enviornment." 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "## 5.2 Register functions to start with ImageJ\n", 138 | "\n", 139 | "Functions can be executed during ImageJ's initialization routine by registering the functions with PyImageJ's callback mechanism `when_imagej_starts()`. This is particularly useful for macOS users in `gui` mode, allowing functions to be called before the Python [REPL/interpreter](https://docs.python.org/3/tutorial/interpreter.html) is [blocked](Initialization.md/#gui-mode).\n", 140 | "\n", 141 | "The following example uses `when_imagej_starts()` callback display a to `uint16` 2D NumPy array it with ImageJ's viewer, print it's dimensions (_i.e._ shape) and open the `RoiManager` while ImageJ initializes.\n", 142 | "\n", 143 | "```python\n", 144 | "import imagej\n", 145 | "import numpy as np\n", 146 | "\n", 147 | "# register functions\n", 148 | "arr = np.random.randint(0, 2**16, size=(256, 256), dtype=np.uint16) # create random 16-bit array\n", 149 | "imagej.when_imagej_starts(lambda ij: ij.RoiManager.getRoiManager()) # open the RoiManager\n", 150 | "imagej.when_imagej_starts(lambda ij: ij.ui().show(ij.py.to_dataset(arr))) # convert and display the array\n", 151 | "imagej.when_imagej_starts(lambda _: print(f\"array shape: {arr.shape}\"))\n", 152 | "\n", 153 | "# initialize imagej\n", 154 | "ij = imagej.init(mode='interactive')\n", 155 | "```" 156 | ] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": "Python 3 (ipykernel)", 162 | "language": "python", 163 | "name": "python3" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": { 167 | "name": "ipython", 168 | "version": 3 169 | }, 170 | "file_extension": ".py", 171 | "mimetype": "text/x-python", 172 | "name": "python", 173 | "nbconvert_exporter": "python", 174 | "pygments_lexer": "ipython3", 175 | "version": "3.10.12" 176 | }, 177 | "vscode": { 178 | "interpreter": { 179 | "hash": "fd4de699765e9fab70e2644720b91b55c1a435ebb41ccdac66a2b7a412168f61" 180 | } 181 | } 182 | }, 183 | "nbformat": 4, 184 | "nbformat_minor": 2 185 | } 186 | -------------------------------------------------------------------------------- /doc/Development.md: -------------------------------------------------------------------------------- 1 | # Developing PyImageJ 2 | 3 | This document describes how to do development-related tasks, 4 | if you want to hack on the PyImageJ code itself. If your goal 5 | is only to *use* PyImageJ to call ImageJ and friends from 6 | Python, you do not need to follow any of these instructions. 7 | 8 | ## Configuring a conda environment for development 9 | 10 | Install [Miniforge3](https://github.com/conda-forge/miniforge#miniforge3). 11 | Then: 12 | 13 | ``` 14 | mamba env create -f dev-environment.yml 15 | mamba activate pyimagej-dev 16 | ``` 17 | 18 | ## Running the automated tests 19 | 20 | ``` 21 | make test 22 | ``` 23 | 24 | ## Building the reference documentation 25 | 26 | ``` 27 | make docs 28 | ``` 29 | 30 | Results are generated to `doc/_build/html`. 31 | Production documentation is available online at 32 | [https://pyimagej.readthedocs.io/](https://pyimagej.readthedocs.io/). 33 | 34 | ## Formatting the code 35 | 36 | ``` 37 | make lint 38 | ``` 39 | 40 | ## Building distribution bundles 41 | 42 | ``` 43 | make dist 44 | ``` 45 | -------------------------------------------------------------------------------- /doc/Headless.md: -------------------------------------------------------------------------------- 1 | # Using PyImageJ without a screen 2 | 3 | It is an increasingly common scenario to want to do image processing on a cloud 4 | computing node (e.g. running notebooks on [Binder](https://mybinder.org/) or 5 | [Google Colab](https://colab.research.google.com)). Unfortunately, the 6 | original ImageJ was only designed to be a GUI-based desktop application, so it 7 | does not natively support true 8 | [headless](https://en.wikipedia.org/wiki/Headless_computer) operation, i.e. 9 | without a display attached. 10 | 11 | ## Using ImageJ in headless mode 12 | 13 | The [ImageJ2](https://imagej.net/software/imagej2) project 14 | supports headless operation for all its functions, due to its careful 15 | [separation of concerns](https://imagej.net/develop/architecture#modularity), 16 | and ImageJ2 includes a 17 | [backwards compatibility layer](https://imagej.net/libs/imagej-legacy) 18 | that supports use some original ImageJ functionality while headless; 19 | the original ImageJ's core classes are 20 | [modified at runtime via Javassist](https://github.com/imagej/ij1-patcher). 21 | 22 | For more information about running ImageJ and/or ImageJ2 in headless mode, 23 | please read the [Running Headless](https://imagej.net/learn/headless) and 24 | [Scripting Headless](https://imagej.net/scripting/headless) pages of the 25 | ImageJ wiki. 26 | 27 | ***Please note:*** Not all original ImageJ functions are accessible while 28 | headless: e.g., many methods of `RoiManager` and `WindowManager` do not work 29 | without a graphical environment. To work around this limitation, you can use 30 | [Xvfb](#using-pyimagej-with-xvfb) to run ImageJ inside a "virtual" graphical environment without a 31 | physical screen present. 32 | 33 | ### Starting PyImageJ in headless mode 34 | 35 | When you initialize PyImageJ with no arguments, 36 | it runs in headless mode by default: 37 | 38 | ```python 39 | import imagej 40 | ij = imagej.init() 41 | ``` 42 | 43 | For clarity, you can explicitly specify headless mode 44 | by passing the `mode='headless'` setting: 45 | 46 | ```python 47 | ij = imagej.init(mode='headless') 48 | ``` 49 | 50 | Under the hood, the headless mode flag initializes the 51 | Java Virtual Machine with `-Djava.awt.headless=true`. 52 | 53 | For more about PyImageJ initialization, see the 54 | [Initialization](Initialization) guide. 55 | 56 | ### Troubleshooting 57 | 58 | See the 59 | [Known Limitations section of the Troubleshooting guide](Troubleshooting.md#known-limitations) 60 | for some further details about what does and does not work headless, and 61 | things to try when having difficulty with ImageJ's behavior in headless mode. 62 | 63 | 64 | ## Using PyImageJ with Xvfb 65 | 66 | Workflows that require headless operation but also need to interact with ImageJ elements that are tied to the GUI, can be achieved with virtual displays. Using Xvfb we can create a virtual frame buffer for ImageJ's GUI elemnts without displaying any screen output. On Linux systems that already have a graphical environment installed (_e.g._ GNOME), you only need to install `xvfb`. 67 | 68 | ```console 69 | $ sudo apt install xvfb 70 | ``` 71 | 72 | However on fresh Linux servers that do not have any installed environment (_e.g._ Ubuntu Server 20.04.3 LTS), additional X11 related packages will need to be installed for PyImageJ. 73 | 74 | ```console 75 | $ sudo apt install libxrender1 libxtst6 libxi6 fonts-dejavu fontconfig 76 | ``` 77 | 78 | After `xvfb` has been installed you can have `xvfb` create the virtual display for you and run a script with: 79 | 80 | ```console 81 | $ xvfb-run -a python script.py 82 | ``` 83 | 84 | Alternatively you can create the virtual frame buffer manually before you start your PyImageJ session: 85 | 86 | ```console 87 | $ export DISPLAY=:1 88 | $ Xvfb $DISPLAY -screen 0 1400x900x16 & 89 | ``` 90 | 91 | In either case however, you need to initialize PyImageJ in `interactive` and not `headless` mode so the GUI can be created in the virtual display: 92 | 93 | ```python 94 | import imagej 95 | 96 | ij = imagej.init(mode='interactive') 97 | ``` 98 | 99 | ### **Headless Xvfb example** 100 | 101 | Here we have an example on how to run PyImageJ headlessly using `imagej.init(mode='interactive')` and Xvfb. In addition to Xvfb, you will also need to have scikit-image installed in your environment to run the `doc/examples/blob_detection_xvfb.py` example. The `blob_detection_xvfb.py` script is the headless version of the `doc/examples/blob_detection_interactive.py` example (please run `blob_detection_interactive.py` to view the scikit-image blob detection output). 102 | 103 | The headless example opens the `test_image.tif` sample image, detects the blobs via scikit-image's Laplacian of Gaussian algorithm, adds the blob detections to the ImageJ `RoiManager`, measures the ROIs and returns a panda's dataframe of the measurement results. To run the example, run the following command to create the virtual frame buffer and run PyImageJ: 104 | 105 | ```console 106 | $ xvfb-run -a python blob_detection_xvfb.py 107 | ``` 108 | 109 | The script should print the results pandas dataframe (the data from ImageJ's `ResultsTable`) with 187 detections. 110 | 111 | ```python 112 | log4j:WARN No appenders could be found for logger (org.bushe.swing.event.EventService). 113 | log4j:WARN Please initialize the log4j system properly. 114 | log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 115 | ImageJ2 version: 2.14.0/1.54f 116 | Output: 117 | Area Mean Min Max 118 | 0 1.267500 3477.416667 2219.0 5312.0 119 | 1 0.422500 2075.500000 1735.0 2529.0 120 | 2 0.422500 1957.750000 1411.0 2640.0 121 | 3 0.422500 1366.500000 1012.0 1913.0 122 | 4 0.422500 2358.500000 2100.0 2531.0 123 | .. ... ... ... ... 124 | 182 0.422500 1205.750000 1124.0 1355.0 125 | 183 7.288125 1362.840580 703.0 2551.0 126 | 184 0.422500 920.500000 830.0 1110.0 127 | 185 0.422500 1345.250000 1260.0 1432.0 128 | 186 0.422500 1097.250000 960.0 1207.0 129 | 130 | [187 rows x 4 columns] 131 | ``` 132 | -------------------------------------------------------------------------------- /doc/Initialization.md: -------------------------------------------------------------------------------- 1 | # How to initialize PyImageJ 2 | 3 | The PyImageJ plugin works by setting up a gateway interface into 4 | [ImageJ2](https://imagej.net/software/imagej2). This gateway interface is 5 | activated using the `imagej.init()` function and yields a wrapped ImageJ2 6 | gateway, the `net.imagej.ImageJ` Java class. This interface has access to all 7 | of the Java-based functions, and also has convenience functions for translating 8 | back and forth between the Python and Java environments. 9 | 10 | Setting up this gateway consists of two steps. The first step is to set Java 11 | options. This step is optional, but must be done first because they cannot be 12 | changed after ImageJ2 is initialized. The second step is to specify what 13 | version of ImageJ2 the gateway will represent. 14 | 15 | ## Quick start 16 | 17 | If all you want is the newest version of ImageJ2, with no custom configuration 18 | (e.g., extra memory allocated to Java), use this: 19 | 20 | ```python 21 | import imagej 22 | ij = imagej.init() 23 | ``` 24 | 25 | It will download and cache ImageJ2, then spin up a gateway for you. 26 | 27 | ### Configuring the JVM 28 | 29 | The ImageJ2 gateway is initialized through a Java Virtual Machine (JVM). 30 | If you want to [configure the 31 | JVM](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html), 32 | it must be done _before_ initializing an ImageJ2 gateway. E.g.: 33 | 34 | ```python 35 | import imagej 36 | import scyjava 37 | scyjava.config.add_option('-Xmx6g') 38 | ij = imagej.init() 39 | ``` 40 | See "With more memory available to Java" below for further details. 41 | 42 | ## Ways to initialize 43 | 44 | PyImageJ can be initialized to call: 45 | * different versions of ImageJ2 or component libraries; 46 | * with or without support for the original ImageJ; 47 | * with or without a graphical user interface (GUI); and 48 | * with additional plugins such as Fiji plugins. 49 | 50 | All initialization methods except for "local installation" will automatically 51 | include the imagej-legacy endpoint unless the `add_legacy=False` flag is given. 52 | 53 | | Requirement | Code | Reproducible? | 54 | |:---------------------------------------------------------|:----------------------------------------------------------------------------------:|:-------------:| 55 | | Newest available version of ImageJ2 | `ij = imagej.init()` | NO | 56 | | Newest available version of ImageJ2 (no original ImageJ) | `ij = imagej.init(add_legacy=False)` | NO | 57 | | Specific version of ImageJ2 | `ij = imagej.init('2.14.0')` | YES | 58 | | Specific version of ImageJ2 (no original ImageJ) | `ij = imagej.init('2.14.0', add_legacy=False)` | YES | 59 | | With a GUI (newest version, blocking) | `ij = imagej.init(mode='gui')` | NO | 60 | | With a GUI (specific version, blocking) | `ij = imagej.init('net.imagej:imagej:2.14.0', mode='gui')` | YES | 61 | | With a GUI (newest version, interactive) | `ij = imagej.init(mode='interactive')` | NO | 62 | | With a GUI (specific version, interactive) | `ij = imagej.init('net.imagej:imagej:2.14.0', mode='interactive')` | YES | 63 | | With Fiji plugins (newest version) | `ij = imagej.init('sc.fiji:fiji')` | NO | 64 | | With Fiji plugins (specific version) | `ij = imagej.init('sc.fiji:fiji:2.14.0')` | YES | 65 | | With a specific plugin (newest version) | `ij = imagej.init(['net.imagej:imagej', 'net.preibisch:BigStitcher'])` | NO | 66 | | With a specific plugin (specific version) | `ij = imagej.init(['net.imagej:imagej:2.14.0', 'net.preibisch:BigStitcher:0.4.1'])`| YES | 67 | | From a local installation | `ij = imagej.init('/Applications/Fiji.app')` | DEPENDS | 68 | 69 | ### Newest available version 70 | 71 | If you want to launch the newest available release version of ImageJ2: 72 | 73 | ```python 74 | import imagej 75 | ij = imagej.init() 76 | ``` 77 | 78 | This invocation will automatically download and cache the newest release of 79 | [net.imagej:imagej](https://maven.scijava.org/#nexus-search;gav~net.imagej~imagej~~~). 80 | 81 | ### Explicitly specified version 82 | 83 | You can specify a particular version, to facilitate reproducibility: 84 | 85 | ```python 86 | import imagej 87 | ij = imagej.init('2.14.0') 88 | ij.getVersion() 89 | ``` 90 | 91 | ### With graphical capabilities 92 | 93 | There are two ways to show the graphical user interface. 94 | 95 | #### GUI mode 96 | 97 | ```python 98 | import imagej 99 | ij = imagej.init(mode='gui') 100 | ``` 101 | 102 | This mode works on all platforms, but will ***block*** further execution 103 | of your script until the ImageJ user interface shuts down. 104 | 105 | _**Note:** For this mode to work on macOS, you will need to install 106 | [PyObjC](https://pyobjc.readthedocs.io/), specifically the `pyobjc-core` and 107 | `pyobjc-framework-cocoa` packages from conda-forge, or `pyobjc` from PyPI._ 108 | 109 | #### Interactive mode 110 | 111 | ```python 112 | import imagej 113 | ij = imagej.init(mode='interactive') 114 | ij.ui().showUI() # if you want to display the GUI immediately 115 | ``` 116 | 117 | This mode returns immediately after initializing ImageJ2, allowing you to 118 | "mix and match" operations performed via Python code with operations 119 | performed via GUI interactions. 120 | 121 | _**Note:** This mode does not work on macOS from plain Python, due to a 122 | limitation in the macOS threading model. It does work in IPython/Jupyter, as 123 | well as via the [Jaunch](https://github.com/apposed/jaunch#readme) launcher._ 124 | 125 | ### Support for the original ImageJ 126 | 127 | By default, the ImageJ2 gateway includes the 128 | [legacy layer](https://imagej.net/libs/imagej-legacy) for backwards 129 | compatibility with [the original ImageJ](https://imagej.net/software/imagej). 130 | The legacy layer is necessary for macros and any original-ImageJ-based plugins. 131 | 132 | If you would rather initialize "pure ImageJ2" without support 133 | for the original ImageJ, you can do so as follows: 134 | 135 | ```python 136 | import imagej 137 | ij = imagej.init(add_legacy=False) 138 | ``` 139 | 140 | _**Note:** With legacy support enabled in a graphical mode, 141 | the JVM and Python will both terminate when ImageJ closes!_ 142 | 143 | ### Including Fiji plugins 144 | 145 | By default, the ImageJ2 gateway will include base ImageJ2+ImageJ functionality 146 | only, without additional plugins such as those that ship with the 147 | [Fiji](https://fiji.sc/) distribution of ImageJ2. 148 | 149 | You can create an ImageJ2 gateway including Fiji plugins as follows: 150 | 151 | ```python 152 | import imagej 153 | ij = imagej.init('sc.fiji:fiji') 154 | ``` 155 | 156 | or at a reproducible version: 157 | 158 | ```python 159 | import imagej 160 | ij = imagej.init('sc.fiji:fiji:2.14.0') 161 | ``` 162 | 163 | ### From a local installation 164 | 165 | If you have a local installation of ImageJ2, such as [Fiji](https://fiji.sc/), 166 | you can wrap an ImageJ2 gateway around it: 167 | 168 | ```python 169 | import imagej 170 | ij = imagej.init('/Applications/Fiji.app') 171 | ``` 172 | 173 | Replace `/Applications/Fiji.app` with the path to your installation. 174 | 175 | ### With more memory available to Java 176 | 177 | Java's virtual machine (the JVM) has a "max heap" value limiting how much 178 | memory it can use. You can increase the value as follows: 179 | 180 | ```python 181 | import imagej, scyjava 182 | scyjava.config.add_option('-Xmx6g') 183 | ij = imagej.init() 184 | ``` 185 | 186 | Replace `6g` with the amount of memory Java should have. Save some 187 | memory for your core operating system and other programs, though; a good 188 | rule of thumb is to give Java no more than 80% of your physical RAM. 189 | 190 | You can also pass 191 | [other JVM arguments](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html). 192 | 193 | ### With a specific plugin 194 | 195 | For plugins available via Maven, you can specify them in the `init` call. E.g.: 196 | 197 | ```python 198 | import imagej 199 | ij = imagej.init(['net.imagej:imagej', 'net.preibisch:BigStitcher']) 200 | ``` 201 | 202 | This can be done for the latest versions as above, or at fixed versions like: 203 | 204 | ```python 205 | import imagej 206 | ij =imagej.init(['net.imagej:imagej:2.14.0', 'net.preibisch:BigStitcher:0.4.1']) 207 | ``` 208 | 209 | ### Plugins without Maven endpoints 210 | 211 | For plugins that are published to a Maven repository, it is preferred to 212 | simply add them to the endpoint, rather than using the below approaches. 213 | 214 | If you wish to use plugins which are not available as Maven artifacts, 215 | you have a couple of options: 216 | 217 | 1. Use a local installation of ImageJ2 with the plugins, as described above. 218 | This is the more recommended approach. 219 | 220 | 2. Specify a remote version of ImageJ2, but set `plugins.dir` to point to a 221 | local directory to discover the plugins from there. For example: 222 | 223 | ```python 224 | import imagej 225 | import scyjava 226 | plugins_dir = '/Applications/Fiji.app/plugins' 227 | scyjava.config.add_option(f'-Dplugins.dir={plugins_dir}') 228 | ij = imagej.init() 229 | ``` 230 | 231 | Where `plugins_dir` is a path to a folder full of ImageJ plugins. 232 | -------------------------------------------------------------------------------- /doc/Install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | There are two supported ways to install PyImageJ: via 4 | [conda](https://conda.io/)/[mamba](https://mamba.readthedocs.io/) or via 5 | [pip](https://packaging.python.org/guides/tool-recommendations/). 6 | Both tools are great 7 | [for different reasons](https://www.anaconda.com/blog/understanding-conda-and-pip). 8 | 9 | ## Installing via conda/mamba 10 | 11 | Note: We strongly recommend using 12 | [Mamba](https://mamba.readthedocs.io/en/latest/user_guide/mamba.html) rather 13 | than plain Conda, because Conda is unfortunately terribly slow at configuring 14 | environments. 15 | 16 | 1. [Install Miniforge3](https://github.com/conda-forge/miniforge#miniforge3). 17 | **OR:** If you already have `mamba` installed, activate conda-forge: 18 | ``` 19 | mamba config append channels conda-forge 20 | mamba config set channel_priority strict 21 | ``` 22 | On the other hand, if you are using `conda` (not recommended): 23 | ``` 24 | conda config --add channels conda-forge 25 | conda config --set channel_priority strict 26 | ``` 27 | 28 | 2. Install PyImageJ into a new environment: 29 | ``` 30 | mamba create -n pyimagej pyimagej openjdk=11 31 | ``` 32 | 33 | This command will install PyImageJ with OpenJDK 11. PyImageJ requires a 34 | minimum of OpenJDK 8. PyImageJ has been tested most thoroughly with OpenJDKs 35 | 8 and 11, but it is likely to work with later OpenJDK versions as well. 36 | 37 | *Please note that openjdk=8 from conda-forge is broken on M1 Mac.* 38 | If you are using an M1 Mac, you should use openjdk=11 or newer. 39 | 40 | 3. Whenever you want to use PyImageJ, activate its environment: 41 | ``` 42 | mamba activate pyimagej 43 | ``` 44 | 45 | ## Installing via pip 46 | 47 | If installing via pip, we recommend using a 48 | [virtualenv](https://virtualenv.pypa.io/) to avoid cluttering up or mangling 49 | your system-wide or user-wide Python environment. Alternately, you can use 50 | mamba just for its virtual environment feature (`mamba create -n pyimagej 51 | python=3.9; mamba activate pyimagej`) and then simply `pip install` everything 52 | into that active environment. 53 | 54 | There are several ways to install things via pip, but we will not enumerate 55 | them all here; these instructions will assume you know what you are doing if 56 | you chose this route over conda/mamba above. 57 | 58 | 1. Install [Python 3](https://python.org/). As of this writing, 59 | PyImageJ has been tested with Python up through 3.13. 60 | 61 | 2. Install OpenJDK 8 or OpenJDK 11. PyImageJ should work with whichever 62 | distribution of OpenJDK you prefer; we recommend 63 | [Zulu JDK+FX 8](https://www.azul.com/downloads/zulu-community/?version=java-8-lts&package=jdk-fx). 64 | Another option is to install openjdk from your platform's package manager. 65 | 66 | 3. Install Maven. You can either 67 | [download it manually](https://maven.apache.org/) or install it via your 68 | platform's package manager, if available there. The `mvn` command must be 69 | available on your system path. 70 | 71 | 4. Install pyimagej via pip: 72 | ``` 73 | pip install pyimagej 74 | ``` 75 | 76 | ## Testing your installation 77 | 78 | Here's one way to test that it works: 79 | ``` 80 | python -c 'import imagej; ij = imagej.init("2.14.0"); print(ij.getVersion())' 81 | ``` 82 | Should print `2.14.0` on the console. 83 | 84 | ## Dynamic installation within Jupyter 85 | 86 | It is possible to dynamically install PyImageJ from within a Jupyter notebook. 87 | 88 | For your first cell, write: 89 | ``` 90 | import sys, os 91 | prefix = sys.prefix.replace("\\", "/") # Handle Windows Paths 92 | %mamba install --yes --prefix {prefix} -c conda-forge pyimagej openjdk=11 93 | jvm_lib_path = [sys.prefix, 'lib', 'jvm'] 94 | 95 | # platform specific JVM lib path locations 96 | if sys.platform == "win32": 97 | jvm_lib_path.insert(1, 'Library') 98 | os.environ['JAVA_HOME'] = os.sep.join(jvm_lib_path) 99 | ``` 100 | 101 | This approach is useful for [JupyterHub](https://jupyter.org/hub) on the cloud, 102 | e.g. [Binder](https://mybinder.org/), to utilize PyImageJ in select notebooks 103 | without advance installation. This reduces time needed to create and launch the 104 | environment, at the expense of a longer startup time the first time a 105 | PyImageJ-enabled notebook is run. See [this itkwidgets example 106 | notebook](https://github.com/InsightSoftwareConsortium/itkwidgets/blob/v0.24.2/examples/ImageJImgLib2.ipynb) 107 | for an example. 108 | 109 | ## Dynamic installation within Google Colab 110 | 111 | It is possible to dynamically install PyImageJ on 112 | [Google Colab](https://colab.research.google.com/). 113 | A major advantage of Google Colab is free GPU in the cloud. 114 | 115 | Here is an example set of notebook cells to run PyImageJ 116 | on Google Colab with a wrapped local Fiji installation: 117 | 118 | 1. Install [condacolab](https://pypi.org/project/condacolab/): 119 | ```bash 120 | !pip install -q condacolab 121 | import condacolab 122 | condacolab.install() 123 | ``` 124 | 125 | 2. Verify that the installation is functional: 126 | ```python 127 | import condacolab 128 | condacolab.check() 129 | ``` 130 | 131 | 3. Install PyImageJ: 132 | ```bash 133 | !mamba install pyimagej openjdk=11 134 | ``` 135 | You can also install other deps here as well (scikit-image, opencv, etc). 136 | 137 | 4. Download and install Fiji, and optionally custom plugins as well: 138 | ```bash 139 | !wget https://downloads.imagej.net/fiji/latest/fiji-linux64.zip > /dev/null && unzip fiji-linux64.zip > /dev/null 140 | !rm fiji-linux64.zip 141 | !wget https://imagej.net/ij/plugins/download/Filter_Rank.class > /dev/null 142 | !mv Filter_Rank.class Fiji.app/plugins 143 | ``` 144 | 145 | 5. Set `JAVA_HOME`: 146 | ```python 147 | import os 148 | os.environ['JAVA_HOME']='/usr/local' 149 | ``` 150 | We need to do this so that the openjdk installed by mamba gets used, 151 | since a conda env is not actually active in this scenario. 152 | 153 | 6. Start PyImageJ wrapping the local Fiji: 154 | ```python 155 | import imagej 156 | ij = imagej.init("/content/Fiji.app") 157 | print(ij.getVersion()) 158 | ``` 159 | 160 | 7. Start running plugins, even custom plugins: 161 | ```python 162 | imp = ij.IJ.openImage("http://imagej.net/images/blobs.gif") 163 | ij.py.run_plugin("Filter Rank", {"window": 3, "randomise": True}, imp=imp) 164 | ij.IJ.resetMinAndMax(imp) 165 | ij.py.run_plugin("Enhance Contrast", {"saturated": 0.35}, imp=imp) 166 | ``` 167 | ## Install pyimagej in Docker 168 | We leverage [Micromamba-docker](https://github.com/mamba-org/micromamba-docker) since `conda activate` will not work. Note that running Python scripts during build is extremely slow. 169 | ```dockerfile 170 | # Micromamba-docker @ https://github.com/mamba-org/micromamba-docker 171 | FROM mambaorg/micromamba:1.0.0 172 | 173 | # Retrieve dependencies 174 | USER root 175 | RUN apt-get update 176 | RUN apt-get install -y wget unzip > /dev/null && rm -rf /var/lib/apt/lists/* > /dev/null 177 | RUN micromamba install -y -n base -c conda-forge \ 178 | python=3.9\ 179 | pyimagej \ 180 | openjdk=11 && \ 181 | micromamba clean --all --yes 182 | ENV JAVA_HOME="/usr/local" 183 | # Set MAMVA_DOCKERFILE_ACTIVATE (otherwise python will not be found) 184 | ARG MAMBA_DOCKERFILE_ACTIVATE=1 185 | # Retrieve ImageJ and source code 186 | RUN wget https://downloads.imagej.net/fiji/latest/fiji-linux64.zip &> /dev/null 187 | RUN unzip fiji-linux64.zip > /dev/null 188 | RUN rm fiji-linux64.zip 189 | # test: note that "Filter Rank" is not added yet, below is just an example. 190 | RUN python -c "import imagej; \ 191 | ij = imagej.init('/tmp/Fiji.app', mode='headless'); \ 192 | print(ij.getVersion()); \ 193 | imp = ij.IJ.openImage('http://imagej.net/images/blobs.gif'); \ 194 | ij.py.run_plugin('Filter Rank', {'window': 3, 'randomise': True}, imp=imp); \ 195 | ij.IJ.resetMinAndMax(imp); \ 196 | ij.py.run_plugin('Enhance Contrast', {'saturated': 0.35}, imp=imp);" 197 | ``` 198 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # PyImageJ Reference Documentation 2 | 3 | To access PyImageJ's installation, initialization, tutorial, troubleshooting and API reference documentation please visit the [PyImageJ ReadTheDocs](https://pyimagej.readthedocs.io/en/latest/index.html) website or build the documentation locally (see [building the reference documentation](https://pyimagej.readthedocs.io/en/latest/Development.html#building-the-reference-documentation) for more details). 4 | -------------------------------------------------------------------------------- /doc/Troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | Are you having trouble getting PyImageJ up and running? 3 | Try the PyImageJ doctor! 4 | 5 | ``` 6 | python -c "import imagej.doctor; imagej.doctor.checkup()" 7 | ``` 8 | 9 | See [The PyImageJ doctor](#the-pyimagej-doctor) below for details. 10 | 11 | ------------------------------------------------------------------------------ 12 | 13 | This document is divided into three main sections: 14 | 15 | 1. [Known Limitations](#known-limitations) 16 | 2. [Debugging Tips](#debugging-tips) 17 | 3. [Common Errors](#common-errors) 18 | 19 | ------------------------------------------------------------------------------ 20 | 21 | ## Known Limitations 22 | 23 | For technical reasons, there are some aspects of ImageJ and ImageJ2 that cannot 24 | fully work from a Python script: 25 | 26 | ### The original ImageJ API is limited in headless mode 27 | 28 | Normally, the original [ImageJ] does not work headless at all. But thanks 29 | to the [ImageJ Legacy Bridge], most aspects of the original ImageJ work 30 | in [headless mode], including via PyImageJ when initializing with 31 | `imagej.init(mode='headless')`. 32 | 33 | That said, there are a couple of major areas of functionality in the original 34 | ImageJ that do not work headless, and therefore do not work headless via 35 | PyImageJ. 36 | 37 | #### ROI Manager 38 | 39 | ImageJ's [ROI Manager] allows you to work with multiple regions of interest 40 | (ROIs) simultaneously. The `ij.plugin.frame.RoiManager` class is the API for 41 | controlling these features. As one might guess just from its package name, 42 | `ij.plugin.frame.RoiManager` is a `java.awt.Frame`, and therefore cannot be 43 | used headless. 44 | 45 | If you need to work with multiple `ij.gui.Roi` objects, one option that works 46 | headless is to group them using an `ij.gui.Overlay`. 47 | 48 | #### WindowManager 49 | 50 | ImageJ's `ij.WindowManager` class consists of static functions for working with 51 | Java AWT windows, including ImageJ's `ij.gui.ImageWindow`. Each ImageJ image is 52 | an `ij.ImagePlus` linked to a corresponding `ij.gui.ImageWindow`. However, in 53 | headless mode, there are no image windows, because they cannot exist headless. 54 | Therefore, attempts to use the functions of the `WindowManager` will fail, with 55 | functions like `WindowManager.getCurrentWindow()` always returning null. 56 | Unfortunately, ImageJ tracks open images via their windows; therefore, you 57 | cannot know which images have previously been opened while running headless, 58 | nor is there an "active image window" while running headless because _there are 59 | no windows_. 60 | 61 | Note that if you are having problems with a `null` or incorrect active image 62 | while **running in `GUI` or `INTERACTIVE` mode (i.e. not `HEADLESS`)**, you 63 | might need to call `ij.py.sync_image(imp)`, where `imp` is the `ij.ImagePlus` 64 | you want to register or update. 65 | 66 | ### Non-blocking INTERACTIVE mode on macOS 67 | 68 | On macOS, the CoreFoundation/AppKit event loop needs to be started from the 69 | main thread before any Java-AWT-specific functions can work. And doing so 70 | blocks the main thread. For this reason, PyImageJ includes two graphical modes, 71 | `GUI` and `INTERACTIVE`, with `GUI` blocking the `imagej.init` invocation, and 72 | `INTERACTIVE` returning immediately... but `INTERACTIVE` can only work on macOS 73 | in certain scenarios. PyImageJ makes a best effort to check your environment 74 | and report when interactive cannot work, and how you might go about fixing it. 75 | 76 | ### Old versions of ImageJ2 77 | 78 | PyImageJ uses some functions of ImageJ2 and supporting libraries that are not 79 | available in older versions of ImageJ2. While it may be possible to initialize 80 | an ImageJ2 gateway with an older version of ImageJ2, certain functionality may 81 | not behave as intended, so we advise to use version 2.11.0 or later if possible. 82 | 83 | ### Starting Python from inside ImageJ 84 | 85 | At the time of this writing, in order to use PyImageJ, you must start Python 86 | first, and initialize ImageJ2 from there. 87 | 88 | We have plans to make it possible to go the other direction: starting ImageJ2 89 | as usual and then calling Python scripts for the Script Editor. But this 90 | architecture is not complete yet; see 91 | [this forum discussion](https://forum.image.sc/t/fiji-conda/59618/11) 92 | for details. 93 | 94 | ------------------------------------------------------------------------------ 95 | 96 | ## Debugging Tips 97 | 98 | ### The PyImageJ doctor 99 | 100 | PyImageJ comes equipped with a troubleshooter that you can run to check for 101 | issues with your Python environment: 102 | 103 | ```python 104 | import imagej.doctor 105 | imagej.doctor.checkup() 106 | ``` 107 | 108 | It is completely standalone, so alternately, you can run the latest version 109 | of the doctor by downloading and running it explicitly: 110 | 111 | ```shell 112 | curl -fsLO https://raw.githubusercontent.com/imagej/pyimagej/main/src/imagej/doctor.py 113 | python doctor.py 114 | ``` 115 | 116 | (If you don't have `curl`, use `wget` or download using a web browser.) 117 | 118 | ### Enabling debug logging 119 | 120 | You can enable more verbose output about what is happening internally, 121 | such as which Java dependencies are being installed by jgo. 122 | 123 | Add the following to the very beginning of your Python code: 124 | 125 | ```python 126 | import imagej.doctor 127 | imagej.doctor.debug_to_stderr() 128 | ``` 129 | 130 | Under the hood, this `debug_to_stderr()` call sets the log level to `DEBUG` 131 | for the relevant PyImageJ dependencies, and adds a logging handler that 132 | emits output to the standard error stream. 133 | 134 | ------------------------------------------------------------------------------ 135 | 136 | ## Common Errors 137 | 138 | ### jgo.jgo.ExecutableNotFound: mvn not found on path ... 139 | 140 | This indicates the Maven executable wasn't found on your system. 141 | 142 | * If you [installed Maven manually](https://maven.apache.org/install.html), make sure the `bin` directory is actually on your `PATH` and start a fresh terminal session. 143 | * If you installed via conda/mamba and are on Windows, you maven have an old `maven` installation from when it [was broken](https://github.com/conda-forge/maven-feedstock/issues/23). You should be able to simply update with `conda update -n pyimagej maven` (adjusted if you named your environment something other than `pyimagej`). 144 | 145 | ### /lib/ext exists, extensions mechanism no longer supported; Use -classpath instead. 146 | 147 | If you installed `maven` via conda/mamba on Windows, you may have gotten a version that was [briefly broken](https://github.com/conda-forge/maven-feedstock/issues/26). You should be able to simply update with `conda update -n pyimagej maven` (adjusted if you named your environment something other than `pyimagej`). 148 | 149 | ### Command died with Signals.SIGKILL: 9 150 | 151 | If you see an error like: 152 | ``` 153 | subprocess.CalledProcessError: Command '['.../jre/bin/java', '-version']' died with . 154 | ``` 155 | it might be that you are using an M1 Mac, and have OpenJDK 8 installed from conda-forge? 156 | Unfortunately, Version 8 of OpenJDK from conda-forge is known to be broken on Mac M1 machines. 157 | 158 | Try `mamba install openjdk=11` to update to version 11, and this problem should go away. 159 | 160 | ### Error in "mvn.CMD -B -f pom.xml" dependency:resolve: 1 161 | 162 | This indicates a problem running Maven on your system. 163 | Maven is needed to fetch Java libraries from the Internet. 164 | 165 | Two common problems are: 166 | 167 | * [Could not transfer artifact](#could-not-transfer-artifact). 168 | You might be behind a firewall. 169 | * [Unable to find valid certification path](#unable-to-find-valid-certification-path). 170 | Your version of OpenJDK might be too old. 171 | 172 | Details on how to address these two scenarios are below. 173 | 174 | Or it might be something else, in which case it will require more debugging 175 | effort. Please post [on the forum](https://forum.image.sc/tag/pyimagej) and 176 | include the results of re-running the same `imagej.init` call after: 177 | 178 | 1. Deleting your `~/.jgo` directory; and 179 | 2. [Enabling debug logging](#enabling-debug-logging) by adding: 180 | ```python 181 | import imagej.doctor 182 | imagej.doctor.debug_to_stderr(debug_maven=True) 183 | ``` 184 | to the top of your script. You can try first without `debug_maven=True`, 185 | but if you still don't get any useful hints in the output, add the 186 | `debug_maven=True` so that `mvn` runs with the `-X` flag to provide us 187 | with the copious amounts of output our bodies crave. 188 | 189 | #### Could not transfer artifact 190 | 191 | If the debugging output includes notices such as: 192 | 193 | ``` 194 | DEBUG:jgo: [ERROR] Non-resolvable import POM: Could not transfer artifact net.imglib2:imglib2-imglyb:pom:1.0.1 from/to scijava.public (https://maven.scijava.org/content/groups/public): Transfer failed for https://maven.scijava.org/content/groups/public/net/imglib2/imglib2-imglyb/1.0.1/imglib2-imglyb-1.0.1.pom @ line 8, column 29: Connect to maven.scijava.org:443 [maven.scijava.org/144.92.48.199] failed: Connection timed out: 195 | ``` 196 | 197 | This suggests you may be behind a firewall that is preventing Maven from 198 | downloading the necessary components. In this case you have a few options 199 | to try: 200 | 201 | 1. Tell Java to use your system proxy settings: 202 | ``` 203 | import os 204 | os.environ["JAVA_TOOL_OPTIONS"] = "-Djava.net.useSystemProxies=true" 205 | ``` 206 | 207 | 2. Configure your proxy settings manually: 208 | (replacing `example.com` and `8080` as appropriate) 209 | ``` 210 | import os 211 | myhost = "example.com" 212 | myport = 8080 213 | os.environ["JAVA_TOOL_OPTIONS"] = ( 214 | f"-Dhttp.proxyHost={myhost}" 215 | + f" -Dhttp.proxyPort={myport}" 216 | + f" -Dhttps.proxyHost={myhost}" 217 | + f" -Dhttps.proxyPort={myport}" 218 | ) 219 | ``` 220 | 221 | 3. Configure your proxy settings 222 | [through Maven](https://www.baeldung.com/maven-behind-proxy) by editing the 223 | `..` block of your `$HOME/.m2/settings.xml` file: 224 | ``` 225 | 226 | 227 | Your company proxy 228 | true 229 | https 230 | example.com 231 | 8080 232 | 233 | 234 | ``` 235 | 236 | 4. [Initialize with a local `Fiji.app` installation](Initialization.md#from-a-local-installation), 237 | so that PyImageJ does not need to download anything else from the Internet. 238 | In this case you will also have to manually download the latest `.jar` files 239 | for 240 | [imglib2-unsafe](https://maven.scijava.org/#nexus-search;quick~imglib2-unsafe) 241 | and 242 | [imglib2-imglyb](https://maven.scijava.org/#nexus-search;quick~imglib2-imglyb) 243 | and place them in your local `Fiji.app/jars` directory, as these are 244 | required for PyImageJ but not part of the standard Fiji distribution. 245 | 246 | #### Unable to find valid certification path 247 | 248 | If the debugging output includes notices such as: 249 | 250 | ``` 251 | Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 252 | at sun.security.validator.PKIXValidator.doBuild (PKIXValidator.java:397) 253 | at sun.security.validator.PKIXValidator.engineValidate (PKIXValidator.java:240) 254 | ``` 255 | 256 | This suggests the version of Java being used is too old and contains outdated 257 | certificate information. This behavior has been confirmed with the `openjdk` 258 | installed from the default conda channel (i.e. `conda install openjdk`). Try 259 | using an openjdk from the 260 | [conda-forge channel](https://anaconda.org/conda-forge/openjdk) instead. 261 | 262 | ### I ran a plugin and see an updated image, but the numpy array and dataset are unchanged 263 | 264 | This bug can occur in certain circumstances when using original ImageJ plugins 265 | which update a corresponding `ImagePlus`. It can be worked around by calling: 266 | 267 | ```python 268 | imp = ij.WindowManager.getCurrentImage() 269 | ij.py.sync_image(imp) 270 | ``` 271 | 272 | ### The same macro gives different results when run multiple times 273 | 274 | This pernicious problem, covered by 275 | [issue #148](https://github.com/imagej/pyimagej/issues/148), has been observed 276 | and documented on [a forum thread](https://forum.image.sc/t/57744). No one has 277 | had time to fully investigate, determine how widespread the problem is, or fix 278 | it. Help wanted! 279 | 280 | ### Original ImageJ classes not found 281 | 282 | If you try to load an original ImageJ class (with package prefix `ij`), 283 | and get a `JavaException: Class not found` error, this is because 284 | the environment was initialized without the original ImageJ included. 285 | See [Initialization.md](Initialization.md). 286 | 287 | ### Not enough memory 288 | 289 | You can increase the memory available to the JVM before starting it. 290 | See [Initialization.md](Initialization.md). 291 | 292 | ### Python hangs when quitting 293 | 294 | It's probably because the JVM is not shutting down cleanly. JPype and scyjava 295 | try their best to shut down the JVM, and PyImageJ does its best to dispose all 296 | ImageJ2 resources when Python wants to shut down. However, in some scenarios 297 | there can still be problems; see 298 | [#153](https://github.com/imagej/pyimagej/issues/153). 299 | 300 | You can try calling `ij.dispose()` yourself before quitting Python. If that is 301 | not enough, you can even call `scyjava.jimport('java.lang.System').exit(0)` 302 | (Java exit) or `sys.exit(0)` (Python exit), either of which will immediately 303 | terminate both Java and Python. 304 | 305 | ### log4j:WARN 306 | 307 | With ImageJ2 v2.3.0 and earlier, there is an obnoxious warning at startup: 308 | 309 | ``` 310 | log4j:WARN No appenders could be found for logger (org.bushe.swing.event.EventService). 311 | log4j:WARN Please initialize the log4j system properly. 312 | log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 313 | ``` 314 | 315 | This can safely be ignored, and will be fixed in the next release of ImageJ2. 316 | 317 | ### TypeError: No matching overloads 318 | 319 | Java has method overloading, whereas Python does not. The JPype library is very 320 | smart about figuring which Java method you intend to call based on the argument 321 | types. But it is not perfect—see e.g. 322 | [jpype-project/jpype#844](https://github.com/jpype-project/jpype/issues/844). 323 | Therefore, you might encounter an error `TypeError: No matching overloads` when 324 | trying to call certain Java methods in some scenarios. Here is an example: 325 | 326 | ```python 327 | >>> ij = imagej.init() 328 | >>> ij.op().create() 329 | Traceback (most recent call last): 330 | File "", line 1, in 331 | TypeError: No matching overloads found for org.scijava.plugin.PTService.create(), options are: 332 | public default org.scijava.plugin.SciJavaPlugin org.scijava.plugin.PTService.create(java.lang.Class) 333 | ``` 334 | 335 | Until JPype is improved, you will need to work around the issue case by case. 336 | For example, to avoid the error above with the `create()` method, you can use: 337 | 338 | ```python 339 | CreateNamespace = imagej.sj.jimport('net.imagej.ops.create.CreateNamespace') 340 | create = ij.op().namespace(CreateNamespace) 341 | ``` 342 | 343 | And then `create` will contain the same object normally accessed via 344 | `ij.op().create()`. 345 | 346 | If you are stuck, please post a topic on the 347 | [Image.sc Forum](https://forum.image.sc/). 348 | 349 | 350 | ### `pip install jpype1` fails on Windows 351 | 352 | There is a known issue installing with `pip` on Windows with Python 3.10. 353 | Please see 354 | [jpype-project/jpype#1009](https://github.com/jpype-project/jpype/issues/1009). 355 | 356 | Until this issue is resolved, we suggest those on Windows either: 357 | * Install with `conda` rather than `pip` (*preferred*). 358 | * Downgrade to Python 3.9. 359 | 360 | [ImageJ]: https://imagej.net/software/imagej 361 | [ImageJ Legacy Bridge]: https://imagej.net/libs/imagej-legacy 362 | [headless mode]: https://imagej.net/learn/headless 363 | [ROI Manager]: https://imagej.nih.gov/ij/docs/guide/146-30.html#fig:The-ROI-Manager 364 | -------------------------------------------------------------------------------- /doc/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | PyImageJ provides a set of wrapper functions for integration between 5 | ImageJ+ImageJ2 and Python. A major advantage of this approach is the ability to 6 | combine ImageJ+ImageJ2 with other tools available from the Python software 7 | ecosystem, e.g. NumPy, SciPy, scikit-image, CellProfiler, OpenCV, and ITK. 8 | 9 | The first step when using PyImageJ is to create an ImageJ2 gateway. 10 | This gateway can point to any official release of ImageJ2 or to a local 11 | installation. Using the gateway, you have full access to the ImageJ2 API, 12 | plus utility functions for translating between Python (NumPy, xarray, 13 | pandas, etc.) and Java (ImageJ, ImageJ2, ImgLib2, etc.) structures. 14 | 15 | Here is an example of opening an image using ImageJ2 and displaying it: 16 | 17 | .. highlight:: python 18 | .. code-block:: python 19 | 20 | # Create an ImageJ2 gateway with the newest available version of ImageJ2. 21 | import imagej 22 | ij = imagej.init() 23 | 24 | # Load an image. 25 | image_url = 'https://imagej.net/images/clown.png' 26 | jimage = ij.io().open(image_url) 27 | 28 | # Convert the image from ImageJ2 to xarray, a package that adds 29 | # labeled datasets to numpy (http://xarray.pydata.org/en/stable/). 30 | image = ij.py.from_java(jimage) 31 | 32 | # Display the image (backed by matplotlib). 33 | ij.py.show(image, cmap='gray') 34 | 35 | Initialization 36 | -------------- 37 | .. currentmodule:: imagej 38 | .. autofunction:: imagej.init 39 | 40 | .. autoclass:: Mode 41 | :members: 42 | :undoc-members: 43 | 44 | Convenience methods 45 | ------------------- 46 | .. currentmodule:: imagej 47 | .. autoclass:: ImageJPython 48 | :members: 49 | 50 | RandomAccessibleInterval (RAI) operators 51 | ---------------------------------------- 52 | .. currentmodule:: imagej 53 | .. autoclass:: RAIOperators 54 | :members: 55 | 56 | ImageJ2 gateway addons 57 | ---------------------- 58 | .. currentmodule:: imagej 59 | .. autoclass:: GatewayAddons 60 | :members: 61 | 62 | ImagePlus addons 63 | ---------------- 64 | .. autoclass:: ImagePlusAddons 65 | :members: 66 | 67 | Interval addons 68 | --------------- 69 | .. currentmodule:: imagej 70 | .. autoclass:: IntervalAddons 71 | :members: 72 | 73 | Euclidean space addons 74 | ---------------------- 75 | .. currentmodule:: imagej 76 | .. autoclass:: EuclideanSpaceAddons 77 | :members: 78 | 79 | Typed space addons 80 | ------------------ 81 | .. currentmodule:: imagej 82 | .. autoclass:: TypedSpaceAddons 83 | :members: 84 | 85 | Annotated space addons 86 | ---------------------- 87 | .. currentmodule:: imagej 88 | .. autoclass:: AnnotatedSpaceAddons 89 | :members: 90 | 91 | PyImageJ submodules 92 | ------------------- 93 | 94 | imagej.convert 95 | ~~~~~~~~~~~~~~ 96 | .. automodule:: convert 97 | :members: 98 | :show-inheritance: 99 | 100 | imagej.dims 101 | ~~~~~~~~~~~ 102 | .. automodule:: dims 103 | :members: 104 | :show-inheritance: 105 | 106 | imagej.doctor 107 | ~~~~~~~~~~~~~ 108 | .. automodule:: doctor 109 | :members: 110 | :show-inheritance: 111 | 112 | imagej.stack 113 | ~~~~~~~~~~~~ 114 | .. automodule:: stack 115 | :members: 116 | :show-inheritance: 117 | 118 | imagej.images 119 | ~~~~~~~~~~~~~ 120 | .. automodule:: images 121 | :members: 122 | :show-inheritance: 123 | -------------------------------------------------------------------------------- /doc/cellprofiler/README.md: -------------------------------------------------------------------------------- 1 | # CellProfiler and PyImageJ - _RunImageJScript_ 2 | 3 | One initial goal in the development of PyImageJ was to improve integration of 4 | ImageJ with [CellProfiler](https://cellprofiler.org/), a Python-based 5 | open-source tool for creating modular image analysis pipelines. 6 | 7 | While CellProfiler has had an existing integration with Java for many years 8 | [[1], [2]], more recently the JPype library has become very robust and now 9 | serves as an ideal library for accessing Java resources from Python. PyImageJ 10 | offers a new opportunity to better bridge these applications in a lightweight 11 | manner without requiring fundamental structural changes to either platform, 12 | yielding a connection that is simpler, more powerful and more performant. We 13 | have accomplished this through the [RunImageJScript] CellProfiler module. 14 | RunImageJScript functions like any other CellProfiler module, allowing the 15 | user to execute an ImageJ2 script as part of their workflow. PyImageJ enables 16 | translation between structures in each domain, mapping script inputs and 17 | outputs to CellProfiler workflow settings. This allows CellProfiler to benefit 18 | from image-analysis algorithms and functionality exclusive to ImageJ without 19 | the speed penalty incurred by previous bridge attempts [[3]]. 20 | 21 | As an example use case, we demonstrated a >3-fold increase in performance of a 22 | CellProfiler workflow that identifies and measures cells (see Figure below) 23 | using ImageJ's [Trainable Weka Segmentation] plugin (TWS) via PyImageJ. We used 24 | the Broad Biomage Benchmark Collection (BBBC) image set [[4]] BBBC030, a set of 25 | DIC images of Chinese Hamster Ovary cells [[5]]; in order to assess 26 | segmentation performance as well as cell merges and splits, the ground truth 27 | outlines were converted to label matrices in CellProfiler and lightly edited 28 | with the original authors' permission. A single image from the set of 60 was 29 | trained with the TWS plugin to distinguish background from cell centers and 30 | cell boundaries, and the classifier model was exported. RunImageJScript was 31 | then used from inside CellProfiler, invoking TWS to apply the classifier onto 32 | each of the 60 images and first segment cell centers and then cell boundaries; 33 | we have previously found17 that separate prediction of cell interiors versus 34 | boundaries decreases the numbers of cells accidentally split into more than one 35 | object or merged with a neighboring cell. This approach outperformed two 36 | CellProfiler-only classical image processing methods of identifying the cells 37 | (Panel B), one using the EnhanceOrSuppressFeatures module to distinguish the 38 | cells from background by their texture, and the other using the EnhanceEdges 39 | module to distinguish the cells from background by looking for edges in the 40 | image, especially at the task of minimizing incorrect merges and splits (Panel 41 | C). As none of the CellProfiler workflows attempted to identify cells touching 42 | the edges of the image, segmentation accuracy was assessed both for the entire 43 | set of ground truth cells and for only the ground truth cells not touching the 44 | cell border, yielding similar results. 45 | 46 | ------------------------------------------------------------------------------ 47 | 48 | ![](figure.svg) 49 | 50 | Figure: RunImageJScript, built on PyImageJ, allows running CellProfiler 51 | workflows on the BBBC030 data set. CellProfiler 4.1 introduced the 52 | RunImageJMacro module which relies upon writing images to disk in order to 53 | exchange data with ImageJ. 54 | 55 | **A**. Runtime of a pipeline using either an internal CellProfiler module (Smooth) 56 | vs rolling ball background subtraction in ImageJ via RunImageJMacro or 57 | RunImageJScript. Comparing execution time indicates that RunImageJScript is 58 | more than 3x faster than RunImageJMacro and has comparable performance to 59 | pipelines that do not use an ImageJ plugin. 60 | 61 | **B**. Accuracy of three segmentation approaches on BBBC030 in CellProfiler. At all 62 | Intersection-over-Union (IoU) thresholds, using RunImageJScript to call 63 | ImageJ’s Trainable Weka Segmentation equals or outperforms CellProfiler-only 64 | approaches. Solid lines are performance on all cells; dashed lines are 65 | performance on non-edge-touching cells, which CellProfiler did not try to 66 | segment. 67 | 68 | **C**. Across all cells, RunImageJScript misses the fewest cells at IoU 0.7, as 69 | well as better balancing errors in splitting and merging. 70 | 71 | **D**. Outlines of segmented classified cells. Cells outlined in teal are 72 | classified as touching at least one other cell, while cells outlined in orange 73 | are classified as isolated. High performance at this task requires minimizing 74 | incorrect merges and splits. 75 | 76 | [1]: https://doi.org/10.4308/hjb.20.4.151 77 | [2]: https://doi.org/10.1002/cpz1.89 78 | [3]: https://doi.org/10.1186/s12859-021-04344-9 79 | [4]: https://doi.org/10.1038/nmeth.2083 80 | [5]: https://doi.org/10.1038/srep30420 81 | [RunImageJScript]: https://imagej.net/plugins/runimagejscript 82 | [Trainable Weka Segmentation]: https://imagej.net/plugins/tws 83 | -------------------------------------------------------------------------------- /doc/cellprofiler/RunImageJMacroWorkflow/RunImageJBallWithMacro.cppipe: -------------------------------------------------------------------------------- 1 | CellProfiler Pipeline: http://www.cellprofiler.org 2 | Version:5 3 | DateRevision:421 4 | GitHash: 5 | ModuleCount:8 6 | HasImagePlaneDetails:False 7 | 8 | Images:[module_num:1|svn_version:'Unknown'|variable_revision_number:2|show_window:False|notes:['To begin creating your project, use the Images module to compile a list of files and/or folders that you want to analyze. You can also specify a set of rules to include only the desired files in your selected folders.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 9 | : 10 | Filter images?:Images only 11 | Select the rule criteria:and (extension does isimage) (directory doesnot containregexp "[\\\\/]\\.") 12 | 13 | Metadata:[module_num:2|svn_version:'Unknown'|variable_revision_number:6|show_window:False|notes:['The Metadata module optionally allows you to extract information describing your images (i.e, metadata) which will be stored along with your measurements. This information can be contained in the file name and/or location, or in an external file.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 14 | Extract metadata?:No 15 | Metadata data type:Text 16 | Metadata types:{} 17 | Extraction method count:1 18 | Metadata extraction method:Extract from file/folder names 19 | Metadata source:File name 20 | Regular expression to extract from file name:^(?P.*)_(?P[A-P][0-9]{2})_s(?P[0-9])_w(?P[0-9]) 21 | Regular expression to extract from folder name:(?P[0-9]{4}_[0-9]{2}_[0-9]{2})$ 22 | Extract metadata from:All images 23 | Select the filtering criteria:and (file does contain "") 24 | Metadata file location:Elsewhere...| 25 | Match file and image metadata:[] 26 | Use case insensitive matching?:No 27 | Metadata file name:None 28 | Does cached metadata exist?:No 29 | 30 | NamesAndTypes:[module_num:3|svn_version:'Unknown'|variable_revision_number:8|show_window:False|notes:['The NamesAndTypes module allows you to assign a meaningful name to each image by which other modules will refer to it.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 31 | Assign a name to:All images 32 | Select the image type:Grayscale image 33 | Name to assign these images:DNA 34 | Match metadata:[] 35 | Image set matching method:Order 36 | Set intensity range from:Image metadata 37 | Assignments count:1 38 | Single images count:0 39 | Maximum intensity:255.0 40 | Process as 3D?:No 41 | Relative pixel spacing in X:1.0 42 | Relative pixel spacing in Y:1.0 43 | Relative pixel spacing in Z:1.0 44 | Select the rule criteria:and (file does contain "") 45 | Name to assign these images:DNA 46 | Name to assign these objects:Cell 47 | Select the image type:Grayscale image 48 | Set intensity range from:Image metadata 49 | Maximum intensity:255.0 50 | 51 | Groups:[module_num:4|svn_version:'Unknown'|variable_revision_number:2|show_window:False|notes:['The Groups module optionally allows you to split your list of images into image subsets (groups) which will be processed independently of each other. Examples of groupings include screening batches, microtiter plates, time-lapse movies, etc.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 52 | Do you want to group your images?:No 53 | grouping metadata count:1 54 | Metadata category:None 55 | 56 | RunImageJMacro:[module_num:5|svn_version:'Unknown'|variable_revision_number:1|show_window:False|notes:[]|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 57 | Hidden:1 58 | Hidden:1 59 | Hidden:0 60 | Executable directory:Elsewhere...|/Applications 61 | Executable:Fiji.app 62 | Macro directory:Elsewhere...|/Users/bcimini/Desktop 63 | Macro:test_ball_macro.py 64 | What variable in your macro defines the folder ImageJ should use?:directory 65 | Select an image to send to your macro:DNA 66 | What should this image temporarily saved as?:dummy.tiff 67 | What is the image filename CellProfiler should load?:backsub.tiff 68 | What should CellProfiler call the loaded image?:backsub 69 | 70 | Smooth:[module_num:6|svn_version:'Unknown'|variable_revision_number:2|show_window:False|notes:[]|batch_state:array([], dtype=uint8)|enabled:False|wants_pause:False] 71 | Select the input image:DNA 72 | Name the output image:backsub 73 | Select smoothing method:Gaussian Filter 74 | Calculate artifact diameter automatically?:No 75 | Typical artifact diameter:16.0 76 | Edge intensity difference:0.1 77 | Clip intensities to 0 and 1?:Yes 78 | 79 | SaveImages:[module_num:7|svn_version:'Unknown'|variable_revision_number:16|show_window:False|notes:[]|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 80 | Select the type of image to save:Image 81 | Select the image to save:backsub 82 | Select method for constructing file names:From image filename 83 | Select image name for file prefix:DNA 84 | Enter single file name:OrigBlue 85 | Number of digits:4 86 | Append a suffix to the image file name?:Yes 87 | Text to append to the image name:_backsub 88 | Saved file format:png 89 | Output file location:Default Output Folder| 90 | Image bit depth:8-bit integer 91 | Overwrite existing files without warning?:No 92 | When to save:Every cycle 93 | Record the file and path information to the saved image?:No 94 | Create subfolders in the output folder?:No 95 | Base image folder:Elsewhere...| 96 | How to save the series:T (Time) 97 | Save with lossless compression?:Yes 98 | 99 | ExportToSpreadsheet:[module_num:8|svn_version:'Unknown'|variable_revision_number:13|show_window:False|notes:[]|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 100 | Select the column delimiter:Comma (",") 101 | Add image metadata columns to your object data file?:No 102 | Add image file and folder names to your object data file?:No 103 | Select the measurements to export:No 104 | Calculate the per-image mean values for object measurements?:No 105 | Calculate the per-image median values for object measurements?:No 106 | Calculate the per-image standard deviation values for object measurements?:No 107 | Output file location:Default Output Folder| 108 | Create a GenePattern GCT file?:No 109 | Select source of sample row name:Metadata 110 | Select the image to use as the identifier:None 111 | Select the metadata to use as the identifier:None 112 | Export all measurement types?:Yes 113 | Press button to select measurements: 114 | Representation of Nan/Inf:NaN 115 | Add a prefix to file names?:Yes 116 | Filename prefix:MyExpt_ 117 | Overwrite existing files without warning?:No 118 | Data to export:Do not use 119 | Combine these object measurements with those of the previous object?:No 120 | File name:DATA.csv 121 | Use the object name for the file name?:Yes 122 | -------------------------------------------------------------------------------- /doc/cellprofiler/RunImageJMacroWorkflow/test_ball_macro.py: -------------------------------------------------------------------------------- 1 | #@String directory 2 | 3 | from ij import IJ 4 | import os 5 | 6 | im = IJ.open(os.path.join(directory, 'dummy.tiff')) 7 | IJ.run(im, "Subtract Background...", "rolling=50") 8 | IJ.saveAs(im,'tiff',os.path.join(directory,'backsub.tiff')) 9 | -------------------------------------------------------------------------------- /doc/cellprofiler/RunImageJScriptRollingBall/RunImageJBall.cppipe: -------------------------------------------------------------------------------- 1 | CellProfiler Pipeline: http://www.cellprofiler.org 2 | Version:5 3 | DateRevision:421 4 | GitHash: 5 | ModuleCount:8 6 | HasImagePlaneDetails:False 7 | 8 | Images:[module_num:1|svn_version:'Unknown'|variable_revision_number:2|show_window:False|notes:['To begin creating your project, use the Images module to compile a list of files and/or folders that you want to analyze. You can also specify a set of rules to include only the desired files in your selected folders.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 9 | : 10 | Filter images?:Images only 11 | Select the rule criteria:and (extension does isimage) (directory doesnot containregexp "[\\\\/]\\.") 12 | 13 | Metadata:[module_num:2|svn_version:'Unknown'|variable_revision_number:6|show_window:False|notes:['The Metadata module optionally allows you to extract information describing your images (i.e, metadata) which will be stored along with your measurements. This information can be contained in the file name and/or location, or in an external file.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 14 | Extract metadata?:No 15 | Metadata data type:Text 16 | Metadata types:{} 17 | Extraction method count:1 18 | Metadata extraction method:Extract from file/folder names 19 | Metadata source:File name 20 | Regular expression to extract from file name:^(?P.*)_(?P[A-P][0-9]{2})_s(?P[0-9])_w(?P[0-9]) 21 | Regular expression to extract from folder name:(?P[0-9]{4}_[0-9]{2}_[0-9]{2})$ 22 | Extract metadata from:All images 23 | Select the filtering criteria:and (file does contain "") 24 | Metadata file location:Elsewhere...| 25 | Match file and image metadata:[] 26 | Use case insensitive matching?:No 27 | Metadata file name:None 28 | Does cached metadata exist?:No 29 | 30 | NamesAndTypes:[module_num:3|svn_version:'Unknown'|variable_revision_number:8|show_window:False|notes:['The NamesAndTypes module allows you to assign a meaningful name to each image by which other modules will refer to it.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 31 | Assign a name to:All images 32 | Select the image type:Grayscale image 33 | Name to assign these images:DNA 34 | Match metadata:[] 35 | Image set matching method:Order 36 | Set intensity range from:Image metadata 37 | Assignments count:1 38 | Single images count:0 39 | Maximum intensity:255.0 40 | Process as 3D?:No 41 | Relative pixel spacing in X:1.0 42 | Relative pixel spacing in Y:1.0 43 | Relative pixel spacing in Z:1.0 44 | Select the rule criteria:and (file does contain "") 45 | Name to assign these images:DNA 46 | Name to assign these objects:Cell 47 | Select the image type:Grayscale image 48 | Set intensity range from:Image metadata 49 | Maximum intensity:255.0 50 | 51 | Groups:[module_num:4|svn_version:'Unknown'|variable_revision_number:2|show_window:False|notes:['The Groups module optionally allows you to split your list of images into image subsets (groups) which will be processed independently of each other. Examples of groupings include screening batches, microtiter plates, time-lapse movies, etc.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 52 | Do you want to group your images?:No 53 | grouping metadata count:1 54 | Metadata category:None 55 | 56 | RunImageJScript:[module_num:5|svn_version:'Unknown'|variable_revision_number:3|show_window:False|notes:['TheRunImageJScriptmodule allows you to run any supported ImageJ script as part of your workflow. First, select your desired initialization method and specify the app directory or endpoint(s) if needed. Then select a script file to be executed by this module. Click the "Get parameters from script" button to detect required inputs for your script: each input will have its own setting created, allowing you to pass data from CellProfiler to ImageJ. After filling in any required inputs you can run the module normally. Note: ImageJ will only be initialized once per CellProfiler session. Note: only numeric, text and image parameters are currently supported. See also ImageJ Scripting: https://imagej.net/Scripting.']|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 57 | Hidden:2 58 | Initialization type:Local 59 | ImageJ directory:Elsewhere...|/Applications 60 | Local App:Fiji.app 61 | Initialization endpoint:sc.fiji:fiji:2.1.0 62 | Script directory:Default Input Folder| 63 | ImageJ Script:test_ball.bsh 64 | :None 65 | Adjust image type?:Yes 66 | :n/a 67 | testImage:DNA 68 | :n/a 69 | Parameter name:testImage 70 | Parameter type:class ij.ImagePlus 71 | Parameter classification:INPUT 72 | [OUTPUT, ij.ImagePlus] backsub:backsub 73 | :n/a 74 | Parameter name:backsub 75 | Parameter type:class ij.ImagePlus 76 | Parameter classification:OUTPUT 77 | 78 | Smooth:[module_num:6|svn_version:'Unknown'|variable_revision_number:2|show_window:False|notes:[]|batch_state:array([], dtype=uint8)|enabled:False|wants_pause:False] 79 | Select the input image:DNA 80 | Name the output image:backsub 81 | Select smoothing method:Gaussian Filter 82 | Calculate artifact diameter automatically?:No 83 | Typical artifact diameter:16.0 84 | Edge intensity difference:0.1 85 | Clip intensities to 0 and 1?:Yes 86 | 87 | SaveImages:[module_num:7|svn_version:'Unknown'|variable_revision_number:16|show_window:False|notes:[]|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 88 | Select the type of image to save:Image 89 | Select the image to save:backsub 90 | Select method for constructing file names:From image filename 91 | Select image name for file prefix:DNA 92 | Enter single file name:OrigBlue 93 | Number of digits:4 94 | Append a suffix to the image file name?:Yes 95 | Text to append to the image name:_backsub 96 | Saved file format:png 97 | Output file location:Default Output Folder| 98 | Image bit depth:8-bit integer 99 | Overwrite existing files without warning?:No 100 | When to save:Every cycle 101 | Record the file and path information to the saved image?:No 102 | Create subfolders in the output folder?:No 103 | Base image folder:Elsewhere...| 104 | How to save the series:T (Time) 105 | Save with lossless compression?:Yes 106 | 107 | ExportToSpreadsheet:[module_num:8|svn_version:'Unknown'|variable_revision_number:13|show_window:False|notes:[]|batch_state:array([], dtype=uint8)|enabled:True|wants_pause:False] 108 | Select the column delimiter:Comma (",") 109 | Add image metadata columns to your object data file?:No 110 | Add image file and folder names to your object data file?:No 111 | Select the measurements to export:No 112 | Calculate the per-image mean values for object measurements?:No 113 | Calculate the per-image median values for object measurements?:No 114 | Calculate the per-image standard deviation values for object measurements?:No 115 | Output file location:Default Output Folder| 116 | Create a GenePattern GCT file?:No 117 | Select source of sample row name:Metadata 118 | Select the image to use as the identifier:None 119 | Select the metadata to use as the identifier:None 120 | Export all measurement types?:Yes 121 | Press button to select measurements: 122 | Representation of Nan/Inf:NaN 123 | Add a prefix to file names?:Yes 124 | Filename prefix:MyExpt_ 125 | Overwrite existing files without warning?:No 126 | Data to export:Do not use 127 | Combine these object measurements with those of the previous object?:No 128 | File name:DATA.csv 129 | Use the object name for the file name?:Yes 130 | -------------------------------------------------------------------------------- /doc/cellprofiler/RunImageJScriptRollingBall/test_ball.bsh: -------------------------------------------------------------------------------- 1 | #@ ImagePlus(label="Testing image", description="Stack or a single 2D image") testImage 2 | #@OUTPUT ImagePlus backsub 3 | 4 | import ij.IJ; 5 | 6 | backsub = testImage.duplicate(); 7 | IJ.run(backsub, "Subtract Background...", "rolling=50"); 8 | -------------------------------------------------------------------------------- /doc/cellprofiler/RunImageJScriptSegmentation/classifier.model: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/cellprofiler/RunImageJScriptSegmentation/classifier.model -------------------------------------------------------------------------------- /doc/cellprofiler/RunImageJScriptSegmentation/test.bsh: -------------------------------------------------------------------------------- 1 | #@ ImagePlus(label="Testing image", description="Stack or a single 2D image") testImage 2 | #@ File(label="Weka model", description="Select the Weka model to apply") modelPath 3 | #@OUTPUT ImagePlus labels 4 | #@OUTPUT ImagePlus probs 5 | 6 | import ij.IJ; 7 | import trainableSegmentation.WekaSegmentation; 8 | import hr.irb.fastRandomForest.FastRandomForest; 9 | 10 | 11 | // create Weka segmentator 12 | seg = new WekaSegmentation(); 13 | seg.loadClassifier( modelPath.getCanonicalPath() ); 14 | 15 | IJ.log( "successfully loaded classifier" ); 16 | 17 | // Apply trained classifier to test image and get probabilities 18 | labels = seg.applyClassifier( testImage, 0, false ); 19 | probs = seg.applyClassifier( testImage, 0, true ); 20 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("../src/imagej/")) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = "PyImageJ" 22 | copyright = "2022 ImageJ2 developers" 23 | author = "ImageJ2 developers" 24 | 25 | 26 | # -- General configuration --------------------------------------------------- 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | "sphinx.ext.autodoc", 33 | "sphinx.ext.viewcode", 34 | "sphinx_search.extension", 35 | "sphinx_copybutton", 36 | "myst_nb", 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ["_templates"] 41 | 42 | # List of patterns, relative to source directory, that match files and 43 | # directories to ignore when looking for source files. 44 | # This pattern also affects html_static_path and html_extra_path. 45 | exclude_patterns = [ 46 | "_build", 47 | "Thumbs.db", 48 | ".DS_Store", 49 | "README.md", 50 | "examples/README.md", 51 | ] 52 | 53 | # -- MyST-Parser/MyST-NB configuration --------------------------------------- 54 | myst_heading_anchors = 4 55 | nb_execution_mode = "off" 56 | 57 | # -- Options for HTML output ------------------------------------------------- 58 | 59 | # Always show the Edit on GitHub buttons 60 | # Set the correct path for Edit on GitHub 61 | html_context = { 62 | 'display_github': True, 63 | 'github_user': 'imagej', 64 | 'github_repo': 'pyimagej', 65 | 'github_version': 'main/doc/', 66 | } 67 | 68 | # The theme to use for HTML and HTML Help pages. See the documentation for 69 | # a list of builtin themes. 70 | # 71 | html_theme = "sphinx_rtd_theme" 72 | 73 | # Add any paths that contain custom static files (such as style sheets) here, 74 | # relative to this directory. They are copied after the builtin static files, 75 | # so a file named "default.css" will overwrite the builtin "default.css". 76 | html_static_path = [] 77 | 78 | # Add the PyImageJ logo 79 | html_logo = "doc-images/logo.svg" 80 | html_theme_options = { 81 | "logo_only": True, 82 | } 83 | -------------------------------------------------------------------------------- /doc/doc-images/imagej_ui_macro_recorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/doc-images/imagej_ui_macro_recorder.png -------------------------------------------------------------------------------- /doc/doc-images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 37 | 39 | 45 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 66 | 70 | 74 | 83 | 88 | 91 | 94 | 97 | 100 | 103 | 106 | 109 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /doc/doc-images/macro_recorder_find_maxima.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/doc-images/macro_recorder_find_maxima.png -------------------------------------------------------------------------------- /doc/doc-images/test_still_analyze_particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/doc-images/test_still_analyze_particles.png -------------------------------------------------------------------------------- /doc/doc-images/test_still_find_maxima.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/doc-images/test_still_find_maxima.png -------------------------------------------------------------------------------- /doc/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains example scripts that use PyImageJ 4 | to accomplish specific tasks and workflows. 5 | 6 | | Script | Description | 7 | |----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| 8 | [blob\_detection\_interactive.py](blob_detection_interactive.py) | Detect blob-like objects using scikit-image and the original ImageJ, interactively. | 9 | [blob\_detection\_xvfb.py](blob_detection_xvfb.py) | Detect blob-like objects using scikit-image and the original ImageJ, non-interactively via [xvfb](../Headless.md). | 10 | -------------------------------------------------------------------------------- /doc/examples/blob_detection_interactive.py: -------------------------------------------------------------------------------- 1 | import imagej 2 | import scyjava as sj 3 | import napari 4 | import xarray as xr 5 | import numpy as np 6 | 7 | from math import sqrt 8 | from skimage.feature import blob_log 9 | from matplotlib import pyplot as plt 10 | 11 | 12 | def find_blobs(image: xr.DataArray, min_sigma: float, max_sigma: float, num_sigma: int, threshold=0.1, show=False) -> np.ndarray: 13 | """ 14 | Find blobs with Laplacian of Gaussian (LoG). 15 | """ 16 | # detect blobs in image 17 | blobs = blob_log(image, min_sigma=min_sigma, max_sigma=max_sigma, num_sigma=num_sigma, threshold=threshold) 18 | blobs[:, 2] = blobs[:, 2] * sqrt(2) 19 | 20 | return blobs 21 | 22 | 23 | def detections_to_pyplot(image: xr.DataArray, detections: np.ndarray): 24 | """ 25 | Display image with detections in matplotlib.pyplot. 26 | """ 27 | fig, ax = plt.subplots(1, 2, figsize=(8,4), sharex=True, sharey=True) 28 | ax[0].imshow(image, interpolation='nearest') 29 | ax[1].imshow(image, interpolation='nearest') 30 | for blob in detections: 31 | y, x, r = blob 32 | c = plt.Circle((x, y), r, color='white', linewidth=1, fill=False) 33 | ax[1].add_patch(c) 34 | 35 | plt.tight_layout 36 | plt.show() 37 | 38 | 39 | def detections_to_imagej(dataset, detections: np.ndarray, add_to_roi_manager=False): 40 | """ 41 | Convert blob detections to ImageJ oval ROIs. 42 | Optionally add the ROIs to the RoiManager. 43 | """ 44 | # get ImageJ resources 45 | OvalRoi = sj.jimport('ij.gui.OvalRoi') 46 | Overlay = sj.jimport('ij.gui.Overlay') 47 | ov = Overlay() 48 | 49 | # convert Dataset to ImagePlus 50 | imp = ij.py.to_imageplus(dataset) 51 | 52 | if add_to_roi_manager: 53 | rm = ij.RoiManager.getRoiManager() 54 | 55 | for i in range(len(detections)): 56 | values = detections[i].tolist() 57 | y = values[0] 58 | x = values[1] 59 | r = values[2] 60 | d = r * 2 61 | roi = OvalRoi(x - r, y - r, d, d) 62 | imp.setRoi(roi) 63 | ov.add(roi) 64 | if add_to_roi_manager: 65 | rm.addRoi(roi) 66 | 67 | imp.setOverlay(ov) 68 | imp.getProcessor().resetMinAndMax() 69 | imp.show() 70 | 71 | 72 | def detections_to_napari(image_array: xr.DataArray, detections: np.ndarray): 73 | """ 74 | Convert blob detections to Napari oval ROIs. 75 | """ 76 | ovals = [] 77 | for i in range(len(detections)): 78 | values = detections[i].tolist() 79 | y = values[0] 80 | x = values[1] 81 | r = values[2] 82 | pos_1 = [y - r, x - r] # top left 83 | pos_2 = [y - r, x + r] # top right 84 | pos_3 = [y + r, x + r] # bottom right 85 | pos_4 = [y + r, x - r] # bottom left 86 | ovals.append([pos_1, pos_2, pos_3, pos_4]) 87 | 88 | napari_detections = np.asarray(ovals) 89 | 90 | viewer = napari.Viewer() 91 | viewer.add_image(image_array) 92 | shapes_layer = viewer.add_shapes() 93 | shapes_layer.add( 94 | napari_detections, 95 | shape_type='ellipse', 96 | edge_width=1, 97 | edge_color='yellow', 98 | face_color='royalblue', 99 | ) 100 | 101 | napari.run() 102 | 103 | 104 | if __name__ == "__main__": 105 | # initialize imagej 106 | ij = imagej.init(mode='interactive') 107 | print(f"ImageJ version: {ij.getVersion()}") 108 | 109 | # load some sample data 110 | img = ij.io().open('../sample-data/test_image.tif') 111 | img_xr = ij.py.from_java(img) 112 | detected_blobs = find_blobs(img_xr, min_sigma=0.5, max_sigma=3, num_sigma=10, threshold=0.0075) 113 | ij.ui().showUI() 114 | detections_to_imagej(img, detected_blobs, True) 115 | detections_to_napari(img_xr, detected_blobs) 116 | detections_to_pyplot(img_xr, detected_blobs) -------------------------------------------------------------------------------- /doc/examples/blob_detection_xvfb.py: -------------------------------------------------------------------------------- 1 | import imagej 2 | import scyjava as sj 3 | import xarray as xr 4 | import numpy as np 5 | 6 | from math import sqrt 7 | from skimage.feature import blob_log 8 | 9 | 10 | def find_blobs(image: xr.DataArray, min_sigma: float, max_sigma: float, num_sigma: int, threshold=0.1) -> np.ndarray: 11 | """ 12 | Find blobs with Laplacian of Gaussian (LoG). 13 | """ 14 | # detect blobs in image 15 | blobs = blob_log(image, min_sigma=min_sigma, max_sigma=max_sigma, num_sigma=num_sigma, threshold=threshold) 16 | blobs[:, 2] = blobs[:, 2] * sqrt(2) 17 | 18 | return blobs 19 | 20 | 21 | def process(image, detections: np.ndarray, add_to_roi_manager=True, multimeasure=True): 22 | """ 23 | Process the blob rois. 24 | """ 25 | # get ImageJ resoures 26 | OvalRoi = sj.jimport('ij.gui.OvalRoi') 27 | Overlay = sj.jimport('ij.gui.Overlay') 28 | ov = Overlay() 29 | 30 | # convert image to imp 31 | imp = ij.py.to_imageplus(image) 32 | 33 | if add_to_roi_manager: 34 | rm = ij.RoiManager.getRoiManager() 35 | 36 | for i in range(len(detections)): 37 | values = detections[i].tolist() 38 | y = values[0] 39 | x = values[1] 40 | r = values[2] 41 | d = r * 2 42 | roi = OvalRoi(x - r, y - r, d, d) 43 | imp.setRoi(roi) 44 | ov.add(roi) 45 | 46 | if add_to_roi_manager: 47 | rm.addRoi(roi) 48 | 49 | imp.setOverlay(ov) 50 | imp.show() 51 | 52 | if rm != None and multimeasure: 53 | rm.runCommand(imp, "Measure") 54 | return ij.ResultsTable.getResultsTable() 55 | 56 | return None 57 | 58 | 59 | def get_dataframe(table): 60 | """ 61 | Convert results table to pandas Dataframe. 62 | """ 63 | Table = sj.jimport('org.scijava.table.Table') 64 | sci_table = ij.convert().convert(table, Table) 65 | 66 | return ij.py.from_java(sci_table) 67 | 68 | 69 | if __name__ == "__main__": 70 | # initialize imagej 71 | ij = imagej.init(mode='interactive') 72 | print(f"ImageJ version: {ij.getVersion()}") 73 | 74 | # load sample data and run blob detection 75 | img = ij.io().open('../sample-data/test_image.tif') 76 | img_xr = ij.py.from_java(img) 77 | detected_blobs = find_blobs(img_xr, min_sigma=0.5, max_sigma=3, num_sigma=10, threshold=0.0075) 78 | results_table = process(img, detected_blobs) 79 | df = get_dataframe(results_table) 80 | print(f"Output: \n{df}") -------------------------------------------------------------------------------- /doc/examples/blob_interactive.rst: -------------------------------------------------------------------------------- 1 | Blob detection (interactive) 2 | ============================ 3 | 4 | This example demonstrates a mixed workflow using PyImageJ to convert a Java image into an :code:`xarray.DataArray` (*i.e.* a metadata wrapped NumPyArray), 5 | run scikit-image's Lapacian of Gaussian (LoG) blob detection (:code:`skimage.feature.blob_log()`) to identify puncta and then convert the blob LoG detections 6 | into ImageJ regions of interest. The image with the blob LoG detections are displayed in ImageJ's image viewer, :code:`matplotlib.pyplot` and napari. 7 | 8 | .. literalinclude:: blob_detection_interactive.py 9 | :language: python -------------------------------------------------------------------------------- /doc/examples/blob_xvfb.rst: -------------------------------------------------------------------------------- 1 | Blob detection (headless) 2 | ============================ 3 | 4 | This example demonstrates a mixed workflow using PyImageJ to convert a Java image into an :code:`xarray.DataArray` (*i.e.* a metadata wrapped NumPyArray), 5 | run scikit-image's Lapacian of Gaussian (LoG) blob detection (:code:`skimage.feature.blob_log()`) to identify puncta, convert the blob LoG detections 6 | into ImageJ regions of interest and perform measurements. This example is intended to run with `X virtual framebuffer`_ in order to achieve a 7 | headless environment. Xvfb is needed in this example, in order to make use of the ImageJ ROI Manager, which is dependent on a graphical 8 | user interface. 9 | 10 | .. literalinclude:: blob_detection_xvfb.py 11 | :language: python 12 | 13 | .. _X virtual framebuffer: https://www.x.org/releases/X11R7.7/doc/man/man1/Xvfb.1.xhtml -------------------------------------------------------------------------------- /doc/imagej-ops-use-cases.rst: -------------------------------------------------------------------------------- 1 | imagej-ops use cases 2 | ==================== 3 | 4 | These are use cases utilizing the :code:`imagej-ops` framework in PyImageJ workflows. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | Deconvolution 10 | GLCM -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. PyImageJ documentation master file, created by 2 | sphinx-quickstart on Fri Apr 8 08:47:48 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to PyImageJ's documentation! 7 | ==================================== 8 | 9 | PyImageJ provides a set of wrapper functions for integration between ImageJ2 and Python. 10 | It also supports the original ImageJ API and data structures. 11 | 12 | A major advantage of this approach is the ability to combine ImageJ and ImageJ2 with 13 | other tools available from the Python software ecosystem, including NumPy, SciPy, 14 | scikit-image, CellProfiler, OpenCV, ITK and many more. 15 | 16 | .. toctree:: 17 | :maxdepth: 3 18 | :caption: 🚀 Getting Started 19 | 20 | Install 21 | Initialization 22 | 23 | .. toctree:: 24 | :maxdepth: 3 25 | :caption: 🪄 How-to guides 26 | 27 | notebooks 28 | Headless 29 | Troubleshooting 30 | 31 | .. toctree:: 32 | :maxdepth: 3 33 | :caption: 🔬 Use cases 34 | 35 | imagej-ops 36 | Integration 37 | Segmentation 38 | other_use_cases 39 | 40 | .. toctree:: 41 | :maxdepth: 3 42 | :caption: 🛠️ Development 43 | 44 | Development 45 | 46 | .. toctree:: 47 | :maxdepth: 3 48 | :caption: 📚 Reference 49 | 50 | api 51 | 52 | Indices and tables 53 | ================== 54 | 55 | * :ref:`genindex` 56 | * :ref:`modindex` 57 | * :ref:`search` 58 | -------------------------------------------------------------------------------- /doc/integration-use-cases.rst: -------------------------------------------------------------------------------- 1 | Integration use cases 2 | ====================== 3 | 4 | These are use cases where PyImageJ has been integrated into other platforms/projects. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | CellProfiler -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/notebooks.rst: -------------------------------------------------------------------------------- 1 | Tutorial notebooks 2 | ================== 3 | 4 | To learn PyImageJ, follow this numbered sequence of tutorial notebooks. If you want to run 5 | these Jupyter notebooks you can get them from the PyImageJ repository `here`_. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | 1 Starting PyImageJ <01-Starting-PyImageJ> 11 | 2 Working with Java classes <02-Working-with-Java-classes-and-Python> 12 | 3 Sending Data to Java <03-Sending-Data-to-Java> 13 | 4 Retrieving Data from Java <04-Retrieving-Data-from-Java> 14 | 5 Convenience methods of PyImageJ <05-Convenience-methods-of-PyImageJ> 15 | 6 Working with Images <06-Working-with-Images> 16 | 7 Running Macros, Scripts and Plugins <07-Running-Macros-Scripts-and-Plugins> 17 | 8 Discover and run ImageJ commands <08-Discover-and-run-ImageJ-commands> 18 | 9 Working with Large Images <09-Working-with-Large-Images> 19 | 10 Using ImageJ Ops <10-Using-ImageJ-Ops> 20 | 11 Working with the original ImageJ <11-Working-with-the-original-ImageJ> 21 | 12 Troubleshooting <12-Troubleshooting> 22 | 23 | .. _here: https://github.com/imagej/pyimagej/tree/main/doc 24 | -------------------------------------------------------------------------------- /doc/other_use_cases.rst: -------------------------------------------------------------------------------- 1 | Other use cases 2 | =============== 3 | 4 | Here are additional links to some other examples using PyImageJ 5 | in other projects. 6 | 7 | * The `pyimagej-dextr`_ repository uses Deep Extreme Cut (DEXTR) 8 | to generate ImageJ ROIs via PyImageJ. 9 | * `PoreSpy`_, a collection of image analysis 10 | tools used to extract information from 3D images of porous materials, 11 | uses some ImageJ filters via PyImageJ. 12 | * Some projects at `LOCI`_ use PyImageJ to invoke 13 | the `Image Stitching`_ and `SIFT-based image registration`_ 14 | plugins of `Fiji`_, together with deep learning for 15 | multimodal image registration: 16 | 17 | * `Image registration based on SIFT feature matching`_ 18 | 19 | * `Generate pseudo modality via CoMIR for multimodal image registration`_ 20 | 21 | * `Non-disruptive collagen characterization in clinical histopathology using cross-modality image synthesis`_ 22 | 23 | * `WSISR - Single image super-resolution for whole slide image using convolutional neural networks and self-supervised color normalization`_ 24 | 25 | To add your usage of PyImageJ to this list, please submit a pull request! 26 | 27 | .. _PyImageJ repository: https://github.com/imagej/pyimagej/tree/main/doc 28 | .. _pyimagej-dextr: https://github.com/imagej/pyimagej-dextr 29 | .. _PoreSpy: https://github.com/PMEAL/porespy 30 | .. _LOCI: https://imagej.net/orgs/loci 31 | .. _Image Stitching: https://imagej.net/plugins/image-stitching 32 | .. _Fiji: https://fiji.sc/ 33 | .. _SIFT-based image registration: https://imagej.net/plugins/linear-stack-alignment-with-sift 34 | .. _Image registration based on SIFT feature matching: https://github.com/uw-loci/automatic-histology-registration-pyimagej/blob/8ad405170ec46dccbdc1c20fbbeb6eaff47b8b76/ij_sift_registration.ipynb 35 | .. _Generate pseudo modality via CoMIR for multimodal image registration: https://github.com/uw-loci/automatic-histology-registration-pyimagej/blob/8ad405170ec46dccbdc1c20fbbeb6eaff47b8b76/pseudo_modality.ipynb 36 | .. _Non-disruptive collagen characterization in clinical histopathology using cross-modality image synthesis: https://github.com/uw-loci/he_shg_synth_workflow/blob/v1.0.0/main.py 37 | .. _WSISR - Single image super-resolution for whole slide image using convolutional neural networks and self-supervised color normalization: https://github.com/uw-loci/demo_wsi_superres/blob/38283031eee4823d332fae1b6b32b5da33fb957f/train_compress.py 38 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | sphinx-copybutton 3 | sphinx_rtd_theme 4 | myst-nb 5 | readthedocs-sphinx-search 6 | -------------------------------------------------------------------------------- /doc/sample-data/test_image.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/sample-data/test_image.tif -------------------------------------------------------------------------------- /doc/sample-data/test_still.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/sample-data/test_still.tif -------------------------------------------------------------------------------- /doc/sample-data/test_still_stack.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/sample-data/test_still_stack.tif -------------------------------------------------------------------------------- /doc/sample-data/test_timeseries.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imagej/pyimagej/f83ad76e470efaaacce2fa969c739a6518bdae08/doc/sample-data/test_timeseries.tif -------------------------------------------------------------------------------- /doc/segmentation-use-cases.rst: -------------------------------------------------------------------------------- 1 | Segmentation use cases 2 | ====================== 3 | 4 | These are use cases demonstrating segmentation workflows with PyImageJ 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | Blob detection (interactive) 10 | Blob detection (headless) 11 | Classic Segmentation 12 | Cellpose/StarDist Segmentation 13 | Puncta Segmentation -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ "setuptools>=77.0.0" ] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pyimagej" 7 | version = "1.7.1.dev0" 8 | description = "Python wrapper for ImageJ" 9 | license = "Apache-2.0" 10 | authors = [{name = "ImageJ2 developers", email = "ctrueden@wisc.edu"}] 11 | readme = "README.md" 12 | keywords = ["java", "imagej", "imagej2", "fiji"] 13 | classifiers = [ 14 | "Development Status :: 5 - Production/Stable", 15 | "Intended Audience :: Developers", 16 | "Intended Audience :: Education", 17 | "Intended Audience :: Science/Research", 18 | "Programming Language :: Python :: 3 :: Only", 19 | "Programming Language :: Python :: 3.9", 20 | "Programming Language :: Python :: 3.10", 21 | "Programming Language :: Python :: 3.11", 22 | "Programming Language :: Python :: 3.12", 23 | "Programming Language :: Python :: 3.13", 24 | "Operating System :: Microsoft :: Windows", 25 | "Operating System :: Unix", 26 | "Operating System :: MacOS", 27 | "Topic :: Scientific/Engineering", 28 | "Topic :: Scientific/Engineering :: Image Processing", 29 | "Topic :: Scientific/Engineering :: Visualization", 30 | "Topic :: Software Development :: Libraries :: Java Libraries", 31 | "Topic :: Software Development :: Libraries :: Python Modules", 32 | "Topic :: Utilities", 33 | ] 34 | 35 | requires-python = ">=3.9" 36 | dependencies = [ 37 | "imglyb >= 2.1.0", 38 | "jgo >= 1.0.3", 39 | "jpype1 >= 1.4.0", 40 | "labeling >= 0.1.14", 41 | "numpy", 42 | "scyjava >= 1.12.0", 43 | "xarray", 44 | ] 45 | 46 | [dependency-groups] 47 | # Development tools 48 | dev = [ 49 | "build", 50 | "myst-nb", 51 | "pre-commit", 52 | "pytest", 53 | "pytest-cov", 54 | "ruff", 55 | "sphinx", 56 | "sphinx_rtd_theme", 57 | "validate-pyproject[all]", 58 | ] 59 | 60 | [project.optional-dependencies] 61 | # Matplotlib display backend 62 | matplotlib = [ 63 | "matplotlib", 64 | ] 65 | # Notebook dependencies 66 | notebooks = [ 67 | "ipywidgets", 68 | "jupyter_contrib_nbextensions", 69 | "notebook < 7.0.0", 70 | "pooch", # for scikit image 71 | "scikit-image", 72 | ] 73 | 74 | [project.scripts] 75 | imagej = "imagej:imagej_main" 76 | 77 | [project.urls] 78 | homepage = "https://github.com/imagej/pyimagej" 79 | documentation = "https://pyimagej.readthedocs.io/" 80 | source = "https://github.com/imagej/pyimagej" 81 | download = "https://pypi.org/project/pyimagej/" 82 | tracker = "https://github.com/imagej/pyimagej/issues" 83 | 84 | [tool.setuptools] 85 | platforms = ["any"] 86 | package-dir = {"" = "src"} 87 | include-package-data = false 88 | 89 | [tool.setuptools.packages.find] 90 | where = ["src"] 91 | namespaces = false 92 | 93 | # ruff configuration 94 | [tool.ruff] 95 | line-length = 88 96 | src = ["src", "tests"] 97 | include = ["pyproject.toml", "src/**/*.py", "tests/**/*.py"] 98 | extend-exclude = ["bin", "build", "dist"] 99 | 100 | [tool.ruff.lint] 101 | extend-ignore = ["E203"] 102 | 103 | [tool.ruff.lint.per-file-ignores] 104 | # Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. 105 | "__init__.py" = ["E402", "F401"] 106 | 107 | [tool.pytest.ini_options] 108 | addopts = "--ignore=docs" 109 | testpaths = [ 110 | "tests", 111 | ] 112 | -------------------------------------------------------------------------------- /src/imagej/_java.py: -------------------------------------------------------------------------------- 1 | """ 2 | Internal utility functions for working with Java objects. 3 | These are not intended for external use in PyImageJ-based scripts! 4 | """ 5 | 6 | import logging 7 | from functools import lru_cache 8 | 9 | from jpype import JArray, JObject 10 | from scyjava import JavaClasses, jimport, jstacktrace 11 | 12 | 13 | def log_exception(logger: logging.Logger, exc: "jc.Throwable") -> None: 14 | if logger.isEnabledFor(logging.DEBUG): 15 | jtrace = jstacktrace(exc) 16 | if jtrace: 17 | logger.debug(jtrace) 18 | 19 | 20 | def unlock_modules(logger: logging.Logger) -> None: 21 | """ 22 | In Java 17 and later, reflection on modularized Java code is disallowed. 23 | Normally, the only way to enable it is by passing --add-opens arguments at 24 | launch, each of which unlocks a single package. This function calls 25 | shenanigans on that requirement and unlocks everything at runtime. The only 26 | argument needed at launch is `--add-opens=java.base/java.lang=ALL-UNNAMED`. 27 | """ 28 | try: 29 | Module = jimport("java.lang.Module") 30 | ModuleLayer = jimport("java.lang.ModuleLayer") 31 | String = jimport("java.lang.String") 32 | 33 | addOpens = Module.class_.getDeclaredMethod( 34 | "implAddOpens", String.class_, Module.class_ 35 | ) 36 | addOpens.setAccessible(True) 37 | addExports = Module.class_.getDeclaredMethod( 38 | "implAddExports", String.class_, Module.class_ 39 | ) 40 | addExports.setAccessible(True) 41 | 42 | # HACK: We need a class from the unnamed module. 43 | unnamed = jimport("org.scijava.Context").class_.getModule() 44 | 45 | for module in ModuleLayer.boot().modules(): 46 | for package in module.getPackages(): 47 | try: 48 | addOpens.invoke(module, package, unnamed) 49 | addExports.invoke(module, package, unnamed) 50 | except Exception as e: 51 | # Continue with other packages 52 | log_exception(logger, e) 53 | 54 | except Exception as e: 55 | log_exception(logger, e) 56 | 57 | 58 | # Import Java resources on demand. 59 | 60 | 61 | @lru_cache(maxsize=None) 62 | def JObjectArray(): 63 | return JArray(JObject) 64 | 65 | 66 | class MyJavaClasses(JavaClasses): 67 | """ 68 | Utility class used to make importing frequently-used Java classes 69 | significantly easier and more readable. 70 | """ 71 | 72 | @JavaClasses.java_import 73 | def Double(self): 74 | return "java.lang.Double" 75 | 76 | @JavaClasses.java_import 77 | def Throwable(self): 78 | return "java.lang.Throwable" 79 | 80 | @JavaClasses.java_import 81 | def ArrayList(self): 82 | return "java.util.ArrayList" 83 | 84 | @JavaClasses.java_import 85 | def ImagePlus(self): 86 | return "ij.ImagePlus" 87 | 88 | @JavaClasses.java_import 89 | def ResultsTable(self): 90 | return "ij.measure.ResultsTable" 91 | 92 | @JavaClasses.java_import 93 | def PolygonRoi(self): 94 | return "ij.gui.PolygonRoi" 95 | 96 | @JavaClasses.java_import 97 | def ImageMetadata(self): 98 | return "io.scif.ImageMetadata" 99 | 100 | @JavaClasses.java_import 101 | def MetadataWrapper(self): 102 | return "io.scif.filters.MetadataWrapper" 103 | 104 | @JavaClasses.java_import 105 | def LabelingIOService(self): 106 | return "io.scif.labeling.LabelingIOService" 107 | 108 | @JavaClasses.java_import 109 | def DefaultLinearAxis(self): 110 | return "net.imagej.axis.DefaultLinearAxis" 111 | 112 | @JavaClasses.java_import 113 | def EnumeratedAxis(self): 114 | return "net.imagej.axis.EnumeratedAxis" 115 | 116 | @JavaClasses.java_import 117 | def Dataset(self): 118 | return "net.imagej.Dataset" 119 | 120 | @JavaClasses.java_import 121 | def ImageJ(self): 122 | return "net.imagej.ImageJ" 123 | 124 | @JavaClasses.java_import 125 | def ImgPlus(self): 126 | return "net.imagej.ImgPlus" 127 | 128 | @JavaClasses.java_import 129 | def Axes(self): 130 | return "net.imagej.axis.Axes" 131 | 132 | @JavaClasses.java_import 133 | def Axis(self): 134 | return "net.imagej.axis.Axis" 135 | 136 | @JavaClasses.java_import 137 | def AxisType(self): 138 | return "net.imagej.axis.AxisType" 139 | 140 | @JavaClasses.java_import 141 | def CalibratedAxis(self): 142 | return "net.imagej.axis.CalibratedAxis" 143 | 144 | @JavaClasses.java_import 145 | def DefaultROITree(self): 146 | return "net.imagej.roi.DefaultROITree" 147 | 148 | @JavaClasses.java_import 149 | def ClassUtils(self): 150 | return "org.scijava.util.ClassUtils" 151 | 152 | @JavaClasses.java_import 153 | def Dimensions(self): 154 | return "net.imglib2.Dimensions" 155 | 156 | @JavaClasses.java_import 157 | def RandomAccessibleInterval(self): 158 | return "net.imglib2.RandomAccessibleInterval" 159 | 160 | @JavaClasses.java_import 161 | def ImgMath(self): 162 | return "net.imglib2.algorithm.math.ImgMath" 163 | 164 | @JavaClasses.java_import 165 | def Img(self): 166 | return "net.imglib2.img.Img" 167 | 168 | @JavaClasses.java_import 169 | def ImgView(self): 170 | return "net.imglib2.img.ImgView" 171 | 172 | @JavaClasses.java_import 173 | def WritablePolygon2D(self): 174 | return "net.imglib2.roi.geom.real.WritablePolygon2D" 175 | 176 | @JavaClasses.java_import 177 | def ImgLabeling(self): 178 | return "net.imglib2.roi.labeling.ImgLabeling" 179 | 180 | @JavaClasses.java_import 181 | def LabelRegions(self): 182 | return "net.imglib2.roi.labeling.LabelRegions" 183 | 184 | @JavaClasses.java_import 185 | def Named(self): 186 | return "org.scijava.Named" 187 | 188 | @JavaClasses.java_import 189 | def Table(self): 190 | return "org.scijava.table.Table" 191 | 192 | @JavaClasses.java_import 193 | def Util(self): 194 | return "net.imglib2.util.Util" 195 | 196 | @JavaClasses.java_import 197 | def Views(self): 198 | return "net.imglib2.view.Views" 199 | 200 | 201 | jc = MyJavaClasses() 202 | -------------------------------------------------------------------------------- /src/imagej/dims.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for querying and manipulating dimensional axis metadata. 3 | """ 4 | 5 | import logging 6 | from typing import List, Tuple, Union 7 | 8 | import numpy as np 9 | import scyjava as sj 10 | import xarray as xr 11 | from jpype import JException, JObject 12 | 13 | from imagej._java import jc 14 | from imagej.images import is_arraylike as _is_arraylike 15 | from imagej.images import is_xarraylike as _is_xarraylike 16 | 17 | _logger = logging.getLogger(__name__) 18 | 19 | 20 | def get_axes( 21 | rai: "jc.RandomAccessibleInterval", 22 | ) -> List["jc.CalibratedAxis"]: 23 | """ 24 | imagej.dims.get_axes(image) is deprecated. Use image.dim_axes instead. 25 | """ 26 | _logger.warning( 27 | "imagej.dims.get_axes(image) is deprecated. Use image.dim_axes instead." 28 | ) 29 | return [ 30 | (JObject(rai.axis(idx), jc.CalibratedAxis)) 31 | for idx in range(rai.numDimensions()) 32 | ] 33 | 34 | 35 | def get_axis_types(rai: "jc.RandomAccessibleInterval") -> List["jc.AxisType"]: 36 | """ 37 | imagej.dims.get_axis_types(image) is deprecated. Use this code instead: 38 | 39 | axis_types = [axis.type() for axis in image.dim_axes] 40 | """ 41 | _logger.warning( 42 | "imagej.dims.get_axis_types(image) is deprecated. Use this code instead:\n" 43 | + "\n" 44 | + " axis_types = [axis.type() for axis in image.dim_axes]" 45 | ) 46 | if _has_axis(rai): 47 | rai_dims = get_dims(rai) 48 | for i in range(len(rai_dims)): 49 | if rai_dims[i].lower() == "c": 50 | rai_dims[i] = "Channel" 51 | if rai_dims[i].lower() == "t": 52 | rai_dims[i] = "Time" 53 | rai_axis_types = [] 54 | for i in range(len(rai_dims)): 55 | rai_axis_types.append(jc.Axes.get(rai_dims[i])) 56 | return rai_axis_types 57 | else: 58 | raise AttributeError( 59 | f"Unsupported Java type: {type(rai)} has no axis attribute." 60 | ) 61 | 62 | 63 | def get_dims(image) -> List[str]: 64 | """ 65 | imagej.dims.get_dims(image) is deprecated. Use image.shape and image.dims instead. 66 | """ 67 | _logger.warning( 68 | "imagej.dims.get_dims(image) is deprecated. Use image.shape and image.dims " 69 | "instead." 70 | ) 71 | if _is_xarraylike(image): 72 | return image.dims 73 | if _is_arraylike(image): 74 | return image.shape 75 | if hasattr(image, "axis"): 76 | axes = get_axes(image) 77 | return _get_axis_labels(axes) 78 | if isinstance(image, jc.RandomAccessibleInterval): 79 | return list(image.dimensionsAsLongArray()) 80 | if isinstance(image, jc.ImagePlus): 81 | shape = image.getDimensions() 82 | return [axis for axis in shape if axis > 1] 83 | raise TypeError(f"Unsupported image type: {image}\n No dimensions or shape found.") 84 | 85 | 86 | def get_shape(image) -> List[int]: 87 | """ 88 | imagej.dims.get_shape(image) is deprecated. Use image.shape instead. 89 | """ 90 | _logger.warning( 91 | "imagej.dims.get_shape(image) is deprecated. Use image.shape instead." 92 | ) 93 | if _is_arraylike(image): 94 | return list(image.shape) 95 | if not sj.isjava(image): 96 | raise TypeError("Unsupported type: " + str(type(image))) 97 | if isinstance(image, jc.Dimensions): 98 | return [image.dimension(d) for d in range(image.numDimensions())] 99 | if isinstance(image, jc.ImagePlus): 100 | shape = image.getDimensions() 101 | return [axis for axis in shape if axis > 1] 102 | raise TypeError(f"Unsupported Java type: {str(sj.jclass(image).getName())}") 103 | 104 | 105 | def reorganize( 106 | rai: "jc.RandomAccessibleInterval", permute_order: List[int] 107 | ) -> "jc.ImgPlus": 108 | """Reorganize the dimension order of a RandomAccessibleInterval. 109 | 110 | Permute the dimension order of an input RandomAccessibleInterval using 111 | a List of ints (i.e. permute_order) to determine the shape of the output ImgPlus. 112 | 113 | :param rai: A RandomAccessibleInterval, 114 | :param permute_order: List of int in which to permute the RandomAccessibleInterval. 115 | :return: A permuted ImgPlus. 116 | """ 117 | img = _dataset_to_imgplus(rai) 118 | 119 | # check for dimension count mismatch 120 | dim_num = rai.numDimensions() 121 | 122 | if len(permute_order) != dim_num: 123 | raise ValueError( 124 | f"Mismatched dimension count: {len(permute_order)} != {dim_num}" 125 | ) 126 | 127 | # get ImageJ resources 128 | ImgView = sj.jimport("net.imglib2.img.ImgView") 129 | 130 | # copy dimensional axes into 131 | axes = [] 132 | for i in range(dim_num): 133 | old_dim = permute_order[i] 134 | axes.append(img.axis(old_dim)) 135 | 136 | # repeatedly permute the image dimensions into shape 137 | rai = img.getImg() 138 | for i in range(dim_num): 139 | old_dim = permute_order[i] 140 | if old_dim == i: 141 | continue 142 | rai = jc.Views.permute(rai, old_dim, i) 143 | 144 | # update index mapping acccordingly...this is hairy ;-) 145 | for j in range(dim_num): 146 | if permute_order[j] == i: 147 | permute_order[j] = old_dim 148 | break 149 | 150 | permute_order[i] = i 151 | 152 | return jc.ImgPlus(ImgView.wrap(rai), img.getName(), axes) 153 | 154 | 155 | def prioritize_rai_axes_order( 156 | axis_types: List["jc.AxisType"], ref_order: List["jc.AxisType"] 157 | ) -> List[int]: 158 | """Prioritize the axes order to match a reference order. 159 | 160 | The input List of 'AxisType' from the image to be permuted 161 | will be prioritized to match (where dimensions exist) to 162 | a reference order (e.g. _python_rai_ref_order). 163 | 164 | :param axis_types: List of 'net.imagej.axis.AxisType' from image. 165 | :param ref_order: List of 'net.imagej.axis.AxisType' from reference order. 166 | :return: List of int for permuting a image (e.g. [0, 4, 3, 1, 2]) 167 | """ 168 | permute_order = [] 169 | for axis in ref_order: 170 | for i in range(len(axis_types)): 171 | if axis == axis_types[i]: 172 | permute_order.append(i) 173 | 174 | for i in range(len(axis_types)): 175 | if axis_types[i] not in ref_order: 176 | permute_order.append(i) 177 | 178 | return permute_order 179 | 180 | 181 | def _assign_axes( 182 | xarr: xr.DataArray, 183 | ) -> List[Union["jc.DefaultLinearAxis", "jc.EnumeratedAxis"]]: 184 | """ 185 | Obtain xarray axes names, origin, scale and convert into ImageJ Axis. Supports both 186 | DefaultLinearAxis and the newer EnumeratedAxis. 187 | 188 | Note that, in many cases, there are small discrepancies between the coordinates. 189 | This can either be actually within the data, or it can be from floating point math 190 | errors. In this case, we delegate to numpy.isclose to tell us whether our 191 | coordinates are linear or not. If our coordinates are nonlinear, and the 192 | EnumeratedAxis type is available, we will use it. Otherwise, this function 193 | returns a DefaultLinearAxis. 194 | 195 | :param xarr: xarray that holds the data. 196 | :return: A list of ImageJ Axis with the specified origin and scale. 197 | """ 198 | axes = [""] * xarr.ndim 199 | for dim in xarr.dims: 200 | axis_str = _convert_dim(dim, "java") 201 | ax_type = jc.Axes.get(axis_str) 202 | ax_num = _get_axis_num(xarr, dim) 203 | coords_arr = xarr.coords[dim] 204 | 205 | # coerce numeric scale 206 | if not _is_numeric_scale(coords_arr): 207 | _logger.warning( 208 | f"The {ax_type.getLabel()} axis is non-numeric and is translated " 209 | "to a linear index." 210 | ) 211 | coords_arr = [np.double(x) for x in np.arange(len(xarr.coords[dim]))] 212 | else: 213 | coords_arr = coords_arr.to_numpy().astype(np.double) 214 | 215 | # check scale linearity 216 | diffs = np.diff(coords_arr) 217 | linear: bool = diffs.size and np.all(np.isclose(diffs, diffs[0])) 218 | 219 | if not linear: 220 | try: 221 | j_coords = [jc.Double(x) for x in coords_arr] 222 | axes[ax_num] = jc.EnumeratedAxis(ax_type, sj.to_java(j_coords)) 223 | except (JException, TypeError): 224 | # if EnumeratedAxis not available - use DefaultLinearAxis 225 | axes[ax_num] = _get_default_linear_axis(coords_arr, ax_type) 226 | else: 227 | axes[ax_num] = _get_default_linear_axis(coords_arr, ax_type) 228 | 229 | return axes 230 | 231 | 232 | def _ends_with_channel_axis(xarr: xr.DataArray) -> bool: 233 | """Check if xarray.DataArray ends in the channel dimension. 234 | :param xarr: xarray.DataArray to check. 235 | :return: Boolean 236 | """ 237 | ends_with_axis = xarr.dims[len(xarr.dims) - 1].lower() in ["c", "ch", "channel"] 238 | return ends_with_axis 239 | 240 | 241 | def _get_axis_num(xarr: xr.DataArray, axis): 242 | """ 243 | Get the xarray -> java axis number due to inverted axis order for C style numpy 244 | arrays (default) 245 | 246 | :param xarr: Xarray to convert 247 | :param axis: Axis number to convert 248 | :return: Axis idx in java 249 | """ 250 | py_axnum = xarr.get_axis_num(axis) 251 | if np.isfortran(xarr.values): 252 | return py_axnum 253 | 254 | if _ends_with_channel_axis(xarr): 255 | if axis == len(xarr.dims) - 1: 256 | return axis 257 | else: 258 | return len(xarr.dims) - py_axnum - 2 259 | else: 260 | return len(xarr.dims) - py_axnum - 1 261 | 262 | 263 | def _get_axes_coords( 264 | axes: List["jc.CalibratedAxis"], dims: List[str], shape: Tuple[int] 265 | ) -> dict: 266 | """ 267 | Get xarray style coordinate list dictionary from a dataset 268 | :param axes: List of ImageJ axes 269 | :param dims: List of axes labels for each dataset axis 270 | :param shape: F-style, or reversed C-style, shape of axes numpy array. 271 | :return: Dictionary of coordinates for each axis. 272 | """ 273 | coords = { 274 | dims[idx]: [ 275 | axes[idx].calibratedValue(position) for position in range(shape[idx]) 276 | ] 277 | for idx in range(len(dims)) 278 | } 279 | return coords 280 | 281 | 282 | def _get_default_linear_axis(coords_arr: np.ndarray, ax_type: "jc.AxisType"): 283 | """ 284 | Create a new DefaultLinearAxis with the given coordinate array and axis type. 285 | 286 | :param coords_arr: A 1D NumPy array. 287 | :return: An instance of net.imagej.axis.DefaultLinearAxis. 288 | """ 289 | scale = coords_arr[1] - coords_arr[0] if len(coords_arr) > 1 else 1 290 | origin = coords_arr[0] if len(coords_arr) > 0 else 0 291 | return jc.DefaultLinearAxis(ax_type, jc.Double(scale), jc.Double(origin)) 292 | 293 | 294 | def _is_numeric_scale(coords_array: np.ndarray) -> bool: 295 | """ 296 | Checks if the coordinates array of the given axis is numeric. 297 | 298 | :param coords_array: A 1D NumPy array. 299 | :return: bool 300 | """ 301 | return np.issubdtype(coords_array.dtype, np.number) 302 | 303 | 304 | def _dataset_to_imgplus(rai: "jc.RandomAccessibleInterval") -> "jc.ImgPlus": 305 | """Get an ImgPlus from a Dataset. 306 | 307 | Get an ImgPlus from a Dataset or just return the RandomAccessibleInterval 308 | if its not a Dataset. 309 | 310 | :param rai: A RandomAccessibleInterval. 311 | :return: The ImgPlus from a Dataset. 312 | """ 313 | if isinstance(rai, jc.Dataset): 314 | return rai.getImgPlus() 315 | else: 316 | return rai 317 | 318 | 319 | def _get_axis_labels(axes: List["jc.CalibratedAxis"]) -> List[str]: 320 | """Get the axes labels from a List of 'CalibratedAxis'. 321 | 322 | Extract the axis labels from a List of 'CalibratedAxis'. 323 | 324 | :param axes: A List of 'CalibratedAxis'. 325 | :return: A list of the axis labels. 326 | """ 327 | return [str((axes[idx].type().getLabel())) for idx in range(len(axes))] 328 | 329 | 330 | def _python_rai_ref_order() -> List["jc.AxisType"]: 331 | """Get the Java style numpy reference order. 332 | 333 | Get a List of 'AxisType' in the Python/scikitimage 334 | preferred order. Note that this reference order is 335 | reversed. 336 | :return: List of dimensions in numpy preferred order. 337 | """ 338 | return [jc.Axes.CHANNEL, jc.Axes.X, jc.Axes.Y, jc.Axes.Z, jc.Axes.TIME] 339 | 340 | 341 | def _convert_dim(dim: str, direction: str) -> str: 342 | """Convert a dimension to Python/NumPy or ImageJ convention. 343 | 344 | Convert a single dimension to Python/NumPy or ImageJ convention by 345 | indicating which direction ('python' or 'java'). A converted dimension 346 | is returned. 347 | 348 | :param dim: A dimension to be converted. 349 | :param direction: 350 | 'python': Convert a single dimension from ImageJ to Python/NumPy convention. 351 | 'java': Convert a single dimension from Python/NumPy to ImageJ convention. 352 | :return: A single converted dimension. 353 | """ 354 | if direction.lower() == "python": 355 | return _to_pydim(dim) 356 | elif direction.lower() == "java": 357 | return _to_ijdim(dim) 358 | else: 359 | return dim 360 | 361 | 362 | def _convert_dims(dimensions: List[str], direction: str) -> List[str]: 363 | """Convert a List of dimensions to Python/NumPy or ImageJ conventions. 364 | 365 | Convert a List of dimensions to Python/Numpy or ImageJ conventions by 366 | indicating which direction ('python' or 'java'). A List of converted 367 | dimentions is returned. 368 | 369 | :param dimensions: List of dimensions (e.g. X, Y, Channel, Z, Time) 370 | :param direction: 371 | 'python': Convert dimensions from ImageJ to Python/NumPy conventions. 372 | 'java': Convert dimensions from Python/NumPy to ImageJ conventions. 373 | :return: List of converted dimensions. 374 | """ 375 | new_dims = [] 376 | 377 | if direction.lower() == "python": 378 | for dim in dimensions: 379 | new_dims.append(_to_pydim(dim)) 380 | return new_dims 381 | elif direction.lower() == "java": 382 | for dim in dimensions: 383 | new_dims.append(_to_ijdim(dim)) 384 | return new_dims 385 | else: 386 | return dimensions 387 | 388 | 389 | def _validate_dim_order(dim_order: List[str], shape: tuple) -> List[str]: 390 | """ 391 | Validate a List of dimensions. If the dimension list is smaller 392 | fill the rest of the list with "dim_n" (following xarrray convention). 393 | 394 | :param dim_order: List of dimensions (e.g. X, Y, Channel, Z, Time) 395 | :param shape: Shape image for the dimension order. 396 | :return: List with "dim_n" dimensions added to match shape length. 397 | """ 398 | dim_len = len(dim_order) 399 | shape_len = len(shape) 400 | if dim_len < shape_len: 401 | d = shape_len - dim_len 402 | for i in range(d): 403 | dim_order.append(f"dim_{i}") 404 | return dim_order 405 | if dim_len > shape_len: 406 | raise ValueError(f"Expected {shape_len} dimensions but got {dim_len}.") 407 | return dim_order 408 | 409 | 410 | def _has_axis(rai: "jc.RandomAccessibleInterval"): 411 | """Check if a RandomAccessibleInterval has axes.""" 412 | if sj.isjava(rai): 413 | return hasattr(rai, "axis") 414 | else: 415 | False 416 | 417 | 418 | def _to_pydim(key: str) -> str: 419 | """Convert ImageJ dimension convention to Python/NumPy.""" 420 | pydims = { 421 | "Time": "t", 422 | "slice": "pln", 423 | "Z": "pln", 424 | "Y": "row", 425 | "X": "col", 426 | "Channel": "ch", 427 | } 428 | 429 | if key in pydims: 430 | return pydims[key] 431 | else: 432 | return key 433 | 434 | 435 | def _to_ijdim(key: str) -> str: 436 | """Convert Python/NumPy dimension convention to ImageJ.""" 437 | ijdims = { 438 | "col": "X", 439 | "x": "X", 440 | "row": "Y", 441 | "y": "Y", 442 | "ch": "Channel", 443 | "c": "Channel", 444 | "pln": "Z", 445 | "z": "Z", 446 | "t": "Time", 447 | } 448 | 449 | if key in ijdims: 450 | return ijdims[key] 451 | else: 452 | return key 453 | -------------------------------------------------------------------------------- /src/imagej/doctor.py: -------------------------------------------------------------------------------- 1 | """ 2 | The PyImageJ doctor provides standalone tools for diagnosing your 3 | environment configuration, and offers advice for correcting problems. 4 | 5 | To run the diagnostics: 6 | 7 | import imagej.doctor 8 | imagej.doctor.checkup() 9 | 10 | To enable debug-level logging: 11 | 12 | import imagej.doctor 13 | imagej.doctor.debug_to_stderr() 14 | import imagej 15 | ij = imagej.init() 16 | """ 17 | 18 | import importlib 19 | import logging 20 | import os 21 | import shutil 22 | import subprocess 23 | import sys 24 | from pathlib import Path 25 | 26 | 27 | def _execute(command): 28 | try: 29 | return ( 30 | subprocess.check_output(command, stderr=subprocess.STDOUT).decode().rstrip() 31 | ) 32 | except Exception as e: 33 | return str(e) 34 | 35 | 36 | def checkup(output=print): 37 | """ 38 | Check your environment for health problems that could prevent PyImageJ from 39 | functioning. 40 | """ 41 | output("") 42 | advice = [] 43 | 44 | output("Checking Python:") 45 | 46 | output(f"--> Python executable = {sys.executable}") 47 | output("") 48 | 49 | output("Checking environment:") 50 | 51 | if "CONDA_PREFIX" in os.environ: 52 | conda_prefix = os.environ["CONDA_PREFIX"] 53 | output(f"--> CONDA_PREFIX = {conda_prefix}") 54 | actual_exe = Path(sys.executable).resolve() 55 | expected_exes = [ 56 | (Path(conda_prefix) / "bin" / "python").resolve(), 57 | (Path(conda_prefix) / "python.exe").resolve(), 58 | ] 59 | if actual_exe in expected_exes: 60 | output("--> Python executable matches Conda environment.") 61 | else: 62 | output("--> Python executable is NOT from that Conda environment!") 63 | indent = "\n * " 64 | advice.append( 65 | "Are you sure you're using the correct Python executable? " 66 | "I expected one of these:" 67 | + indent 68 | + indent.join(map(str, expected_exes)) 69 | ) 70 | else: 71 | output("--> It looks like you are NOT running inside a Conda environment.") 72 | advice.append("Did you intend to activate a Conda environment?") 73 | output("") 74 | 75 | output("Checking Python dependencies:") 76 | 77 | dependencies = { 78 | "jgo": "jgo", 79 | "scyjava": "scyjava", 80 | "imglyb": "imglyb", 81 | "pyimagej": "imagej", 82 | } 83 | for package_name, module_name in dependencies.items(): 84 | try: 85 | m = importlib.import_module(module_name) 86 | output(f"--> {package_name}: {m.__file__}") 87 | except ImportError: 88 | output(f"--> {package_name}: MISSING") 89 | advice.append(f"Are you sure the {package_name} package is installed?") 90 | 91 | output("") 92 | 93 | output("Checking Maven:") 94 | 95 | mvn_executable = shutil.which("mvn") 96 | output(f"--> Maven executable = {mvn_executable or 'NOT FOUND!'}") 97 | if mvn_executable: 98 | output(f"$ mvn -v\n{_execute([mvn_executable, '-v'])}") 99 | output("") 100 | else: 101 | advice.append("Install maven using conda or your system package manager") 102 | 103 | output("Checking Java:") 104 | 105 | if "JAVA_HOME" in os.environ: 106 | output(f"--> JAVA_HOME = {os.environ['JAVA_HOME']}") 107 | else: 108 | output("--> JAVA_HOME is NOT set!") 109 | advice.append( 110 | "Activate a conda environment with openjdk installed, " 111 | "or set JAVA_HOME manually." 112 | ) 113 | java_executable = shutil.which("java") 114 | output(f"--> Java executable = {java_executable or 'NOT FOUND!'}") 115 | if not java_executable: 116 | advice.append("Install openjdk using conda or your system package manager") 117 | 118 | output(f"$ java -version\n{_execute(['java', '-version'])}") 119 | output("") 120 | 121 | # TODO: More checks still needed! 122 | # - Does java executable match JAVA_HOME? 123 | # - Firewall configuration? 124 | # - Can mvn retrieve artifacts? (try mvn dependency:copy with suitable timeout?) 125 | # - Is Maven Central accessible? Is maven.scijava.org accessible? 126 | # - Where is your Maven repository cache? 127 | # - Where is jgo caching your environments? 128 | # - Are you using mamba? (quality of life advice) 129 | 130 | if advice: 131 | output("Questions and advice for you:") 132 | for line in advice: 133 | output(f"--> {line}") 134 | else: 135 | output("Great job! All looks good.") 136 | 137 | 138 | def debug_to_stderr(logger=None, debug_maven=False): 139 | """ 140 | Enable debug logging to the standard error stream. 141 | 142 | :param logger: The logger for which debug logging should go to stderr, 143 | or None to enable it for all known loggers across 144 | PyImageJ's dependency stack (e.g.: jgo, imglyb, scyjava). 145 | :param debug_maven: Enable Maven debug logging. It's very verbose, 146 | so this flag is False by default, but if jgo is having 147 | problems resolving the environment, such as failure to 148 | download needed JAR files, try setting this flag to 149 | True for more details on where things go wrong. 150 | """ 151 | if logger is None: 152 | debug_to_stderr("jgo.jgo._logger") 153 | debug_to_stderr("scyjava._logger") 154 | debug_to_stderr("scyjava.config._logger") 155 | debug_to_stderr("imagej._logger") 156 | debug_to_stderr("imagej.dims._logger") 157 | if debug_maven: 158 | # Tell scyjava to tell jgo to tell Maven to enable 159 | # debug logging via its -X command line flag. 160 | try: 161 | import scyjava.config 162 | 163 | scyjava.config.set_verbose(2) 164 | except ImportError: 165 | logging.exception("Failed to enable scyjava verbose mode.") 166 | return 167 | 168 | elif type(logger) is str: 169 | module_name, logger_attr = logger.rsplit(".", 1) 170 | try: 171 | m = importlib.import_module(module_name) 172 | logger = getattr(m, logger_attr) 173 | except ImportError: 174 | logging.exception("Failed to enable debug logging for %s.", logger) 175 | return 176 | 177 | logger.addHandler(logging.StreamHandler(sys.stderr)) 178 | logger.setLevel(logging.DEBUG) 179 | 180 | 181 | if __name__ == "__main__": 182 | checkup() 183 | -------------------------------------------------------------------------------- /src/imagej/images.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for creating and working with images. 3 | """ 4 | 5 | import logging 6 | 7 | import numpy as np 8 | import scyjava as sj 9 | from jpype import JException 10 | 11 | from imagej._java import jc 12 | 13 | _logger = logging.getLogger(__name__) 14 | 15 | 16 | # fmt: off 17 | _imglib2_types = { 18 | "net.imglib2.type.logic.NativeBoolType": "bool_", 19 | "net.imglib2.type.logic.BitType": "bool_", 20 | "net.imglib2.type.logic.BoolType": "bool_", 21 | "net.imglib2.type.numeric.integer.ByteType": "int8", 22 | "net.imglib2.type.numeric.integer.ByteLongAccessType": "int8", 23 | "net.imglib2.type.numeric.integer.ShortType": "int16", 24 | "net.imglib2.type.numeric.integer.ShortLongAccessType": "int16", 25 | "net.imglib2.type.numeric.integer.IntType": "int32", 26 | "net.imglib2.type.numeric.integer.IntLongAccessType": "int32", 27 | "net.imglib2.type.numeric.integer.LongType": "int64", 28 | "net.imglib2.type.numeric.integer.LongLongAccessType": "int64", 29 | "net.imglib2.type.numeric.integer.UnsignedByteType": "uint8", 30 | "net.imglib2.type.numeric.integer.UnsignedByteLongAccessType": "uint8", 31 | "net.imglib2.type.numeric.integer.UnsignedShortType": "uint16", 32 | "net.imglib2.type.numeric.integer.UnsignedShortLongAccessType": "uint16", 33 | "net.imglib2.type.numeric.integer.UnsignedIntType": "uint32", 34 | "net.imglib2.type.numeric.integer.UnsignedIntLongAccessType": "uint32", 35 | "net.imglib2.type.numeric.integer.UnsignedLongType": "uint64", 36 | "net.imglib2.type.numeric.integer.UnsignedLongLongAccessType": "uint64", 37 | # "net.imglib2.type.numeric.ARGBType": "argb", 38 | # "net.imglib2.type.numeric.ARGBLongAccessType": "argb", 39 | "net.imglib2.type.numeric.real.FloatType": "float32", 40 | "net.imglib2.type.numeric.real.FloatLongAccessType": "float32", 41 | "net.imglib2.type.numeric.real.DoubleType": "float64", 42 | "net.imglib2.type.numeric.real.DoubleLongAccessType": "float64", 43 | # "net.imglib2.type.numeric.complex.ComplexFloatType": "cfloat32", 44 | # "net.imglib2.type.numeric.complex.ComplexFloatLongAccessType": "cfloat32", 45 | # "net.imglib2.type.numeric.complex.ComplexDoubleType": "cfloat64", 46 | # "net.imglib2.type.numeric.complex.ComplexDoubleLongAccessType": "cfloat64", 47 | } 48 | # fmt: on 49 | 50 | 51 | def is_arraylike(arr): 52 | """ 53 | Return True iff the object is arraylike: possessing 54 | .shape, .dtype, .__array__, and .ndim attributes. 55 | 56 | :param arr: The object to check for arraylike properties 57 | :return: True iff the object is arraylike 58 | """ 59 | return ( 60 | hasattr(arr, "shape") 61 | and hasattr(arr, "dtype") 62 | and hasattr(arr, "__array__") 63 | and hasattr(arr, "ndim") 64 | ) 65 | 66 | 67 | def is_memoryarraylike(arr): 68 | """ 69 | Return True iff the object is memoryarraylike: 70 | an arraylike object whose .data type is memoryview. 71 | 72 | :param arr: The object to check for memoryarraylike properties 73 | :return: True iff the object is memoryarraylike 74 | """ 75 | return ( 76 | is_arraylike(arr) 77 | and hasattr(arr, "data") 78 | and type(arr.data).__name__ == "memoryview" 79 | ) 80 | 81 | 82 | def is_xarraylike(xarr): 83 | """ 84 | Return True iff the object is xarraylike: 85 | possessing .values, .dims, and .coords attributes, 86 | and whose .values are arraylike. 87 | 88 | :param arr: The object to check for xarraylike properties 89 | :return: True iff the object is xarraylike 90 | """ 91 | return ( 92 | hasattr(xarr, "values") 93 | and hasattr(xarr, "dims") 94 | and hasattr(xarr, "coords") 95 | and is_arraylike(xarr.values) 96 | ) 97 | 98 | 99 | def create_ndarray(image) -> np.ndarray: 100 | """ 101 | Create a NumPy ndarray with the same dimensions as the given image. 102 | 103 | :param image: The image whose shape the new ndarray will match. 104 | :return: The newly constructed ndarray with matching dimensions. 105 | """ 106 | try: 107 | dtype_to_use = dtype(image) 108 | except TypeError: 109 | dtype_to_use = np.dtype("float64") 110 | 111 | # get shape of image and invert 112 | shape = list(image.shape) 113 | 114 | # reverse shape if image is a RandomAccessibleInterval 115 | if isinstance(image, jc.RandomAccessibleInterval): 116 | shape.reverse() 117 | 118 | return np.zeros(shape, dtype=dtype_to_use) 119 | 120 | 121 | def copy_rai_into_ndarray( 122 | ij: "jc.ImageJ", rai: "jc.RandomAccessibleInterval", narr: np.ndarray 123 | ) -> None: 124 | """ 125 | Copy an ImgLib2 RandomAccessibleInterval into a NumPy ndarray. 126 | 127 | The input RandomAccessibleInterval is copied into the pre-initialized 128 | NumPy ndarray with either "fast copy" via 'net.imglib2.util.ImgUtil.copy' 129 | if available or the slower "copy.rai" method. Note that the input 130 | RandomAccessibleInterval and NumPy ndarray must have reversed dimensions 131 | relative to each other (e.g. [t, z, y, x, c] and [c, x, y, z, t]). 132 | 133 | :param ij: The ImageJ2 gateway (see imagej.init) 134 | :param rai: The RandomAccessibleInterval. 135 | :param narr: A NumPy ndarray with the same (reversed) shape 136 | as the input RandomAccessibleInterval. 137 | """ 138 | if not isinstance(rai, jc.RandomAccessibleInterval): 139 | raise TypeError("rai is not a RAI") 140 | if not is_arraylike(narr): 141 | raise TypeError("narr is not arraylike") 142 | 143 | # Suppose all mechanisms fail. Any one of these might be the one that was 144 | # "supposed" to work. 145 | failure_exceptions = [] 146 | 147 | # Check imglib2 version for fast copy availability. 148 | imglib2_version = sj.get_version(jc.RandomAccessibleInterval) 149 | if sj.is_version_at_least(imglib2_version, "5.9.0"): 150 | try: 151 | # ImgLib2 is new enough to use net.imglib2.util.ImgUtil.copy. 152 | ImgUtil = sj.jimport("net.imglib2.util.ImgUtil") 153 | ImgUtil.copy(rai, sj.to_java(narr)) 154 | return narr 155 | except JException as exc: 156 | # Try another method 157 | failure_exceptions.append( 158 | _format_copy_exception(exc.toString(), "net.imglib2.util.ImgUtil.copy") 159 | ) 160 | 161 | # Check imagej-common version for fast copy availability. 162 | imagej_common_version = sj.get_version(jc.Dataset) 163 | if sj.is_version_at_least(imagej_common_version, "0.30.0"): 164 | try: 165 | # ImageJ Common is new enough to use (deprecated) 166 | # net.imagej.util.Images.copy. 167 | Images = sj.jimport("net.imagej.util.Images") 168 | Images.copy(rai, sj.to_java(narr)) 169 | return narr 170 | except JException as exc: 171 | # Try another method 172 | failure_exceptions.append( 173 | _format_copy_exception(exc.toString(), "net.imglib2.util.Images.copy") 174 | ) 175 | 176 | # Fall back to copying with ImageJ Ops's copy.rai op. In theory, Ops 177 | # should always be faster. But in practice, the copy.rai operation is 178 | # slower than the hardcoded ones above. If we were to fix Ops to be 179 | # fast always, we could eliminate the above special casing. 180 | try: 181 | ij.op().run("copy.rai", sj.to_java(narr), rai) 182 | return 183 | except JException as exc: 184 | # Try another method 185 | failure_exceptions.append( 186 | _format_copy_exception( 187 | exc.toString(), "net.imagej.ops.copy.CopyNamespace.rai" 188 | ) 189 | ) 190 | 191 | # Failed 192 | failure_msg = "\n".join(failure_exceptions) 193 | raise Exception("\n" + failure_msg) 194 | 195 | 196 | def dtype(image_or_type) -> np.dtype: 197 | """Get the dtype of the input image as a numpy.dtype object. 198 | 199 | Note: for Java-based images, this is different than the image's dtype 200 | property, because ImgLib2-based images report their dtype as a subclass 201 | of net.imglib2.type.Type, and ImagePlus images do not yet implement 202 | the dtype function (see https://github.com/imagej/pyimagej/issues/194). 203 | 204 | :param image_or_type: 205 | | A NumPy ndarray. 206 | | OR A NumPy ndarray dtype. 207 | | OR An ImgLib2 image ('net.imglib2.Interval'). 208 | | OR An ImageJ2 Dataset ('net.imagej.Dataset'). 209 | | OR An ImageJ ImagePlus ('ij.ImagePlus'). 210 | 211 | :return: Input image dtype. 212 | """ 213 | if isinstance(image_or_type, np.dtype): 214 | return image_or_type 215 | if is_arraylike(image_or_type): 216 | return image_or_type.dtype 217 | if not sj.isjava(image_or_type): 218 | raise TypeError("Unsupported type: " + str(type(image_or_type))) 219 | 220 | # -- ImgLib2 types -- 221 | if isinstance(image_or_type, sj.jimport("net.imglib2.type.Type")): 222 | for c in _imglib2_types: 223 | if isinstance(image_or_type, sj.jimport(c)): 224 | return np.dtype(_imglib2_types[c]) 225 | raise TypeError(f"Unsupported ImgLib2 type: {image_or_type}") 226 | 227 | # -- ImgLib2 images -- 228 | if isinstance(image_or_type, sj.jimport("net.imglib2.IterableInterval")): 229 | imglib2_type = image_or_type.firstElement() 230 | return dtype(imglib2_type) 231 | if isinstance(image_or_type, jc.RandomAccessibleInterval): 232 | imglib2_type = jc.Util.getTypeFromInterval(image_or_type) 233 | return dtype(imglib2_type) 234 | 235 | # -- Original ImageJ images -- 236 | if jc.ImagePlus and isinstance(image_or_type, jc.ImagePlus): 237 | imagej_type = image_or_type.getType() 238 | imagej_types = { 239 | jc.ImagePlus.GRAY8: "uint8", 240 | jc.ImagePlus.GRAY16: "uint16", 241 | # NB: ImageJ's 32-bit type is float32, not uint32. 242 | jc.ImagePlus.GRAY32: "float32", 243 | } 244 | for t in imagej_types: 245 | if imagej_type == t: 246 | return np.dtype(imagej_types[t]) 247 | raise TypeError(f"Unsupported original ImageJ type: {imagej_type}") 248 | 249 | raise TypeError("Unsupported Java type: " + str(sj.jclass(image_or_type).getName())) 250 | 251 | 252 | def _format_copy_exception(exc: str, fun_name: str) -> str: 253 | """Format copy exceptions strings. 254 | 255 | :param exc: Exception as a String. 256 | :param fun_name: Name of the function producing the exception. 257 | :return: The formatted exception. 258 | """ 259 | # format cast exception 260 | exc = str(exc) 261 | if "cannot be cast to" in exc: 262 | m = exc.split(" ") 263 | from_class = m[m.index("cannot") - 1] 264 | # special case if "class" is present or not 265 | ci = m.index("cast") 266 | if m[ci + 2] == "class": 267 | to_class = m[ci + 3] 268 | else: 269 | to_class = m[ci + 2] 270 | return ( 271 | f"Error: Unsupported type cast via {fun_name}\n" 272 | f" Source type: {from_class}\n" 273 | f" Target type: {to_class}" 274 | ) 275 | else: 276 | return exc 277 | -------------------------------------------------------------------------------- /src/imagej/stack.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for manipulating image stacks. 3 | """ 4 | 5 | from typing import List, Tuple 6 | 7 | import scyjava as sj 8 | 9 | 10 | def rai_slice(rai, imin: Tuple, imax: Tuple, istep: Tuple): 11 | """Slice ImgLib2 images. 12 | 13 | Slice ImgLib2 images using Python's slice notation to define the 14 | desired slice range. Returned interval includes both imin and imax 15 | 16 | :param rai: An ImgLib2 RandomAccessibleInterval 17 | :param imin: Tuple of minimum interval range values. 18 | :param imax: Tuple of maximum interval range values. 19 | :return: Sliced ImgLib2 RandomAccessibleInterval. 20 | """ 21 | 22 | Views = sj.jimport("net.imglib2.view.Views") 23 | shape = rai.shape 24 | imin_fix = sj.jarray("j", [len(shape)]) 25 | imax_fix = sj.jarray("j", [len(shape)]) 26 | dim_itr = range(len(shape)) 27 | 28 | for py_dim, j_dim in zip(dim_itr, dim_itr): 29 | # Set minimum 30 | if imin[py_dim] is None: 31 | index = 0 32 | else: 33 | index = imin[py_dim] 34 | if index < 0: 35 | index += shape[j_dim] 36 | imin_fix[j_dim] = index 37 | # Set maximum 38 | if imax[py_dim] is None: 39 | index = shape[j_dim] - 1 40 | else: 41 | index = imax[py_dim] 42 | if index < 0: 43 | index += shape[j_dim] 44 | imax_fix[j_dim] = index 45 | 46 | istep_fix = sj.jarray("j", [istep]) 47 | 48 | if _index_within_range(imin_fix, shape) and _index_within_range(imax_fix, shape): 49 | intervaled = Views.interval(rai, imin_fix, imax_fix) 50 | stepped = Views.subsample(intervaled, istep_fix) 51 | 52 | # TODO: better match NumPy squeeze behavior. See imagej/pyimagej#1231 53 | dimension_reduced = Views.dropSingletonDimensions(stepped) 54 | return dimension_reduced 55 | 56 | 57 | def _index_within_range(query: List[int], source: List[int]) -> bool: 58 | """Check if query is within range of source index. 59 | :param query: List of query int 60 | :param source: List of soure int 61 | """ 62 | dim_num = len(query) 63 | for i in range(dim_num): 64 | if query[i] > source[i]: 65 | raise IndexError( 66 | f"index {query[i]} is out of bound for axis {i} with size {source[i]}" 67 | ) 68 | 69 | return True 70 | -------------------------------------------------------------------------------- /tests/test_callbacks.py: -------------------------------------------------------------------------------- 1 | from scyjava import jimport 2 | 3 | 4 | def test_when_imagej_starts(ij): 5 | """ 6 | The ImageJ2 gateway test fixture registers a callback function via 7 | when_imagej_starts, which injects a small piece of data into the gateway 8 | object. We check for that data here to make sure the callback happened. 9 | """ 10 | System = jimport("java.lang.System") 11 | version = str(System.getProperty("java.version")) 12 | digits = version.split(".") 13 | major = digits[1] if digits[0] == "1" else digits[0] 14 | assert major == getattr(ij, "_java_version", None) 15 | -------------------------------------------------------------------------------- /tests/test_ctypes.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | import pytest 4 | import scyjava as sj 5 | 6 | parameters = [ 7 | (ctypes.c_bool, "net.imglib2.type.logic.BoolType", True), 8 | (ctypes.c_byte, "net.imglib2.type.numeric.integer.ByteType", 4), 9 | (ctypes.c_ubyte, "net.imglib2.type.numeric.integer.UnsignedByteType", 4), 10 | (ctypes.c_int8, "net.imglib2.type.numeric.integer.ByteType", 4), 11 | (ctypes.c_uint8, "net.imglib2.type.numeric.integer.UnsignedByteType", 4), 12 | (ctypes.c_short, "net.imglib2.type.numeric.integer.ShortType", 4), 13 | (ctypes.c_ushort, "net.imglib2.type.numeric.integer.UnsignedShortType", 4), 14 | (ctypes.c_int16, "net.imglib2.type.numeric.integer.ShortType", 4), 15 | (ctypes.c_uint16, "net.imglib2.type.numeric.integer.UnsignedShortType", 4), 16 | (ctypes.c_int32, "net.imglib2.type.numeric.integer.IntType", 4), 17 | (ctypes.c_uint32, "net.imglib2.type.numeric.integer.UnsignedIntType", 4), 18 | (ctypes.c_uint64, "net.imglib2.type.numeric.integer.UnsignedLongType", 4), 19 | (ctypes.c_int64, "net.imglib2.type.numeric.integer.LongType", 4), 20 | (ctypes.c_uint64, "net.imglib2.type.numeric.integer.UnsignedLongType", 4), 21 | (ctypes.c_longlong, "net.imglib2.type.numeric.integer.LongType", 4), 22 | (ctypes.c_ulonglong, "net.imglib2.type.numeric.integer.UnsignedLongType", 4), 23 | (ctypes.c_float, "net.imglib2.type.numeric.real.FloatType", 4.5), 24 | (ctypes.c_double, "net.imglib2.type.numeric.real.DoubleType", 4.5), 25 | ] 26 | 27 | 28 | # -- Tests -- 29 | 30 | 31 | @pytest.mark.parametrize(argnames="ctype,jtype_str,value", argvalues=parameters) 32 | def test_ctype_to_realtype(ij, ctype, jtype_str, value): 33 | py_type = ctype(value) 34 | # Convert the ctype into a RealType 35 | converted = ij.py.to_java(py_type) 36 | jtype = sj.jimport(jtype_str) 37 | assert isinstance(converted, jtype) 38 | assert converted.get() == value 39 | # Convert the RealType back into a ctype 40 | converted_back = ij.py.from_java(converted) 41 | assert isinstance(converted_back, ctype) 42 | assert converted_back.value == value 43 | -------------------------------------------------------------------------------- /tests/test_doctor.py: -------------------------------------------------------------------------------- 1 | import imagej.doctor 2 | 3 | # -- Tests -- 4 | 5 | 6 | def test_checkup(): 7 | output = [] 8 | imagej.doctor.checkup(output.append) 9 | 10 | if output[-1].startswith("--> "): 11 | # There is some advice; let's skip past it. 12 | while output[-1].startswith("--> "): 13 | output.pop() 14 | assert output[-1] == "Questions and advice for you:" 15 | else: 16 | # No advice; all was well. 17 | assert output[-1] == "Great job! All looks good." 18 | -------------------------------------------------------------------------------- /tests/test_fiji.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import scyjava as sj 3 | 4 | # -- Tests -- 5 | 6 | 7 | def test_plugins_load_using_pairwise_stitching(ij): 8 | try: 9 | sj.jimport("plugin.Stitching_Pairwise") 10 | except TypeError: 11 | pytest.skip("No Pairwise Stitching plugin available. Skipping test.") 12 | 13 | if not ij.legacy: 14 | pytest.skip("No original ImageJ. Skipping test.") 15 | if ij.ui().isHeadless(): 16 | pytest.skip("No GUI. Skipping test.") 17 | 18 | tile1 = ij.IJ.createImage("Tile1", "8-bit random", 512, 512, 1) 19 | tile2 = ij.IJ.createImage("Tile2", "8-bit random", 512, 512, 1) 20 | args = {"first_image": tile1.getTitle(), "second_image": tile2.getTitle()} 21 | ij.py.run_plugin("Pairwise stitching", args) 22 | result_name = ij.WindowManager.getCurrentImage().getTitle() 23 | 24 | ij.IJ.run("Close All", "") 25 | 26 | assert result_name == "Tile1<->Tile2" 27 | -------------------------------------------------------------------------------- /tests/test_labeling.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List 2 | 3 | import numpy as np 4 | import pytest 5 | import scyjava as sj 6 | 7 | # -- Fixtures -- 8 | 9 | 10 | @pytest.fixture(scope="module") 11 | def py_labeling(): 12 | import labeling as lb 13 | 14 | a = np.zeros((4, 4), np.int32) 15 | a[:2] = 1 16 | example1_images = [] 17 | example1_images.append(a) 18 | b = a.copy() 19 | b[:2] = 2 20 | example1_images.append(np.flip(b.transpose())) 21 | c = a.copy() 22 | c[:2] = 3 23 | example1_images.append(np.flip(c)) 24 | d = a.copy() 25 | d[:2] = 4 26 | example1_images.append(d.transpose()) 27 | 28 | merger = lb.Labeling.fromValues(np.zeros((4, 4), np.int32)) 29 | merger.iterate_over_images(example1_images, source_ids=["a", "b", "c", "d"]) 30 | return merger 31 | 32 | 33 | @pytest.fixture(scope="module") 34 | def java_labeling(ij): 35 | img = np.zeros((4, 4), dtype=np.int32) 36 | img[:2, :2] = 6 37 | img[:2, 2:] = 3 38 | img[2:, :2] = 7 39 | img[2:, 2:] = 4 40 | img_java = ij.py.to_java(img) 41 | example_lists = [[], [1], [2], [1, 2], [2, 3], [3], [1, 4], [3, 4]] 42 | sets = [set(example) for example in example_lists] 43 | sets_java = ij.py.to_java(sets) 44 | 45 | ImgLabeling = sj.jimport("net.imglib2.roi.labeling.ImgLabeling") 46 | return ImgLabeling.fromImageAndLabelSets(img_java, sets_java) 47 | 48 | 49 | # -- Helpers -- 50 | 51 | 52 | def assert_labels_equality( 53 | exp: Dict[str, Any], act: Dict[str, Any], ignored_keys: List[str] 54 | ): 55 | for key in exp.keys(): 56 | if key in ignored_keys: 57 | continue 58 | assert exp[key] == act[key] 59 | 60 | 61 | # -- Tests -- 62 | 63 | 64 | def test_py_to_java(ij, py_labeling, java_labeling): 65 | j_convert = ij.py.to_java(py_labeling) 66 | # Assert indexImg equality 67 | expected_img = ij.py.from_java(java_labeling.getIndexImg()) 68 | actual_img = ij.py.from_java(j_convert.getIndexImg()) 69 | assert np.array_equal(expected_img, actual_img) 70 | # Assert label sets equality 71 | expected_labels = ij.py.from_java(java_labeling.getMapping().getLabelSets()) 72 | actual_labels = ij.py.from_java(j_convert.getMapping().getLabelSets()) 73 | assert expected_labels == actual_labels 74 | 75 | 76 | def test_java_to_py(ij, py_labeling, java_labeling): 77 | # Convert 78 | p_convert = ij.py.from_java(java_labeling) 79 | # Assert indexImg equality 80 | exp_img, exp_labels = py_labeling.get_result() 81 | act_img, act_labels = p_convert.get_result() 82 | assert np.array_equal(exp_img, act_img) 83 | # Assert (APPLICABLE) metadata equality 84 | # Skipping numSources - ImgLabeling doesn't have this 85 | # Skipping indexImg - py_labeling wasn't loaded from file 86 | assert_labels_equality( 87 | vars(exp_labels), vars(act_labels), ["numSources", "indexImg"] 88 | ) 89 | 90 | 91 | def test_py_java_py(ij, py_labeling): 92 | # Convert 93 | to_java = ij.py.to_java(py_labeling) 94 | back_to_py = ij.py.from_java(to_java) 95 | print(py_labeling.label_sets) 96 | print(back_to_py.label_sets) 97 | # Assert indexImg equality 98 | exp_img, exp_labels = py_labeling.get_result() 99 | act_img, act_labels = back_to_py.get_result() 100 | assert np.array_equal(exp_img, act_img) 101 | # Assert (APPLICABLE) metadata equality 102 | # Skipping numSources - ImgLabeling doesn't have this 103 | # Skipping indexImg - py_labeling wasn't loaded from file 104 | assert_labels_equality( 105 | vars(exp_labels), vars(act_labels), ["numSources", "indexImg"] 106 | ) 107 | -------------------------------------------------------------------------------- /tests/test_legacy.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import numpy as np 4 | import pytest 5 | import scyjava as sj 6 | 7 | # -- Fixtures -- 8 | 9 | 10 | @pytest.fixture(scope="module") 11 | def arr(): 12 | empty_array = np.zeros([512, 512]) 13 | return empty_array 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def results_table(ij): 18 | if ij.legacy and ij.legacy.isActive(): 19 | ResultsTable = sj.jimport("ij.measure.ResultsTable") 20 | rt = ResultsTable.getResultsTable() 21 | 22 | # add column headers 23 | for i in range(5): 24 | rt.setHeading(i, f"Column {i}") 25 | 26 | # add data rows 27 | for i in range(3): 28 | rt.incrementCounter() 29 | for j in range(5): 30 | rt.addValue(f"Column {j}", random.randint(1, 100)) 31 | else: 32 | pytest.skip("No original ImageJ. Skipping fixture.") 33 | 34 | return rt 35 | 36 | 37 | # -- Helpers -- 38 | 39 | 40 | def ensure_gui_available(ij): 41 | if ij.ui().isHeadless(): 42 | pytest.skip("No GUI. Skipping test.") 43 | 44 | 45 | def ensure_legacy_disabled(ij): 46 | if ij.legacy and ij.legacy.isActive(): 47 | pytest.skip("Original ImageJ installed. Skipping test.") 48 | 49 | 50 | def ensure_legacy_enabled(ij): 51 | if not ij.legacy or not ij.legacy.isActive(): 52 | pytest.skip("No original ImageJ. Skipping test.") 53 | 54 | 55 | # -- Tests -- 56 | 57 | 58 | def test_convert_imageplus_to_python(ij): 59 | ensure_legacy_enabled(ij) 60 | 61 | w = 30 62 | h = 20 63 | imp = ij.IJ.createImage("Ramp", "16-bit ramp", w, h, 2, 3, 5) 64 | xarr = ij.py.from_java(imp) 65 | assert xarr.dims == ("t", "pln", "row", "col", "ch") 66 | assert xarr.shape == (5, 3, h, w, 2) 67 | 68 | index = 0 69 | for c in range(imp.getNChannels()): 70 | for z in range(imp.getNSlices()): 71 | for t in range(imp.getNFrames()): 72 | index += 1 73 | ip = imp.getStack().getProcessor(index) 74 | # NB: The commented out loop is super slow because Python + JNI. 75 | # Instead, we grab the whole plane as a Java array and massage it. 76 | # for y in range(imp.getHeight()): 77 | # for x in range(imp.getWidth()): 78 | # assert plane[y,x] == xarr[t,z,y,x,c] 79 | plane = np.frombuffer( 80 | bytearray(ip.getPixels()), count=w * h, dtype=np.uint16 81 | ).reshape(h, w) 82 | assert all((plane == xarr[t, z, :, :, c]).data.flatten()) 83 | 84 | 85 | def test_run_plugin(ij): 86 | ensure_legacy_enabled(ij) 87 | 88 | ramp = ij.IJ.createImage("Tile1", "8-bit ramp", 10, 10, 1) 89 | ij.py.run_plugin("Gaussian Blur...", args={"sigma": 3}, imp=ramp) 90 | values = [ramp.getPixel(x, y)[0] for x in range(10) for y in range(10)] 91 | # fmt: off 92 | assert values == [ 93 | 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 94 | 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 95 | 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 96 | 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 97 | 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, # noqa: E131 98 | 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, # noqa: E131 99 | 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, # noqa: E131 100 | 168, 168, 168, 168, 168, 168, 168, 168, 168, 168, # noqa: E131 101 | 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, # noqa: E131 102 | 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 # noqa: E131 103 | ] 104 | # fmt: on 105 | 106 | 107 | def test_get_imageplus_synchronizes_from_imagej_to_imagej2(ij, arr): 108 | ensure_legacy_enabled(ij) 109 | ensure_gui_available(ij) 110 | 111 | original = arr[0, 0] 112 | ds = ij.py.to_java(arr) 113 | ij.ui().show(ds) 114 | macro = """run("Add...", "value=5");""" 115 | ij.py.run_macro(macro) 116 | 117 | assert arr[0, 0] == original + 5 118 | 119 | 120 | def test_synchronize_from_imagej_to_numpy(ij, arr): 121 | ensure_legacy_enabled(ij) 122 | ensure_gui_available(ij) 123 | 124 | original = arr[0, 0] 125 | ds = ij.py.to_dataset(arr) 126 | ij.ui().show(ds) 127 | imp = ij.py.active_imageplus() 128 | imp.getProcessor().add(5) 129 | ij.py.sync_image(imp) 130 | 131 | assert arr[0, 0] == original + 5 132 | 133 | 134 | def test_window_to_numpy_converts_active_image_to_xarray(ij, arr): 135 | ensure_legacy_enabled(ij) 136 | ensure_gui_available(ij) 137 | 138 | ds = ij.py.to_dataset(arr) 139 | ij.ui().show(ds) 140 | new_arr = ij.py.active_xarray() 141 | assert (arr == new_arr.values).all 142 | 143 | 144 | def test_functions_throw_warning_if_legacy_not_enabled(ij): 145 | ensure_legacy_disabled(ij) 146 | 147 | with pytest.raises(ImportError): 148 | ij.py.active_imageplus() 149 | 150 | 151 | def test_results_table_to_pandas_dataframe(ij, results_table): 152 | ensure_legacy_enabled(ij) 153 | 154 | df = ij.py.from_java(results_table) 155 | for col in range(5): 156 | rt_col = list(results_table.getColumn(col)) 157 | df_col = df[f"Column {col}"].tolist() 158 | assert rt_col == df_col 159 | -------------------------------------------------------------------------------- /tests/test_ops.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scyjava as sj 3 | 4 | # -- Tests -- 5 | 6 | 7 | def test_frangi(ij): 8 | input_array = np.array( 9 | [[1000, 1000, 1000, 2000, 3000], [5000, 8000, 13000, 21000, 34000]] 10 | ) 11 | result = np.zeros(input_array.shape) 12 | ij.op().filter().frangiVesselness( 13 | ij.py.to_java(result), ij.py.to_java(input_array), [1, 1], 4 14 | ) 15 | correct_result = np.array( 16 | [[0, 0, 0, 0.94282, 0.94283], [0, 0, 0, 0.94283, 0.94283]] 17 | ) 18 | result = np.ndarray.round(result, decimals=5) 19 | assert (result == correct_result).all() 20 | 21 | 22 | def test_gaussian(ij): 23 | input_array = np.array( 24 | [[1000, 1000, 1000, 2000, 3000], [5000, 8000, 13000, 21000, 34000]] 25 | ) 26 | sigmas = [10.0] * 2 27 | output_array = ij.op().filter().gauss(ij.py.to_java(input_array), sigmas) 28 | result = [] 29 | correct_result = [8435, 8435, 8435, 8435] 30 | ra = output_array.randomAccess() 31 | for x in [0, 1]: 32 | for y in [0, 1]: 33 | ra.setPosition(x, y) 34 | result.append(ra.get().get()) 35 | assert result == correct_result 36 | 37 | 38 | def test_top_hat(ij): 39 | ArrayList = sj.jimport("java.util.ArrayList") 40 | HyperSphereShape = sj.jimport("net.imglib2.algorithm.neighborhood.HyperSphereShape") 41 | Views = sj.jimport("net.imglib2.view.Views") 42 | 43 | result = [] 44 | correct_result = [0, 0, 0, 1000, 2000, 4000, 7000, 12000, 20000, 33000] 45 | 46 | input_array = np.array( 47 | [[1000, 1000, 1000, 2000, 3000], [5000, 8000, 13000, 21000, 34000]] 48 | ) 49 | output_array = np.zeros(input_array.shape) 50 | java_out = Views.iterable(ij.py.to_java(output_array)) 51 | java_in = ij.py.to_java(input_array) 52 | shapes = ArrayList() 53 | shapes.add(HyperSphereShape(5)) 54 | 55 | ij.op().morphology().topHat(java_out, java_in, shapes) 56 | itr = java_out.iterator() 57 | while itr.hasNext(): 58 | result.append(itr.next().get()) 59 | 60 | assert result == correct_result 61 | 62 | 63 | def test_image_math(ij): 64 | Views = sj.jimport("net.imglib2.view.Views") 65 | 66 | input_array = np.array([[1, 1, 2], [3, 5, 8]]) 67 | result = [] 68 | correct_result = [192, 198, 205, 192, 198, 204] 69 | java_in = Views.iterable(ij.py.to_java(input_array)) 70 | java_out = ( 71 | ij.op() 72 | .image() 73 | .equation(java_in, "64 * (Math.sin(0.1 * p[0]) + Math.cos(0.1 * p[1])) + 128") 74 | ) 75 | 76 | itr = java_out.iterator() 77 | while itr.hasNext(): 78 | result.append(itr.next().get()) 79 | assert result == correct_result 80 | -------------------------------------------------------------------------------- /tests/test_rai_arraylike.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | import scyjava as sj 4 | 5 | # -- Fixtures -- 6 | 7 | 8 | @pytest.fixture 9 | def img(): 10 | # Create img 11 | ArrayImgs = sj.jimport("net.imglib2.img.array.ArrayImgs") 12 | img = ArrayImgs.bytes(2, 3, 4) 13 | # Insert a different value into each index 14 | tmp_val = 1 15 | cursor = img.cursor() 16 | while cursor.hasNext(): 17 | cursor.next().set(tmp_val) 18 | tmp_val += 1 19 | # Return the new img 20 | return img 21 | 22 | 23 | # -- Tests -- 24 | 25 | 26 | def test_slice_index(img): 27 | assert img[0, 0, 0].get() == 1 28 | 29 | 30 | def test_slice_index_negative(img): 31 | assert img[-1, -1, -1].get() == 24 32 | 33 | 34 | def test_slice_2d(img): 35 | Views = sj.jimport("net.imglib2.view.Views") 36 | expected = Views.hyperSlice(img, 0, 0) 37 | actual = img[0, :, :] 38 | for i in range(3): 39 | for j in range(4): 40 | assert expected[i, j] == actual[i, j] 41 | 42 | 43 | def test_slice_2d_negative(img): 44 | Views = sj.jimport("net.imglib2.view.Views") 45 | expected = Views.hyperSlice(img, 0, 1) 46 | actual = img[-1, :, :] 47 | for i in range(3): 48 | for j in range(4): 49 | assert expected[i, j] == actual[i, j] 50 | 51 | 52 | def test_slice_1d(img): 53 | Views = sj.jimport("net.imglib2.view.Views") 54 | expected = Views.hyperSlice(Views.hyperSlice(img, 0, 0), 0, 0) 55 | actual = img[0, 0, :] 56 | for i in range(4): 57 | assert expected[i] == actual[i] 58 | 59 | 60 | def test_slice_1d_negative(img): 61 | Views = sj.jimport("net.imglib2.view.Views") 62 | expected = Views.hyperSlice(Views.hyperSlice(img, 0, 1), 0, 1) 63 | actual = img[-1, -2, :] 64 | for i in range(4): 65 | assert expected[i] == actual[i] 66 | 67 | 68 | def test_slice_int(img): 69 | Views = sj.jimport("net.imglib2.view.Views") 70 | expected = Views.hyperSlice(img, 0, 0) 71 | actual = img[0] 72 | for i in range(3): 73 | for j in range(4): 74 | assert expected[i, j] == actual[i, j] 75 | 76 | 77 | def test_slice_not_enough_dims(img): 78 | Views = sj.jimport("net.imglib2.view.Views") 79 | expected = Views.hyperSlice(Views.hyperSlice(img, 0, 0), 0, 0) 80 | actual = img[0, 0] 81 | for i in range(4): 82 | assert expected[i] == actual[i] 83 | 84 | 85 | def test_step(img): 86 | # Create a stepped img via Views 87 | Views = sj.jimport("net.imglib2.view.Views") 88 | steps = sj.jarray("j", 3) 89 | steps[0] = 1 90 | steps[1] = 1 91 | steps[2] = 2 92 | expected = Views.subsample(img, steps) 93 | # Create a stepped img via slicing notation 94 | actual = img[:, :, ::2] 95 | for i in range(2): 96 | for j in range(3): 97 | for k in range(2): 98 | assert expected[i, j, k] == actual[i, j, k] 99 | 100 | 101 | def test_step_not_enough_dims(img): 102 | # Create a stepped img via Views 103 | Views = sj.jimport("net.imglib2.view.Views") 104 | steps = sj.jarray("j", 3) 105 | steps[0] = 2 106 | steps[1] = 1 107 | steps[2] = 1 108 | expected = Views.subsample(img, steps) 109 | expected = Views.dropSingletonDimensions(expected) 110 | # Create a stepped img via slicing notation 111 | actual = img[::2] 112 | for i in range(3): 113 | for j in range(4): 114 | assert expected[i, j] == actual[i, j] 115 | 116 | 117 | def test_slice_and_step(img): 118 | # Create a stepped img via Views 119 | Views = sj.jimport("net.imglib2.view.Views") 120 | intervaled = Views.hyperSlice(img, 0, 0) 121 | steps = sj.jarray("j", 2) 122 | steps[0] = 1 123 | steps[1] = 2 124 | expected = Views.subsample(intervaled, steps) 125 | # Create a stepped img via slicing notation 126 | actual = img[:1, :, ::2] 127 | for i in range(3): 128 | for j in range(2): 129 | assert expected[i, j] == actual[i, j] 130 | 131 | 132 | def test_shape(img): 133 | assert hasattr(img, "shape") 134 | assert img.shape == (2, 3, 4) 135 | 136 | 137 | def test_dtype(img): 138 | assert hasattr(img, "dtype") 139 | ByteType = sj.jimport("net.imglib2.type.numeric.integer.ByteType") 140 | assert img.dtype == ByteType 141 | 142 | 143 | def test_ndim(img): 144 | assert hasattr(img, "ndim") 145 | assert img.ndim == 3 146 | 147 | 148 | def test_transpose1d(img): 149 | img = img[0, 0] 150 | transpose = img.T 151 | for i in range(2): 152 | assert transpose[i] == img[i] 153 | 154 | 155 | def test_transpose2d(img): 156 | img = img[0] 157 | transpose = img.T 158 | for i in range(3): 159 | for j in range(2): 160 | assert transpose[i, j] == img[j, i] 161 | 162 | 163 | def test_transpose3d(img): 164 | transpose = img.T 165 | for i in range(4): 166 | for j in range(3): 167 | for k in range(2): 168 | assert transpose[i, j, k] == img[k, j, i] 169 | 170 | 171 | def test_addition(img): 172 | actual = img + img 173 | expected = np.multiply(img, 2) 174 | for i in range(2): 175 | for j in range(3): 176 | for k in range(4): 177 | assert expected[i, j, k] == actual[i, j, k] 178 | 179 | 180 | def test_subtraction(img): 181 | actual = img - img 182 | expected = np.multiply(img, 0) 183 | for i in range(2): 184 | for j in range(3): 185 | for k in range(4): 186 | assert expected[i, j, k] == actual[i, j, k] 187 | 188 | 189 | def test_multiplication(img): 190 | actual = img * img 191 | expected = np.multiply(img, img) 192 | for i in range(2): 193 | for j in range(3): 194 | for k in range(4): 195 | assert expected[i, j, k] == actual[i, j, k] 196 | 197 | 198 | def test_division(img): 199 | actual = img / img 200 | expected = np.divide(img, img) 201 | for i in range(2): 202 | for j in range(3): 203 | for k in range(4): 204 | assert expected[i, j, k] == actual[i, j, k] 205 | --------------------------------------------------------------------------------