{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# SciJava in Detail" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook dives into technical details of the SciJava Common library upon which ImageJ2 is built.\n", "\n", "It is recommended that you first read and understand the Fundamentals of ImageJ notebook before tackling this one.\n", "\n", "Let's get started by discussing a little bit about the [Architecture](http://imagej.net/Architecture) of ImageJ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The ImageJ software stack" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [ImageJ software stack](http://imagej.net/Architecture#Core_libraries) is composed of the following core libraries:\n", "\n", "
\n", "\n", "* [SciJava Common](http://imagej.net/SciJava_Common) - The SciJava application container and plugin framework.\n", "* [ImgLib2](http://imagej.net/ImgLib2) - The N-dimensional image data model.\n", "* [ImageJ Common](http://imagej.net/ImageJ_Common) - Metadata-rich image data structures and SciJava extensions.\n", "* [ImageJ Ops](http://imagej.net/ImageJ_Ops) - The framework for reusable image processing operations.\n", "* [SCIFIO](http://imagej.net/SCIFIO) - The framework for N-dimensional image I/O.\n", "\n", "These libraries form the basis of ImageJ-based software.\n", "\n", "
\n", "\n", "Important design goals of ImageJ include:\n", "\n", "* __Modularity.__ ImageJ libraries provide a good [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns). Developers in need of specific functionality may depend on only those components which are relevant, rather than needing to add a dependency to the entire ImageJ software stack.\n", "\n", "* __UI agnosticm.__ The core libraries take great pains to be _UI agnostic_ with no dependencies on packages such as `java.awt` or `javax.swing`. It should be possible to build a [user interface](https://en.wikipedia.org/wiki/Graphical_user_interface) (UI) on top of the core libraries without needing to change the library code itself. There are several proof-of-concept UIs for ImageJ using different UI frameworks, including [Swing](https://github.com/imagej/imagej-ui-swing), [AWT](https://github.com/imagej/imagej-ui-awt), [Apache Pivot](https://github.com/scijava/scijava-ui-pivot) and [JavaFX](https://github.com/cmongis/imagejfx).\n", "\n", "* __Extensibility.__ ImageJ provides many different types of plugins, and it is possible to extend the system with your own new types of plugins. See the \"Extending ImageJ\" tutorials for details.\n", "\n", "For further details, see the [Architecture](https://imagej.net/Architecture) page." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SciJava plugins" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First and foremost, [SciJava Common](http://imagej.net/SciJava_Common) is a plugin framework—a base for developing highly modular and extensible Java applications. All plugins available on Java's classpath are automatically discovered and made available. This is accomplished by scanning classpath resources for the file path META-INF/json/org.scijava.plugin.Plugin. Such files are generated at compile time by a Java annotation processor that writes them in response to @Plugin annotations on Java classes.\n", "\n", "For example, here is the metadata describing plugins of the `net.imagej:imagej` artifact itself:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Added new repo: imagej.public\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "03dd6547-cbe0-43ea-bd2f-7dd66e8a9184", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "{\"class\":\"net.imagej.ImageJ\",\"values\":{\"type\":\"org.scijava.Gateway\"}}\n", "{\"class\":\"net.imagej.app.ToplevelImageJApp\",\"values\":{\"name\":\"ImageJ\",\"priority\":101.0,\"type\":\"org.scijava.app.App\"}}" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%classpath config resolver imagej.public https://maven.imagej.net/content/groups/public\n", "%classpath add mvn net.imagej imagej 2.0.0-rc-71\n", "net.imagej.ImageJ.class.getResource(\"/META-INF/json/org.scijava.plugin.Plugin\").\n", " openStream().getText().replaceAll(\"\\\\}\\\\{\", \"}\\n{\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This metadata aligns precisely with the `@Plugin` declarations in `ImageJ.java` and `ToplevelImageJApp.java`, respectively:\n", " \n", "```java\n", "@Plugin(type = Gateway.class)\n", "public class ImageJ extends AbstractGateway\n", "\n", "@Plugin(type = App.class, name = ImageJApp.NAME,\n", "\tpriority = ImageJApp.PRIORITY + 1)\n", "public class ToplevelImageJApp extends ImageJApp\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Comparison with ImageJ 1.x\n", "\n", "Here is a \"cheat sheet\" listing the available plugin types of ImageJ 1.x, and their ImageJ2 counterparts:\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Plugin type ImageJ 1.x ImageJ2
General-purpose commandij.plugin.PlugInnet.imagej.command.Command
Image processing operationij.plugin.filter.PlugInFilternet.imagej.ops.Op
Tool (toolbar icon + behavior)ij.plugin.tool.PlugInToolnet.imagej.tool.Tool
File format reader/writerij.plugin.PlugIn + HandleExtraFileTypesorg.scijava.io.IOPlugin
\n", "\n", "There are many other SciJava and ImageJ2 plugin types; see Fundamentals of ImageJ for a complete list." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The SciJava `Context`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Everything in a SciJava application is unified by a single `org.scijava.Context` object: a collection of `Service` plugins, each of which provide [API](https://en.wikipedia.org/wiki/Application_programming_interface) and holds local state information. The `Context` keeps track of the currently available _plugins_ and _services_.\n", "\n", "Each application is responsible for creating its own `Context` to manage plugins and contextual state.\n", "\n", "Consider the following (hopefully familiar by now) invocation:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "ImageJ v2.0.0-rc-71 is ready to go." ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" }, { "name": "stdout", "output_type": "stream", "text": [ "[INFO] Success!\n" ] } ], "source": [ "ij = new net.imagej.ImageJ()\n", "\"ImageJ v${ij.getVersion()} is ready to go.\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The cell above creates a new `net.imagej.ImageJ` gateway object, which creates a new `org.scijava.Context` internally.\n", "\n", "You can obtain the `Context` itself from the gateway:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "org.scijava.Context@28191fb1" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ctx = ij.context()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Context` keeps two very central data structures: a `PluginIndex` and a `ServiceIndex`:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "There are 1562 plugins, 91 of which are services." ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"There are ${ctx.getPluginIndex().size()} plugins, ${ctx.getServiceIndex().size()} of which are services.\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Requesting services" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here are five ways to request services.\n", "\n", "### 1. Retrieve a core service from the gateway" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "org.scijava.event.DefaultEventService [priority = 100000.0]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ij.event()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2. Retrieve it from the context via `service`" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "org.scijava.event.DefaultEventService [priority = 100000.0]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ctx.service(org.scijava.event.EventService.class)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the service does not exist, `service` throws `NoSuchServiceException`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3. Retrieve it from the context via `getService`" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "org.scijava.event.DefaultEventService [priority = 100000.0]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ctx.getService(org.scijava.event.EventService.class)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the service does not exist, `getService` returns `null`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4. Declare a `#@` script parameter\n", "\n", "When writing a [SciJava script](https://imagej.net/Scripting), you can use the [`#@` parameter syntax](https://imagej.net/Script_Parameters):" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Got an EventService: org.scijava.event.DefaultEventService [priority = 100000.0]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "script = \"\"\"\n", "#@ EventService es\n", "\"Got an EventService: \" + es\n", "\"\"\"\n", "ij.script().run('script.groovy', script, true).get().getReturnValue()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If no such service is available, an exception will be thrown." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5. Annotate a field with `@Parameter`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When writing a Java class, the `@Parameter` annotation can be used to mark fields for which you want SciJava services to be injected automatically.\n", "\n", "Typically, ImageJ plugin developers will be writing `Service` and/or `Command` plugins. If you need to use another plugin—for example, the `LogService`—you should not manually create it as this effectively disconnects you from your `Context`. Instead, you should ask your `Context` for an instance by adding a field of the desired type and annotating it with the `@Parameter` annotation. For example:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "MyPlugin" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import org.scijava.command.Command\n", "import org.scijava.log.LogService\n", "import org.scijava.plugin.Parameter\n", "import org.scijava.plugin.Plugin\n", "\n", "@Plugin(type = Command.class)\n", "public class MyPlugin implements Command {\n", " \n", " // This @Parameter notation is 'asking' the Context\n", " // for an instance of LogService.\n", " @Parameter\n", " private LogService log;\n", " \n", " @Parameter\n", " private String message;\n", " \n", " @Override\n", " public void run() {\n", " // Just use the LogService!\n", " // There is no need to construct it, since the Context\n", " // has already provided an appropriate instance.\n", " log.info(message);\n", " }\n", "}\n", "\n", "// Save a reference to the class for later.\n", "myPluginClass = MyPlugin.class\n", "\n", "// TODO: Figure out why the log message in this command is swallowed.\n", "// Execute our sample command.\n", "ij.command().run(MyPlugin.class, true, \"message\", \"Success!\").get()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This will allow the Context to provide you with—i.e., [inject](https://en.wikipedia.org/wiki/Dependency_injection)—the appropriate instance of your requested service." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In some rare cases, manual plugin construction is unavoidable. Understand that if the MyPlugin class above is manually constructed—i.e. via new MyPlugin()—the LogService parameter will be null. Automatic population only occurs if the plugin instance itself is retrieved via the framework. When you must manually construct a plugin instance, you can still re-connect it to an existing Context via its injection mechanism:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[INFO] null\n" ] }, { "data": { "text/plain": [ "null" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Manually create a plugin instance.\n", "// It is not connected to a Context yet\n", "plugin = new MyPlugin()\n", " \n", "// Inject the plugin instance with our Context.\n", "ctx.inject(plugin)\n", " \n", "// Now that our plugin is injected, we can use it with the\n", "// knowledge that its service parameters have been populated.\n", "plugin.run() // but message is still null" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Services" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Services](http://imagej.net/SciJava_Common#Services) are—surprise!—SciJava Plugins. Just like plugins, there are Service interfaces and implementing classes. This allows a proper separation between the Service's public contract and the details of its implementation.\n", "\n", "Services are defined as interfaces, with concrete implementations as plugins. This design provides [seams](http://c2.com/cgi/wiki?SoftwareSeam) in the right places so that behavior at every level can be customized and overridden.\n", "\n", "Services provide two important functions to the SciJava framework: utility methods and persistent state. If you want to add reusable Java methods that can be used throughout the SciJava framework, then you should create a Service to provide this functionality. If you need to track Context-wide variables or configuration, a Service should be used to encapsulate that state.\n", "\n", "Conceptually, a Service satisfies the role of [static utility classes](https://en.wikipedia.org/wiki/Utility_class) on a per-Context basis. In this way, only one [instance](http://math.hws.edu/javanotes/c5/s1.html ) of each Service class can be associated with a given Context instance; an association that occurs automatically during Context creation. Furthermore, when a Context is asked for an implementation of a given Service, only the highest priority instance will be returned.\n", "\n", "Services often build on or reuse functionality defined in each other. For example, the PluginService sees ubiquitous use in retrieving and working with plugin instances. For such reuse, @Parameter annotation can be used to declare inter-service requirements. During Context startup, these relationships will be resolved automatically." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "HelloService ready" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import org.scijava.service.SciJavaService\n", "import org.scijava.service.Service\n", "import org.scijava.service.AbstractService\n", "import org.scijava.app.StatusService\n", "import org.scijava.plugin.Plugin\n", "import org.scijava.plugin.Parameter\n", "\n", "// Example Service Interface:\n", "public interface HelloService extends SciJavaService {\n", " public void sayHello()\n", "}\n", "\n", "// Example implementation:\n", "@Plugin(type = Service.class)\n", "public class DefaultHelloService extends AbstractService implements HelloService {\n", "\n", " @Parameter\n", " private StatusService status;\n", "\n", " @Override\n", " public void initialize() {\n", " // initialize as little as possible here\n", " }\n", "\n", " @Override\n", " public void sayHello() {\n", " status.showStatus(\"Howdy!\");\n", " }\n", "}\n", "\n", "\"HelloService ready\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Commands" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whereas `Service`s provide internal functionality, `Command`s are plugins designed to be executed as one-offs, typically interacting with users to achieve some desired outcome. When opening the ImageJ GUI, Commands are what populate your menu structure: exposing functionality and algorithms in a way that can be consumed by non-developers.\n", "\n", "When writing `Command`s you will often declare `@Parameter`s on fields that _cannot_ be resolved automatically by the `Context`—for example, numeric values or file paths. Instead of being instantiated at `Context` startup as a `Service` would be, `Command`s are created and executed on demand.\n", "\n", "When a `Command` is executed, it goes through a series of pre-processing steps to populate its `@Parameter`s using its associated `Context`. If any parameters are left unresolved and a UI is available, the framework will automatically build and display an appropriate dialog to get user input. In this way, input harvesting is decoupled from functional operation—allowing developers to focus on what's really important without repetition of code. This also means that `Command`s can typically run [headlessly](http://imagej.net/Headless) without any extra development effort.\n", "\n", "A common pattern in `Command` development is to wrap `Service` functionality. For example, opening an image from a path is a fundamental operation in ImageJ. To this end, developers can directly use the `DatasetIOService`. Users then get this same functionality from the menus via the `OpenDataset` command—which itself simply calls into the `DatasetIOService`.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Gateways" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A `Gateway` is a plugin intended to make life easier for developers. It wraps a `Context`, offering type-safe access to core services. Everything you can do with a gateway you can also do without it—but the gateway object makes the API much more succinct and convenient.\n", "\n", "Each major layer of the ImageJ software stack has its own `Gateway`:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ce5070b2-b58f-47fd-9fb2-37f09ed43ef8", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ij.plugin().getPluginsOfType(org.scijava.Gateway.class).stream().map{info -> [\n", " \"Class\": info.loadClass().getName(),\n", " \"Location\": info.getLocation().replaceAll('.*/(.*\\\\.jar)$', '$1')\n", "]}.collect()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "TODO: corresponding service marker interfaces: `SciJavaService`, `ImageJService`, `SCIFIOService`" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a3ab94ed-3555-4bc6-beaf-54cd280a0435", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// Create a new SciJava gateway wrapping our existing Context.\n", "sj = new org.scijava.SciJava(ctx)\n", "\n", "// Now bask in the convenience!\n", "import org.scijava.service.Service\n", "[\n", " \"Plugin count\" : sj.plugin().getPlugins().size(),\n", " \"Module count\" : sj.module().getModules().size(),\n", " \"Service count\" : sj.plugin().getPluginsOfType(Service.class).size(),\n", " \"SciJava version\" : sj.getVersion(),\n", " \"Where is SciJava?\" : sj.getApp().getLocation()\n", "]" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d296eba0-3dc0-4a42-9971-23244eaf90da", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// We don't _need_ the gateway; we could use each service directly instead.\n", "pluginService = ctx.service(org.scijava.plugin.PluginService.class)\n", "moduleService = ctx.service(org.scijava.module.ModuleService.class)\n", "\n", "import org.scijava.service.Service\n", "[\n", " \"Plugin count\" : pluginService.getPlugins().size(),\n", " \"Module count\" : moduleService.getModules().size(),\n", " \"Service count\" : pluginService.getPluginsOfType(Service.class).size()\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Other plugins and services" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because virtually everything is a plugin in ImageJ, there are too many to explicitly enumerate, let alone cover in a tutorial. To get ideas for functionality that can be added, a good starting point is to look for services in the [javadoc](http://javadoc.imagej.net/), or the [ImageJ search portal](http://search.imagej.net/ ). Many service types have supplemental plugins for easy functional extension. In particular, the [imagej-common](http://imagej.net/ImageJ_Common) and [scijava-common](http://imagej.net/SciJava_Common) repositories will contain plugin definitions for many essential operations.\n", "\n", "A brief list of some of the more useful plugin types to extend:\n", "\n", "* Ops provide a reusable set of image processing algorithms.\n", "* Image formats allow new types of images to be opened in ImageJ.\n", "* Converters allow the framework to interchange types, outside of normal Java class hierarchy restrictions.\n", "* Input Preprocessors give you control over the population of @Parameters.\n", "* Displays control how UI elements are presented to users.\n", "\n", "If you know the function you want to modify but can't determine its location in the code, please [ask other developers](http://imagej.net/Help). You're part of the community now!\n" ] } ], "metadata": { "kernelspec": { "display_name": "Groovy", "language": "groovy", "name": "groovy" }, "language_info": { "codemirror_mode": "groovy", "file_extension": ".groovy", "mimetype": "", "name": "Groovy", "nbconverter_exporter": "", "version": "2.4.3" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "307px" }, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }