[ImageJ-devel] (headless) command execution

Curtis Rueden ctrueden at wisc.edu
Thu Apr 10 15:21:46 CDT 2014


Hi Stefan,

> Maybe I was unclear on this issue. May question is, what does a method
> of a Command (e.g. its initializer, the run method) or rather the
> implementor may assumed to have "happend".

No worries, you were clear. The term "preconditions" is a technical
software engineering term, so I know what you meant. I just dodged the
question a bit there, figuring my detailed answers later would clarify
everything. But I guess it still was not clear, sorry...

* The run() method of a module can assume that all required input
parameters have been filled (i.e., are non-null). And the corresponding
post-condition of run() is that all required output parameters are now
filled.

* The initialize() method of a module cannot necessarily assume anything
about the state of its parameters. As you pointed out, ImageJ2 has various
commands right now that *do* assume certain parameters have been filled via
preprocessing. This works in practice (at least from the ImageJ
application), but is not particularly rigorous.

* Similarly, ImageJ2 has several commands which assume their initializer
has been called, but it is not stated as a formal precondition. Most
probably it makes sense to state that in the javadoc, would you agree? In
other words: if you aren't going to include the InitPreprocessor in the
preprocessing chain, you are still somehow responsible for calling
module.initialize() before calling module.run(). Is that fair?

So the main sticky point is what to do about initialize() requiring certain
parameters to be already filled. This is useful for dynamic modules, but
also just for computing defaults -- e.g., setting default
brightness/contrast min & max values to the current display min max of the
input image. So it seems there are two "layers" of input parameters: those
needed before initialize() and those not needed for it.

Maybe we need a way to formally flag these "required before initialize"
parameters in the annotation? What do you think? Would that help you?

In any case, now is the perfect time to get the module framework fully
solidified, since we are on the cusp of migrating it into the
(non-image-specific) SciJava layer. After the June 1 release, we will not
be able to introduce any more breaking changes.

> Besides interesting for a programmer of a Command we are interested in
> this issue to be able to wrap IJ2 commands in order to run them from
> our GUI (originaly design for alida operators), our command line
> runner, and to support them in our graphical editor for alida/mitobo
> operators and hopefully ij2 commands (at least head less and non
> DynamicCommands)

One thing that may interest you is the recent ImageJ OPS announcement:
   http://developer.imagej.net/2014/04/04/announcing-imagej-ops

An Op is just an ImageJ command intended to be fully functional -- i.e.,
all inputs and outputs declared explicitly as parameters, with no side
effects. They are essentially "lower level" commands intended for image
processing, and they might be another good integration point for Alida.
Really, your current implementation should gain access to them "for free"
since they are just a subtype of Commands, but perhaps it makes sense to
special case them somehow since they are in many ways friendlier for
headless & non-dynamic situations?

> I am not sure whether we can recycle all IJ2s preprocessors as there
> might be differences in the concept of parameter handling.

Actually, there is a fair chance you could. It's just that the
ActiveFooPreprocessor style things may either: A) always return null for
you; or B) be populated via some alternate means that you can specify by
extending the DefaultDisplayService, DefaultImageDisplayService, etc., with
overridden method implementations that draw the "active" objects from
somewhere else.

Alternately, it is just fine if they are always null, as long as you pass
them in via the moduleService#run method "Object..." or "Map<String,
Object>" arguments.

> In the attached zip of a tiny maven project using ij2 (2.0.0-beta-7.8)
> there is a command RunTestOp which tries to invoke a IJTestOp command
> "by hand".

It seems the mailing list stripped your attachment. Can you please post on
GitHub?

> I first create a CommandInfo for IJTestOp, then a CommandModule for this
info,
> collects all preprocessors and (try to) invoke them for the CommandModule
created.
> I disabled invoking of the InitPreprocessor, as it crashes with a null
> pointer exception:
>   Exception in thread "main" java.lang.NullPointerException at
>     imagej.command.CommandModule.initialize(CommandModule.java:144)

