├── .github └── workflows │ └── build.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── LICENSE.txt ├── Makefile ├── README.md ├── bin ├── check.sh ├── clean.sh ├── fmt.sh ├── lint.sh ├── setup.sh └── test.sh ├── codecov.yml ├── conftest.py ├── dev-environment.yml ├── environment.yml ├── examples └── wrap_arraylike.py ├── pyproject.toml ├── src └── imglyb │ ├── OSXAWTwrapper.py │ ├── __init__.py │ ├── accesses.py │ ├── caches.py │ ├── config.py │ ├── imglib_ndarray.py │ ├── ndarray_like_as_img.py │ ├── reference_store.py │ ├── types.py │ └── util.py └── tests ├── test_imglyb.py └── test_version.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-cross-platform: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macos-latest] 19 | python-version: ["3.8", "3.12"] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{matrix.python-version}} 27 | 28 | - name: Setup Java 29 | uses: actions/setup-java@v3 30 | with: 31 | java-version: '8' 32 | distribution: 'zulu' 33 | 34 | - name: Install pyimagej 35 | run: | 36 | python -m pip install --upgrade pip 37 | python -m pip install -e ".[dev]" 38 | 39 | - name: Test ImageJ 40 | run: | 41 | python -m pytest -s -p no:faulthandler --color=yes 42 | echo "Done!" 43 | 44 | ensure-clean-code: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v2 48 | - uses: actions/setup-python@v3 49 | 50 | - name: Lint code 51 | run: | 52 | python -m pip install ruff 53 | ruff check 54 | ruff format --check 55 | 56 | conda-dev-test: 57 | name: Test Conda Development Setup And Code Coverage 58 | runs-on: ubuntu-latest 59 | defaults: 60 | # Steps that rely on the activated environment must be run with this shell setup. 61 | # See https://github.com/marketplace/actions/setup-miniconda#important 62 | run: 63 | shell: bash -l {0} 64 | steps: 65 | - uses: actions/checkout@v2 66 | - name: Cache conda 67 | uses: actions/cache@v4 68 | env: 69 | # Increase this value to reset cache if dev-environment.yml has not changed 70 | CACHE_NUMBER: 0 71 | with: 72 | path: ~/conda_pkgs_dir 73 | key: 74 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ hashFiles('dev-environment.yml') }} 75 | - uses: conda-incubator/setup-miniconda@v2 76 | with: 77 | # Create env with dev packages 78 | auto-update-conda: true 79 | python-version: 3.9 80 | miniforge-version: latest 81 | environment-file: dev-environment.yml 82 | # Activate imglyb-dev environment 83 | activate-environment: imglyb-dev 84 | auto-activate-base: false 85 | # Use mamba for faster setup 86 | use-mamba: true 87 | - name: Test imglyb 88 | run: | 89 | python -m pytest tests/ -p no:faulthandler --cov-report=xml --cov=. 90 | # We could do this in its own action, but we'd have to setup the environment again. 91 | - name: Upload Coverage to Codecov 92 | uses: codecov/codecov-action@v2 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .classpath 3 | .project 4 | .settings 5 | .vscode 6 | target 7 | __pycache__ 8 | build 9 | dist 10 | *.pyc 11 | imglyb.egg-info 12 | 13 | # setuptools_scm 14 | src/imglyb/version.py 15 | 16 | # VSCode 17 | .vscode 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "test" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true 7 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 - 2020, Howard Hughes Medical Institute. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: 2 | @echo "Available targets:\n\ 3 | clean - remove build files and directories\n\ 4 | setup - create mamba developer environment\n\ 5 | lint - run code formatters and linters\n\ 6 | test - run automated test suite\n\ 7 | dist - generate release archives\n\ 8 | \n\ 9 | Remember to 'mamba activate imglyb-dev' first!" 10 | 11 | clean: 12 | bin/clean.sh 13 | 14 | setup: 15 | bin/setup.sh 16 | 17 | check: 18 | @bin/check.sh 19 | 20 | lint: check 21 | bin/lint.sh 22 | 23 | fmt: check 24 | bin/fmt.sh 25 | 26 | test: check 27 | bin/test.sh 28 | 29 | dist: check clean 30 | python -m build 31 | 32 | .PHONY: test 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build status](https://github.com/imglib/imglyb/actions/workflows/build.yml/badge.svg)](https://github.com/imglib/imglyb/actions/workflows/build.yml) 2 | 3 | # imglyb 4 | 5 | `imglyb` aims at connecting two worlds that have been seperated for too long: 6 | * Python with [numpy](https://github.com/numpy/numpy) 7 | * Java with [ImgLib2](https://github.com/imglib/imglib2) 8 | 9 | `imglyb` uses [jpype](http://jpype.org) to access `numpy` arrays and expose 10 | them to `ImgLib2` through 11 | [`imglib2-imglyb`](https://github.com/imglib/imglib2-imglyb). 12 | This means **shared memory** between `numpy` and `ImgLib2`, i.e. any `ImgLib2` 13 | algorithm can run on `numpy` arrays without creating copies of the data! 14 | For example, Python users can now make use of the 15 | [BigDataViewer extension](https://github.com/imglib/imglyb-bdv) to visualize dense volumetric 16 | data. 17 | 18 | If you are interested in using `imglyb`, have a look at the `examples` folder 19 | and extend the examples as needed! 20 | 21 | **Note**: 22 | [`NEP 18`](https://numpy.org/neps/nep-0018-array-function-protocol.html) has 23 | the potential to improve `numpy` - `imglib` interoperability, especially when 24 | converting `imglib2` data structures to `numpy`. 25 | 26 | ## Installation 27 | 28 | ### Prerequisites 29 | 30 | `imglyb` has been tested on Linux, macOS, and Windows. 31 | 32 | The following tools are required: 33 | 34 | * Python 3 35 | * Java 8 or 11 JDK (JRE is not enough) 36 | * [Apache Maven](https://maven.apache.org/) 37 | 38 | If you use [conda](https://conda.io/), these will be installed for you. 39 | 40 | ### Installing with conda 41 | 42 | ```shell 43 | conda install -c conda-forge imglyb 44 | ``` 45 | 46 | ### Installing with pip 47 | 48 | First, install the prerequisites above. Then run: 49 | 50 | ```shell 51 | pip install imglyb 52 | ``` 53 | 54 | It is recommended to do this from inside a virtualenv or conda environment, 55 | rather than system-wide. 56 | 57 | ### Installing from source 58 | 59 | First, install the prerequisites above. Then run: 60 | 61 | ```shell 62 | git clone https://github.com/imglib/imglyb 63 | cd imglyb 64 | pip install -e . 65 | ``` 66 | 67 | It is recommended to do this from inside a virtualenv or conda environment, 68 | rather than system-wide. 69 | 70 | ## Usage 71 | 72 | It is suggested to follow and extend the examples in the `examples` folder 73 | according to your needs. 74 | 75 | Or, for a higher-level way to use `imglyb`, check out 76 | [pyimagej](https://github.com/imagej/pyimagej). 77 | 78 | ## Known Issues 79 | 80 | ### AWT on macOS 81 | 82 | AWT and Cocoa do not get along perfectly. In general, the Cocoa event loop 83 | needs to be started before the JVM is loaded. (Thanks to @tpietzsch for 84 | figuring this out!) This requires some macOS specific code, written using 85 | `PyObjC`, to properly start up and shut down the Cocoa application and start 86 | the Java/Python code within it. 87 | 88 | The `OSXAWTwrapper.py` script included in the `imglyb` library provides an 89 | example of Cocoa code and can be used to run the `imglyb` examples. Two 90 | packages from `PyObjC` are required for this wrapper (`pyobjc-core` and 91 | `pyobjc-framework-cocoa`), and they should be installed with `imglyb` 92 | on macOS. 93 | 94 | When running the wrapper, one can either provide the name of the target module 95 | (as if using `python -m`) or the full path to the target script. So using the 96 | module name, the command to run the "butterfly" script in `imglyb-examples` 97 | looks like this: 98 | ```shell 99 | python imglyb/OSXAWTwrapper.py imglyb-examples.butterfly 100 | ``` 101 | Running `OSXAWTwrapper.py` via `python -m` does not work at this time. 102 | -------------------------------------------------------------------------------- /bin/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case "$CONDA_PREFIX" in 4 | */imglyb-dev) 5 | ;; 6 | *) 7 | echo "Please run 'make setup' and then 'mamba activate imglyb-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 src/*.egg-info 10 | -------------------------------------------------------------------------------- /bin/fmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(dirname "$0") 4 | cd "$dir/.." 5 | 6 | exitCode=0 7 | ruff check --fix 8 | code=$?; test $code -eq 0 || exitCode=$code 9 | 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 | ruff check 8 | code=$?; test $code -eq 0 || exitCode=$code 9 | ruff format --check 10 | code=$?; test $code -eq 0 || exitCode=$code 11 | 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 | if [ $# -gt 0 ] 7 | then 8 | python -m pytest -p no:faulthandler $@ 9 | else 10 | python -m pytest -p no:faulthandler tests/ 11 | fi 12 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imglib/imglyb/fd057709ed180889f181daf3fefd89e8702a142e/codecov.yml -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | from re import S 2 | import scyjava 3 | import pytest 4 | import jgo, logging, sys 5 | 6 | 7 | @pytest.fixture(scope="session") 8 | def sj_fixture(request): 9 | """ 10 | Start the JVM through scyjava once for the whole test environment 11 | :param request: Pytest variable passed in to fixtures 12 | """ 13 | scyjava.config.add_option("-Djava.awt.headless=true") 14 | scyjava.start_jvm() 15 | 16 | yield scyjava 17 | -------------------------------------------------------------------------------- /dev-environment.yml: -------------------------------------------------------------------------------- 1 | # Use this file to construct an environment 2 | # for developing imglyb from source. 3 | # 4 | # First, install mambaforge: 5 | # 6 | # https://github.com/conda-forge/miniforge#mambaforge 7 | # 8 | # Then run: 9 | # 10 | # mamba env create -f dev-environment.yml 11 | # mamba activate imglyb-dev 12 | # 13 | # In addition to the dependencies needed for using imglyb, it 14 | # includes tools for developer-related actions like running 15 | # automated tests (pytest) and linting the code (black). If you 16 | # want an environment without these tools, use environment.yml. 17 | 18 | name: imglyb-dev 19 | channels: 20 | - conda-forge 21 | dependencies: 22 | - python >= 3.8 23 | # Project dependencies 24 | - jpype1 25 | - numpy 26 | - scyjava 27 | # Developer tools 28 | - build 29 | - pre-commit 30 | - pytest 31 | - pytest-cov 32 | - ruff 33 | - toml 34 | # Project from source 35 | - pip 36 | - pip: 37 | - validate-pyproject[all] 38 | - -e . 39 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | # Use this file to construct an environment for 2 | # working with imglyb in a runtime environment. 3 | # 4 | # First, install mambaforge: 5 | # 6 | # https://github.com/conda-forge/miniforge#mambaforge 7 | # 8 | # Then run: 9 | # 10 | # mamba env create 11 | # mamba activate imglyb 12 | # 13 | # It includes the dependencies needed for using imglyb, but not tools 14 | # for developer-related actions like running automated tests (pytest), 15 | # linting the code (black), and generating the API documentation (sphinx). 16 | # If you want an environment including these tools, use dev-environment.yml. 17 | 18 | name: imglyb 19 | channels: 20 | - conda-forge 21 | dependencies: 22 | - python >= 3.8 23 | # Project dependencies 24 | - jpype1 25 | - numpy 26 | - scyjava 27 | # Project from source 28 | - pip 29 | - pip: 30 | - -e . 31 | -------------------------------------------------------------------------------- /examples/wrap_arraylike.py: -------------------------------------------------------------------------------- 1 | import imglyb 2 | import numpy as np 3 | import scyjava 4 | 5 | scyjava.start_jvm() 6 | 7 | Views = imglyb.util.Views 8 | 9 | shape = (3, 5) 10 | data = np.arange(np.prod(shape)).reshape(shape) 11 | block_size = (2, 2) 12 | img, _ = imglyb.as_cell_img(data, block_size, access_type="native", cache=1) 13 | 14 | print(data) 15 | print(img.toString()) 16 | cursor = Views.flatIterable(img).cursor() 17 | 18 | print("Cell Img") 19 | while cursor.hasNext(): 20 | print(cursor.next().toString()) 21 | 22 | print() 23 | print("ndarray") 24 | for d in data.flat: 25 | print(d) 26 | print(data) 27 | 28 | print() 29 | print("cells") 30 | cursor = img.getCells().cursor() 31 | cellIdx = 0 32 | while cursor.hasNext(): 33 | cell = cursor.next() 34 | access = cell.getData() 35 | print(f"Cell index: {cellIdx} {access}") 36 | size = cell.size() 37 | for idx in range(0, size): 38 | print(access.getValue(idx)) 39 | cellIdx += 1 40 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "imglyb" 7 | version = "2.1.1.dev0" 8 | description = "A python module to bring together the worlds of NumPy (Python) and ImgLib2 (Java)." 9 | license = "BSD-2-Clause" 10 | authors = [ 11 | {name = "Philipp Hanslovsky"}, 12 | {name = "Curtis Rueden", email = "ctrueden@wisc.edu"}, 13 | {name = "Edward Evans"}, 14 | {name = "Mark Hiner"}, 15 | {name = "Gabriel Selzer"}, 16 | ] 17 | readme = "README.md" 18 | keywords = ["java", "maven", "cross-language", "numpy", "imglib2"] 19 | classifiers = [ 20 | "Development Status :: 5 - Production/Stable", 21 | "Intended Audience :: Developers", 22 | "Intended Audience :: Education", 23 | "Intended Audience :: Science/Research", 24 | "Programming Language :: Python :: 3 :: Only", 25 | "Programming Language :: Python :: 3.8", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | "Programming Language :: Python :: 3.12", 30 | "Operating System :: Microsoft :: Windows", 31 | "Operating System :: Unix", 32 | "Operating System :: MacOS", 33 | "Topic :: Scientific/Engineering", 34 | "Topic :: Scientific/Engineering :: Image Processing", 35 | "Topic :: Scientific/Engineering :: Visualization", 36 | "Topic :: Software Development :: Libraries :: Java Libraries", 37 | "Topic :: Software Development :: Libraries :: Python Modules", 38 | "Topic :: Utilities", 39 | ] 40 | 41 | # NB: Keep this in sync with environment.yml AND dev-environment.yml! 42 | requires-python = ">=3.8" 43 | dependencies = [ 44 | "numpy", 45 | "jpype1 >= 1.3.0", 46 | "scyjava >= 1.3.0", 47 | ] 48 | 49 | [project.optional-dependencies] 50 | # NB: Keep this in sync with dev-environment.yml! 51 | dev = [ 52 | "build", 53 | "pytest", 54 | "pre-commit", 55 | "pytest-cov", 56 | "ruff", 57 | "toml", 58 | "validate-pyproject[all]", 59 | ] 60 | 61 | [project.urls] 62 | homepage = "https://github.com/imglib/imglyb" 63 | documentation = "https://github.com/imglib/imglyb/blob/main/README.md" 64 | source = "https://github.com/imglib/imglyb" 65 | download = "https://pypi.org/project/imglyb/" 66 | tracker = "https://github.com/imglib/imglyb/issues" 67 | 68 | [tool.setuptools] 69 | package-dir = {"" = "src"} 70 | include-package-data = false 71 | 72 | [tool.setuptools.packages.find] 73 | where = ["src"] 74 | namespaces = false 75 | 76 | # ruff configuration 77 | [tool.ruff] 78 | line-length = 88 79 | src = ["src", "tests"] 80 | include = ["pyproject.toml", "src/**/*.py", "tests/**/*.py"] 81 | extend-exclude = ["bin", "build", "dist"] 82 | 83 | [tool.ruff.lint] 84 | extend-ignore = ["E203"] 85 | 86 | [tool.ruff.lint.per-file-ignores] 87 | # Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. 88 | "__init__.py" = ["E402", "F401"] 89 | 90 | [tool.pytest.ini_options] 91 | addopts = "--ignore=docs" 92 | testpaths = [ 93 | "tests", 94 | ] 95 | -------------------------------------------------------------------------------- /src/imglyb/OSXAWTwrapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script is a wrapper to allow Java code that uses AWT to run properly on 3 | macOS. It starts the Cocoa event loop before Java and keeps Cocoa happy. 4 | 5 | See https://github.com/kivy/pyjnius/issues/151 for more. 6 | 7 | In particular, this wrapper allows one to run the code from imglyb-examples. 8 | 9 | usage: python OSXAWTwrapper.py [module name | script path] [module or script parameters] 10 | 11 | NB: Since the creation of this script, the imglyb project switched from pyjnius 12 | to jpype, and this script is not really necessary anymore. You can instead use 13 | jpype.setupGuiEnvironment(...), passing a function that does Java AWT things, 14 | and it will be executed on the correct thread. 15 | """ 16 | 17 | import os 18 | import sys 19 | 20 | usage = ( 21 | "usage: python OSXAWTwrapper.py " 22 | "[module name | script path] " 23 | "[module or script parameters]" 24 | ) 25 | 26 | 27 | def runAwtStuff(): 28 | import runpy 29 | 30 | # user can provide either a module or a path to a script; 31 | # either way, need to remove it from sys.argv, 32 | # because the wrapped module or script might parse sys.argv for its own 33 | # reasons: 34 | if len(sys.argv) > 1: 35 | name = sys.argv[1] 36 | sys.argv.remove(name) 37 | 38 | # whether module or script, need to set the run_name for things to work 39 | # as expected! 40 | try: 41 | if os.path.exists(name): 42 | runpy.run_path(name, run_name="__main__") 43 | else: 44 | runpy.run_module(name, run_name="__main__") 45 | except Exception as e: 46 | print("exception occurred while running {}: {}".format(name, e)) 47 | 48 | # lots can go wrong here, and exceptions can bubble up from 49 | # the Java layer, too; uncomment lines below to print 50 | # more information on exception 51 | # note: different exceptions have different attributes, so you 52 | # might need to adjust the lines below; use dir(e) to see 53 | # what you have available when you are debugging 54 | # print("exception details: ") 55 | # print("e.args: ", e.args) 56 | # print("e.__class__: ", e.__class__) 57 | # print("e.stacktrace: ") 58 | # for line in e.stacktrace: 59 | # print("\t", line) 60 | # if Java throws a reflection error, you might want this: 61 | # print("e.innermessage", e.innermessage) 62 | else: 63 | print(usage) 64 | print("no module or script specified") 65 | 66 | 67 | def main(): 68 | try: 69 | import objc 70 | from AppKit import ( 71 | NSApp, 72 | NSApplication, 73 | NSApplicationActivationPolicyRegular, 74 | NSObject, 75 | ) 76 | from PyObjCTools import AppHelper 77 | 78 | # from Foundation import * 79 | 80 | class AppDelegate(NSObject): 81 | def init(self): 82 | self = objc.super(AppDelegate, self).init() 83 | if self is None: 84 | return None 85 | return self 86 | 87 | def runjava_(self, arg): 88 | runAwtStuff() 89 | # we need to terminate explicitly, or it'll hang when 90 | # the wrapped code exits 91 | NSApp().terminate_(self) 92 | 93 | def applicationDidFinishLaunching_(self, aNotification): 94 | self.performSelectorInBackground_withObject_("runjava:", 0) 95 | 96 | NSApplication.sharedApplication() 97 | delegate = AppDelegate.alloc().init() 98 | NSApp().setDelegate_(delegate) 99 | # this is necessary to have keyboard events sent to the UI; 100 | # basically this call makes the script act like an OS X application, 101 | # with Dock icon and everything 102 | NSApp.setActivationPolicy_(NSApplicationActivationPolicyRegular) 103 | AppHelper.runEventLoop() 104 | except ModuleNotFoundError: 105 | print("Skipping OSXAWTwrapper - module 'objc' is not installed") 106 | 107 | 108 | if __name__ == "__main__": 109 | main() 110 | -------------------------------------------------------------------------------- /src/imglyb/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import lru_cache 3 | from typing import Any, Callable, Dict 4 | 5 | import scyjava 6 | import scyjava.config as sjconf 7 | 8 | from .config import get_imglib2_imglyb_version 9 | from .config import version as __version__ # noqa: F401 10 | from .imglib_ndarray import ImgLibReferenceGuard as _ImgLibReferenceGuard 11 | from .ndarray_like_as_img import ( 12 | as_cell_img, 13 | as_cell_img_with_array_accesses, 14 | as_cell_img_with_native_accesses, 15 | ) 16 | from .util import to_imglib, to_imglib_argb 17 | 18 | # Declare public API in a PEP8-compliant way. 19 | # https://peps.python.org/pep-0008/#public-and-internal-interfaces 20 | __all__ = [ 21 | "as_cell_img", 22 | "as_cell_img_with_array_accesses", 23 | "as_cell_img_with_native_accesses", 24 | "to_imglib", 25 | "to_imglib_argb", 26 | ] 27 | 28 | __author__ = "ImgLib2 developers" 29 | 30 | _logger = logging.getLogger(__name__) 31 | 32 | _imglib2_imglyb_version = get_imglib2_imglyb_version() 33 | _IMGLIB2_IMGLYB_ENDPOINT = "net.imglib2:imglib2-imglyb:{}".format( 34 | _imglib2_imglyb_version 35 | ) 36 | sjconf.endpoints.append(_IMGLIB2_IMGLYB_ENDPOINT) 37 | 38 | 39 | def to_numpy(source): 40 | scyjava.start_jvm() 41 | return _ImgLibReferenceGuard(source) 42 | 43 | 44 | # This is a duplication of work in scyjava. 45 | # It should be removed once https://github.com/scijava/scyjava/issues/40 46 | # has been solved. 47 | 48 | # Set of module properties 49 | _CONSTANTS: Dict[str, Callable] = {} 50 | 51 | 52 | def constant(func: Callable[[], Any], cache=True) -> Callable[[], Any]: 53 | """ 54 | Turns a function into a property of this module 55 | Functions decorated with this property must have a 56 | leading underscore! 57 | :param func: The function to turn into a property 58 | """ 59 | if func.__name__[0] != "_": 60 | raise ValueError( 61 | f"""Function {func.__name__} must have 62 | a leading underscore in its name 63 | to become a module property!""" 64 | ) 65 | name = func.__name__[1:] 66 | if cache: 67 | func = (lru_cache(maxsize=None))(func) 68 | _CONSTANTS[name] = func 69 | return func 70 | 71 | 72 | def __getattr__(name): 73 | """ 74 | Runs as a fallback when this module does not have an 75 | attribute. 76 | :param name: The name of the attribute being searched for. 77 | """ 78 | if name in _CONSTANTS: 79 | return _CONSTANTS[name]() 80 | raise AttributeError(f"module '{__name__}' has no attribute '{name}'") 81 | -------------------------------------------------------------------------------- /src/imglyb/accesses.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scyjava 3 | 4 | 5 | def _java_setup(): 6 | """ 7 | Lazy initialization function for Java-dependent data structures. 8 | Do not call this directly; use scyjava.start_jvm() instead. 9 | """ 10 | 11 | global Accesses 12 | Accesses = scyjava.jimport("net.imglib2.img.basictypeaccess.Accesses") 13 | 14 | global ByteArray 15 | ByteArray = scyjava.jimport("net.imglib2.img.basictypeaccess.array.ByteArray") 16 | global CharArray 17 | CharArray = scyjava.jimport("net.imglib2.img.basictypeaccess.array.CharArray") 18 | global DoubleArray 19 | DoubleArray = scyjava.jimport("net.imglib2.img.basictypeaccess.array.DoubleArray") 20 | global FloatArray 21 | FloatArray = scyjava.jimport("net.imglib2.img.basictypeaccess.array.FloatArray") 22 | global IntArray 23 | IntArray = scyjava.jimport("net.imglib2.img.basictypeaccess.array.IntArray") 24 | global LongArray 25 | LongArray = scyjava.jimport("net.imglib2.img.basictypeaccess.array.LongArray") 26 | global ShortArray 27 | ShortArray = scyjava.jimport("net.imglib2.img.basictypeaccess.array.ShortArray") 28 | 29 | global VolatileByteArray 30 | VolatileByteArray = scyjava.jimport( 31 | "net.imglib2.img.basictypeaccess.volatiles.array.VolatileByteArray" 32 | ) 33 | global VolatileCharArray 34 | VolatileCharArray = scyjava.jimport( 35 | "net.imglib2.img.basictypeaccess.volatiles.array.VolatileCharArray" 36 | ) 37 | global VolatileDoubleArray 38 | VolatileDoubleArray = scyjava.jimport( 39 | "net.imglib2.img.basictypeaccess.volatiles.array.VolatileDoubleArray" 40 | ) 41 | global VolatileFloatArray 42 | VolatileFloatArray = scyjava.jimport( 43 | "net.imglib2.img.basictypeaccess.volatiles.array.VolatileFloatArray" 44 | ) 45 | global VolatileIntArray 46 | VolatileIntArray = scyjava.jimport( 47 | "net.imglib2.img.basictypeaccess.volatiles.array.VolatileIntArray" 48 | ) 49 | global VolatileLongArray 50 | VolatileLongArray = scyjava.jimport( 51 | "net.imglib2.img.basictypeaccess.volatiles.array.VolatileLongArray" 52 | ) 53 | global VolatileShortArray 54 | VolatileShortArray = scyjava.jimport( 55 | "net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray" 56 | ) 57 | 58 | global ByteUnsafe 59 | ByteUnsafe = scyjava.jimport( 60 | "net.imglib2.img.basictypelongaccess.unsafe.ByteUnsafe" 61 | ) 62 | global CharUnsafe 63 | CharUnsafe = scyjava.jimport( 64 | "net.imglib2.img.basictypelongaccess.unsafe.CharUnsafe" 65 | ) 66 | global DoubleUnsafe 67 | DoubleUnsafe = scyjava.jimport( 68 | "net.imglib2.img.basictypelongaccess.unsafe.DoubleUnsafe" 69 | ) 70 | global FloatUnsafe 71 | FloatUnsafe = scyjava.jimport( 72 | "net.imglib2.img.basictypelongaccess.unsafe.FloatUnsafe" 73 | ) 74 | global IntUnsafe 75 | IntUnsafe = scyjava.jimport("net.imglib2.img.basictypelongaccess.unsafe.IntUnsafe") 76 | global LongUnsafe 77 | LongUnsafe = scyjava.jimport( 78 | "net.imglib2.img.basictypelongaccess.unsafe.LongUnsafe" 79 | ) 80 | global ShortUnsafe 81 | ShortUnsafe = scyjava.jimport( 82 | "net.imglib2.img.basictypelongaccess.unsafe.ShortUnsafe" 83 | ) 84 | 85 | 86 | scyjava.when_jvm_starts(_java_setup) 87 | 88 | 89 | # does not work with strided accesses, currently 90 | def as_array_access(ndarray, volatile=False): 91 | scyjava.start_jvm() 92 | if ndarray.dtype == np.uint8 or ndarray.dtype == np.int8: 93 | return _as_array_access( 94 | ndarray, 95 | ByteUnsafe, 96 | lambda n: VolatileByteArray(n, True) if volatile else ByteArray(n), 97 | ) 98 | elif ndarray.dtype == np.uint16 or ndarray.dtype == np.int16: 99 | return _as_array_access( 100 | ndarray, 101 | ShortUnsafe, 102 | lambda n: VolatileShortArray(n, True) if volatile else ShortArray(n), 103 | ) 104 | elif ndarray.dtype == np.uint32 or ndarray.dtype == np.int32: 105 | return _as_array_access( 106 | ndarray, 107 | IntUnsafe, 108 | lambda n: VolatileIntArray(n, True) if volatile else IntArray(n), 109 | ) 110 | elif ndarray.dtype == np.uint64 or ndarray.dtype == np.int64: 111 | return _as_array_access( 112 | ndarray, 113 | LongUnsafe, 114 | lambda n: VolatileLongArray(n, True) if volatile else LongArray(n), 115 | ) 116 | elif ndarray.dtype == np.float32: 117 | return _as_array_access( 118 | ndarray, 119 | FloatUnsafe, 120 | lambda n: VolatileFloatArray(n, True) if volatile else FloatArray(n), 121 | ) 122 | elif ndarray.dtype == np.float64: 123 | return _as_array_access( 124 | ndarray, 125 | DoubleUnsafe, 126 | lambda n: VolatileDoubleArray(n, True) if volatile else DoubleArray(n), 127 | ) 128 | 129 | 130 | def _as_array_access(ndarray, src_clazz, tgt_clazz): 131 | src = src_clazz(ndarray.ctypes.data) 132 | tgt = tgt_clazz(ndarray.size) 133 | Accesses.copyAny(src, 0, tgt, 0, ndarray.size) 134 | return tgt 135 | -------------------------------------------------------------------------------- /src/imglyb/caches.py: -------------------------------------------------------------------------------- 1 | import scyjava 2 | 3 | 4 | def _java_setup(): 5 | """ 6 | Lazy initialization function for Java-dependent data structures. 7 | Do not call this directly; use scyjava.start_jvm() instead. 8 | """ 9 | global BoundedSoftRefLoaderCache 10 | BoundedSoftRefLoaderCache = scyjava.jimport( 11 | "net.imglib2.cache.ref.BoundedSoftRefLoaderCache" 12 | ) 13 | global GuardedStrongRefLoaderCache 14 | GuardedStrongRefLoaderCache = scyjava.jimport( 15 | "net.imglib2.cache.ref.GuardedStrongRefLoaderCache" 16 | ) 17 | global SoftRefLoaderCache 18 | SoftRefLoaderCache = scyjava.jimport("net.imglib2.cache.ref.SoftRefLoaderCache") 19 | global WeakRefLoaderCache 20 | WeakRefLoaderCache = scyjava.jimport("net.imglib2.cache.ref.WeakRefLoaderCache") 21 | 22 | 23 | scyjava.when_jvm_starts(_java_setup) 24 | -------------------------------------------------------------------------------- /src/imglyb/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from scyjava import get_version 4 | 5 | version = get_version("imglyb") 6 | 7 | _default_imglib2_imglyb_version = "1.1.0" 8 | _imglib2_imglyb_version = os.getenv( 9 | "IMGLIB2_IMGLYB_VERSION", _default_imglib2_imglyb_version 10 | ) 11 | 12 | 13 | def set_imglib2_imglyb_version(version): 14 | global _imglib2_imglyb_version 15 | _imglib2_imglyb_version = version 16 | 17 | 18 | def get_imglib2_imglyb_version(): 19 | global _imglib2_imglyb_version 20 | return _imglib2_imglyb_version 21 | -------------------------------------------------------------------------------- /src/imglyb/imglib_ndarray.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | import jpype 4 | import numpy as np 5 | import scyjava 6 | 7 | from imglyb import util 8 | 9 | 10 | def _java_setup(): 11 | """ 12 | Lazy initialization function for Java-dependent data structures. 13 | Do not call this directly; use scyjava.start_jvm() instead. 14 | """ 15 | global Intervals 16 | Intervals = scyjava.jimport("net.imglib2.util.Intervals") 17 | 18 | 19 | scyjava.when_jvm_starts(_java_setup) 20 | 21 | dtype_selector = { 22 | "FloatType": np.dtype("float32"), 23 | "FloatLongAccessType": np.dtype("float32"), 24 | "DoubleType": np.dtype("float64"), 25 | "DoubleLongAccessType": np.dtype("float64"), 26 | "ByteType": np.dtype("int8"), 27 | "ByteLongAccessType": np.dtype("int8"), 28 | "UnsignedByteType": np.dtype("uint8"), 29 | "UnsignedByteLongAccessType": np.dtype("uint8"), 30 | "ShortType": np.dtype("int16"), 31 | "ShortLongAccessType": np.dtype("int16"), 32 | "UnsignedShortType": np.dtype("uint16"), 33 | "UnisgnedShortLongAccessType": np.dtype("uint16"), 34 | "IntType": np.dtype("int32"), 35 | "IntLongAccessType": np.dtype("int32"), 36 | "UnsignedIntType": np.dtype("uint32"), 37 | "UnsignedIntLongAccessType": np.dtype("uint32"), 38 | "LongType": np.dtype("int64"), 39 | "LongLongAccessType": np.dtype("int64"), 40 | "UnsignedLongType": np.dtype("uint64"), 41 | "UnsignedLongLongAccessType": np.dtype("uint64"), 42 | } 43 | 44 | ctype_conversions_imglib = { 45 | "FloatType": ctypes.c_float, 46 | "FloatLongAccessType": ctypes.c_float, 47 | "DoubleType": ctypes.c_double, 48 | "DoubleLongAccessType": ctypes.c_double, 49 | "ByteType": ctypes.c_int8, 50 | "ByteLongAccessType": ctypes.c_int8, 51 | "UnsignedByteType": ctypes.c_uint8, 52 | "UnsignedByteLongAccessType": ctypes.c_uint8, 53 | "ShortType": ctypes.c_int16, 54 | "ShortLongAccessType": ctypes.c_int16, 55 | "UnsignedShortType": ctypes.c_uint16, 56 | "UnsignedShortLongAccessType": ctypes.c_uint16, 57 | "IntType": ctypes.c_int32, 58 | "IntLongAccessType": ctypes.c_int32, 59 | "UnsignedIntType": ctypes.c_uint32, 60 | "UnsignedIntLongAccessType": ctypes.c_uint32, 61 | "LongType": ctypes.c_int64, 62 | "LongLongAccessType": ctypes.c_int64, 63 | "UnsignedLongType": ctypes.c_uint64, 64 | "UnsignedLongLongAccessType": ctypes.c_uint64, 65 | } 66 | 67 | 68 | def get_address(rai): 69 | scyjava.start_jvm() 70 | class_name = scyjava.to_python(util.Helpers.classNameSimple(rai)) 71 | class_name_full = scyjava.to_python(util.Helpers.className(rai)) 72 | if class_name in ("ArrayImg", "UnsafeImg"): 73 | img_class = scyjava.jimport(class_name_full) 74 | access = jpype.JObject(rai, img_class).update(None) 75 | access_type = scyjava.to_python(util.Helpers.className(access)) 76 | 77 | if "basictypelongaccess.unsafe" in access_type: 78 | return jpype.JObject(access, access_type).getAddress() 79 | else: 80 | raise ValueError(f"Excpected unsafe access but got {access_type}") 81 | 82 | else: 83 | raise ValueError( 84 | "Excpected ArrayImg or UnsafeImg but got {}".format(class_name) 85 | ) 86 | 87 | 88 | class ImgLibReferenceGuard(np.ndarray): 89 | def __new__(cls, rai): 90 | # Access the address of the rai's data 91 | access = rai.randomAccess() 92 | rai.min(access) 93 | imglib_type = util.Helpers.classNameSimple(access.get()) 94 | address = get_address(rai) 95 | 96 | # Set python properties 97 | shape = tuple(Intervals.dimensionsAsLongArray(rai))[::-1] 98 | dtype = dtype_selector[imglib_type] 99 | 100 | # Create a ctypes pointer to the data 101 | pointer = ctypes.cast( 102 | address, ctypes.POINTER(ctype_conversions_imglib[imglib_type]) 103 | ) 104 | 105 | # Create a new numpy array 106 | obj = np.ndarray.__new__( 107 | cls, 108 | buffer=np.ctypeslib.as_array(pointer, shape=shape), 109 | shape=shape, 110 | dtype=dtype, 111 | ) 112 | obj.setflags(write=True) 113 | # Maintain a reference to the java object 114 | obj.rai = rai 115 | return obj 116 | 117 | def __array_finalize__(self, obj): 118 | if obj is None: 119 | return 120 | self.rai = obj.rai 121 | 122 | 123 | HANDLED_FUNCTIONS = {} 124 | 125 | 126 | class NumpyView(np.ndarray): 127 | def __new__(cls, rai): 128 | access = rai.randomAccess() 129 | rai.min(access) 130 | imglib_type = util.Helpers.classNameSimple(access.get()) 131 | dtype = dtype_selector[imglib_type] 132 | 133 | shape = tuple(Intervals.dimensionsAsLongArray(rai))[::-1] 134 | obj = super().__new__(NumpyView, shape=shape, dtype=dtype) 135 | 136 | # Maintain a reference to the java object 137 | obj.rai = rai 138 | return obj 139 | 140 | def __init__(self, rai): 141 | self.rai = rai 142 | 143 | def __array_finalize__(self, obj): 144 | if obj is None: 145 | return 146 | self.rai = obj.rai 147 | 148 | def __array_function__(self, func, types, args, kwargs): 149 | if func not in HANDLED_FUNCTIONS: 150 | return NotImplemented 151 | # Note: this allows subclasses that don"t override 152 | # __array_function__ to handle DiagonalArray objects. 153 | if not all(issubclass(t, self.__class__) for t in types): 154 | return NotImplemented 155 | return HANDLED_FUNCTIONS[func](*args, **kwargs) 156 | 157 | def __getitem__(self, key): 158 | if isinstance(key, slice): 159 | print("Got a slice") 160 | elif isinstance(key, int): 161 | Views = scyjava.jimport("net.imglib2.view.Views") 162 | return NumpyView( 163 | Views.hyperSlice(self.rai, self.rai.numDimensions() - 1, key) 164 | ) 165 | elif isinstance(key, tuple): 166 | ra = self.rai.randomAccess() 167 | for i in range(len(key)): 168 | ra.setPosition(key[i] % self.shape[i], len(key) - 1 - i) 169 | val = ra.get().get() 170 | if val != 0: 171 | print(val) 172 | return ra.get().get() 173 | else: 174 | raise ValueError(f"Cannot parse key {key}") 175 | 176 | def __setitem__(self, key, value): 177 | # print(f"key: {key}") 178 | # return 0 179 | if isinstance(key, slice): 180 | print("Got a slice") 181 | elif isinstance(key, int): 182 | print("Got an int") 183 | elif isinstance(key, tuple): 184 | ra = self.rai.randomAccess() 185 | for i in range(len(key)): 186 | ra.setPosition(key[i] % self.shape[i], len(key) - 1 - i) 187 | ra.get().set(value) 188 | else: 189 | raise ValueError(f"Cannot parse key {key}") 190 | 191 | def all(self): 192 | return np.all(self) 193 | 194 | def any(self): 195 | return np.any(self) 196 | 197 | 198 | def implements(np_function): 199 | "Register an __array_function__ implementation for DiagonalArray objects." 200 | 201 | def decorator(func): 202 | HANDLED_FUNCTIONS[np_function] = func 203 | return func 204 | 205 | return decorator 206 | 207 | 208 | @implements(np.all) 209 | def np_all(arr: NumpyView): 210 | for index in np.ndindex(*arr.shape): 211 | if not arr[index]: 212 | return False 213 | return True 214 | 215 | 216 | @implements(np.any) 217 | def np_any(arr: NumpyView): 218 | for index in np.ndindex(*arr.shape): 219 | if arr[index]: 220 | return True 221 | return False 222 | 223 | 224 | if __name__ == "__main__": 225 | ArrayImgs = scyjava.jimport("net.imglib2.img.array.ArrayImgs") 226 | UnsafeUtil = scyjava.jimport( 227 | "net.imglib2.img.basictypelongaccess.unsafe.UnsafeUtil" 228 | ) 229 | Arrays = scyjava.jimport("java.util.Arrays") 230 | OwningFloatUnsafe = scyjava.jimport( 231 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningFloatUnsafe" 232 | ) 233 | Fraction = scyjava.jimport("net.imglib2.util.Fraction") 234 | LongStream = scyjava.jimport("java.util.stream.LongStream") 235 | 236 | shape = (2, 3, 4) 237 | n_elements = int(np.prod(shape)) 238 | data_store = OwningFloatUnsafe(n_elements) 239 | dim_array = LongStream.of(*shape).toArray() 240 | print(Arrays.toString(dim_array)) 241 | rai = util.Helpers.toArrayImg( 242 | jpype.JObject(util.Helpers.className(data_store), data_store), dim_array 243 | ) 244 | # rai = ArrayImgs.floats( *shape ) 245 | c = rai.cursor() 246 | count = 23 247 | while c.hasNext(): 248 | c.next().setReal(count) 249 | count += 1 250 | print(util.Helpers.className(rai.randomAccess().get())) 251 | print(util.Helpers.classNameSimple(rai.randomAccess().get())) 252 | arr = ImgLibReferenceGuard(rai) 253 | print(arr, arr.mean()) 254 | c = rai.cursor() 255 | c.fwd() 256 | c.next().setReal(0) 257 | print(arr, arr.mean()) 258 | -------------------------------------------------------------------------------- /src/imglyb/ndarray_like_as_img.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import math 3 | 4 | import numpy as np 5 | import scyjava 6 | from jpype import JException, JImplements, JOverride 7 | 8 | from . import accesses, caches, types, util 9 | from .reference_store import ReferenceStore 10 | 11 | _global_reference_store = ReferenceStore() 12 | 13 | _logger = logging.getLogger(__name__) 14 | 15 | 16 | def _java_setup(): 17 | """ 18 | Lazy initialization function for Java-dependent data structures. 19 | Do not call this directly; use scyjava.start_jvm() instead. 20 | """ 21 | global PythonHelpers 22 | PythonHelpers = scyjava.jimport("net.imglib2.python.Helpers") 23 | 24 | # non-owning 25 | global _ByteUnsafe 26 | _ByteUnsafe = scyjava.jimport( 27 | "net.imglib2.img.basictypelongaccess.unsafe.ByteUnsafe" 28 | ) 29 | global _CharUnsafe 30 | _CharUnsafe = scyjava.jimport( 31 | "net.imglib2.img.basictypelongaccess.unsafe.CharUnsafe" 32 | ) 33 | global _DoubleUnsafe 34 | _DoubleUnsafe = scyjava.jimport( 35 | "net.imglib2.img.basictypelongaccess.unsafe.DoubleUnsafe" 36 | ) 37 | global _FloatUnsafe 38 | _FloatUnsafe = scyjava.jimport( 39 | "net.imglib2.img.basictypelongaccess.unsafe.FloatUnsafe" 40 | ) 41 | global _IntUnsafe 42 | _IntUnsafe = scyjava.jimport("net.imglib2.img.basictypelongaccess.unsafe.IntUnsafe") 43 | global _LongUnsafe 44 | _LongUnsafe = scyjava.jimport( 45 | "net.imglib2.img.basictypelongaccess.unsafe.LongUnsafe" 46 | ) 47 | global _ShortUnsafe 48 | _ShortUnsafe = scyjava.jimport( 49 | "net.imglib2.img.basictypelongaccess.unsafe.ShortUnsafe" 50 | ) 51 | 52 | global _unsafe_for_dtype 53 | _unsafe_for_dtype = { 54 | np.dtype("complex64"): _FloatUnsafe, 55 | np.dtype("complex128"): _DoubleUnsafe, 56 | np.dtype("float32"): _FloatUnsafe, 57 | np.dtype("float64"): _DoubleUnsafe, 58 | np.dtype("int8"): _ByteUnsafe, 59 | np.dtype("int16"): _ShortUnsafe, 60 | np.dtype("int32"): _IntUnsafe, 61 | np.dtype("int64"): _LongUnsafe, 62 | np.dtype("uint8"): _ByteUnsafe, 63 | np.dtype("uint16"): _ShortUnsafe, 64 | np.dtype("uint32"): _IntUnsafe, 65 | np.dtype("uint64"): _LongUnsafe, 66 | } 67 | 68 | # owning 69 | global _OwningByteUnsafe 70 | _OwningByteUnsafe = scyjava.jimport( 71 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningByteUnsafe" 72 | ) 73 | global _OwningCharUnsafe 74 | _OwningCharUnsafe = scyjava.jimport( 75 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningCharUnsafe" 76 | ) 77 | global _OwningDoubleUnsafe 78 | _OwningDoubleUnsafe = scyjava.jimport( 79 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningDoubleUnsafe" 80 | ) 81 | global _OwningFloatUnsafe 82 | _OwningFloatUnsafe = scyjava.jimport( 83 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningFloatUnsafe" 84 | ) 85 | global _OwningIntUnsafe 86 | _OwningIntUnsafe = scyjava.jimport( 87 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningIntUnsafe" 88 | ) 89 | global _OwningLongUnsafe 90 | _OwningLongUnsafe = scyjava.jimport( 91 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningLongUnsafe" 92 | ) 93 | global _OwningShortUnsafe 94 | _OwningShortUnsafe = scyjava.jimport( 95 | "net.imglib2.img.basictypelongaccess.unsafe.owning.OwningShortUnsafe" 96 | ) 97 | 98 | global _unsafe_owning_for_dtype 99 | _unsafe_owning_for_dtype = { 100 | np.dtype("complex64"): lambda size: _OwningFloatUnsafe(2 * size), 101 | np.dtype("complex128"): lambda size: _OwningDoubleUnsafe(2 * size), 102 | np.dtype("float32"): _OwningFloatUnsafe, 103 | np.dtype("float64"): _OwningDoubleUnsafe, 104 | np.dtype("int8"): _OwningByteUnsafe, 105 | np.dtype("int16"): _OwningShortUnsafe, 106 | np.dtype("int32"): _OwningIntUnsafe, 107 | np.dtype("int64"): _OwningLongUnsafe, 108 | np.dtype("uint8"): _OwningByteUnsafe, 109 | np.dtype("uint16"): _OwningShortUnsafe, 110 | np.dtype("uint32"): _OwningIntUnsafe, 111 | np.dtype("uint64"): _OwningLongUnsafe, 112 | } 113 | 114 | global MakeAccessFunction 115 | 116 | @JImplements("java.util.function.LongFunction") 117 | class MakeAccessFunction: 118 | """ 119 | Implements a java `LongFunction` that can be passed into 120 | `PythonHelpers.imgFromFunc` and 121 | `PythonHelpers.imgWithCellLoaderFromFunc`. 122 | """ 123 | 124 | def __init__(self, func): 125 | self.func = func 126 | 127 | @JOverride 128 | def apply(self, index): 129 | access = self.func(index) 130 | return access 131 | 132 | 133 | scyjava.when_jvm_starts(_java_setup) 134 | 135 | 136 | def identity(x): 137 | """ 138 | Returns the input 139 | """ 140 | return x 141 | 142 | 143 | def _chunk_index_to_slices(shape, chunk_shape, cell_index): 144 | grid_dimensions = tuple( 145 | int(math.ceil(s / sh)) for s, sh in zip(shape, chunk_shape) 146 | )[::-1] 147 | 148 | chunk_min = [] 149 | ndims = len(grid_dimensions) 150 | 151 | i = cell_index 152 | for d in range(ndims): 153 | c = i % grid_dimensions[d] 154 | chunk_min.append(c) 155 | i = (i - c) // grid_dimensions[d] 156 | 157 | chunk_min = chunk_min[::-1] 158 | 159 | slices = tuple(slice(c * cs, (c + 1) * cs) for c, cs in zip(chunk_min, chunk_shape)) 160 | 161 | return slices 162 | 163 | 164 | def _get_chunk(array, chunk_shape, chunk_index, chunk_as_array): 165 | slices = _chunk_index_to_slices(array.shape, chunk_shape, chunk_index) 166 | sliced = array[slices] 167 | array = chunk_as_array(sliced) 168 | return np.ascontiguousarray(array) 169 | 170 | 171 | def _get_chunk_access_array( 172 | array, chunk_shape, index, chunk_as_array, use_volatile_access=True 173 | ): 174 | try: 175 | chunk = _get_chunk(array, chunk_shape, index, chunk_as_array) 176 | dtype = types.for_np_dtype(chunk.dtype, volatile=False) 177 | ptype = dtype.getNativeTypeFactory().getPrimitiveType() 178 | # TODO check ratio for integral value first? 179 | ratio = int(dtype.getEntitiesPerPixel().getRatio()) 180 | return accesses.Accesses.asArrayAccess( 181 | util._get_address(chunk), chunk.size * ratio, use_volatile_access, ptype 182 | ) 183 | 184 | except JException as e: 185 | _logger.error(scyjava.jstacktrace(e)) 186 | raise e 187 | 188 | 189 | def _get_chunk_access_unsafe( 190 | array, chunk_shape, index, chunk_as_array, reference_store 191 | ): 192 | try: 193 | chunk = np.ascontiguousarray( 194 | _get_chunk(array, chunk_shape, index, chunk_as_array) 195 | ) 196 | address = util._get_address(chunk) 197 | ref_id = reference_store.get_next_id() 198 | 199 | def remove_reference(): 200 | _logger.debug("Removing ref id %d", ref_id) 201 | reference_store.remove_reference(ref_id) 202 | 203 | owner = util.RunnableFromFunc(remove_reference) 204 | reference_store.add_reference(ref_id, (chunk, owner)) 205 | access = _access_factory_for(chunk.dtype, owning=False)(address, owner) 206 | return access 207 | 208 | except JException as e: 209 | _logger.error(scyjava.jstacktrace(e)) 210 | raise e 211 | 212 | 213 | def as_cell_img( 214 | array, 215 | chunk_shape, 216 | cache, 217 | *, 218 | access_type="native", 219 | chunk_as_array=identity, 220 | **kwargs, 221 | ): 222 | """ 223 | Wrap an arbitrary ndarray-like object as an ImgLib2 cached cell img. 224 | 225 | :param array: The arbitrary ndarray-like object to be wrapped 226 | :param chunk_shape: The shape of `array`. In many cases, this is just `array.shape`. 227 | :param cache: Can be `int` or an ImgLib2 `LoaderCache`. If `int` (recommended), 228 | use a :py:data:`imglyb.caches.BoundedSoftRefLoaderCache` that is 229 | bounded to `cache` elements. `LoaderCache`s are available in 230 | :py:mod:`imglyb.caches`. 231 | :param access_type: Can be either `'native'` or `'array'`. If `'native'`, 232 | use the native memory of the contiguous ndarray of a chunk directly. 233 | If `'array'`, copy the native memory into a Java array and use the Java 234 | array as access. 235 | :param chunk_as_array: Defines conversion of a chunk created by slicing into a 236 | :py:class:`numpy.ndarray`. 237 | :param kwargs: Optional arguments that may depend on the value passed for 238 | `access_type`, e.g `use_volatile_access` is relevant only for 239 | `access_type == 'array'`. 240 | :return: A tuple that holds the wrapped image at `0` and a reference store at `1` 241 | to ensure that Python references are not being garbage collected 242 | while still in use in the JVM. the reference store should stay in 243 | scope as long as the wrapped image is intended to be used. 244 | """ 245 | scyjava.start_jvm() 246 | 247 | access_type_function_mapping = { 248 | "array": as_cell_img_with_array_accesses, 249 | "native": as_cell_img_with_native_accesses, 250 | } 251 | 252 | if access_type not in access_type_function_mapping: 253 | raise Exception( 254 | f"Invalid access type: `{access_type}'. " 255 | f"Choose one of {access_type_function_mapping.keys()}" 256 | ) 257 | 258 | cache = caches.BoundedSoftRefLoaderCache(cache) if isinstance(cache, int) else cache 259 | 260 | return access_type_function_mapping[access_type]( 261 | array, chunk_shape, chunk_as_array, cache, **kwargs 262 | ) 263 | 264 | 265 | # TODO is it bad style to use **kwargs to ignore unexpected kwargs? 266 | def as_cell_img_with_array_accesses( 267 | array, chunk_shape, chunk_as_array, cache, *, use_volatile_access=True, **kwargs 268 | ): 269 | scyjava.start_jvm() 270 | 271 | access_generator = MakeAccessFunction( 272 | lambda index: _get_chunk_access_array( 273 | array, 274 | chunk_shape, 275 | index, 276 | chunk_as_array, 277 | use_volatile_access=use_volatile_access, 278 | ) 279 | ) 280 | reference_store = ReferenceStore() 281 | reference_store.add_reference_with_new_id(access_generator) 282 | 283 | shape = array.shape[::-1] 284 | chunk_shape = chunk_shape[::-1] 285 | 286 | # TODO use imgFromFunc instead of imgWithCellLoaderFromFunct here 287 | img = PythonHelpers.imgWithCellLoaderFromFunc( 288 | shape, 289 | chunk_shape, 290 | access_generator, 291 | types.for_np_dtype(array.dtype, volatile=False), 292 | # TODO do not load first block here, just create a length-one access 293 | accesses.as_array_access( 294 | _get_chunk(array, chunk_shape, 0, chunk_as_array=chunk_as_array), 295 | volatile=use_volatile_access, 296 | ), 297 | cache, 298 | ) 299 | 300 | return img, reference_store 301 | 302 | 303 | # TODO is it bad style to use **kwargs to ignore unexpected kwargs? 304 | def as_cell_img_with_native_accesses( 305 | array, chunk_shape, chunk_as_array, cache, **kwargs 306 | ): 307 | scyjava.start_jvm() 308 | 309 | reference_store = ReferenceStore() 310 | access_generator = MakeAccessFunction( 311 | lambda index: _get_chunk_access_unsafe( 312 | array, chunk_shape, index, chunk_as_array, reference_store 313 | ) 314 | ) 315 | reference_store.add_reference_with_new_id(access_generator) 316 | 317 | shape = array.shape[::-1] 318 | chunk_shape = chunk_shape[::-1] 319 | 320 | try: 321 | img = PythonHelpers.imgFromFunc( 322 | shape, 323 | chunk_shape, 324 | access_generator, 325 | types.for_np_dtype(array.dtype, volatile=False), 326 | _access_factory_for(array.dtype, owning=False)(1, None), 327 | cache, 328 | ) 329 | 330 | except JException as e: 331 | _logger.error(scyjava.jstacktrace(e)) 332 | raise e 333 | 334 | return img, reference_store 335 | 336 | 337 | def _access_factory_for(dtype, owning): 338 | return _unsafe_owning_for_dtype[dtype] if owning else _unsafe_for_dtype[dtype] 339 | -------------------------------------------------------------------------------- /src/imglyb/reference_store.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | """ 4 | 5 | """ 6 | 7 | 8 | class ReferenceStore(object): 9 | def __init__(self): 10 | self.lock = threading.RLock() 11 | self.store = dict() 12 | self.next_id = 0 13 | 14 | def add_reference(self, ref_id, reference): 15 | with self.lock: 16 | self.store[ref_id] = reference 17 | 18 | def add_reference_with_new_id(self, reference): 19 | ref_id = self.get_next_id() 20 | self.add_reference(ref_id, reference) 21 | return ref_id 22 | 23 | def get_next_id(self): 24 | with self.lock: 25 | next_id = self.next_id 26 | self.next_id += 1 27 | return next_id 28 | 29 | def remove_reference(self, ref_id): 30 | with self.lock: 31 | if ref_id in self.store: 32 | del self.store[ref_id] 33 | 34 | def clear(self): 35 | with self.lock: 36 | self.store.clear() 37 | 38 | def reset_next_id(self): 39 | with self.lock: 40 | self.next_id = 0 41 | 42 | def number_of_stored_references(self): 43 | return len(self.store) 44 | -------------------------------------------------------------------------------- /src/imglyb/types.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scyjava 3 | 4 | 5 | def _java_setup(): 6 | """ 7 | Lazy initialization function for Java-dependent data structures. 8 | Do not call this directly; use scyjava.start_jvm() instead. 9 | """ 10 | 11 | global NativeType 12 | NativeType = scyjava.jimport("net.imglib2.type.NativeType") 13 | 14 | global FloatType 15 | FloatType = scyjava.jimport("net.imglib2.type.numeric.real.FloatType") 16 | global DoubleType 17 | DoubleType = scyjava.jimport("net.imglib2.type.numeric.real.DoubleType") 18 | global ByteType 19 | ByteType = scyjava.jimport("net.imglib2.type.numeric.integer.ByteType") 20 | global UnsignedByteType 21 | UnsignedByteType = scyjava.jimport( 22 | "net.imglib2.type.numeric.integer.UnsignedByteType" 23 | ) 24 | global ShortType 25 | ShortType = scyjava.jimport("net.imglib2.type.numeric.integer.ShortType") 26 | global UnsignedShortType 27 | UnsignedShortType = scyjava.jimport( 28 | "net.imglib2.type.numeric.integer.UnsignedShortType" 29 | ) 30 | global IntType 31 | IntType = scyjava.jimport("net.imglib2.type.numeric.integer.IntType") 32 | global UnsignedIntType 33 | UnsignedIntType = scyjava.jimport( 34 | "net.imglib2.type.numeric.integer.UnsignedIntType" 35 | ) 36 | global LongType 37 | LongType = scyjava.jimport("net.imglib2.type.numeric.integer.LongType") 38 | global UnsignedLongType 39 | UnsignedLongType = scyjava.jimport( 40 | "net.imglib2.type.numeric.integer.UnsignedLongType" 41 | ) 42 | 43 | global VolatileFloatType 44 | VolatileFloatType = scyjava.jimport("net.imglib2.type.volatiles.VolatileFloatType") 45 | global VolatileDoubleType 46 | VolatileDoubleType = scyjava.jimport( 47 | "net.imglib2.type.volatiles.VolatileDoubleType" 48 | ) 49 | global VolatileByteType 50 | VolatileByteType = scyjava.jimport("net.imglib2.type.volatiles.VolatileByteType") 51 | global VolatileUnsignedByteType 52 | VolatileUnsignedByteType = scyjava.jimport( 53 | "net.imglib2.type.volatiles.VolatileUnsignedByteType" 54 | ) 55 | global VolatileShortType 56 | VolatileShortType = scyjava.jimport("net.imglib2.type.volatiles.VolatileShortType") 57 | global VolatileUnsignedShortType 58 | VolatileUnsignedShortType = scyjava.jimport( 59 | "net.imglib2.type.volatiles.VolatileUnsignedShortType" 60 | ) 61 | global VolatileIntType 62 | VolatileIntType = scyjava.jimport("net.imglib2.type.volatiles.VolatileIntType") 63 | global VolatileUnsignedIntType 64 | VolatileUnsignedIntType = scyjava.jimport( 65 | "net.imglib2.type.volatiles.VolatileUnsignedIntType" 66 | ) 67 | global VolatileLongType 68 | VolatileLongType = scyjava.jimport("net.imglib2.type.volatiles.VolatileLongType") 69 | global VolatileUnsignedLongType 70 | VolatileUnsignedLongType = scyjava.jimport( 71 | "net.imglib2.type.volatiles.VolatileUnsignedLongType" 72 | ) 73 | 74 | 75 | scyjava.when_jvm_starts(_java_setup) 76 | 77 | 78 | def for_np_dtype(dtype, volatile=False): 79 | scyjava.start_jvm() 80 | if dtype == np.uint8: 81 | return VolatileUnsignedByteType() if volatile else UnsignedByteType() 82 | if dtype == np.int8: 83 | return VolatileByteType() if volatile else ByteType() 84 | 85 | if dtype == np.uint16: 86 | return VolatileUnsignedShortType() if volatile else UnsignedShortType() 87 | if dtype == np.int16: 88 | return VolatileShortType() if volatile else ShortType() 89 | 90 | if dtype == np.uint32: 91 | return VolatileUnsignedIntType() if volatile else UnsignedIntType() 92 | if dtype == np.int32: 93 | return VolatileIntType() if volatile else IntType() 94 | 95 | if dtype == np.uint64: 96 | return VolatileUnsignedLongType() if volatile else UnsignedLongType() 97 | if dtype == np.int64: 98 | return VolatileLongType() if volatile else LongType() 99 | 100 | if dtype == np.float32: 101 | return VolatileFloatType() if volatile else FloatType() 102 | if dtype == np.float64: 103 | return VolatileDoubleType() if volatile else DoubleType() 104 | -------------------------------------------------------------------------------- /src/imglyb/util.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import numpy as np 4 | import scyjava 5 | from jpype import JArray, JImplements, JLong, JOverride 6 | 7 | _logger = logging.getLogger(__name__) 8 | 9 | __all__ = ("to_imglib", "to_imglib_argb") 10 | 11 | 12 | def _java_setup(): 13 | """ 14 | Lazy initialization function for Java-dependent data structures. 15 | Do not call this directly; use scyjava.start_jvm() instead. 16 | """ 17 | 18 | # java 19 | global Random 20 | Random = scyjava.jimport("java.util.Random") 21 | 22 | # imglib 23 | try: 24 | global Helpers 25 | Helpers = scyjava.jimport("net.imglib2.python.Helpers") 26 | global NumpyToImgLibConversions 27 | NumpyToImgLibConversions = scyjava.jimport( 28 | "net.imglib2.python.NumpyToImgLibConversions" 29 | ) 30 | global NumpyToImgLibConversionsWithStride 31 | NumpyToImgLibConversionsWithStride = scyjava.jimport( 32 | "net.imglib2.python.NumpyToImgLibConversionsWithStride" 33 | ) 34 | global Views 35 | Views = scyjava.jimport("net.imglib2.view.Views") 36 | except TypeError as e: 37 | _logger.error( 38 | "Failed to import ImgLib2 helper classes. " 39 | "Please ensure imglib2-imglyb is present on the classpath." 40 | ) 41 | raise e 42 | 43 | # Guard 44 | global ReferenceGuardingRandomAccessibleInterval 45 | ReferenceGuardingRandomAccessibleInterval = scyjava.jimport( 46 | "net.imglib2.python.ReferenceGuardingRandomAccessibleInterval" 47 | ) 48 | 49 | global numpy_dtype_to_conversion_method 50 | numpy_dtype_to_conversion_method = { 51 | np.dtype("complex64"): NumpyToImgLibConversions.toComplexFloat, 52 | np.dtype("complex128"): NumpyToImgLibConversions.toComplexDouble, 53 | np.dtype("float32"): NumpyToImgLibConversions.toFloat, 54 | np.dtype("float64"): NumpyToImgLibConversions.toDouble, 55 | np.dtype("bool_"): NumpyToImgLibConversions.toNativeBool, 56 | np.dtype("int8"): NumpyToImgLibConversions.toByte, 57 | np.dtype("int16"): NumpyToImgLibConversions.toShort, 58 | np.dtype("int32"): NumpyToImgLibConversions.toInt, 59 | np.dtype("int64"): NumpyToImgLibConversions.toLong, 60 | np.dtype("uint8"): NumpyToImgLibConversions.toUnsignedByte, 61 | np.dtype("uint16"): NumpyToImgLibConversions.toUnsignedShort, 62 | np.dtype("uint32"): NumpyToImgLibConversions.toUnsignedInt, 63 | np.dtype("uint64"): NumpyToImgLibConversions.toUnsignedLong, 64 | } 65 | 66 | global numpy_dtype_to_conversion_with_stride_method 67 | numpy_dtype_to_conversion_with_stride_method = { 68 | np.dtype("complex64"): NumpyToImgLibConversionsWithStride.toComplexFloat, 69 | np.dtype("complex128"): NumpyToImgLibConversionsWithStride.toComplexDouble, 70 | np.dtype("float32"): NumpyToImgLibConversionsWithStride.toFloat, 71 | np.dtype("float64"): NumpyToImgLibConversionsWithStride.toDouble, 72 | np.dtype("bool_"): NumpyToImgLibConversionsWithStride.toNativeBool, 73 | np.dtype("int8"): NumpyToImgLibConversionsWithStride.toByte, 74 | np.dtype("int16"): NumpyToImgLibConversionsWithStride.toShort, 75 | np.dtype("int32"): NumpyToImgLibConversionsWithStride.toInt, 76 | np.dtype("int64"): NumpyToImgLibConversionsWithStride.toLong, 77 | np.dtype("uint8"): NumpyToImgLibConversionsWithStride.toUnsignedByte, 78 | np.dtype("uint16"): NumpyToImgLibConversionsWithStride.toUnsignedShort, 79 | np.dtype("uint32"): NumpyToImgLibConversionsWithStride.toUnsignedInt, 80 | np.dtype("uint64"): NumpyToImgLibConversionsWithStride.toUnsignedLong, 81 | } 82 | 83 | global ReferenceGuard 84 | 85 | @JImplements( 86 | "net.imglib2.python.ReferenceGuardingRandomAccessibleInterval$ReferenceHolder" 87 | ) 88 | class ReferenceGuard: 89 | def __init__(self, *args, **kwargs): 90 | self.args = args 91 | 92 | global GenericMouseMotionListener 93 | 94 | @JImplements("java.awt.event.MouseMotionListener") 95 | class GenericMouseMotionListener: 96 | def __init__(self, mouse_dragged=lambda e: None, mouse_moved=lambda e: None): 97 | self.mouse_dragged = mouse_dragged 98 | self.mouse_moved = mouse_moved 99 | 100 | @JOverride 101 | def mouseDragged(self, e): 102 | self.mouse_dragged(e) 103 | 104 | @JOverride 105 | def mouseMoved(self, e): 106 | self.mouse_moved(e) 107 | 108 | global RunnableFromFunc 109 | 110 | @JImplements("java.lang.Runnable") 111 | class RunnableFromFunc: 112 | def __init__(self, func): 113 | self.func = func 114 | 115 | @JOverride 116 | def run(self): 117 | _logger.debug("Running function %s", self.func) 118 | self.func() 119 | 120 | 121 | scyjava.when_jvm_starts(_java_setup) 122 | 123 | 124 | def _get_address(source): 125 | return source.ctypes.data 126 | 127 | 128 | # how to use type hints for python < 3.5? 129 | def to_imglib(source): 130 | scyjava.start_jvm() 131 | return ReferenceGuardingRandomAccessibleInterval( 132 | _to_imglib(source), ReferenceGuard(source) 133 | ) 134 | 135 | 136 | # how to use type hints for python < 3.5? 137 | def _to_imglib(source): 138 | address = _get_address(source) 139 | long_address = JLong(address) 140 | long_arr_source = JArray(JLong)(source.shape[::-1]) 141 | 142 | if source.dtype not in numpy_dtype_to_conversion_method: 143 | raise NotImplementedError( 144 | "Cannot convert dtype to ImgLib2 type yet: {}".format(source.dtype) 145 | ) 146 | elif source.flags["CARRAY"]: 147 | return numpy_dtype_to_conversion_method[source.dtype]( 148 | long_address, *long_arr_source 149 | ) 150 | else: 151 | stride = np.array(source.strides[::-1]) / source.itemsize 152 | long_arr_stride = JArray(JLong)(stride) 153 | return numpy_dtype_to_conversion_with_stride_method[source.dtype]( 154 | long_address, long_arr_stride, long_arr_source 155 | ) 156 | 157 | 158 | def to_imglib_argb(source): 159 | scyjava.start_jvm() 160 | return ReferenceGuardingRandomAccessibleInterval( 161 | _to_imglib_argb(source), ReferenceGuard(source) 162 | ) 163 | 164 | 165 | def _to_imglib_argb(source): 166 | address = _get_address(source) 167 | long_address = JLong(address) 168 | long_arr_source = JArray(JLong)(source.shape[::-1]) 169 | 170 | if not (source.dtype == np.dtype("int32") or source.dtype == np.dtype("uint32")): 171 | raise NotImplementedError("source.dtype must be int32 or uint32") 172 | if source.flags["CARRAY"]: 173 | return NumpyToImgLibConversions.toARGB(long_address, *long_arr_source) 174 | else: 175 | stride = np.array(source.strides[::-1]) / source.itemsize 176 | long_arr_stride = JArray(JLong)(stride) 177 | return NumpyToImgLibConversionsWithStride.toARGB( 178 | long_address, long_arr_stride, long_arr_source 179 | ) 180 | -------------------------------------------------------------------------------- /tests/test_imglyb.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | import scyjava 4 | from jpype import JArray, JInt, JLong 5 | 6 | import imglyb 7 | from imglyb.imglib_ndarray import NumpyView 8 | 9 | 10 | class TestImglyb(object): 11 | def test_arraylike(self, sj_fixture): 12 | Views = imglyb.util.Views 13 | 14 | shape = (3, 5) 15 | data = np.arange(np.prod(shape)).reshape(shape) 16 | block_size = (2, 2) 17 | img, _ = imglyb.as_cell_img(data, block_size, access_type="native", cache=1) 18 | 19 | cursor = Views.flatIterable(img).cursor() 20 | expected = 0 21 | while cursor.hasNext(): 22 | assert expected == cursor.next().get() 23 | expected += 1 24 | 25 | @pytest.fixture 26 | def unsafe_img(self): 27 | UnsafeImgs = scyjava.jimport("net.imglib2.img.unsafe.UnsafeImgs") 28 | return UnsafeImgs.bytes(2, 3, 4) 29 | 30 | def test_to_numpy_returns_ndarray(self, unsafe_img): 31 | thing = imglyb.to_numpy(unsafe_img) 32 | assert isinstance(thing, np.ndarray) 33 | 34 | def test_to_numpy_modification(self, unsafe_img): 35 | thing = imglyb.to_numpy(unsafe_img) 36 | 37 | fill_value = 2 38 | 39 | thing.fill(fill_value) 40 | 41 | cursor = unsafe_img.cursor() 42 | while cursor.hasNext(): 43 | assert cursor.next().get() == fill_value 44 | 45 | parameterizations = [ 46 | (True, np.bool_, "net.imglib2.type.logic.NativeBoolType"), 47 | (-100, np.int8, "net.imglib2.type.numeric.integer.ByteType"), 48 | (200, np.uint8, "net.imglib2.type.numeric.integer.UnsignedByteType"), 49 | (-100, np.int16, "net.imglib2.type.numeric.integer.ShortType"), 50 | (200, np.uint16, "net.imglib2.type.numeric.integer.UnsignedShortType"), 51 | (-100, np.int32, "net.imglib2.type.numeric.integer.IntType"), 52 | (200, np.uint32, "net.imglib2.type.numeric.integer.UnsignedIntType"), 53 | (-100, np.int64, "net.imglib2.type.numeric.integer.LongType"), 54 | (200, np.uint64, "net.imglib2.type.numeric.integer.UnsignedLongType"), 55 | (5.5, np.float32, "net.imglib2.type.numeric.real.FloatType"), 56 | (5.5, np.float64, "net.imglib2.type.numeric.real.DoubleType"), 57 | ] 58 | 59 | @pytest.mark.parametrize("value, dtype, jtype", parameterizations) 60 | def test_np_arr_to_RAI_realType(self, value, dtype, jtype): 61 | """Tests convesion of each supported Imglib2 data type""" 62 | img = np.array([[value]], dtype=dtype) 63 | java_img = imglyb.to_imglib(img) 64 | RAI = scyjava.jimport("net.imglib2.RandomAccessibleInterval") 65 | assert isinstance(java_img, RAI) 66 | ra = java_img.randomAccess() 67 | element = ra.setPositionAndGet(JArray(JInt)(2)) 68 | assert isinstance(element, scyjava.jimport(jtype)) 69 | assert value == element.get() 70 | 71 | parameterizations = [ 72 | (-100, np.int8, "bytes"), 73 | (200, np.uint8, "unsignedBytes"), 74 | (-100, np.int16, "shorts"), 75 | (-100, np.int32, "ints"), 76 | (200, np.uint32, "unsignedInts"), 77 | (-100, np.int64, "longs"), 78 | (200, np.uint64, "unsignedLongs"), 79 | (5.5, np.float32, "floats"), 80 | (5.5, np.float64, "doubles"), 81 | ] 82 | 83 | @pytest.mark.parametrize("value, dtype, func", parameterizations) 84 | def test_RAI_realType_to_np_arr(self, value, dtype, func): 85 | """Tests convesion of each supported Imglib2 data type""" 86 | # Create the test image 87 | dims = JArray(JLong)(2) 88 | dims[:] = [1, 1] 89 | UnsafeImgs = scyjava.jimport("net.imglib2.img.unsafe.UnsafeImgs") 90 | function = getattr(UnsafeImgs, func) 91 | assert function 92 | j_img = function(dims) 93 | # Set the only value in the image to value 94 | j_img.randomAccess().setPositionAndGet(JArray(JInt)(2)).set(value) 95 | # Convert the image to a numpy array 96 | img = imglyb.to_numpy(j_img) 97 | assert isinstance(img, np.ndarray) 98 | assert dtype == img.dtype 99 | assert value == img[0, 0] 100 | 101 | 102 | class TestRAIAsNumpyArray: 103 | @pytest.fixture 104 | def img(self): 105 | ArrayImgs = scyjava.jimport("net.imglib2.img.array.ArrayImgs") 106 | img = ArrayImgs.unsignedBytes(2, 3, 4) 107 | tmp_val = 1 108 | cursor = img.cursor() 109 | while cursor.hasNext(): 110 | cursor.next().set(tmp_val) 111 | tmp_val = tmp_val + 1 112 | return img 113 | 114 | @pytest.fixture 115 | def raiAsNumpyArray(self, img): 116 | # populate each index with a unique value 117 | return NumpyView(img) 118 | 119 | def test_get_tuple(self, img, raiAsNumpyArray): 120 | ra = img.randomAccess() 121 | arr = JArray(JInt)(3) 122 | arr[:] = [1, 1, 1] 123 | j_val = ra.setPositionAndGet(arr).get() 124 | assert j_val == raiAsNumpyArray[1, 1, 1] 125 | 126 | def test_get_int(self, img, raiAsNumpyArray): 127 | for slice_val in range(len(raiAsNumpyArray)): 128 | slice = raiAsNumpyArray[slice_val] 129 | for i in range(slice.shape[0]): 130 | for j in range(slice.shape[1]): 131 | assert slice[i, j] == raiAsNumpyArray[slice_val, i, j] 132 | 133 | def test_change_rai(self, img, raiAsNumpyArray): 134 | """Tests that changes in the img affect the numpy array""" 135 | ra = img.randomAccess() 136 | arr = JArray(JInt)(3) 137 | arr[:] = [1, 1, 1] 138 | inserted_val = 100 139 | ra.setPositionAndGet(arr).set(inserted_val) 140 | assert inserted_val == raiAsNumpyArray[1, 1, 1] 141 | 142 | def test_change_ndarray(self, img, raiAsNumpyArray): 143 | """Tests that changes in the numpy array affect the img""" 144 | # Change the value 145 | inserted_val = 100 146 | raiAsNumpyArray[1, 1, 1] = inserted_val 147 | ra = img.randomAccess() 148 | arr = JArray(JInt)(3) 149 | arr[:] = [1, 1, 1] 150 | assert inserted_val == ra.setPositionAndGet(arr).get() 151 | 152 | def test_size(self, img, raiAsNumpyArray: np.ndarray): 153 | """Tests any behavior.""" 154 | Intervals = scyjava.jimport("net.imglib2.util.Intervals") 155 | assert Intervals.numElements(img) == raiAsNumpyArray.size 156 | 157 | @pytest.fixture 158 | def simple_wrapper(self, simple_img): 159 | # populate each index with a unique value 160 | return NumpyView(simple_img) 161 | 162 | def test_all(self, img, raiAsNumpyArray: np.ndarray): 163 | """Tests any behavior.""" 164 | # simple_img starts out with a one -> Should be true 165 | assert raiAsNumpyArray.all() 166 | assert np.all(raiAsNumpyArray) 167 | 168 | # set all values to zero 169 | cursor = img.cursor() 170 | while cursor.hasNext(): 171 | cursor.next().set(0) 172 | # should be false 173 | assert not raiAsNumpyArray.all() 174 | assert not np.all(raiAsNumpyArray) 175 | 176 | def test_any(self, img, raiAsNumpyArray: np.ndarray): 177 | """Tests any behavior.""" 178 | # simple_img starts out with a one -> Should be true 179 | assert raiAsNumpyArray.any() 180 | assert np.any(raiAsNumpyArray) 181 | 182 | # set all values to zero 183 | cursor = img.cursor() 184 | while cursor.hasNext(): 185 | cursor.next().set(0) 186 | 187 | # now should be false 188 | assert not raiAsNumpyArray.any() 189 | assert not np.any(raiAsNumpyArray) 190 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import scyjava 4 | import toml 5 | 6 | import imglyb 7 | 8 | 9 | def _expected_version(): 10 | """ 11 | Get the project version from pyproject.toml. 12 | """ 13 | pyproject = toml.load(Path(__file__).parents[1] / "pyproject.toml") 14 | return pyproject["project"]["version"] 15 | 16 | 17 | def test_version(): 18 | # First, ensure that the version is correct 19 | assert _expected_version() == imglyb.__version__ 20 | 21 | # Then, ensure that we get the correct version via get_version 22 | assert _expected_version() == scyjava.get_version("imglyb") 23 | --------------------------------------------------------------------------------