Skip to content

Commit

Permalink
WIP - start work on NapariImageJ model class
Browse files Browse the repository at this point in the history
Unfortunately, to make the REPL output work will require changes to
SciJava Common. So that's next I guess... then we can utilize the new
API here in napari-imagej, with an updated scijava-common min version.
  • Loading branch information
ctrueden committed Jul 26, 2023
1 parent 8d9d1c2 commit ce2276c
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 67 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()
63 changes: 20 additions & 43 deletions src/napari_imagej/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from napari_imagej import settings
from napari_imagej.utilities.logging import log_debug

# -- Constants --
# -- Constants -- #

minimum_versions = {
"io.scif:scifio": "0.45.0",
Expand All @@ -34,62 +34,39 @@
"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":
"""
Creates the ImageJ instance
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
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 -- #

def _configure_imagej() -> Dict[str, Any]:
"""
Configures scyjava and pyimagej.
Configure scyjava and pyimagej.
This function returns the settings that must be passed in the
actual initialization call.
Expand All @@ -112,9 +89,9 @@ def _configure_imagej() -> Dict[str, Any]:
return init_settings


def _validate_imagej():
def _validate_imagej(ij: "jc.ImageJ"):
"""
Helper function to ensure minimum requirements on java component versions
Ensure minimum requirements on java component versions are met.
"""
# If we want to require a minimum version for a java component, we need to
# be able to find our current version. We do that by querying a Java class
Expand All @@ -131,7 +108,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 +130,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
45 changes: 45 additions & 0 deletions src/napari_imagej/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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_output_stream = None
self._repl_callbacks = None

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

@property
def repl(self) -> "jc.ScriptREPL":
if self._repl is None:
ctx = self.ij.context()
# CTR START HERE -- we need to pass a smarter OutputStream,
# but unfortunately java.io.OutputStream is not an interface,
# so we cannot implement it from Python. Probably the best
# solution is to update ScriptREPL on the SciJava Common side
# to support a functional interface for emitting output lines,
# rather than using the OutputStream class for this purpose.
# There is only one place in ScriptREPL where anything besides
# println is called -- it calls print repeatedly -- and we
# could instead do that in a StringBuilder per line.
#
# Anyway, once we have a way to receive lines of output from
# the REPL, we can forward them to callbacks registered here,
# and the REPL widget can register callbacks that take care
# of dumping the output to its output pane as desired.
self._repl_output_stream = jc.ByteArrayOutputStream()
self._repl = jc.ScriptREPL(ctx, self._repl_output_stream)
self._repl.lang("jython")
return self._repl

def add_repl_callback(self, repl_callback) -> None:
self._repl_callbacks.append(repl_callback)
def repl_output(self) -> str:
return self._repl_output_stream
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, isjava
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: isjava(obj) and ij().convert().supports(obj, jc.DatasetView),
predicate=lambda obj: isjava(obj) and 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)
13 changes: 4 additions & 9 deletions src/napari_imagej/widgets/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,20 @@
from qtpy.QtWidgets import QComboBox, QLineEdit, QTextEdit, QVBoxLayout, QWidget

from napari_imagej.java import ij, jc
from napari_imagej.model import NapariImageJ


class REPLWidget(QWidget):
def __init__(self, script_repl: "ScriptREPL" = None, parent: QWidget = None):
def __init__(self, nij: NapariImageJ, parent: QWidget = None):
"""
Initialize the REPLWidget.
:param script_repl: The ScriptREPL object for evaluating commands.
:param nij: The NapariImageJ model object to use when evaluating commands.
:param parent: The parent widget (optional).
"""
super().__init__(parent)

output_stream = ByteArrayOutputStream()
self.script_repl = (
script_repl
if script_repl
else jc.ScriptREPL(ij().context(), output_stream)
)
self.script_repl.lang("jython")
self.script_repl = nij.repl

layout = QVBoxLayout(self)

Expand Down

0 comments on commit ce2276c

Please sign in to comment.