From c74e1f973805de1c6b4eb4f00c4afc94d4040030 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 8 Jun 2023 14:04:09 -0400 Subject: [PATCH 01/20] Add REPL widget supporting the SciJava ScriptREPL --- src/napari_imagej/resources/repl.svg | 47 +++++++++++++++ src/napari_imagej/widgets/menu.py | 42 ++++++++++++++ src/napari_imagej/widgets/repl.py | 86 ++++++++++++++++++++++++++++ tests/widgets/test_menu.py | 6 +- 4 files changed, 179 insertions(+), 2 deletions(-) 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 cc6067da..c0b07d5d 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -21,6 +21,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.""" @@ -42,6 +44,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) @@ -256,6 +261,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() diff --git a/tests/widgets/test_menu.py b/tests/widgets/test_menu.py index f024f135..9bf2883f 100644 --- a/tests/widgets/test_menu.py +++ b/tests/widgets/test_menu.py @@ -27,6 +27,7 @@ FromIJButton, GUIButton, NapariImageJMenu, + REPLButton, SettingsButton, ToIJButton, ToIJDetailedButton, @@ -175,14 +176,15 @@ def clean_gui_elements(asserter, ij, viewer: Viewer): def test_widget_layout(gui_widget: NapariImageJMenu): """Tests the number and expected order of imagej_widget children""" subwidgets = gui_widget.children() - assert len(subwidgets) == 6 + assert len(subwidgets) == 7 assert isinstance(subwidgets[0], QHBoxLayout) assert isinstance(subwidgets[1], FromIJButton) assert isinstance(subwidgets[2], ToIJButton) assert isinstance(subwidgets[3], ToIJDetailedButton) assert isinstance(subwidgets[4], GUIButton) - assert isinstance(subwidgets[5], SettingsButton) + assert isinstance(subwidgets[5], REPLButton) + assert isinstance(subwidgets[6], SettingsButton) def test_GUIButton_layout_headful(qtbot, asserter, ij, gui_widget: NapariImageJMenu): From fd95851e9dc19080fa215f1b8a6689c3708f1e21 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 8 Jun 2023 14:09:39 -0400 Subject: [PATCH 02/20] 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 c0b07d5d..9d2fdd92 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -284,7 +284,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 78764493707d46de6eb10d302652aac3e327aecd Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 8 Jun 2023 14:32:55 -0400 Subject: [PATCH 03/20] 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 338ba9b468cd7b4ffc9fb36bdc4ff4cb48ca8fcf Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Wed, 14 Jun 2023 23:06:33 -0400 Subject: [PATCH 04/20] 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 9d2fdd92..9c88c53b 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -77,6 +77,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) @@ -266,6 +270,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 436fc4feca4f8f032ab43b0a9034e4482c886c0e Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 6 Jul 2023 12:47:57 -0400 Subject: [PATCH 05/20] 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 9c88c53b..190787c1 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -45,9 +45,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 b6df31252eaaf55fb8f9248359b0ace55fb9823a Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 6 Jul 2023 13:01:51 -0400 Subject: [PATCH 06/20] 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 b9fb12f09dc94c790b7aa12717802b3d7d3b3d9a Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 21 Jul 2023 15:40:29 -0700 Subject: [PATCH 07/20] 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 4bacb413..8a3c6e72 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -314,6 +314,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 fcdbb78d23ff2053946de1ed50c2cf77dc571e46 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sat, 22 Jul 2023 11:52:07 -0700 Subject: [PATCH 08/20] 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 190787c1..c67e4306 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -296,7 +296,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 ad7ba745413a3449c119fc98fb870ae8c6c416fe Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 25 Jul 2023 08:22:49 -0500 Subject: [PATCH 09/20] Migrate core ScriptREPL logic out of menu.py And use jc mechanism instead of jimport for REPL-related classes. --- src/napari_imagej/java.py | 6 +++++- src/napari_imagej/widgets/menu.py | 9 --------- src/napari_imagej/widgets/repl.py | 13 ++++++++++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index 8a3c6e72..0fe7cb31 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -288,7 +288,7 @@ def BigInteger(self): @blocking_import def ByteArrayOutputStream(self): - return "java.io.ByteArrayOutputStream" + "java.io.ByteArrayOutputStream" @blocking_import def Date(self): @@ -388,6 +388,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 c67e4306..f92010b9 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -281,15 +281,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 0ed08d1507607429f01f9ba26622d82cc55ba86c Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 3 Aug 2023 15:33:10 -0500 Subject: [PATCH 10/20] 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 0fe7cb31..06e0dd87 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -21,7 +21,7 @@ from napari_imagej import settings from napari_imagej.utilities.logging import log_debug -# -- Constants -- +# -- Constants -- # minimum_versions = { "io.scif:scifio": "0.45.0", @@ -39,7 +39,6 @@ _ij = None - def ij(): if _ij is None: raise Exception( @@ -47,10 +46,11 @@ def ij(): ) return _ij +# -- Public functions -- # def init_ij() -> "jc.ImageJ": """ - Creates the ImageJ instance + Create an ImageJ2 gateway. """ global _ij if _ij: @@ -87,10 +87,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. @@ -115,7 +116,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 241e7604157bb6c028fa7e43bff8482d24b9fb96 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 3 Aug 2023 15:35:56 -0500 Subject: [PATCH 11/20] 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 | 61 ++-- tests/widgets/test_result_runner.py | 4 +- 6 files changed, 179 insertions(+), 197 deletions(-) diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index 06e0dd87..a22d7372 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -12,11 +12,10 @@ - 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 @@ -169,539 +168,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 51c2485a..0f065de9 100644 --- a/tests/test_scripting.py +++ b/tests/test_scripting.py @@ -4,7 +4,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 @@ -13,7 +13,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 3ac0395c..b37f9c14 100644 --- a/tests/types/test_trackmate.py +++ b/tests/types/test_trackmate.py @@ -6,23 +6,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 c2181a0d..013771a1 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,125 +4,126 @@ from typing import List +from napari_imagej.java import NijJavaClasses from jpype import JImplements, JOverride -from napari_imagej.java import JavaClasses +from scyjava import JavaClasses -class JavaClassesTest(JavaClasses): +class JavaClassesTest(NijJavaClasses): """ 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 a4184559..597192d0 100644 --- a/tests/widgets/test_result_runner.py +++ b/tests/widgets/test_result_runner.py @@ -5,13 +5,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 4dc88fb52d91bf85c635dc0a2bc0d5129bd0ced9 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 3 Aug 2023 16:55:41 -0500 Subject: [PATCH 12/20] 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. --- bin/test.sh | 2 +- src/napari_imagej/__init__.py | 4 ++ src/napari_imagej/java.py | 63 +++++++------------ src/napari_imagej/model.py | 41 ++++++++++++ src/napari_imagej/readers/trackMate_reader.py | 7 ++- src/napari_imagej/settings.py | 2 +- src/napari_imagej/types/converters/images.py | 13 ++-- src/napari_imagej/types/converters/labels.py | 7 ++- .../types/converters/trackmate.py | 12 ++-- 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 | 41 ++++++------ src/napari_imagej/widgets/napari_imagej.py | 31 ++++----- src/napari_imagej/widgets/repl.py | 46 ++++++-------- src/napari_imagej/widgets/result_tree.py | 9 +-- src/napari_imagej/widgets/widget_utils.py | 21 ++++--- tests/conftest.py | 9 ++- tests/test_scripting.py | 2 +- tests/types/test_trackmate.py | 2 +- tests/utils.py | 4 +- tests/widgets/test_napari_imagej.py | 14 ++--- tests/widgets/test_result_runner.py | 2 +- 23 files changed, 196 insertions(+), 173 deletions(-) create mode 100644 src/napari_imagej/model.py diff --git a/bin/test.sh b/bin/test.sh index 8c46088d..a815c910 100755 --- a/bin/test.sh +++ b/bin/test.sh @@ -6,7 +6,7 @@ cd "$dir/.." modes=" | Testing ImageJ2 + original ImageJ |NAPARI_IMAGEJ_INCLUDE_IMAGEJ_LEGACY=TRUE | Testing ImageJ2 standalone |NAPARI_IMAGEJ_INCLUDE_IMAGEJ_LEGACY=FALSE -| Testing Fiji Is Just ImageJ(2) |NAPARI_IMAGEJ_IMAGEJ_DIRECTORY_OR_ENDPOINT=sc.fiji:fiji:2.13.1 +| Testing Fiji Is Just ImageJ(2) |NAPARI_IMAGEJ_IMAGEJ_DIRECTORY_OR_ENDPOINT=sc.fiji:fiji:2.15.0 " echo "$modes" | while read mode 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 a22d7372..8c6d63b9 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 @@ -15,7 +13,7 @@ from typing import Any, Dict import imagej -from scyjava import config, get_version, is_version_at_least, jimport, JavaClasses +from scyjava import JavaClasses, config, get_version, is_version_at_least, jimport from napari_imagej import settings from napari_imagej.utilities.logging import log_debug @@ -29,65 +27,50 @@ "net.imagej:imagej-ops": "0.49.0", "net.imglib2:imglib2-unsafe": "1.0.0", "net.imglib2:imglib2-imglyb": "1.1.0", - "org.scijava:scijava-common": "2.94.0", + "org.scijava:scijava-common": "2.95.0", "org.scijava:scijava-search": "2.0.2", "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 -- # + def _configure_imagej() -> Dict[str, Any]: """ Configure scyjava and pyimagej. @@ -113,7 +96,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. """ @@ -132,7 +115,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(): @@ -154,11 +137,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") @@ -196,10 +179,6 @@ def Double(self): def Float(self): return "java.lang.Float" - @JavaClasses.java_import - def ImageJ(self): - return "net.imagej.ImageJ" - @JavaClasses.java_import def Integer(self): return "java.lang.Integer" @@ -270,7 +249,7 @@ def BigInteger(self): @JavaClasses.java_import def ByteArrayOutputStream(self): - "java.io.ByteArrayOutputStream" + return "java.io.ByteArrayOutputStream" @JavaClasses.java_import def Date(self): @@ -632,6 +611,10 @@ def EnumeratedAxis(self): def ImageDisplay(self): return "net.imagej.display.ImageDisplay" + @JavaClasses.java_import + def ImageJ(self): + return "net.imagej.ImageJ" + @JavaClasses.java_import def ImgPlus(self): return "net.imagej.ImgPlus" diff --git a/src/napari_imagej/model.py b/src/napari_imagej/model.py new file mode 100644 index 00000000..6066602b --- /dev/null +++ b/src/napari_imagej/model.py @@ -0,0 +1,41 @@ +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.function.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 623e356a..b2ae6e59 100644 --- a/src/napari_imagej/readers/trackMate_reader.py +++ b/src/napari_imagej/readers/trackMate_reader.py @@ -7,7 +7,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, @@ -40,7 +41,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() @@ -51,7 +52,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/settings.py b/src/napari_imagej/settings.py index d6adc5f0..c915fa65 100644 --- a/src/napari_imagej/settings.py +++ b/src/napari_imagej/settings.py @@ -48,7 +48,7 @@ # -- Constants -- default_java_components = [ - "net.imagej:imagej:2.13.1", + "net.imagej:imagej:2.15.0", ] defaults = { "imagej_directory_or_endpoint": "", diff --git a/src/napari_imagej/types/converters/images.py b/src/napari_imagej/types/converters/images.py index 78a93da3..3c4cc1e3 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 f396862b..65b96cdd 100644 --- a/src/napari_imagej/types/converters/labels.py +++ b/src/napari_imagej/types/converters/labels.py @@ -8,7 +8,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 @@ -42,7 +43,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) @@ -56,4 +57,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..fe0ce629 100644 --- a/src/napari_imagej/types/converters/trackmate.py +++ b/src/napari_imagej/types/converters/trackmate.py @@ -6,8 +6,8 @@ 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.java import NijJavaClasses from napari_imagej.types.converters import java_to_py_converter @@ -36,7 +36,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 +106,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 +119,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() @@ -127,7 +127,7 @@ def _trackMate_model_to_tracks(obj: "jc.ROITree"): return model_and_image_to_tracks(model, src_image) -class TrackMateClasses(JavaClasses): +class TrackMateClasses(NijJavaClasses): # TrackMate Types @JavaClasses.java_import diff --git a/src/napari_imagej/types/type_conversions.py b/src/napari_imagej/types/type_conversions.py index f5a0c4fc..c3302f8a 100644 --- a/src/napari_imagej/types/type_conversions.py +++ b/src/napari_imagej/types/type_conversions.py @@ -22,7 +22,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 @@ -202,6 +203,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 719030bd..53c5974a 100644 --- a/src/napari_imagej/utilities/_module_utils.py +++ b/src/napari_imagej/utilities/_module_utils.py @@ -22,7 +22,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 @@ -43,7 +44,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): @@ -190,7 +191,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 @@ -315,7 +316,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, @@ -359,7 +360,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 @@ -390,7 +391,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): @@ -409,7 +410,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()) @@ -440,7 +441,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 @@ -499,7 +500,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) @@ -523,7 +524,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, @@ -653,7 +654,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 6b73e22d..4edc29da 100644 --- a/src/napari_imagej/utilities/event_subscribers.py +++ b/src/napari_imagej/utilities/event_subscribers.py @@ -6,7 +6,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 @@ -52,7 +53,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 @@ -67,7 +68,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""" @@ -120,5 +121,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 f92010b9..5c7eda30 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -14,14 +14,12 @@ from qtpy.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QWidget from scyjava import is_arraylike -from napari_imagej import settings -from napari_imagej.java import ij +from napari_imagej import nij, settings 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 +from napari_imagej.widgets.widget_utils import _IMAGE_LAYER_TYPES, DetailExportDialog class NapariImageJMenu(QWidget): @@ -55,20 +53,19 @@ 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() - .ui() + 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) @@ -85,12 +82,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): @@ -163,7 +160,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() @@ -189,20 +186,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()) @@ -213,7 +210,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) @@ -228,7 +225,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!") @@ -281,7 +278,7 @@ def __init__(self, viewer: Viewer): self._widget = None def _add_repl_to_dock(self): - self._widget = REPLWidget(self.repl) + self._widget = REPLWidget(nij=nij) self._widget.visible = False self.viewer.window.add_dock_widget(self._widget, name="napari-imagej REPL") diff --git a/src/napari_imagej/widgets/napari_imagej.py b/src/napari_imagej/widgets/napari_imagej.py index e21ea640..683d499b 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 @@ -16,7 +16,8 @@ from qtpy.QtWidgets import 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 jc from napari_imagej.utilities._module_utils import _non_layer_widget from napari_imagej.utilities.event_subscribers import ( NapariEventSubscriber, @@ -181,7 +182,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()) @@ -231,8 +232,8 @@ def run(self): Functionality partitioned into functions by subwidget. """ try: - # Initialize ImageJ - init_ij() + # Block until ImageJ2 is initialized. + _ = nij.ij # Finalize the menu self.widget.menu.finalize() # Finalize the search bar @@ -268,33 +269,33 @@ def searchCompleted(self, event: "jc.SearchEvent"): listener_arr = JArray(jc.SearchListener)( [NapariImageJSearchListener(self.widget.result_tree.model().process)] ) - self.widget.result_tree.model()._searchOperation = ( - ij().get("org.scijava.search.SearchService").search(listener_arr) - ) + self.widget.result_tree.model()._searchOperation = 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.model()._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.model().insert_searcher.emit(searcher) 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..591256f9 100644 --- a/src/napari_imagej/widgets/repl.py +++ b/src/napari_imagej/widgets/repl.py @@ -3,29 +3,24 @@ 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.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 +35,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 +53,21 @@ 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.output_textedit.append(f">>> {input_text}") + 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 b6a83f6f..f06d468e 100644 --- a/src/napari_imagej/widgets/result_tree.py +++ b/src/napari_imagej/widgets/result_tree.py @@ -13,7 +13,8 @@ from qtpy.QtWidgets import QAction, QMenu, QTreeView 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 _get_icon, python_actions_for @@ -85,13 +86,13 @@ def __init__( # 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.NORMAL # Set QtPy properties self.setEditable(False) self.setFlags(self.flags() | Qt.ItemIsUserCheckable) - checked = ij().get("org.scijava.search.SearchService").enabled(searcher) + checked = nij.ij.get("org.scijava.search.SearchService").enabled(searcher) self.setCheckState(Qt.Checked if checked else Qt.Unchecked) def __lt__(self, other): @@ -172,7 +173,7 @@ def _generate_result_items(self, event: "jc.SearchEvent") -> List[SearchResultIt def _detect_check_change(self, item): if isinstance(item, SearcherItem): checked = item.checkState() == Qt.Checked - ij().get("org.scijava.search.SearchService").setEnabled( + nij.ij.get("org.scijava.search.SearchService").setEnabled( item.searcher, checked ) if not checked and item.hasChildren(): diff --git a/src/napari_imagej/widgets/widget_utils.py b/src/napari_imagej/widgets/widget_utils.py index 1f03cd09..acaff348 100644 --- a/src/napari_imagej/widgets/widget_utils.py +++ b/src/napari_imagej/widgets/widget_utils.py @@ -22,7 +22,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, @@ -36,7 +37,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 @@ -61,8 +62,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( @@ -76,10 +77,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( @@ -283,15 +284,15 @@ 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()) @lru_cache diff --git a/tests/conftest.py b/tests/conftest.py index 0afda18a..146a9b2f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,8 +9,7 @@ import pytest from napari import Viewer -from napari_imagej import settings -from napari_imagej.java import init_ij +from napari_imagej import nij, settings from napari_imagej.widgets.menu import NapariImageJMenu from napari_imagej.widgets.napari_imagej import NapariImageJWidget @@ -48,10 +47,10 @@ def ij(): # if we don't add this. if sys.platform == "darwin": viewer = Viewer() - ij = init_ij() + ij = nij.ij viewer.close() else: - ij = init_ij() + ij = nij.ij yield ij @@ -92,7 +91,7 @@ def gui_widget(viewer) -> Generator[NapariImageJMenu, None, None]: widget: NapariImageJMenu = NapariImageJMenu(viewer) # Wait for ImageJ initialization - init_ij() + _ = nij.ij # Finalize widget widget.finalize() diff --git a/tests/test_scripting.py b/tests/test_scripting.py index 0f065de9..4fe37b6a 100644 --- a/tests/test_scripting.py +++ b/tests/test_scripting.py @@ -3,8 +3,8 @@ """ from magicgui import magicgui - from scyjava import JavaClasses + from napari_imagej.utilities._module_utils import functionify_module_execution diff --git a/tests/types/test_trackmate.py b/tests/types/test_trackmate.py index b37f9c14..a9b0403e 100644 --- a/tests/types/test_trackmate.py +++ b/tests/types/test_trackmate.py @@ -6,8 +6,8 @@ import numpy as np import pytest -from scyjava import JavaClasses from napari.layers import Labels, Tracks +from scyjava import JavaClasses from napari_imagej import settings from napari_imagej.types.converters.trackmate import TrackMateClasses, trackmate_present diff --git a/tests/utils.py b/tests/utils.py index 013771a1..19d20b56 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,11 +4,11 @@ from typing import List -from napari_imagej.java import NijJavaClasses from jpype import JImplements, JOverride - from scyjava import JavaClasses +from napari_imagej.java import NijJavaClasses + class JavaClassesTest(NijJavaClasses): """ diff --git a/tests/widgets/test_napari_imagej.py b/tests/widgets/test_napari_imagej.py index 875dccb3..9c52c7be 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, @@ -63,7 +63,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: @@ -181,7 +181,7 @@ def test_imagej_search_tree_disable(ij, imagej_widget: NapariImageJWidget, asser # Disable the searcher, assert the proper ImageJ response searcher_item.setCheckState(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 ) ) @@ -189,7 +189,7 @@ def test_imagej_search_tree_disable(ij, imagej_widget: NapariImageJWidget, asser # Enabled the searcher, assert the proper ImageJ response searcher_item.setCheckState(Qt.Checked) asserter( - lambda: ij.get("org.scijava.search.SearchService").enabled( + lambda: nij.ij.get("org.scijava.search.SearchService").enabled( searcher_item.searcher ) ) @@ -201,7 +201,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)) root = imagej_widget.result_tree.model().invisibleRootItem() asserter(lambda: root.rowCount() == numSearchers) @@ -248,12 +248,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 597192d0..bba9054e 100644 --- a/tests/widgets/test_result_runner.py +++ b/tests/widgets/test_result_runner.py @@ -4,8 +4,8 @@ import pytest from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget - from scyjava import JavaClasses + from napari_imagej.widgets.layouts import QFlowLayout from napari_imagej.widgets.result_runner import ResultRunner From 7b56601a7c2eb5117a3dd2c55b9e3bec04abebe1 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 30 Sep 2024 22:08:52 -0500 Subject: [PATCH 13/20] Move button tooltips to ctors --- src/napari_imagej/widgets/menu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index 5c7eda30..1c936238 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -43,11 +43,9 @@ 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(): @@ -268,6 +266,7 @@ class REPLButton(IJMenuButton): def __init__(self, viewer: Viewer): super().__init__(viewer) self.viewer = viewer + self.setToolTip("Show/hide the SciJava REPL") self.setEnabled(False) @@ -300,6 +299,7 @@ class SettingsButton(IJMenuButton): def __init__(self, viewer: Viewer): super().__init__(viewer) + self.setToolTip("Show napari-imagej settings") self.clicked.connect(self._update_settings) self.setting_change.connect(self._handle_settings_change) From 91d25f7f7461d3a298fabe229667645d5f5030d1 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 30 Sep 2024 22:11:55 -0500 Subject: [PATCH 14/20] REPLButton: Add _icon method Dynamically recolors button on appearance changes --- src/napari_imagej/widgets/menu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index 1c936238..fa9d8b91 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -270,12 +270,12 @@ def __init__(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 _icon(self): + return QColoredSVGIcon(resource_path("repl")) + def _add_repl_to_dock(self): self._widget = REPLWidget(nij=nij) self._widget.visible = False From abcf5783cfce90e41a8a81d85a95c4d0fc3d7a24 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 1 Oct 2024 09:46:20 -0500 Subject: [PATCH 15/20] Add simple regression test --- tests/widgets/test_menu.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/widgets/test_menu.py b/tests/widgets/test_menu.py index 9bf2883f..bc607791 100644 --- a/tests/widgets/test_menu.py +++ b/tests/widgets/test_menu.py @@ -237,6 +237,20 @@ def test_GUIButton_layout_headless(popup_handler, gui_widget: NapariImageJMenu): popup_handler(button.clicked.emit, popup_func) +def test_script_repl(asserter, qtbot, ij, viewer: Viewer, gui_widget: NapariImageJMenu): + repl_button = gui_widget.repl_button + + # Press the button - ensure the script repl appears as a dock widget + qtbot.mouseClick(repl_button, Qt.LeftButton, delay=1) + + # the Viewer gives us no (public) API to query dock widgets, + # so the best we can do is to ensure that the parent is set on our widget + def find_repl() -> bool: + return repl_button._widget.parent() is not None + + asserter(find_repl) + + def test_active_data_send(asserter, qtbot, ij, gui_widget: NapariImageJMenu): if settings.headless(): pytest.skip("Only applies when not running headlessly") From 69b8b4f8d1504e5ff59eeee3abd94091f501541f Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 1 Oct 2024 11:45:50 -0500 Subject: [PATCH 16/20] Move REPL docs to the Usage page --- doc/Configuration.rst | 9 --------- doc/Usage.rst | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/Configuration.rst b/doc/Configuration.rst index e4f21e21..c1d395a0 100644 --- a/doc/Configuration.rst +++ b/doc/Configuration.rst @@ -95,15 +95,6 @@ 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/doc/Usage.rst b/doc/Usage.rst index bb2bd435..10406807 100644 --- a/doc/Usage.rst +++ b/doc/Usage.rst @@ -57,3 +57,13 @@ Note that these buttons are only enabled when there is a ``Layer`` that can be t Using the |advanced export| button, users can provide metadata for richer data transfer to the ImageJ UI The |import| button can be used to transfer the **active** ImageJ window back into the napari application. + +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. + From 2b233b9674d4f62c355656bc7da79069b6f12c76 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 1 Oct 2024 13:32:43 -0500 Subject: [PATCH 17/20] Small bugfixes --- src/napari_imagej/java.py | 5 ++++- src/napari_imagej/model.py | 9 +++++++-- src/napari_imagej/widgets/menu.py | 5 ++++- src/napari_imagej/widgets/repl.py | 15 +++++++++------ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/napari_imagej/java.py b/src/napari_imagej/java.py index 8c6d63b9..b7a9fb8f 100644 --- a/src/napari_imagej/java.py +++ b/src/napari_imagej/java.py @@ -152,7 +152,6 @@ def _optional_requirements(ij: "jc.ImageJ"): class NijJavaClasses(JavaClasses): - # Java Primitives @JavaClasses.java_import @@ -353,6 +352,10 @@ def SciJavaEvent(self): def ScriptREPL(self): return "org.scijava.script.ScriptREPL" + @JavaClasses.java_import + def ScriptService(self): + return "org.scijava.script.ScriptService" + @JavaClasses.java_import def Searcher(self): return "org.scijava.search.Searcher" diff --git a/src/napari_imagej/model.py b/src/napari_imagej/model.py index 6066602b..aec8ea84 100644 --- a/src/napari_imagej/model.py +++ b/src/napari_imagej/model.py @@ -33,8 +33,13 @@ def accept(self, t): for callback in model._repl_callbacks: callback(s) - self._repl = jc.ScriptREPL(ctx, "jython", REPLOutput()) - self._repl.lang("jython") + scriptService = ctx.service(jc.ScriptService) + # Find a Pythonic script language (might be Jython) + names = [lang.getLanguageName() for lang in scriptService.getLanguages()] + name_pref = next(n for n in names if "python" in n.toLowerCase()) + self._repl = jc.ScriptREPL(ctx, name_pref, REPLOutput()) + # NB: Adds bindings + self._repl.initialize(True) return self._repl def add_repl_callback(self, repl_callback) -> None: diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index fa9d8b91..c2c8ca1a 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -279,7 +279,10 @@ def _icon(self): def _add_repl_to_dock(self): self._widget = REPLWidget(nij=nij) self._widget.visible = False - self.viewer.window.add_dock_widget(self._widget, name="napari-imagej REPL") + self.viewer.window.add_dock_widget( + self._widget, + name="napari-imagej REPL", + ) def _toggle_repl(self): """ diff --git a/src/napari_imagej/widgets/repl.py b/src/napari_imagej/widgets/repl.py index 591256f9..f43b482b 100644 --- a/src/napari_imagej/widgets/repl.py +++ b/src/napari_imagej/widgets/repl.py @@ -20,15 +20,9 @@ def __init__(self, nij: NapariImageJ, parent: QWidget = None): """ 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) @@ -41,6 +35,15 @@ def __init__(self, nij: NapariImageJ, parent: QWidget = None): self.input_lineedit.returnPressed.connect(self.process_input) layout.addWidget(self.input_lineedit) + self.script_repl = nij.repl + self.language_combo.addItems( + [str(el) for el in list(self.script_repl.getInterpretedLanguages())] + ) + self.language_combo.setCurrentText( + str(self.script_repl.getInterpreter().getLanguage()) + ) + self.language_combo.currentTextChanged.connect(self.change_language) + def change_language(self, language: str): """ Change the active scripting language of the REPL. From c8dd1c14260f49a4a8748e0289addaef3fc8b68c Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 1 Oct 2024 14:29:41 -0500 Subject: [PATCH 18/20] Move ScriptREPL widget to bottom --- src/napari_imagej/widgets/menu.py | 3 +-- src/napari_imagej/widgets/repl.py | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/napari_imagej/widgets/menu.py b/src/napari_imagej/widgets/menu.py index c2c8ca1a..381494ab 100644 --- a/src/napari_imagej/widgets/menu.py +++ b/src/napari_imagej/widgets/menu.py @@ -280,8 +280,7 @@ def _add_repl_to_dock(self): self._widget = REPLWidget(nij=nij) self._widget.visible = False self.viewer.window.add_dock_widget( - self._widget, - name="napari-imagej REPL", + self._widget, name="napari-imagej REPL", area="bottom" ) def _toggle_repl(self): diff --git a/src/napari_imagej/widgets/repl.py b/src/napari_imagej/widgets/repl.py index f43b482b..ac5f81c0 100644 --- a/src/napari_imagej/widgets/repl.py +++ b/src/napari_imagej/widgets/repl.py @@ -5,7 +5,14 @@ """ from qtpy.QtGui import QTextCursor -from qtpy.QtWidgets import QComboBox, QLineEdit, QTextEdit, QVBoxLayout, QWidget +from qtpy.QtWidgets import ( + QComboBox, + QHBoxLayout, + QLineEdit, + QTextEdit, + QVBoxLayout, + QWidget, +) from napari_imagej.model import NapariImageJ @@ -22,18 +29,21 @@ def __init__(self, nij: NapariImageJ, parent: QWidget = None): layout = QVBoxLayout(self) - self.language_combo = QComboBox(self) - 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)) + h_layout = QHBoxLayout() + layout.addLayout(h_layout) + + self.language_combo = QComboBox(self) + h_layout.addWidget(self.language_combo) + self.input_lineedit = QLineEdit(self) self.input_lineedit.returnPressed.connect(self.process_input) - layout.addWidget(self.input_lineedit) + h_layout.addWidget(self.input_lineedit) self.script_repl = nij.repl self.language_combo.addItems( From c9bdf44f88d836a80a5a86b78a186123f790906b Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 1 Oct 2024 14:30:13 -0500 Subject: [PATCH 19/20] Usage.rst: Update image link --- doc/Usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Usage.rst b/doc/Usage.rst index 10406807..64be7ba8 100644 --- a/doc/Usage.rst +++ b/doc/Usage.rst @@ -63,7 +63,7 @@ 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 +.. figure:: https://media.imagej.net/napari-imagej/0.2.0/script_repl.png The REPL can be shown/hidden by clicking on the command prompt icon. From 4dbbc62460ebb93a34e4fec5570e3167071b8129 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 1 Oct 2024 14:54:19 -0500 Subject: [PATCH 20/20] CI: Use Miniforge --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0c2df58..e7f8d356 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,7 +122,7 @@ jobs: # Create env with dev packages auto-update-conda: true python-version: "3.10" - miniforge-variant: Mambaforge + miniforge-version: latest environment-file: dev-environment.yml # Activate napari-imagej-dev environment activate-environment: napari-imagej-dev