That error indicates that the CommandModule did not have a context injected
properly. From your description, it sounds like your code is more low-level
than it needs to be. But I'd have to see the code to be certain.

For customizing which pre- and post-processors get run, it is enough to do:

pre = pluginService.createInstancesOfType(PreprocessorPlugin.class);
post = pluginService.createInstancesOfType(PostprocessorPlugin.class);
// then loop over pre and post, removing blacklisted entries
// alternately, loop over pre and post, populating whitelists with desired
entries
moduleService.run(moduleInfo, pre, post, inputName1, inputValue1,
inputName2, inputValue2, ...etc...)

Is that how you were doing it?

// or if you really want to avoid creating instances which you then throw
away...
// you can write:
//   infos = pluginService.getPluginsOfType(PreprocessorPlugin.class)
// and/or:
//   info = pluginService.getPlugin(InitPreprocessor.class)
// get the infos you want onto a list and then call:
//   pre = pluginService.createInstances(info);
// or even this way:
//   pre.add(pluginService.createInstance(InitPreprocessor.class);
// the advantage being that all context is injected automatically for you.

> And the ImageDisplayService is also still null.

Regardless of which preprocessors you use, such service parameters should
not be null, as long as the context has that service associated with it;
see below. Again, I'm guessing you didn't inject the context...

> BTW: when diving a little bit into the code I realized that Service
> and Context @Parameters of Commands are nor reflected in the
> CommandInfo. Somehow I was confused when I saw, that the
> ServicePreprocessor's process method looks for Service and Context
> parameters in the moduls CommandInfo.inputs(). Could there ever by
> any?

Yes. :-) You have found a wrinkle that I didn't expect anyone to notice yet.

In the case of Modules generally (not just Commands), there can certainly
be Service and Context parameters. The prime example there is scripts: we
want to annotate scripts written various languages such as Jython with a
header comment that declares all the typed input and output parameters,
including services. The ServicePreprocessor will take care of filling them,
and the ModuleInfo API will include them when iterating the inputs.

However, in the case of Commands specifically, the Service and Context
fields annotated with @Parameter will actually *not* be reported as
parameters by the ModuleInfo API. It was changed 8 months ago:


https://github.com/imagej/imagej/commit/5a86785506f94dc0e58d5860559903b23fb64187

Unfortunately, I did not bother to elaborate in my commit message on *why*
"we do then need to have a special exclusion for such parameters in the
CommandInfo wrapper" and I've forgotten what problems it caused not to have
that special exclusion.

So this is certainly inconsistent and confusing. Do you think that Commands
should also include service and context inputs when iterating them? If so,
we can try removing that exclusion and see what explodes... ;-)

> I was aware of the option to create a context but was/am reluctant to
> use it (in excess) due to overhead considerations. Just looking at the
> logging output it seems pretty time consuming. Is this impression
> wrong?

Regardless of how you do things, you should be able to keep reusing the
same context. You can either create one static one as I described, or you
can create one non-static one and then pass it around everywhere to things
that need it (either via constructors, setters, or by injecting it as a
@Parameter -- whatever pattern suits your fancy).

That said, we have put a fair amount of effort into optimizing context
creation. What it comes down to is which services are part of the context.
Creating a full-blown ImageJ context (all available services, huge
classpath) on my system takes about 2000ms right now; creating one with
only core SciJava services takes 450ms. This is actually much slower than
last I checked, probably due to the Eclipse Helper making absolutely sure
the annotation metadata is up to date. We will certainly be optimizing this
more in the future -- e.g., if you run outside of the Eclipse development
environment, the Eclipse Helper should not bog things down.

Also, as of
https://github.com/scijava/scijava-common/commit/0fd0e2916d39505e792f1bbd8e56e3de0f1f8904,
the log spam when creating a context has been eliminated by default. (It
got to the point where those log messages were really annoying me. ;-) The
next release will of course include this change.

