{"id":19093389,"url":"https://github.com/imagej/ij1-patcher","last_synced_at":"2025-04-30T12:44:08.059Z","repository":{"id":14328941,"uuid":"17038357","full_name":"imagej/ij1-patcher","owner":"imagej","description":"Extension points for ImageJ via runtime patching to support (limited) headless operation and ImageJ2's legacy layer","archived":false,"fork":false,"pushed_at":"2025-03-17T20:36:08.000Z","size":884,"stargazers_count":5,"open_issues_count":8,"forks_count":6,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-04-19T02:23:12.469Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imagej.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2014-02-20T22:47:56.000Z","updated_at":"2025-03-17T20:36:12.000Z","dependencies_parsed_at":"2022-09-07T10:11:55.906Z","dependency_job_id":"774caa7d-0f56-4ebf-9baa-69fa1dbbb032","html_url":"https://github.com/imagej/ij1-patcher","commit_stats":null,"previous_names":[],"tags_count":51,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imagej%2Fij1-patcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imagej%2Fij1-patcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imagej%2Fij1-patcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imagej%2Fij1-patcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imagej","download_url":"https://codeload.github.com/imagej/ij1-patcher/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251702961,"owners_count":21630130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T03:24:27.089Z","updated_at":"2025-04-30T12:44:08.035Z","avatar_url":"https://github.com/imagej.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![](https://img.shields.io/maven-central/v/net.imagej/ij1-patcher.svg)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22net.imagej%22%20AND%20a%3A%22ij1-patcher%22)\n[![](https://github.com/imagej/ij1-patcher/actions/workflows/build-main.yml/badge.svg)](https://github.com/imagej/ij1-patcher/actions/workflows/build-main.yml)\n\n# Welcome to the ImageJ patcher\n\n## What is it?\n\nThe `ij1-patcher` injects extension points into ImageJ -- i.e., code that\nallows code outside of ImageJ to override some functions in ways that ImageJ's\ndesign does not normally support. For example, it becomes possible to open a\nsophisticated, syntax-highlighting editor instead of ImageJ's default text\neditor.\n\nThe patches optionally include (limited) support for so-called \"headless\" mode:\nrunning ImageJ without a graphical user environment. See below for details.\n\nIt also offers a convenient way to encapsulate multiple ImageJ \"instances\"\nfrom each other (ImageJ relies heavily on static settings;\n`IJ.getInstance()` is supposed to return *the* only ImageJ instance): a\n`LegacyEnvironment` contains a completely insulated class loader that does not\ninterfere with ImageJ instances living in other class loaders. Example:\n\n```java\n\t// The first parameter is a class loader, asking for a new,\n\t// special-purpose class loader to be created; the second parameter\n\t// asks for headless mode.\n\tLegacyEnvironment ij1 = new LegacyEnvironment(null, true);\n\tij1.runMacro(\"print('Hello, world!');\");\n```\n\n## How can I use it?\n\nAdd the following section to your `pom.xml` file (if you do not use\n[Maven](https://maven.apache.org/) yet, [you should](https://imagej.net/Maven)):\n\n```xml\n\t\u003cdependencies\u003e\n\t\t...\n\t\t\u003cdependency\u003e\n\t\t\t\u003cgroupId\u003enet.imagej\u003c/groupId\u003e\n\t\t\t\u003cartifactId\u003eij1-patcher\u003c/artifactId\u003e\n\t\t\t\u003cversion\u003e0.1.0\u003c/version\u003e\n\t\t\u003c/dependency\u003e\n\t\u003c/dependencies\u003e\n```\n\nThen, create a `LegacyEnvironment`:\n\n```java\n\t// The first parameter is a class loader, asking for a new,\n\t// special-purpose class loader to be created; the second parameter\n\t// asks for headless mode.\n\tLegacyEnvironment ij1 = new LegacyEnvironment(null, true);\n\tij1.runMacro(\"open('\" + path + \"');\");\n\t[... process the image ...]\n\tij1.runMacro(\"saveAs('jpeg', '\" + outputPath + \"');\");\n```\n\nNote: it is not currently possible to inject an `ImagePlus` instance into the\nlegacy environment directly: There will be two different definitions of the\n`ImagePlus` class involved -- the one from the calling class loader and the one\nin the encapsulated class loader.  It is impossible to assign an instance of one\nto a variable of the other.\n\n## How does it work?\n\nThe runtime patches are applied through [Javassist](http://www.javassist.org), a\nlibrary offering tools to manipulate Java bytecode. This is needed to get the\nchanges into ImageJ code. The runtime patches live in `LegacyInjector`,\n`LegacyExtensions` and `LegacyHeadless` (the latter being applied only when the\nheadless hacks are asked for).\n\nTo offer an encapsulated ImageJ instance, a new special-purpose class loader\n(to be precise, a LegacyClassLoader) is created that contains only the patched\nImageJ classes, plus a select few classes required for callbacks. In\naddition, it will share the very special `LegacyHooks` class with the calling\nclass loader (i.e. this class will not be defined in the legacy class loader,\nbut its class definition as per the calling class loader will be reused).\n\nThe patched ImageJ classes interact with the \"outside\" world via the\n`LegacyHooks` class, an abstract base class which gets called by the patched\nImageJ classes at appropriate times, e.g. when an exception needs to be\ndisplayed.\n\nBy default, the patched ImageJ will instantiate the `EssentialLegacyHooks`\nspecialization of the `LegacyHooks` and install these hooks into the `_hooks`\nfield that gets patched into the `ij.IJ` class. That way, there is always an\ninstance, and the patched code does not check for `null` first. The\n`EssentialLegacyHooks` will also look for an initializer class -- to be loaded\nin ImageJ `PluginClassLoader` -- and if one is found, instantiate and run\nit as a `Runnable`. By default, the initializer class is\n`net.imagej.legacy.plugin.LegacyInitializer` -- to support ImageJ2 -- but it can\nbe overridden by setting the system property `ij1.patcher.initializer` to the\nclass name to use instead.\n\nTo add new extensions, the `LegacyExtensions` class should be extended by\n* adding the necessary extension points at the end of the `LegacyHooks` class\n  (with default implementations, for forward compatibility, so that users of\n  `ij1-patcher` are able to use subclasses of `LegacyHooks` without requiring\n  changes after upgrading to a new `ij1-patcher` version),\n* adding a new method to the end that applies the necessary runtime patches,\n* and calling that method in `LegacyExtensions`' `injectHooks` method.\n\nAs the `LegacyHooks` class definition needs to be shared with the calling class\nloader, it **must not** use any ImageJ classes!\n\nWhen configuring the `LegacyEnvironment` -- e.g. disabling handling for the\nij1.plugin.dirs system property -- what really happens is that a `Callback`\nis added to the `LegacyInjector` instance of the environment. This callback\nwill patch the constructor of the `EssentialLegacyHooks` accordingly. As that\n`EssentialLegacyHooks` class definition will be written out when using the\n`writeJar` method, the configuration will be hard-coded into the written-out\n`.jar` file, too.\n\nFor details about the headless mode, see the section below.\n\n## Where does it come from?\n\n[ImageJ](https://imagej.net/software/imagej) is a very successful project that\n-- as any major project -- benefits from some refactoring from time to time:\n\n* A first attempt at improving ImageJ was made in the\n\t[`ImageJA`](https://imagej.net/libs/imageja) project, which started as a\n\tlightweight fork of ImageJ, then became a Mavenized version of ImageJ,\n\tautomatically tracking ImageJ's releases, and finally was retired in 2022\n\tonce ImageJ itself started publishing its own releases to Maven Central.\n\n* [Fiji](https://fiji.sc/), a related project aiming to provide a distribution of\n\tImageJ, tapped into the ImageJA project to provide extension points not offered\n\tby ImageJ, and later also to provide the headless mode (see below). Over\n\ttime, maintaining the ImageJA fork became very burdensome.\n\n* With [ImageJ2](https://imagej.net/software/imagej2), a major effort was\n\tstarted to provide a new software architecture. The benefits to the Fiji\n\tproject were immediately obvious and over the course of time, Fiji first\n\timitated ImageJ2's approach by replacing the ImageJA-specific ImageJ patches\n\twith runtime patches (see above). Later, the Fiji-specific patches moved from\n\tFiji's `fiji-compat` component into ImageJ's `ij-legacy` component.\n\n* In the last step, the runtime patching code in ImageJ was separated out from\n  the legacy service code, giving rise to this here `ij1-patcher` project.\n\n## What is this \"headless\" mode about?\n\nTraditionally, ImageJ was only ever intended to be a single application for a\nsingle user on one single machine with one single task at each given moment.\nThat implementation detail shows through the way macros are recorded: in order\nto make a plugin recordable, one has to instantiate a `GenericDialog` and show\nit to the user. When the same plugin is played back from a macro, the dialog's\nvalues are populated from the macro options and the dialog is not displayed.\n\nThe problem is when the dialog cannot be instantiated because Java is running\nwithout a user interface. Which is quite common in today's clouds.\n\nOriginally devised in ImageJA (see above), `ij1-patcher`'s headless mode\nprovides *limited* support to run in a headless environment. It does so by\nreplacing `GenericDialog`'s superclass with a fake dialog class that does not\nrequire a graphical user environment. This works with well-behaved plugins that\nuse the `GenericDialog` class in the intended way, but it breaks down when the\nplugin tries to display GUI elements or assumes that the dialog itself is a\nsubclass of `java.awt.Dialog`.\n\nSummary: the headless mode works for most plugins but fails for plugins assuming\nthat a graphical user environment is readily available.\n\nPlease see the ImageJ wiki's\n[Running Headless](https://imagej.net/learn/headless) page for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimagej%2Fij1-patcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimagej%2Fij1-patcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimagej%2Fij1-patcher/lists"}