diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f2430370..4c9c7924 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,9 +9,6 @@ on: - main tags: - "*-[0-9]+.*" - pull_request: - branches: - - main env: NAPARI_IMAGEJ_TEST_TIMEOUT: 60000 diff --git a/doc/Configuration.rst b/doc/Configuration.rst index c1d395a0..e4f21e21 100644 --- a/doc/Configuration.rst +++ b/doc/Configuration.rst @@ -95,6 +95,15 @@ One common use case for this feature is to increase the maximum heap space avail Specifying 32GB of memory available to ImageJ ecosystem routines in the JVM. +Using the SciJava REPL +-------------------------------- + +You can use the SciJava REPL to interactively run SciJava code. This makes it possible to do things like paste existing SciJava scripts into the REPL. More information on scripting in SciJava can be found `here `_. + +.. figure:: https://media.imagej.net/napari-imagej/scijava_repl.png + + The REPL can be shown/hidden by clicking on the command prompt icon. + .. _Fiji: https://imagej.net/software/fiji/ .. _ImageJ2: https://imagej.net/software/imagej2/ diff --git a/src/napari_imagej/__init__.py b/src/napari_imagej/__init__.py index d8a694ef..13f7a49d 100644 --- a/src/napari_imagej/__init__.py +++ b/src/napari_imagej/__init__.py @@ -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() diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index b4a132d3..d9968067 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -4,23 +4,20 @@ 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 - object whose fields are lazily-loaded Java Class instances. """ -from typing import Any, Callable, Dict +from typing import Any, Dict import imagej -from jpype import JClass -from scyjava import config, get_version, is_version_at_least, jimport, jvm_started +from scyjava import config, get_version, is_version_at_least, jimport, JavaClasses from napari_imagej import settings from napari_imagej.utilities.logging import log_debug -# -- Constants -- +# -- Constants -- # minimum_versions = { "io.scif:scifio": "0.45.0", @@ -34,62 +31,46 @@ "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 + 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 -- # + 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. @@ -112,9 +93,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 @@ -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(): @@ -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") @@ -167,527 +148,517 @@ def _optional_requirements(): return optionals -class JavaClasses(object): - def blocking_import(func: Callable[[], str]) -> Callable[[], JClass]: - """ - A decorator used to lazily evaluate a java import. - func is a function of a Python class that takes no arguments and - returns a string identifying a Java class by name. - - Using that function, this decorator creates a property - that when called: - * Blocks until the ImageJ gateway has been created - * Imports the class identified by the function - """ - - @property - def inner(self): - if not jvm_started(): - raise Exception() - try: - return jimport(func(self)) - except TypeError: - return None - - return inner +class NijJavaClasses(JavaClasses): # Java Primitives - @blocking_import + @JavaClasses.java_import def Boolean(self): return "java.lang.Boolean" - @blocking_import + @JavaClasses.java_import def Byte(self): return "java.lang.Byte" - @blocking_import + @JavaClasses.java_import def Class(self): return "java.lang.Class" - @blocking_import + @JavaClasses.java_import def Character(self): return "java.lang.Character" - @blocking_import + @JavaClasses.java_import def Double(self): return "java.lang.Double" - @blocking_import + @JavaClasses.java_import def Float(self): return "java.lang.Float" - @blocking_import + @JavaClasses.java_import + def ImageJ(self): + return "net.imagej.ImageJ" + + @JavaClasses.java_import def Integer(self): return "java.lang.Integer" - @blocking_import + @JavaClasses.java_import def Long(self): return "java.lang.Long" - @blocking_import + @JavaClasses.java_import def Number(self): return "java.lang.Number" - @blocking_import + @JavaClasses.java_import def Short(self): return "java.lang.Short" - @blocking_import + @JavaClasses.java_import def String(self): return "java.lang.String" # Java Array Primitives - @blocking_import + @JavaClasses.java_import def Boolean_Arr(self): return "[Z" - @blocking_import + @JavaClasses.java_import def Byte_Arr(self): return "[B" - @blocking_import + @JavaClasses.java_import def Character_Arr(self): return "[C" - @blocking_import + @JavaClasses.java_import def Double_Arr(self): return "[D" - @blocking_import + @JavaClasses.java_import def Float_Arr(self): return "[F" - @blocking_import + @JavaClasses.java_import def Integer_Arr(self): return "[I" - @blocking_import + @JavaClasses.java_import def Long_Arr(self): return "[J" - @blocking_import + @JavaClasses.java_import def Short_Arr(self): return "[S" # Vanilla Java Classes - @blocking_import + @JavaClasses.java_import def ArrayList(self): return "java.util.ArrayList" - @blocking_import + @JavaClasses.java_import def BigDecimal(self): return "java.math.BigDecimal" - @blocking_import + @JavaClasses.java_import def BigInteger(self): return "java.math.BigInteger" - @blocking_import + @JavaClasses.java_import def Date(self): return "java.util.Date" - @blocking_import + @JavaClasses.java_import def Enum(self): return "java.lang.Enum" - @blocking_import + @JavaClasses.java_import def File(self): return "java.io.File" - @blocking_import + @JavaClasses.java_import def HashMap(self): return "java.util.HashMap" - @blocking_import + @JavaClasses.java_import def Path(self): return "java.nio.file.Path" - @blocking_import + @JavaClasses.java_import def Window(self): return "java.awt.Window" + @JavaClasses.java_import + def ScriptException(self): + return "javax.script.ScriptException" + # SciJava Types - @blocking_import + @JavaClasses.java_import def DisplayPostprocessor(self): return "org.scijava.display.DisplayPostprocessor" - @blocking_import + @JavaClasses.java_import def FileWidget(self): return "org.scijava.widget.FileWidget" - @blocking_import + @JavaClasses.java_import def InputHarvester(self): return "org.scijava.widget.InputHarvester" - @blocking_import + @JavaClasses.java_import def Module(self): return "org.scijava.module.Module" - @blocking_import + @JavaClasses.java_import def ModuleEvent(self): return "org.scijava.module.event.ModuleEvent" - @blocking_import + @JavaClasses.java_import def ModuleCanceledEvent(self): return "org.scijava.module.event.ModuleCanceledEvent" - @blocking_import + @JavaClasses.java_import def ModuleErroredEvent(self): return "org.scijava.module.event.ModuleErroredEvent" - @blocking_import + @JavaClasses.java_import def ModuleExecutedEvent(self): return "org.scijava.module.event.ModuleExecutedEvent" - @blocking_import + @JavaClasses.java_import def ModuleExecutingEvent(self): return "org.scijava.module.event.ModuleExecutingEvent" - @blocking_import + @JavaClasses.java_import def ModuleFinishedEvent(self): return "org.scijava.module.event.ModuleFinishedEvent" - @blocking_import + @JavaClasses.java_import def ModuleInfo(self): return "org.scijava.module.ModuleInfo" - @blocking_import + @JavaClasses.java_import def ModuleItem(self): return "org.scijava.module.ModuleItem" - @blocking_import + @JavaClasses.java_import def ModuleStartedEvent(self): return "org.scijava.module.event.ModuleStartedEvent" - @blocking_import + @JavaClasses.java_import def PostprocessorPlugin(self): return "org.scijava.module.process.PostprocessorPlugin" - @blocking_import + @JavaClasses.java_import def PreprocessorPlugin(self): return "org.scijava.module.process.PreprocessorPlugin" - @blocking_import + @JavaClasses.java_import def ResultsPostprocessor(self): return "org.scijava.table.process.ResultsPostprocessor" - @blocking_import + @JavaClasses.java_import def SciJavaEvent(self): return "org.scijava.event.SciJavaEvent" - @blocking_import + @JavaClasses.java_import + def ScriptREPL(self): + return "org.scijava.script.ScriptREPL" + + @JavaClasses.java_import def Searcher(self): return "org.scijava.search.Searcher" - @blocking_import + @JavaClasses.java_import def SearchEvent(self): return "org.scijava.search.SearchEvent" - @blocking_import + @JavaClasses.java_import def SearchListener(self): return "org.scijava.search.SearchListener" - @blocking_import + @JavaClasses.java_import def SearchResult(self): return "org.scijava.search.SearchResult" - @blocking_import + @JavaClasses.java_import def Table(self): return "org.scijava.table.Table" - @blocking_import + @JavaClasses.java_import def Types(self): return "org.scijava.util.Types" - @blocking_import + @JavaClasses.java_import def UIComponent(self): return "org.scijava.widget.UIComponent" - @blocking_import + @JavaClasses.java_import def UIShownEvent(self): return "org.scijava.ui.event.UIShownEvent" - @blocking_import + @JavaClasses.java_import def UserInterface(self): return "org.scijava.ui.UserInterface" # ImageJ Legacy Types - @blocking_import + @JavaClasses.java_import def LegacyCommandInfo(self): return "net.imagej.legacy.command.LegacyCommandInfo" # ImgLib2 Types - @blocking_import + @JavaClasses.java_import def BitType(self): return "net.imglib2.type.logic.BitType" - @blocking_import + @JavaClasses.java_import def BooleanType(self): return "net.imglib2.type.BooleanType" - @blocking_import + @JavaClasses.java_import def ColorTable(self): return "net.imglib2.display.ColorTable" - @blocking_import + @JavaClasses.java_import def ColorTable8(self): return "net.imglib2.display.ColorTable8" - @blocking_import + @JavaClasses.java_import def ColorTables(self): return "net.imagej.display.ColorTables" - @blocking_import + @JavaClasses.java_import def ComplexType(self): return "net.imglib2.type.numeric.ComplexType" - @blocking_import + @JavaClasses.java_import def DoubleType(self): return "net.imglib2.type.numeric.real.DoubleType" - @blocking_import + @JavaClasses.java_import def Img(self): return "net.imglib2.img.Img" - @blocking_import + @JavaClasses.java_import def IntegerType(self): return "net.imglib2.type.numeric.IntegerType" - @blocking_import + @JavaClasses.java_import def IterableInterval(self): return "net.imglib2.IterableInterval" - @blocking_import + @JavaClasses.java_import def LongType(self): return "net.imglib2.type.numeric.integer.LongType" - @blocking_import + @JavaClasses.java_import def NumericType(self): return "net.imglib2.type.numeric.NumericType" - @blocking_import + @JavaClasses.java_import def OutOfBoundsFactory(self): return "net.imglib2.outofbounds.OutOfBoundsFactory" - @blocking_import + @JavaClasses.java_import def OutOfBoundsBorderFactory(self): return "net.imglib2.outofbounds.OutOfBoundsBorderFactory" - @blocking_import + @JavaClasses.java_import def OutOfBoundsMirrorExpWindowingFactory(self): return "net.imglib2.outofbounds.OutOfBoundsMirrorExpWindowingFactory" - @blocking_import + @JavaClasses.java_import def OutOfBoundsMirrorFactory(self): return "net.imglib2.outofbounds.OutOfBoundsMirrorFactory" - @blocking_import + @JavaClasses.java_import def OutOfBoundsPeriodicFactory(self): return "net.imglib2.outofbounds.OutOfBoundsPeriodicFactory" - @blocking_import + @JavaClasses.java_import def OutOfBoundsRandomValueFactory(self): return "net.imglib2.outofbounds.OutOfBoundsRandomValueFactory" - @blocking_import + @JavaClasses.java_import def RandomAccessible(self): return "net.imglib2.RandomAccessible" - @blocking_import + @JavaClasses.java_import def RandomAccessibleInterval(self): return "net.imglib2.RandomAccessibleInterval" - @blocking_import + @JavaClasses.java_import def RealPoint(self): return "net.imglib2.RealPoint" - @blocking_import + @JavaClasses.java_import def RealType(self): return "net.imglib2.type.numeric.RealType" # ImgLib2-algorithm Types - @blocking_import + @JavaClasses.java_import def CenteredRectangleShape(self): return "net.imglib2.algorithm.neighborhood.CenteredRectangleShape" - @blocking_import + @JavaClasses.java_import def DiamondShape(self): return "net.imglib2.algorithm.neighborhood.DiamondShape" - @blocking_import + @JavaClasses.java_import def DiamondTipsShape(self): return "net.imglib2.algorithm.neighborhood.DiamondTipsShape" - @blocking_import + @JavaClasses.java_import def HorizontalLineShape(self): return "net.imglib2.algorithm.neighborhood.HorizontalLineShape" - @blocking_import + @JavaClasses.java_import def HyperSphereShape(self): return "net.imglib2.algorithm.neighborhood.HyperSphereShape" - @blocking_import + @JavaClasses.java_import def PairOfPointsShape(self): return "net.imglib2.algorithm.neighborhood.PairOfPointsShape" - @blocking_import + @JavaClasses.java_import def PeriodicLineShape(self): return "net.imglib2.algorithm.neighborhood.PeriodicLineShape" - @blocking_import + @JavaClasses.java_import def RectangleShape(self): return "net.imglib2.algorithm.neighborhood.RectangleShape" - @blocking_import + @JavaClasses.java_import def Shape(self): return "net.imglib2.algorithm.neighborhood.Shape" # ImgLib2-roi Types - @blocking_import + @JavaClasses.java_import def Box(self): return "net.imglib2.roi.geom.real.Box" - @blocking_import + @JavaClasses.java_import def ClosedWritableBox(self): return "net.imglib2.roi.geom.real.ClosedWritableBox" - @blocking_import + @JavaClasses.java_import def ClosedWritableEllipsoid(self): return "net.imglib2.roi.geom.real.ClosedWritableEllipsoid" - @blocking_import + @JavaClasses.java_import def ClosedWritablePolygon2D(self): return "net.imglib2.roi.geom.real.ClosedWritablePolygon2D" - @blocking_import + @JavaClasses.java_import def DefaultWritableLine(self): return "net.imglib2.roi.geom.real.DefaultWritableLine" - @blocking_import + @JavaClasses.java_import def DefaultWritablePolyline(self): return "net.imglib2.roi.geom.real.DefaultWritablePolyline" - @blocking_import + @JavaClasses.java_import def DefaultWritableRealPointCollection(self): return "net.imglib2.roi.geom.real.DefaultWritableRealPointCollection" - @blocking_import + @JavaClasses.java_import def ImgLabeling(self): return "net.imglib2.roi.labeling.ImgLabeling" - @blocking_import + @JavaClasses.java_import def Line(self): return "net.imglib2.roi.geom.real.Line" - @blocking_import + @JavaClasses.java_import def PointMask(self): return "net.imglib2.roi.geom.real.PointMask" - @blocking_import + @JavaClasses.java_import def Polygon2D(self): return "net.imglib2.roi.geom.real.Polygon2D" - @blocking_import + @JavaClasses.java_import def Polyline(self): return "net.imglib2.roi.geom.real.Polyline" - @blocking_import + @JavaClasses.java_import def RealPointCollection(self): return "net.imglib2.roi.geom.real.RealPointCollection" - @blocking_import + @JavaClasses.java_import def SuperEllipsoid(self): return "net.imglib2.roi.geom.real.SuperEllipsoid" # ImageJ2 Types - @blocking_import + @JavaClasses.java_import def Axes(self): return "net.imagej.axis.Axes" - @blocking_import + @JavaClasses.java_import def Dataset(self): return "net.imagej.Dataset" - @blocking_import + @JavaClasses.java_import def DatasetView(self): return "net.imagej.display.DatasetView" - @blocking_import + @JavaClasses.java_import def DefaultLinearAxis(self): return "net.imagej.axis.DefaultLinearAxis" - @blocking_import + @JavaClasses.java_import def DefaultROITree(self): return "net.imagej.roi.DefaultROITree" - @blocking_import + @JavaClasses.java_import def EnumeratedAxis(self): return "net.imagej.axis.EnumeratedAxis" - @blocking_import + @JavaClasses.java_import def ImageDisplay(self): return "net.imagej.display.ImageDisplay" - @blocking_import + @JavaClasses.java_import def ImgPlus(self): return "net.imagej.ImgPlus" - @blocking_import + @JavaClasses.java_import def Mesh(self): return "net.imagej.mesh.Mesh" - @blocking_import + @JavaClasses.java_import def NaiveDoubleMesh(self): return "net.imagej.mesh.naive.NaiveDoubleMesh" - @blocking_import + @JavaClasses.java_import def ROITree(self): return "net.imagej.roi.ROITree" # ImageJ Types - @blocking_import + @JavaClasses.java_import def ImagePlus(self): return "ij.ImagePlus" - @blocking_import + @JavaClasses.java_import def Roi(self): return "ij.gui.Roi" # ImageJ-Legacy Types - @blocking_import + @JavaClasses.java_import def IJRoiWrapper(self): return "net.imagej.legacy.convert.roi.IJRoiWrapper" # ImageJ-Ops Types - @blocking_import + @JavaClasses.java_import def Initializable(self): return "net.imagej.ops.Initializable" - @blocking_import + @JavaClasses.java_import def OpInfo(self): return "net.imagej.ops.OpInfo" - @blocking_import + @JavaClasses.java_import def OpSearcher(self): return "net.imagej.ops.search.OpSearcher" # Scifio-Labeling Types - @blocking_import + @JavaClasses.java_import def LabelingIOService(self): return "io.scif.labeling.LabelingIOService" -jc = JavaClasses() +jc = NijJavaClasses() diff --git a/src/napari_imagej/model.py b/src/napari_imagej/model.py new file mode 100644 index 00000000..4437168e --- /dev/null +++ b/src/napari_imagej/model.py @@ -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) diff --git a/src/napari_imagej/readers/trackMate_reader.py b/src/napari_imagej/readers/trackMate_reader.py index 1e04719d..1ac84e33 100644 --- a/src/napari_imagej/readers/trackMate_reader.py +++ b/src/napari_imagej/readers/trackMate_reader.py @@ -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, @@ -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() @@ -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") diff --git a/src/napari_imagej/resources/repl.svg b/src/napari_imagej/resources/repl.svg new file mode 100644 index 00000000..c6a785bc --- /dev/null +++ b/src/napari_imagej/resources/repl.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + diff --git a/src/napari_imagej/types/converters/images.py b/src/napari_imagej/types/converters/images.py index 0c2e00f4..8b005e37 100644 --- a/src/napari_imagej/types/converters/images.py +++ b/src/napari_imagej/types/converters/images.py @@ -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: @@ -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, @@ -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 = [ @@ -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 diff --git a/src/napari_imagej/types/converters/labels.py b/src/napari_imagej/types/converters/labels.py index 4126c36c..54e647b0 100644 --- a/src/napari_imagej/types/converters/labels.py +++ b/src/napari_imagej/types/converters/labels.py @@ -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 @@ -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) @@ -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) diff --git a/src/napari_imagej/types/converters/trackmate.py b/src/napari_imagej/types/converters/trackmate.py index 6ea707f0..5090831b 100644 --- a/src/napari_imagej/types/converters/trackmate.py +++ b/src/napari_imagej/types/converters/trackmate.py @@ -4,10 +4,9 @@ import numpy as np from napari.layers import Labels, Tracks -from scyjava import Priority +from scyjava import JavaClasses, Priority -from napari_imagej import settings -from napari_imagej.java import JavaClasses, ij +from napari_imagej import nij, settings from napari_imagej.types.converters import java_to_py_converter @@ -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): @@ -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) @@ -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() @@ -130,39 +129,39 @@ def _trackMate_model_to_tracks(obj: "jc.ROITree"): class TrackMateClasses(JavaClasses): # TrackMate Types - @JavaClasses.blocking_import + @JavaClasses.java_import def BranchTableView(self): return "fiji.plugin.trackmate.visualization.table.BranchTableView" - @JavaClasses.blocking_import + @JavaClasses.java_import def ConvexBranchesDecomposition(self): return "fiji.plugin.trackmate.graph.ConvexBranchesDecomposition" - @JavaClasses.blocking_import + @JavaClasses.java_import def LabelImgExporter(self): return "fiji.plugin.trackmate.action.LabelImgExporter" - @JavaClasses.blocking_import + @JavaClasses.java_import def Model(self): return "fiji.plugin.trackmate.Model" - @JavaClasses.blocking_import + @JavaClasses.java_import def Spot(self): return "fiji.plugin.trackmate.Spot" - @JavaClasses.blocking_import + @JavaClasses.java_import def SpotOverlay(self): return "fiji.plugin.trackmate.visualization.hyperstack.SpotOverlay" - @JavaClasses.blocking_import + @JavaClasses.java_import def TMUtils(self): return "fiji.plugin.trackmate.util.TMUtils" - @JavaClasses.blocking_import + @JavaClasses.java_import def TrackMate(self): return "fiji.plugin.trackmate.TrackMate" - @JavaClasses.blocking_import + @JavaClasses.java_import def TrackOverlay(self): return "fiji.plugin.trackmate.visualization.hyperstack.TrackOverlay" diff --git a/src/napari_imagej/types/type_conversions.py b/src/napari_imagej/types/type_conversions.py index ba8c60ec..a627e978 100644 --- a/src/napari_imagej/types/type_conversions.py +++ b/src/napari_imagej/types/type_conversions.py @@ -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 @@ -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) diff --git a/src/napari_imagej/utilities/_module_utils.py b/src/napari_imagej/utilities/_module_utils.py index f259d640..17d70be0 100644 --- a/src/napari_imagej/utilities/_module_utils.py +++ b/src/napari_imagej/utilities/_module_utils.py @@ -21,7 +21,8 @@ from pandas import DataFrame from scyjava import JavaIterable, JavaList, JavaMap, JavaSet, is_arraylike, jstacktrace -from napari_imagej.java import ij, jc +from napari_imagej import nij +from napari_imagej.java import jc from napari_imagej.types.type_conversions import type_hint_for from napari_imagej.types.type_utils import type_displayable_in_napari from napari_imagej.types.widget_mappings import preferred_widget_for @@ -42,7 +43,7 @@ def _preprocess_to_harvester(module) -> List["jc.PreprocessorPlugin"]: :return: The list of preprocessors that have not yet run. """ - preprocessors = ij().plugin().createInstancesOfType(jc.PreprocessorPlugin) + preprocessors = nij.ij.plugin().createInstancesOfType(jc.PreprocessorPlugin) for i, preprocessor in enumerate(preprocessors): # if preprocessor is an InputHarvester, stop and return the remaining list if isinstance(preprocessor, jc.InputHarvester): @@ -189,7 +190,7 @@ def _param_default_or_none(input: "jc.ModuleItem") -> Optional[Any]: # Parameter uses an internal type to denote a required parameter. return _empty try: - return ij().py.from_java(default) + return nij.ij.py.from_java(default) except Exception: return default @@ -314,7 +315,7 @@ def _pure_module_outputs( continue _handle_output( - ij().py.from_java(output_entry.getValue()), + nij.ij.py.from_java(output_entry.getValue()), _devise_layer_name(info, name), info, layer_outputs, @@ -358,7 +359,7 @@ def _add_napari_metadata( info: "jc.ModuleInfo", unresolved_inputs: List["jc.ModuleItem"], ) -> None: - module_name = ij().py.from_java(info.getTitle()) + module_name = nij.ij.py.from_java(info.getTitle()) execute_module.__doc__ = f"Invoke ImageJ2's {module_name}" execute_module.__name__ = module_name execute_module.__qualname__ = module_name @@ -389,7 +390,7 @@ def _add_param_metadata(metadata: dict, key: str, value: Any) -> None: if value is None: return try: - py_value = ij().py.from_java(value) + py_value = nij.ij.py.from_java(value) if isinstance(py_value, JavaMap): py_value = dict(py_value) elif isinstance(py_value, JavaSet): @@ -408,7 +409,7 @@ def _add_scijava_metadata( ) -> Dict[str, Dict[str, Any]]: metadata = {} for input in unresolved_inputs: - key = ij().py.from_java(input.getName()) + key = nij.ij.py.from_java(input.getName()) param_map = {} _add_param_metadata(param_map, "max", input.getMaximumValue()) _add_param_metadata(param_map, "min", input.getMinimumValue()) @@ -439,7 +440,7 @@ def _get_postprocessors(): on SciJava Modules from napari-imagej """ # Discover all postprocessors - postprocessors = ij().plugin().createInstancesOfType(jc.PostprocessorPlugin) + postprocessors = nij.ij.plugin().createInstancesOfType(jc.PostprocessorPlugin) problematic_postprocessors = ( # HACK: This particular postprocessor is trying to create a Display @@ -498,7 +499,7 @@ def module_execute( start_time = perf_counter() # Create user input map - resolved_java_args = ij().py.jargs(*user_resolved_inputs) + resolved_java_args = nij.ij.py.jargs(*user_resolved_inputs) input_map = jc.HashMap() for module_item, input in zip(unresolved_inputs, resolved_java_args): input_map.put(module_item.getName(), input) @@ -522,7 +523,7 @@ def module_execute( # before the module can update it through its own execution. pm.init_progress(module) # Run the module asynchronously using the ModuleService - ij().module().run( + nij.ij.module().run( module, remaining_preprocessors, postprocessors, @@ -652,7 +653,7 @@ def process(self, module: "jc.Module"): "display_results_in_new_window", ) if display_externally is not None and len(widget_outputs) > 0: - name = "Result: " + ij().py.from_java(module.getInfo().getTitle()) + name = "Result: " + nij.ij.py.from_java(module.getInfo().getTitle()) self.output_handler( {"data": widget_outputs, "name": name, "external": display_externally} ) diff --git a/src/napari_imagej/utilities/event_subscribers.py b/src/napari_imagej/utilities/event_subscribers.py index 758ff8d0..26ff9572 100644 --- a/src/napari_imagej/utilities/event_subscribers.py +++ b/src/napari_imagej/utilities/event_subscribers.py @@ -5,7 +5,8 @@ from jpype import JImplements, JOverride from qtpy.QtCore import Signal -from napari_imagej.java import ij, jc +from napari_imagej import nij +from napari_imagej.java import jc from napari_imagej.utilities.logging import log_debug @@ -51,7 +52,7 @@ def __init__(self): def onEvent(self, event): if not self.initialized: # add our custom settings to the User Interface - if ij().legacy and ij().legacy.isActive(): + if nij.ij.legacy and nij.ij.legacy.isActive(): self._ij1_UI_setup() self._ij2_UI_setup(event.getUI()) self.initialized = True @@ -66,7 +67,7 @@ def equals(self, other): def _ij1_UI_setup(self): """Configure the ImageJ Legacy GUI""" - ij().IJ.getInstance().exitWhenQuitting(False) + nij.ij.IJ.getInstance().exitWhenQuitting(False) def _ij2_UI_setup(self, ui: "jc.UserInterface"): """Configure the ImageJ2 Swing GUI behavior""" @@ -119,5 +120,5 @@ def windowDeactivated(self, event): pass listener = NapariAdapter() - ij().object().addObject(listener) + nij.ij.object().addObject(listener) window.addWindowListener(listener) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index ca72ff01..1b527282 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -13,13 +13,15 @@ from qtpy.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QWidget from scyjava import is_arraylike +from napari_imagej import nij from napari_imagej import settings -from napari_imagej.java import ij from napari_imagej.resources import resource_path from napari_imagej.utilities.event_subscribers import UIShownListener from napari_imagej.utilities.events import subscribe, unsubscribe from napari_imagej.widgets.widget_utils import _IMAGE_LAYER_TYPES, DetailExportDialog +from napari_imagej.widgets.repl import REPLWidget + class NapariImageJMenu(QWidget): """Container widget comprising the napari-imagej menu bar.""" @@ -41,26 +43,31 @@ def __init__(self, viewer: Viewer): self.gui_button: GUIButton = GUIButton(viewer) self.layout().addWidget(self.gui_button) + self.repl_button: REPLButton = REPLButton(viewer) + self.repl_button.setToolTip("Show/hide the SciJava REPL") + self.layout().addWidget(self.repl_button) + self.settings_button: SettingsButton = SettingsButton(viewer) + self.settings_button.setToolTip("Show napari-imagej settings") self.layout().addWidget(self.settings_button) if settings.headless(): self.gui_button.clicked.connect(self.gui_button.disable_popup) else: - # NB We need to call ij().ui().showUI() on the GUI thread. + # NB We need to call nij.ij.ui().showUI() on the GUI thread. # TODO: Use PyImageJ functionality # see https://github.com/imagej/pyimagej/pull/260 def show_ui(): - if ij().ui().isVisible(): - ij().thread().queue( - lambda: ij() + if nij.ij.ui().isVisible(): + nij.ij.thread().queue( + lambda: nij.ij .ui() .getDefaultUI() .getApplicationFrame() .setVisible(True) ) else: - ij().thread().queue(lambda: ij().ui().showUI()) + nij.ij.thread().queue(lambda: nij.ij.ui().showUI()) self.gui_button.clicked.connect(show_ui) @@ -71,14 +78,18 @@ def finalize(self): self.gui_button.setIcon(self.gui_button._icon()) self.gui_button.setEnabled(True) self.gui_button.setToolTip("Display ImageJ2 UI") + + # Now the REPL can be enabled + self.repl_button.setEnabled(True) + # Subscribe UIShownListener self.subscriber = UIShownListener() - subscribe(ij(), self.subscriber) + subscribe(nij.ij, self.subscriber) def __del__(self): # Unsubscribe UIShownListener if self.subscriber: - unsubscribe(ij(), self.subscriber) + unsubscribe(nij.ij, self.subscriber) class IJMenuButton(QPushButton): @@ -151,7 +162,7 @@ def send_active_layer(self): if layer: # Queue UI call on the EDT # TODO: Use EventQueue.invokeLater scyjava wrapper, once it exists - ij().thread().queue(lambda: ij().ui().show(ij().py.to_java(layer))) + nij.ij.thread().queue(lambda: nij.ij.ui().show(nij.ij.py.to_java(layer))) else: self.handle_no_choices() @@ -177,20 +188,20 @@ def _icon(self): return QColoredSVGIcon(resource_path("import")) def _get_objects(self, t): - compatibleInputs = ij().convert().getCompatibleInputs(t) - compatibleInputs.addAll(ij().object().getObjects(t)) + compatibleInputs = nij.ij.convert().getCompatibleInputs(t) + compatibleInputs.addAll(nij.ij.object().getObjects(t)) return list(compatibleInputs) def get_active_layer(self) -> None: # HACK: Sync ImagePlus before transferring # This code can be removed once # https://github.com/imagej/imagej-legacy/issues/286 is solved. - if ij().legacy and ij().legacy.isActive(): - current_image_plus = ij().WindowManager.getCurrentImage() + if nij.ij.legacy and nij.ij.legacy.isActive(): + current_image_plus = nij.ij.WindowManager.getCurrentImage() if current_image_plus is not None: - ij().py.sync_image(current_image_plus) + nij.ij.py.sync_image(current_image_plus) # Get the active view from the active image display - ids = ij().get("net.imagej.display.ImageDisplayService") + ids = nij.ij.get("net.imagej.display.ImageDisplayService") # TODO: simplify to no-args once # https://github.com/imagej/imagej-legacy/pull/287 is merged. view = ids.getActiveDatasetView(ids.getActiveImageDisplay()) @@ -201,7 +212,7 @@ def get_active_layer(self) -> None: def _add_layer(self, view): # Convert the object into Python - py_image = ij().py.from_java(view) + py_image = nij.ij.py.from_java(view) # Create and add the layer if isinstance(py_image, Layer): self.viewer.add_layer(py_image) @@ -216,7 +227,7 @@ def _add_layer(self, view): self.viewer.add_layer(itm) # Other elif is_arraylike(py_image): - name = ij().object().getName(view) + name = nij.ij.object().getName(view) self.viewer.add_image(data=py_image, name=name) else: raise ValueError(f"{view} cannot be displayed in napari!") @@ -255,6 +266,36 @@ def disable_popup(self): ) +class REPLButton(IJMenuButton): + def __init__(self, viewer: Viewer): + super().__init__(viewer) + self.viewer = viewer + + self.setEnabled(False) + + icon = QColoredSVGIcon(resource_path("repl")) + self.setIcon(icon.colored(theme=viewer.theme)) + + self.clicked.connect(self._toggle_repl) + self._widget = None + + def _add_repl_to_dock(self): + self._widget = REPLWidget(self.repl) + self._widget.visible = False + self.viewer.window.add_dock_widget(self._widget, name="napari-imagej REPL") + + def _toggle_repl(self): + """ + Toggle visibility the SciJava REPL widget. + """ + if not self._widget: + self._add_repl_to_dock() + else: + self.viewer.window.remove_dock_widget(self._widget) + self._widget.close() + self._widget = None + + class SettingsButton(IJMenuButton): # Signal used to identify changes to user settings setting_change = Signal(bool) diff --git a/src/napari_imagej/widgets/napari_imagej.py b/src/napari_imagej/widgets/napari_imagej.py index 9ef004b9..6d0ae5f1 100644 --- a/src/napari_imagej/widgets/napari_imagej.py +++ b/src/napari_imagej/widgets/napari_imagej.py @@ -1,8 +1,8 @@ """ -This module contains ImageJWidget, the top-level QWidget enabling +This module contains NapariImageJWidget, the top-level QWidget enabling graphical access to ImageJ functionality. -This Widget is made accessible to napari through napari.yml +This widget is made accessible to napari through napari.yml. """ from traceback import format_exception from typing import Callable @@ -15,7 +15,8 @@ from qtpy.QtWidgets import QTreeWidgetItem, QVBoxLayout, QWidget from scyjava import jstacktrace, when_jvm_stops -from napari_imagej.java import ij, init_ij, jc +from napari_imagej import nij +from napari_imagej.java import init_ij, jc from napari_imagej.utilities._module_utils import _non_layer_widget from napari_imagej.utilities.event_subscribers import ( NapariEventSubscriber, @@ -183,7 +184,7 @@ def _update_progress(self, event: "jc.ModuleEvent"): ): pm.close(module) if isinstance(event, jc.ModuleErroredEvent): - if not ij().ui().isVisible(): + if not nij.ij.ui().isVisible(): # TODO Use napari's error handler once it works better # see https://github.com/imagej/napari-imagej/issues/234 module_title = str(module.getInfo().getTitle()) @@ -233,8 +234,8 @@ def run(self): Functionality partitioned into functions by subwidget. """ try: - # Initialize ImageJ - init_ij() + # Block until ImageJ2 is initialized. + ij = nij.ij # Finalize the menu self.widget.menu.finalize() # Finalize the search bar @@ -271,19 +272,19 @@ def searchCompleted(self, event: "jc.SearchEvent"): [NapariImageJSearchListener(self.widget.result_tree.process)] ) self.widget.result_tree._searchOperation = ( - ij().get("org.scijava.search.SearchService").search(listener_arr) + nij.ij.get("org.scijava.search.SearchService").search(listener_arr) ) # Make sure that the search stops when we close napari # Otherwise the Java threads like to continue when_jvm_stops(self.widget.result_tree._searchOperation.terminate) # Add SearcherTreeItems for each Searcher - searchers = ij().plugin().createInstancesOfType(jc.Searcher) + searchers = nij.ij.plugin().createInstancesOfType(jc.Searcher) for searcher in searchers: self.widget.result_tree.insert.emit( SearcherTreeItem( searcher, - checked=ij() + checked=nij.ij .get("org.scijava.search.SearchService") .enabled(searcher), expanded=False, @@ -291,20 +292,20 @@ def searchCompleted(self, event: "jc.SearchEvent"): ) def _finalize_info_bar(self): - self.widget.info_box.version_bar.setText(f"ImageJ2 v{ij().getVersion()}") + self.widget.info_box.version_bar.setText(f"ImageJ2 v{nij.ij.getVersion()}") def _finalize_subscribers(self): # Progress bar subscriber self.progress_listener = ProgressBarListener(self.widget.progress_handler) - subscribe(ij(), self.progress_listener) + subscribe(nij.ij, self.progress_listener) # Debug printer subscriber if is_debug(): self.event_listener = NapariEventSubscriber() - subscribe(ij(), self.event_listener) + subscribe(nij.ij, self.event_listener) def _clean_subscribers(self): # Unsubscribe listeners if hasattr(self, "progress_listener"): - unsubscribe(ij(), self.progress_listener) + unsubscribe(nij.ij, self.progress_listener) if hasattr(self, "event_listener"): - unsubscribe(ij(), self.event_listener) + unsubscribe(nij.ij, self.event_listener) diff --git a/src/napari_imagej/widgets/repl.py b/src/napari_imagej/widgets/repl.py new file mode 100644 index 00000000..35cce06e --- /dev/null +++ b/src/napari_imagej/widgets/repl.py @@ -0,0 +1,72 @@ +""" +A widget that provides access to the SciJava REPL. + +This supports all of the languages of SciJava. +""" +from qtpy.QtGui import QTextCursor +from qtpy.QtWidgets import QComboBox, QLineEdit, QTextEdit, QVBoxLayout, QWidget + +from napari_imagej.model import NapariImageJ + + +class REPLWidget(QWidget): + def __init__(self, nij: NapariImageJ, parent: QWidget = None): + """ + Initialize the REPLWidget. + + :param nij: The NapariImageJ model object to use when evaluating commands. + :param parent: The parent widget (optional). + """ + super().__init__(parent) + + self.script_repl = nij.repl + + layout = QVBoxLayout(self) + + self.language_combo = QComboBox(self) + self.language_combo.addItems( + [str(el) for el in list(self.script_repl.getInterpretedLanguages())] + ) + self.language_combo.currentTextChanged.connect(self.change_language) + layout.addWidget(self.language_combo) + + self.output_textedit = QTextEdit(self) + self.output_textedit.setReadOnly(True) + layout.addWidget(self.output_textedit) + + nij.add_repl_callback(lambda s: self.process_output(s)) + + self.input_lineedit = QLineEdit(self) + self.input_lineedit.returnPressed.connect(self.process_input) + layout.addWidget(self.input_lineedit) + + def change_language(self, language: str): + """ + Change the active scripting language of the REPL. + + :param language: The new scripting language to use. + """ + self.script_repl.lang(language) + self.output_textedit.clear() + + def process_input(self): + """ + Process the user input and evaluate it using the REPL. + """ + input_text = self.input_lineedit.text() + self.input_lineedit.clear() + + # Evaluate the input using REPL's evaluate method. + self.script_repl.evaluate(input_text) + + def process_output(self, s): + """ + Display output given from the REPL in the output text area. + """ + self.output_textedit.append(s) + + # Scroll to the bottom of the output text area. + cursor = self.output_textedit.textCursor() + cursor.movePosition(QTextCursor.MoveOperation.End) + self.output_textedit.setTextCursor(cursor) + self.output_textedit.ensureCursorVisible() diff --git a/src/napari_imagej/widgets/result_tree.py b/src/napari_imagej/widgets/result_tree.py index 4a35a91c..2d6737b6 100644 --- a/src/napari_imagej/widgets/result_tree.py +++ b/src/napari_imagej/widgets/result_tree.py @@ -9,7 +9,8 @@ from qtpy.QtWidgets import QAction, QMenu, QTreeWidget, QTreeWidgetItem 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.utilities.logging import log_debug from napari_imagej.widgets.widget_utils import python_actions_for @@ -53,11 +54,11 @@ def __init__( :param expanded: Indicates whether this SearcherTreeItem should start expanded """ super().__init__() - self.title = ij().py.from_java(searcher.title()) + self.title = nij.ij.py.from_java(searcher.title()) self._searcher = searcher # Finding the priority is tricky - Searchers don't know their priority # To find it we have to ask the pluginService. - plugin_info = ij().plugin().getPlugin(searcher.getClass()) + plugin_info = nij.ij.plugin().getPlugin(searcher.getClass()) self.priority = plugin_info.getPriority() if plugin_info else priority # Set QtPy properties @@ -202,14 +203,14 @@ def _register_item_change(self, item: QTreeWidgetItem, column: int): if column == 0: if isinstance(item, SearcherTreeItem): checked = item.checkState(0) == Qt.Checked - ij().get("org.scijava.search.SearchService").setEnabled( + nij.ij.get("org.scijava.search.SearchService").setEnabled( item._searcher, checked ) if not checked: item.update([]) def _get_matching_item(self, searcher: "jc.Searcher") -> Optional[SearcherTreeItem]: - name: str = ij().py.from_java(searcher.title()) + name: str = nij.ij.py.from_java(searcher.title()) matches = self.findItems(name, Qt.MatchStartsWith, 0) if len(matches) == 0: return None diff --git a/src/napari_imagej/widgets/widget_utils.py b/src/napari_imagej/widgets/widget_utils.py index 578d829b..07948b28 100644 --- a/src/napari_imagej/widgets/widget_utils.py +++ b/src/napari_imagej/widgets/widget_utils.py @@ -20,7 +20,8 @@ QWidget, ) -from napari_imagej.java import ij, jc +from napari_imagej import nij +from napari_imagej.java import jc from napari_imagej.utilities._module_utils import ( execute_function_modally, functionify_module_execution, @@ -34,7 +35,7 @@ def python_actions_for( ): actions = [] # Iterate over all available python actions - searchService = ij().get("org.scijava.search.SearchService") + searchService = nij.ij.get("org.scijava.search.SearchService") for action in searchService.actions(result): action_name = str(action.toString()) # Add buttons for the java action @@ -59,8 +60,8 @@ def execute_result(modal: bool): return [] if ( - ij().legacy - and ij().legacy.isActive() + nij.ij.legacy + and nij.ij.legacy.isActive() and isinstance(moduleInfo, jc.LegacyCommandInfo) ): reply = QMessageBox.question( @@ -74,10 +75,10 @@ def execute_result(modal: bool): QMessageBox.Yes | QMessageBox.No, ) if reply == QMessageBox.Yes: - ij().thread().queue(lambda: ij().ui().showUI()) + nij.ij.thread().queue(lambda: nij.ij.ui().showUI()) return - module = ij().module().createModule(moduleInfo) + module = nij.ij.module().createModule(moduleInfo) # preprocess using napari GUI func, param_options = functionify_module_execution( @@ -281,12 +282,12 @@ def pass_to_ij(): img = self.img_container.combo.currentData() roi = self.roi_container.combo.currentData() # Convert the selections to Java equivalents - j_img = ij().py.to_java( + j_img = nij.ij.py.to_java( img, dim_order=self.dims_container.provided_labels() ) if roi: - j_img.getProperties().put("rois", ij().py.to_java(roi)) + j_img.getProperties().put("rois", nij.ij.py.to_java(roi)) # Show the resulting image - ij().ui().show(j_img) + nij.ij.ui().show(j_img) - ij().thread().queue(lambda: pass_to_ij()) + nij.ij.thread().queue(lambda: pass_to_ij()) diff --git a/tests/test_scripting.py b/tests/test_scripting.py index 42183af6..f0dd0924 100644 --- a/tests/test_scripting.py +++ b/tests/test_scripting.py @@ -3,7 +3,7 @@ """ from magicgui import magicgui -from napari_imagej.java import JavaClasses +from scyjava import JavaClasses from napari_imagej.utilities._module_utils import functionify_module_execution @@ -12,7 +12,7 @@ class JavaClassesTest(JavaClasses): Here we override JavaClasses to get extra test imports """ - @JavaClasses.blocking_import + @JavaClasses.java_import def DefaultModuleService(self): return "org.scijava.module.DefaultModuleService" diff --git a/tests/types/test_trackmate.py b/tests/types/test_trackmate.py index 9cb014bf..52b953be 100644 --- a/tests/types/test_trackmate.py +++ b/tests/types/test_trackmate.py @@ -5,23 +5,23 @@ import numpy as np import pytest +from scyjava import JavaClasses from napari.layers import Labels, Tracks from napari_imagej import settings -from napari_imagej.java import JavaClasses from napari_imagej.types.converters.trackmate import TrackMateClasses, trackmate_present class TestTrackMateClasses(TrackMateClasses): - @JavaClasses.blocking_import + @JavaClasses.java_import def DisplaySettings(self): return "fiji.plugin.trackmate.gui.displaysettings.DisplaySettings" - @JavaClasses.blocking_import + @JavaClasses.java_import def HyperStackDisplayer(self): return "fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer" - @JavaClasses.blocking_import + @JavaClasses.java_import def SelectionModel(self): return "fiji.plugin.trackmate.SelectionModel" diff --git a/tests/utils.py b/tests/utils.py index eda8f349..e9a80c99 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -5,7 +5,7 @@ from jpype import JImplements, JOverride -from napari_imagej.java import JavaClasses +from scyjava import JavaClasses class JavaClassesTest(JavaClasses): @@ -13,115 +13,115 @@ class JavaClassesTest(JavaClasses): Here we override JavaClasses to get extra test imports """ - @JavaClasses.blocking_import + @JavaClasses.java_import def ArrayImg(self): return "net.imglib2.img.array.ArrayImg" - @JavaClasses.blocking_import + @JavaClasses.java_import def ArrayImgs(self): return "net.imglib2.img.array.ArrayImgs" - @JavaClasses.blocking_import + @JavaClasses.java_import def Axes(self): return "net.imagej.axis.Axes" - @JavaClasses.blocking_import + @JavaClasses.java_import def BoolType(self): return "net.imglib2.type.logic.BoolType" - @JavaClasses.blocking_import + @JavaClasses.java_import def ByteType(self): return "net.imglib2.type.numeric.integer.ByteType" - @JavaClasses.blocking_import + @JavaClasses.java_import def ClassesSearcher(self): return "org.scijava.search.classes.ClassesSearcher" - @JavaClasses.blocking_import + @JavaClasses.java_import def ClassSearchResult(self): return "org.scijava.search.classes.ClassSearchResult" - @JavaClasses.blocking_import + @JavaClasses.java_import def DefaultMutableModuleItem(self): return "org.scijava.module.DefaultMutableModuleItem" - @JavaClasses.blocking_import + @JavaClasses.java_import def DefaultMutableModuleInfo(self): return "org.scijava.module.DefaultMutableModuleInfo" - @JavaClasses.blocking_import + @JavaClasses.java_import def DoubleArray(self): return "org.scijava.util.DoubleArray" - @JavaClasses.blocking_import + @JavaClasses.java_import def EuclideanSpace(self): return "net.imglib2.EuclideanSpace" - @JavaClasses.blocking_import + @JavaClasses.java_import def FloatType(self): return "net.imglib2.type.numeric.real.FloatType" - @JavaClasses.blocking_import + @JavaClasses.java_import def Frame(self): return "java.awt.Frame" - @JavaClasses.blocking_import + @JavaClasses.java_import def IllegalArgumentException(self): return "java.lang.IllegalArgumentException" - @JavaClasses.blocking_import + @JavaClasses.java_import def ImageDisplay(self): return "net.imagej.display.ImageDisplay" - @JavaClasses.blocking_import + @JavaClasses.java_import def IntType(self): return "net.imglib2.type.numeric.integer.IntType" - @JavaClasses.blocking_import + @JavaClasses.java_import def ItemIO(self): return "org.scijava.ItemIO" - @JavaClasses.blocking_import + @JavaClasses.java_import def ItemVisibility(self): return "org.scijava.ItemVisibility" - @JavaClasses.blocking_import + @JavaClasses.java_import def ModuleSearchResult(self): return "org.scijava.search.module.ModuleSearchResult" - @JavaClasses.blocking_import + @JavaClasses.java_import def OpSearchResult(self): return "net.imagej.ops.search.OpSearchResult" - @JavaClasses.blocking_import + @JavaClasses.java_import def ScriptInfo(self): return "org.scijava.script.ScriptInfo" - @JavaClasses.blocking_import + @JavaClasses.java_import def ShortType(self): return "net.imglib2.type.numeric.integer.ShortType" - @JavaClasses.blocking_import + @JavaClasses.java_import def System(self): return "java.lang.System" - @JavaClasses.blocking_import + @JavaClasses.java_import def UnsignedByteType(self): return "net.imglib2.type.numeric.integer.UnsignedByteType" - @JavaClasses.blocking_import + @JavaClasses.java_import def UnsignedShortType(self): return "net.imglib2.type.numeric.integer.UnsignedShortType" - @JavaClasses.blocking_import + @JavaClasses.java_import def UnsignedIntType(self): return "net.imglib2.type.numeric.integer.UnsignedIntType" - @JavaClasses.blocking_import + @JavaClasses.java_import def UnsignedLongType(self): return "net.imglib2.type.numeric.integer.UnsignedLongType" - @JavaClasses.blocking_import + @JavaClasses.java_import def WindowEvent(self): return "java.awt.event.WindowEvent" diff --git a/tests/widgets/test_napari_imagej.py b/tests/widgets/test_napari_imagej.py index b6fe770a..35ef8f04 100644 --- a/tests/widgets/test_napari_imagej.py +++ b/tests/widgets/test_napari_imagej.py @@ -8,7 +8,7 @@ from qtpy.QtCore import Qt from qtpy.QtWidgets import QApplication, QLabel, QPushButton, QTextEdit, QVBoxLayout -from napari_imagej.java import ij +from napari_imagej import nij from napari_imagej.utilities.event_subscribers import ( ProgressBarListener, UIShownListener, @@ -56,7 +56,7 @@ def _run_buttons(imagej_widget: NapariImageJWidget): def _ensure_searchers_available(imagej_widget: NapariImageJWidget, asserter): tree = imagej_widget.result_tree # Find the ModuleSearcher - numSearchers = len(ij().plugin().getPluginsOfType(jc.Searcher)) + numSearchers = len(nij.ij.plugin().getPluginsOfType(jc.Searcher)) try: asserter(lambda: tree.topLevelItemCount() == numSearchers) except Exception: @@ -170,7 +170,7 @@ def test_imagej_search_tree_disable(ij, imagej_widget: NapariImageJWidget, asser # Disable the searcher, assert the proper ImageJ response searcher_item.setCheckState(0, Qt.Unchecked) asserter( - lambda: not ij.get("org.scijava.search.SearchService").enabled( + lambda: not nij.ij.get("org.scijava.search.SearchService").enabled( searcher_item._searcher ) ) @@ -178,7 +178,7 @@ def test_imagej_search_tree_disable(ij, imagej_widget: NapariImageJWidget, asser # Enabled the searcher, assert the proper ImageJ response searcher_item.setCheckState(0, Qt.Checked) asserter( - lambda: ij.get("org.scijava.search.SearchService").enabled( + lambda: nij.ij.get("org.scijava.search.SearchService").enabled( searcher_item._searcher ) ) @@ -190,7 +190,7 @@ def test_widget_finalization(ij, imagej_widget: NapariImageJWidget, asserter): # Ensure that all Searchers are represented in the tree with a top level # item - numSearchers = len(ij.plugin().getPluginsOfType(jc.Searcher)) + numSearchers = len(nij.ij.plugin().getPluginsOfType(jc.Searcher)) asserter(lambda: imagej_widget.result_tree.topLevelItemCount() == numSearchers) @@ -232,12 +232,12 @@ def test_info_validity(imagej_widget: NapariImageJWidget, qtbot, asserter): """ # Wait for the info to populate - ij() + ij = nij.ij # Check the version info_box = imagej_widget.info_box - asserter(lambda: info_box.version_bar.text() == f"ImageJ2 v{ij().getVersion()}") + asserter(lambda: info_box.version_bar.text() == f"ImageJ2 v{ij.getVersion()}") def test_handle_output_layer(imagej_widget: NapariImageJWidget, qtbot, asserter): diff --git a/tests/widgets/test_result_runner.py b/tests/widgets/test_result_runner.py index bec93104..77c2af8a 100644 --- a/tests/widgets/test_result_runner.py +++ b/tests/widgets/test_result_runner.py @@ -4,13 +4,13 @@ import pytest from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget -from napari_imagej.java import JavaClasses +from scyjava import JavaClasses from napari_imagej.widgets.layouts import QFlowLayout from napari_imagej.widgets.result_runner import ResultRunner class JavaClassesTest(JavaClasses): - @JavaClasses.blocking_import + @JavaClasses.java_import def ModuleSearchResult(self): return "org.scijava.search.module.ModuleSearchResult"