> In addition I am able to create only on instance of imagej.ImageJ, the
> second constructor crashes. See the file createContext.log in the zip
> which results from running RunTestOp. (It seems to me the creating a
> second instance of DefaultLegacyService fails, and I faintly remeber
> to have read somewhere that only one instance of the
> DefaultLegacyService may exist ??)

Indeed, trying to create two contexts each with a DefaultLegacyService
fails right now. The reasons are complex, but in short, it is a bug. And
unfortunately one we will not be able to address before the June release.
The problem can be avoided by either: A) calling context.dispose() on the
first context before creating the second one; or B) creating additional
contexts beyond the first without a LegacyService (admittedly awkward to
code since you can no longer use the default Context() constructor).

If you urgently need support for multiple simultaneous ImageJ contexts
created with the default constructor while ij-legacy is on the classpath,
and you want to try fixing the bug yourself, we would be happy to elaborate
further so that you can give it a shot.

> The Command EquationDataValues.java ist annotated as headless and has
> a @Parameter of type Button. Is this as intended?

Yes, the "Button" is just a marker interface indicating that the input
harvester UI should render a button with that label which when pressed
calls that callback method. In the case of no input harvester UI, there
will be no button, and the callback will hence never occur. But that should
not interfere with headless operation. Such Button parameters can be safely
ignored on your end.

Note that we recently made the Button interface extend the Optional
interface to clarify the fact that Button "values" are always allowed to be
null, even if the @Parameter of the Button does not declare "required =
false". See:
https://github.com/imagej/imagej/commit/09ed9f9b6178312d86083094732b2a1201f82e16

Let's hope our next exchange does not need to be quite so long winded...

Regards,
Curtis


On Thu, Apr 10, 2014 at 9:01 AM, Stefan Posch <posch at informatik.uni-halle.de
> wrote:

