From 71224d7a107dfb023b2841e6f93fd9691dbc2fb7 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 8 Jun 2023 14:04:09 -0400 Subject: [PATCH 01/12] Add a REPL widget that supports the SciJava ScriptREPL --- src/napari_imagej/resources/repl.svg | 47 +++++++++++++++ src/napari_imagej/widgets/menu.py | 42 ++++++++++++++ src/napari_imagej/widgets/repl.py | 86 ++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 src/napari_imagej/resources/repl.svg create mode 100644 src/napari_imagej/widgets/repl.py diff --git a/src/napari_imagej/resources/repl.svg b/src/napari_imagej/resources/repl.svg new file mode 100644 index 00000000..56af36d5 --- /dev/null +++ b/src/napari_imagej/resources/repl.svg @@ -0,0 +1,47 @@ + + + + + + + + >_ + + diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index ca72ff01..258782f3 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -20,6 +20,8 @@ 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,6 +43,9 @@ def __init__(self, viewer: Viewer): self.gui_button: GUIButton = GUIButton(viewer) self.layout().addWidget(self.gui_button) + self.repl_button: REPLButton = REPLButton(viewer) + self.layout().addWidget(self.repl_button) + self.settings_button: SettingsButton = SettingsButton(viewer) self.layout().addWidget(self.settings_button) @@ -255,6 +260,43 @@ def disable_popup(self): ) +class REPLButton(IJMenuButton): + def __init__(self, viewer: Viewer): + super().__init__(viewer) + self.viewer = viewer + + 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): + from scyjava import jimport + + ByteArrayOutputStream = jimport("java.io.ByteArrayOutputStream") + ScriptREPL = jimport("org.scijava.script.ScriptREPL") + + output_stream = ByteArrayOutputStream() + self.repl = ScriptREPL(ij().context(), "jython", output_stream) + self.repl.lang("jython") + + self._widget = REPLWidget(self.repl) + self._widget.visible = False + self.viewer.window.add_dock_widget(self._widget) + + def _toggle_repl(self): + """ + Spawn a popup allowing the user to configure napari-imagej settings. + """ + 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/repl.py b/src/napari_imagej/widgets/repl.py new file mode 100644 index 00000000..17137a72 --- /dev/null +++ b/src/napari_imagej/widgets/repl.py @@ -0,0 +1,86 @@ +""" +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 + + +class REPLWidget(QWidget): + def __init__(self, script_repl, parent=None): + """ + Initialize the REPLWidget. + + Parameters: + - script_repl (ScriptREPL): The ScriptREPL object for evaluating commands. + - parent (QWidget): The parent widget (optional). + + Set up the user interface and connect signals for the REPLWidget. + """ + super().__init__(parent) + self.script_repl = script_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) + + self.input_lineedit = QLineEdit(self) + self.input_lineedit.returnPressed.connect(self.process_input) + layout.addWidget(self.input_lineedit) + + def change_language(self, language): + """ + Change the scripting language of the ScriptREPL object. + + Parameters: + - language (str): The selected scripting language. + + Update the scripting language setting of the ScriptREPL object based on + the user's selection. + """ + self.script_repl.lang(language) + self.output_textedit.clear() + + def process_input(self): + """ + Process the user input and evaluate it using the ScriptREPL. + + Display the results in the output text area. + """ + input_text = self.input_lineedit.text() + self.input_lineedit.clear() + + from scyjava import jimport + + ScriptException = jimport("javax.script.ScriptException") + + # Catch script errors when evaluating + try: + # Evaluate the input using interpreter's eval method + result = self.script_repl.getInterpreter().eval(input_text) + + # Display the result in the output text area + self.output_textedit.append(f">>> {input_text}") + self.output_textedit.append(str(result)) + except ScriptException as e: + # Display the exception message in the output text area + self.output_textedit.append(f">>> {input_text}") + self.output_textedit.append(f"Error: {str(e)}") + finally: + self.output_textedit.append("") + + # 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() From 06a9160d99a2798ea2887a39ab94b867883afa7b Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 8 Jun 2023 14:09:39 -0400 Subject: [PATCH 02/12] Add title to REPL dock widget --- src/napari_imagej/widgets/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index 258782f3..73d01a89 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -283,7 +283,7 @@ def _add_repl_to_dock(self): self._widget = REPLWidget(self.repl) self._widget.visible = False - self.viewer.window.add_dock_widget(self._widget) + self.viewer.window.add_dock_widget(self._widget, name="napari-imagej REPL") def _toggle_repl(self): """ From e196f8f92e42590da0a58660373601a83336b823 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 8 Jun 2023 14:32:55 -0400 Subject: [PATCH 03/12] Fix SVG to work with dark mode --- src/napari_imagej/resources/repl.svg | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/napari_imagej/resources/repl.svg b/src/napari_imagej/resources/repl.svg index 56af36d5..c6a785bc 100644 --- a/src/napari_imagej/resources/repl.svg +++ b/src/napari_imagej/resources/repl.svg @@ -29,19 +29,18 @@ inkscape:groupmode="layer" id="layer1" transform="translate(-4.1093503,-5.8935793)"> - >_ + style="font-weight:bold;font-size:5.64444px;font-family:'Mukta Mahee';-inkscape-font-specification:'Mukta Mahee, Bold';stroke-width:0.264583"> + + + From bd0c73191df07e2521bcaf495e24491a3506c584 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Wed, 14 Jun 2023 23:06:33 -0400 Subject: [PATCH 04/12] Disable REPL button until IJ2 is initialized --- src/napari_imagej/widgets/menu.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index 73d01a89..a1e0b940 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -76,6 +76,10 @@ 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) @@ -265,6 +269,8 @@ 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)) From 112e930759ef0730cd7dbe5fe553339d4532de1c Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 6 Jul 2023 12:47:57 -0400 Subject: [PATCH 05/12] Add tooltips for REPL and Settings buttons --- src/napari_imagej/widgets/menu.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index a1e0b940..93b58dcd 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -44,9 +44,11 @@ def __init__(self, viewer: 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(): From a79a8bbb85d3e5e1fe6a64a1e70dd66770733092 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 6 Jul 2023 13:01:51 -0400 Subject: [PATCH 06/12] Add section in docs about REPL --- doc/Configuration.rst | 9 +++++++++ 1 file changed, 9 insertions(+) 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/ From 376d3244e568592093a9e41d3fc9e8330915ee11 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 21 Jul 2023 15:40:29 -0700 Subject: [PATCH 07/12] Import ScriptException using jc mechanism --- src/napari_imagej/java.py | 4 ++++ src/napari_imagej/widgets/repl.py | 8 +++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index b4a132d3..7eb30b60 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -309,6 +309,10 @@ def Path(self): def Window(self): return "java.awt.Window" + @blocking_import + def ScriptException(self): + return "javax.script.ScriptException" + # SciJava Types @blocking_import diff --git a/src/napari_imagej/widgets/repl.py b/src/napari_imagej/widgets/repl.py index 17137a72..d7584759 100644 --- a/src/napari_imagej/widgets/repl.py +++ b/src/napari_imagej/widgets/repl.py @@ -6,6 +6,8 @@ from qtpy.QtGui import QTextCursor from qtpy.QtWidgets import QComboBox, QLineEdit, QTextEdit, QVBoxLayout, QWidget +from napari_imagej.java import jc + class REPLWidget(QWidget): def __init__(self, script_repl, parent=None): @@ -60,10 +62,6 @@ def process_input(self): input_text = self.input_lineedit.text() self.input_lineedit.clear() - from scyjava import jimport - - ScriptException = jimport("javax.script.ScriptException") - # Catch script errors when evaluating try: # Evaluate the input using interpreter's eval method @@ -72,7 +70,7 @@ def process_input(self): # Display the result in the output text area self.output_textedit.append(f">>> {input_text}") self.output_textedit.append(str(result)) - except ScriptException as e: + except jc.ScriptException as e: # Display the exception message in the output text area self.output_textedit.append(f">>> {input_text}") self.output_textedit.append(f"Error: {str(e)}") From 47dcaa50dccd26eb7c565a6438e3aa641943c5dc Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sat, 22 Jul 2023 11:52:07 -0700 Subject: [PATCH 08/12] Improve docstrings and type hints --- src/napari_imagej/widgets/menu.py | 2 +- src/napari_imagej/widgets/repl.py | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index 93b58dcd..625a6ba4 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -295,7 +295,7 @@ def _add_repl_to_dock(self): def _toggle_repl(self): """ - Spawn a popup allowing the user to configure napari-imagej settings. + Toggle visibility the SciJava REPL widget. """ if not self._widget: self._add_repl_to_dock() diff --git a/src/napari_imagej/widgets/repl.py b/src/napari_imagej/widgets/repl.py index d7584759..ca882695 100644 --- a/src/napari_imagej/widgets/repl.py +++ b/src/napari_imagej/widgets/repl.py @@ -14,11 +14,8 @@ def __init__(self, script_repl, parent=None): """ Initialize the REPLWidget. - Parameters: - - script_repl (ScriptREPL): The ScriptREPL object for evaluating commands. - - parent (QWidget): The parent widget (optional). - - Set up the user interface and connect signals for the REPLWidget. + :param script_repl: The ScriptREPL object for evaluating commands. + :param parent: The parent widget (optional). """ super().__init__(parent) self.script_repl = script_repl @@ -40,22 +37,18 @@ def __init__(self, script_repl, parent=None): self.input_lineedit.returnPressed.connect(self.process_input) layout.addWidget(self.input_lineedit) - def change_language(self, language): + def change_language(self, language: str): """ - Change the scripting language of the ScriptREPL object. - - Parameters: - - language (str): The selected scripting language. + Change the active scripting language of the REPL. - Update the scripting language setting of the ScriptREPL object based on - the user's selection. + :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 ScriptREPL. + Process the user input and evaluate it using the REPL. Display the results in the output text area. """ From 7dc2cbb4874539f4a953768b79d6c3f4b43377fc Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 25 Jul 2023 08:22:49 -0500 Subject: [PATCH 09/12] Migrate core ScriptREPL logic out of menu.py And use jc mechanism instead of jimport for REPL-related classes. --- src/napari_imagej/java.py | 8 ++++++++ src/napari_imagej/widgets/menu.py | 9 --------- src/napari_imagej/widgets/repl.py | 13 ++++++++++--- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index 7eb30b60..c91b7731 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -285,6 +285,10 @@ def BigDecimal(self): def BigInteger(self): return "java.math.BigInteger" + @blocking_import + def ByteArrayOutputStream(self): + "java.io.ByteArrayOutputStream" + @blocking_import def Date(self): return "java.util.Date" @@ -383,6 +387,10 @@ def ResultsPostprocessor(self): def SciJavaEvent(self): return "org.scijava.event.SciJavaEvent" + @blocking_import + def ScriptREPL(self): + return "org.scijava.script.ScriptREPL" + @blocking_import def Searcher(self): return "org.scijava.search.Searcher" diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index 625a6ba4..a3254dcb 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -280,15 +280,6 @@ def __init__(self, viewer: Viewer): self._widget = None def _add_repl_to_dock(self): - from scyjava import jimport - - ByteArrayOutputStream = jimport("java.io.ByteArrayOutputStream") - ScriptREPL = jimport("org.scijava.script.ScriptREPL") - - output_stream = ByteArrayOutputStream() - self.repl = ScriptREPL(ij().context(), "jython", output_stream) - self.repl.lang("jython") - self._widget = REPLWidget(self.repl) self._widget.visible = False self.viewer.window.add_dock_widget(self._widget, name="napari-imagej REPL") diff --git a/src/napari_imagej/widgets/repl.py b/src/napari_imagej/widgets/repl.py index ca882695..65a0f543 100644 --- a/src/napari_imagej/widgets/repl.py +++ b/src/napari_imagej/widgets/repl.py @@ -6,11 +6,11 @@ from qtpy.QtGui import QTextCursor from qtpy.QtWidgets import QComboBox, QLineEdit, QTextEdit, QVBoxLayout, QWidget -from napari_imagej.java import jc +from napari_imagej.java import ij, jc class REPLWidget(QWidget): - def __init__(self, script_repl, parent=None): + def __init__(self, script_repl: "ScriptREPL" = None, parent: QWidget = None): """ Initialize the REPLWidget. @@ -18,7 +18,14 @@ def __init__(self, script_repl, parent=None): :param parent: The parent widget (optional). """ super().__init__(parent) - self.script_repl = script_repl + + output_stream = ByteArrayOutputStream() + self.script_repl = ( + script_repl + if script_repl + else jc.ScriptREPL(ij().context(), output_stream) + ) + self.script_repl.lang("jython") layout = QVBoxLayout(self) From e573756fb865c504b1124e97d95fdbdbb46d36fb Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 3 Aug 2023 15:33:10 -0500 Subject: [PATCH 10/12] Clean up java submodule comments and whitespace --- src/napari_imagej/java.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index c91b7731..b509ff40 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -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", @@ -38,7 +38,6 @@ _ij = None - def ij(): if _ij is None: raise Exception( @@ -46,10 +45,11 @@ def ij(): ) return _ij +# -- Public functions -- # def init_ij() -> "jc.ImageJ": """ - Creates the ImageJ instance + Create an ImageJ2 gateway. """ global _ij if _ij: @@ -86,10 +86,11 @@ def init_ij() -> "jc.ImageJ": 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. @@ -114,7 +115,7 @@ def _configure_imagej() -> Dict[str, Any]: def _validate_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 From 8cec6e0ba5d5da73f2bbf527500e35ffc4ebbfcc Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 3 Aug 2023 15:35:56 -0500 Subject: [PATCH 11/12] Use scyjava JavaClasses for java class imports --- src/napari_imagej/java.py | 277 ++++++++---------- .../types/converters/trackmate.py | 22 +- tests/test_scripting.py | 4 +- tests/types/test_trackmate.py | 8 +- tests/utils.py | 58 ++-- tests/widgets/test_result_runner.py | 4 +- 6 files changed, 177 insertions(+), 196 deletions(-) diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index b509ff40..d8f216e2 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -11,11 +11,10 @@ * 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 @@ -168,539 +167,521 @@ 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 ByteArrayOutputStream(self): "java.io.ByteArrayOutputStream" - @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" - @blocking_import + @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" - @blocking_import + @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/types/converters/trackmate.py b/src/napari_imagej/types/converters/trackmate.py index 6ea707f0..33516ab8 100644 --- a/src/napari_imagej/types/converters/trackmate.py +++ b/src/napari_imagej/types/converters/trackmate.py @@ -4,10 +4,10 @@ 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.java import ij from napari_imagej.types.converters import java_to_py_converter @@ -130,39 +130,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/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_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" From 0aebd2b539c6afefe713c90eb6efc8b85b5dd605 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 3 Aug 2023 16:55:41 -0500 Subject: [PATCH 12/12] WIP: Centralize ImageJ2 gateway into nij object This provides a place to keep track of not only the ImageJ2 gateway, but also any affiliated data structures, such as our singleton ScriptREPL. --- .github/workflows/build.yml | 3 -- src/napari_imagej/__init__.py | 4 ++ src/napari_imagej/java.py | 53 ++++++------------- src/napari_imagej/model.py | 40 ++++++++++++++ src/napari_imagej/readers/trackMate_reader.py | 7 +-- src/napari_imagej/types/converters/images.py | 13 ++--- src/napari_imagej/types/converters/labels.py | 7 +-- .../types/converters/trackmate.py | 9 ++-- src/napari_imagej/types/type_conversions.py | 5 +- src/napari_imagej/utilities/_module_utils.py | 23 ++++---- .../utilities/event_subscribers.py | 9 ++-- src/napari_imagej/widgets/menu.py | 34 ++++++------ src/napari_imagej/widgets/napari_imagej.py | 29 +++++----- src/napari_imagej/widgets/repl.py | 44 ++++++--------- src/napari_imagej/widgets/result_tree.py | 11 ++-- src/napari_imagej/widgets/widget_utils.py | 21 ++++---- tests/widgets/test_napari_imagej.py | 14 ++--- 17 files changed, 170 insertions(+), 156 deletions(-) create mode 100644 src/napari_imagej/model.py 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/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 d8f216e2..d9968067 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -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 @@ -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 -- # @@ -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. """ @@ -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") @@ -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" 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/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 33516ab8..5090831b 100644 --- a/src/napari_imagej/types/converters/trackmate.py +++ b/src/napari_imagej/types/converters/trackmate.py @@ -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 @@ -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() 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 a3254dcb..1b527282 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -13,8 +13,8 @@ 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 @@ -54,20 +54,20 @@ def __init__(self, viewer: Viewer): 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) @@ -84,12 +84,12 @@ def finalize(self): # 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): @@ -162,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() @@ -188,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()) @@ -212,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) @@ -227,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!") 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 index 65a0f543..35cce06e 100644 --- a/src/napari_imagej/widgets/repl.py +++ b/src/napari_imagej/widgets/repl.py @@ -6,26 +6,20 @@ from qtpy.QtGui import QTextCursor 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) @@ -40,6 +34,8 @@ def __init__(self, script_repl: "ScriptREPL" = None, parent: QWidget = None): 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) @@ -56,28 +52,20 @@ def change_language(self, language: str): def process_input(self): """ Process the user input and evaluate it using the REPL. - - Display the results in the output text area. """ input_text = self.input_lineedit.text() self.input_lineedit.clear() - # Catch script errors when evaluating - try: - # Evaluate the input using interpreter's eval method - result = self.script_repl.getInterpreter().eval(input_text) - - # Display the result in the output text area - self.output_textedit.append(f">>> {input_text}") - self.output_textedit.append(str(result)) - except jc.ScriptException as e: - # Display the exception message in the output text area - self.output_textedit.append(f">>> {input_text}") - self.output_textedit.append(f"Error: {str(e)}") - finally: - self.output_textedit.append("") - - # Scroll to the bottom of the output text area + # 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) 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/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):