Skip to content

Commit

Permalink
WIP: Centralize ImageJ2 gateway into nij object
Browse files Browse the repository at this point in the history
This provides a place to keep track of not only the ImageJ2 gateway, but
also any affiliated data structures, such as our singleton ScriptREPL.
  • Loading branch information
ctrueden committed Aug 3, 2023
1 parent 8cec6e0 commit 0aebd2b
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 156 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ on:
- main
tags:
- "*-[0-9]+.*"
pull_request:
branches:
- main

env:
NAPARI_IMAGEJ_TEST_TIMEOUT: 60000
Expand Down
4 changes: 4 additions & 0 deletions src/napari_imagej/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,9 @@

import scyjava as sj

from napari_imagej.model import NapariImageJ

__author__ = "ImageJ2 developers"
__version__ = sj.get_version("napari-imagej")

nij = NapariImageJ()
53 changes: 15 additions & 38 deletions src/napari_imagej/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
Notable functions included in the module:
* init_ij()
- used to create the ImageJ instance.
* ij()
- used to access the ImageJ instance.
Notable fields included in the module:
* jc
Expand Down Expand Up @@ -33,57 +31,40 @@
"sc.fiji:TrackMate": "7.11.0",
}

# -- ImageJ API -- #

_ij = None

def ij():
if _ij is None:
raise Exception(
"The ImageJ instance has not yet been initialized! Please run init_ij()"
)
return _ij

# -- Public functions -- #


def init_ij() -> "jc.ImageJ":
"""
Create an ImageJ2 gateway.
"""
global _ij
if _ij:
return _ij
log_debug("Initializing ImageJ2")

# determine whether imagej is already running
imagej_already_initialized: bool = hasattr(imagej, "gateway") and imagej.gateway

# -- CONFIGURATION -- #

# Configure napari-imagej
from napari_imagej.types.converters import install_converters

install_converters()

log_debug("Completed JVM Configuration")

# -- INITIALIZATION -- #

# Launch ImageJ
if imagej_already_initialized:
_ij = imagej.gateway
else:
_ij = imagej.init(**_configure_imagej())
ij = (
imagej.gateway
if hasattr(imagej, "gateway") and imagej.gateway
else imagej.init(**_configure_imagej())
)

# Log initialization
log_debug(f"Initialized at version {_ij.getVersion()}")
log_debug(f"Initialized at version {ij.getVersion()}")

# -- VALIDATION -- #

# Validate PyImageJ
_validate_imagej()
_validate_imagej(ij)

return ij

return _ij

# -- Private functions -- #

Expand Down Expand Up @@ -112,7 +93,7 @@ def _configure_imagej() -> Dict[str, Any]:
return init_settings


def _validate_imagej():
def _validate_imagej(ij: "jc.ImageJ"):
"""
Ensure minimum requirements on java component versions are met.
"""
Expand All @@ -131,7 +112,7 @@ def _validate_imagej():
"org.scijava:scijava-common": jc.Module,
"org.scijava:scijava-search": jc.Searcher,
}
component_requirements.update(_optional_requirements())
component_requirements.update(_optional_requirements(ij))
# Find version that violate the minimum
violations = []
for component, cls in component_requirements.items():
Expand All @@ -153,11 +134,11 @@ def _validate_imagej():
raise RuntimeError(failure_str)


def _optional_requirements():
def _optional_requirements(ij: "jc.ImageJ"):
optionals = {}
# Add additional minimum versions for legacy components
if _ij.legacy and _ij.legacy.isActive():
optionals["net.imagej:imagej-legacy"] = _ij.legacy.getClass()
if ij.legacy and ij.legacy.isActive():
optionals["net.imagej:imagej-legacy"] = ij.legacy.getClass()
# Add additional minimum versions for fiji components
try:
optionals["sc.fiji:TrackMate"] = jimport("fiji.plugin.trackmate.TrackMate")
Expand Down Expand Up @@ -267,10 +248,6 @@ def BigDecimal(self):
def BigInteger(self):
return "java.math.BigInteger"