> Hi Curtis,
>
> thank you very much for the details answers (and I did not mean to press
> you with
> regard to "delays" - this goes, of course, also for the future).
>
> Though very helpful a few issues are still open:
>
> >> What are the precoditions assumed when running a (headless) command
> > What happens is highly dependent on *how* you invoke the command...
>
>    Maybe I was unclear on this issue. May question is, what does a method
>    of a Command (e.g. its initializer, the run method) or rather the
> implementor
>    may assumed to have "happend".
>
>    Or other way round: in case the run-method of OpenFile command used in
> the tutorial
>    requires the initializer to have been executed:
>    Is the run-method responsible to check, if it has already been executed
> (e.g. if called
>    via the invokeWithArgs or invokeWihtMap methods of the tutorial) or not
>    (if e.g. called via invokeFrmoJava).
>    Or is it the callers responsibility to ensure the the initializer has
> been already been
>    executed before actually running the OpenFile command (if it indeed
> requires the initializer to
>    have been invoked beforehand).
>
>    The same questions goes for the initializer method: may it assume that
> a single Dataset
>    parameter has already been set? And if so, may it asume other
> prerequisites as well?
>    E.g. it seems to me that the initializer of DuplicateImage would
> probably crash if
>    the inputDisplay is null.
>    Or is it the other way round: That whoever (indirectly) invokes the
> initializer is
>    responsible for certain parameters to be set in advance.
>
>    Besides interesting for a programmer of a Command we are interested in
> this issue
>    to be able to wrap IJ2 commands in order to run them from our GUI
> (originaly design
>    for alida operators), our command line runner,
>    and to support them in our graphical editor for alida/mitobo operators
> and hopefully
>    ij2 commands (at least head less and non DynamicCommands)
>
> > It should be case that Alida can reuse the default pre- and
> post-processing
> > plugin stack -- in other words, you should be able to pass "process=true"
> > to the ModuleService#run and everything will "just work". Let us know if
> > not, and we can troubleshoot. As long as no UI has been shown, you will
> be
> > in headless mode and no dialogs should ever be shown. (If one does pop
> up,
> > it is probably a bug.)
>
>   I am not sure whether we can recycle all IJ2s preprocessors as there
> might be differences
>   in the concept of parameter handling.
>
>   But trying to reused (some of) them "by hand", not by using the Modules
> run method,
>   I somehow got stuck, as obviously I do not understand the concepts
>   properly. Maybe you are willing to help with this.
>
>   In the attached zip of a tiny maven project using ij2 (2.0.0-beta-7.8)
>   there is a command RunTestOp which tries to invoke a IJTestOp command
> "by hand".
>   I first create a CommandInfo for IJTestOp, then a CommandModule for this
> info,
>   collects all preprocessors and (try to) invoke them for the
> CommandModule created.
>   I disabled invoking of the InitPreprocessor, as it crashes with a null
> pointer exception:
>     Exception in thread "main" java.lang.NullPointerException
>         at imagej.command.CommandModule.initialize(CommandModule.java:144)
>         at
> imagej.module.process.InitPreprocessor.process(InitPreprocessor.java:61)
>         at mainroutine.RunTestOp.main(RunTestOp.java:49)
>
>   Additionally it seems to be that creating the CommandInfo already
> invokes preprocessors,
>   however the initializer of IJTestOp is not invoked.
>
>   And the ImageDisplayService is also still null.
>
>   BTW: when diving a little bit into the code I realized that
>    Service and Context @Parameters of Commands are nor reflected in the
> CommandInfo.
>    Somehow I was confused when I saw, that the ServicePreprocessor's
> process method
>    looks for Service and Context parameters in the moduls
> CommandInfo.inputs().
>    Could there ever by any?
>
> > modules should not be dynamic. Dynamic commands are *much* more
> challenging
> > to support across many different environments (CellProfiler, KNIME,
> OMERO,
> > Alida, etc.) *much* more challenging.
>  Is it easily possible to state what makes a Command a DynamicCommand,
>  and what is the difference to interactive() commands?
>
> > On the CLI, we'll harvest values from the user at the same point in time
> > that we currently do it via the UI. So most of the other preprocessing
> will
> > be done; there will be a "CLIInputHarvesterPlugin" that prompts the user
> to
> > type in these values using System.in or similar. We have not yet created
> > this preprocessor plugin, but it would be very straightforward. If you
> need
> > this, let me know -- it would be a fun side project. :-)
>    We have one "command line oprunner" for alida
>    (which does not work interactively, thus can be used also from
> shell-scripts,
>    e.g. for parameter tuning). And as noted above would like to support
> also
>    ij2 commands.
>
> > The SciJava context is definitely not a singleton. But you can use it
> that
> > way if you want: just create one a static variable in your own codebase
> > somewhere; e.g.:
> >
> >
> > public static final imagej.ImageJ IJ = new imagej.ImageJ();
>
>    I was aware of the option to create a context but was/am reluctant to
> use
>    it (in excess) due to overhead considerations. Just looking at the
> logging output
>    it seems pretty time consuming. Is this impression wrong?
>
>    In addition I am able to create only on instance of imagej.ImageJ, the
> second
>    constructor crashes. See the file createContext.log in the zip which
> results from
>    running RunTestOp.
>    (It seems to me the creating a second instance of DefaultLegacyService
> fails,
>    and I faintly remeber to have read somewhere that only one instance of
> the
>    DefaultLegacyService may exist ??)
>
> A new (and for the moment last) question:
>   The Command EquationDataValues.java ist annotated as headless and has a
> @Parameter
>   of type Button. Is this as intended?
>
> Again thanks a lot
>
> Best regards    Stefan
>
> _______________________________________________
> ImageJ-devel mailing list
> ImageJ-devel at imagej.net
> http://imagej.net/mailman/listinfo/imagej-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://imagej.net/pipermail/imagej-devel/attachments/20140410/1869bd7e/attachment-0001.html>


More information about the ImageJ-devel mailing list