@JavaClasses.java_import
def ByteArrayOutputStream(self):
"java.io.ByteArrayOutputStream"

@JavaClasses.java_import
def Date(self):
return "java.util.Date"
Expand Down
40 changes: 40 additions & 0 deletions src/napari_imagej/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from jpype import JImplements, JOverride

from napari_imagej.java import init_ij, jc


class NapariImageJ:
"""
An object offering a central access point to napari-imagej's core business logic.
"""
def __init__(self):
self._ij = None
self._repl = None
self._repl_callbacks = []

@property
def ij(self):
if self._ij is None:
self._ij = init_ij()
return self._ij

@property
def repl(self) -> "jc.ScriptREPL":
if self._repl is None:
ctx = self.ij.context()
model = self

@JImplements("java.util.consumer.Consumer")
class REPLOutput:
@JOverride
def accept(self, t):
s = str(t)
for callback in model._repl_callbacks:
callback(s)

self._repl = jc.ScriptREPL(ctx, "jython", REPLOutput())
self._repl.lang("jython")
return self._repl

def add_repl_callback(self, repl_callback) -> None:
self._repl_callbacks.append(repl_callback)
7 changes: 4 additions & 3 deletions src/napari_imagej/readers/trackMate_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from napari.utils import progress
from scyjava import jimport

from napari_imagej.java import ij, init_ij, jc
from napari_imagej import nij
from napari_imagej.java import jc
from napari_imagej.types.converters.trackmate import (
model_and_image_to_tracks,
trackmate_present,
Expand Down Expand Up @@ -39,7 +40,7 @@ def napari_get_reader(path):

def reader_function(path):
pbr = progress(total=4, desc="Importing TrackMate XML: Starting JVM")
init_ij()
ij = nij.ij
TmXMLReader = jimport("fiji.plugin.trackmate.io.TmXmlReader")
pbr.update()

Expand All @@ -50,7 +51,7 @@ def reader_function(path):
pbr.update()

pbr.set_description("Importing TrackMate XML: Converting Image")
py_imp = ij().py.from_java(imp)
py_imp = ij.py.from_java(imp)
pbr.update()

pbr.set_description("Importing TrackMate XML: Converting Tracks and ROIs")
Expand Down
13 changes: 7 additions & 6 deletions src/napari_imagej/types/converters/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
from scyjava import Priority
from xarray import DataArray

from napari_imagej.java import ij, jc
from napari_imagej import nij
from napari_imagej.java import jc
from napari_imagej.types.converters import java_to_py_converter, py_to_java_converter
from napari_imagej.utilities.logging import log_debug


@java_to_py_converter(
predicate=lambda obj: ij().convert().supports(obj, jc.DatasetView),
predicate=lambda obj: nij.ij.convert().supports(obj, jc.DatasetView),
priority=Priority.VERY_HIGH + 1,
)
def _java_image_to_image_layer(image: Any) -> Image:
Expand All @@ -33,9 +34,9 @@ def _java_image_to_image_layer(image: Any) -> Image:
:return: a napari Image layer
"""
# Construct a DatasetView from the Java image
view = ij().convert().convert(image, jc.DatasetView)
view = nij.ij.convert().convert(image, jc.DatasetView)
# Construct an xarray from the DatasetView
xarr: DataArray = java_to_xarray(ij(), view.getData())
xarr: DataArray = java_to_xarray(nij.ij, view.getData())
# Construct a map of Image layer parameters
kwargs = dict(
data=xarr,
Expand All @@ -59,7 +60,7 @@ def _image_layer_to_dataset(image: Image, **kwargs) -> "jc.Dataset":
:return: a Dataset
"""
# Construct a dataset from the data
dataset: "jc.Dataset" = ij().py.to_dataset(image.data, **kwargs)
dataset: "jc.Dataset" = nij.ij.py.to_dataset(image.data, **kwargs)

# Clean up the axes
axes = [
Expand Down Expand Up @@ -94,7 +95,7 @@ def _image_layer_to_dataset(image: Image, **kwargs) -> "jc.Dataset":
properties = dataset.getProperties()
for k, v in image.metadata.items():
try:
properties.put(ij().py.to_java(k), ij().py.to_java(v))
properties.put(nij.ij.py.to_java(k), nij.ij.py.to_java(v))
except Exception:
log_debug(f"Could not add property ({k}, {v}) to dataset {dataset}:")
return dataset
Expand Down
7 changes: 4 additions & 3 deletions src/napari_imagej/types/converters/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from napari.layers import Labels
from scyjava import Priority

from napari_imagej.java import ij, jc
from napari_imagej import nij
from napari_imagej.java import jc
from napari_imagej.types.converters import java_to_py_converter, py_to_java_converter


Expand Down Expand Up @@ -41,7 +42,7 @@ def _imglabeling_to_layer(imgLabeling: "jc.ImgLabeling") -> Labels:
:param imgLabeling: the Java ImgLabeling
:return: a Labels layer
"""
labeling: Labeling = imglabeling_to_labeling(ij(), imgLabeling)
labeling: Labeling = imglabeling_to_labeling(nij.ij, imgLabeling)
return _labeling_to_layer(labeling)


Expand All @@ -55,4 +56,4 @@ def _layer_to_imglabeling(layer: Labels) -> "jc.ImgLabeling":
:return: the Java ImgLabeling
"""
labeling: Labeling = _layer_to_labeling(layer)
return ij().py.to_java(labeling)
return nij.ij.py.to_java(labeling)
9 changes: 4 additions & 5 deletions src/napari_imagej/types/converters/trackmate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from napari.layers import Labels, Tracks
from scyjava import JavaClasses, Priority

from napari_imagej import settings
from napari_imagej.java import ij
from napari_imagej import nij, settings
from napari_imagej.types.converters import java_to_py_converter


Expand Down Expand Up @@ -36,7 +35,7 @@ def track_overlay_predicate(obj):
if not trackmate_present():
return False
# TrackMate data is wrapped in ImageJ Rois - we need ImageJ Legacy
if not (ij().legacy and ij().legacy.isActive()):
if not (nij.ij.legacy and nij.ij.legacy.isActive()):
return False
# TrackMate data will be wrapped within a ROITree
if not isinstance(obj, jc.ROITree):
Expand Down Expand Up @@ -106,7 +105,7 @@ def model_and_image_to_tracks(model: "jc.Model", imp: "jc.ImagePlus"):
java_label_img = jc.LabelImgExporter.createLabelImagePlus(
model, imp, False, False, False
)
py_label_img = ij().py.from_java(java_label_img)
py_label_img = nij.ij.py.from_java(java_label_img)
labels = Labels(data=py_label_img.data, name=rois_name)

return (tracks, labels)
Expand All @@ -119,7 +118,7 @@ def _trackMate_model_to_tracks(obj: "jc.ROITree"):
"""
Converts a TrackMate overlay into a napari Tracks layer
"""
trackmate_plugins = ij().object().getObjects(jc.TrackMate)
trackmate_plugins = nij.ij.object().getObjects(jc.TrackMate)
if len(trackmate_plugins) == 0:
raise IndexError("Expected a TrackMate instance, but there was none!")
model: jc.Model = trackmate_plugins[-1].getModel()
Expand Down
5 changes: 3 additions & 2 deletions src/napari_imagej/types/type_conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
from jpype import JObject
from scyjava import Priority

from napari_imagej.java import ij, jc
from napari_imagej import nij
from napari_imagej.java import jc
from napari_imagej.types.enum_likes import enum_like
from napari_imagej.types.enums import py_enum_for
from napari_imagej.types.type_hints import type_hints
Expand Down Expand Up @@ -201,6 +202,6 @@ def canConvertChecker(item: "jc.ModuleItem") -> Optional[Type]:
"""

def isAssignable(from_type, to_type) -> bool:
return ij().convert().supports(from_type, to_type)
return nij.ij.convert().supports(from_type, to_type)

return _checkerUsingFunc(item, isAssignable)
Loading

0 comments on commit 0aebd2b

Please sign in to comment.