These are chat archives for fiji/fiji

3rd
May 2017
Philipp Hanslovsky
@hanslovsky
May 03 2017 14:29
@dietzc @ctrueden I rewrote my class to use an empty constructor and I can add a command to the menu like that from within python
Curtis Rueden
@ctrueden
May 03 2017 14:29
All SciJava plugins need to have a public noargs constructor (implicitly or explicitly).
@hanslovsky Glad to hear it is working!
Philipp Hanslovsky
@hanslovsky
May 03 2017 14:32
Unfortunately, I cannot create the class within python then. As a workaround, I compiled a Java class that implements Command and has a static Runnable. The user can set this Runnable from python but it also means, that for each separate use case in python, I would need to create a class from Java.
To clarify, this is what I have in Java:

import org.scijava.command.Command;

public class PythonCommand implements Command
{

    private static Runnable runnable;

    public PythonCommand()
    {

    }

    public PythonCommand( final Runnable runnable )
    {
        super();
        setRunnable( runnable );
    }

    public static void setRunnable( final Runnable runnable )
    {
        PythonCommand.runnable = runnable;
    }

    @Override
    public void run()
    {
        runnable.run();
    }

}
And this is how I call it in python:
class PythonRunnable( PythonJavaClass ):

    __javainterfaces__ = [ 'java/lang/Runnable' ]

    def __init__( self, command ): 
        super( PythonRunnable, self ).__init__()
        self.command = command     

    @java_method( '()V' )          
    def run( self ):               
        self.command()             


PythonCommand = autoclass( 'net.imglib2.python.PythonCommand' )
pr = PythonRunnable( lambda : print( "I am a command!" ) )
dummy_command = PythonCommand( pr )
Philipp Hanslovsky
@hanslovsky
May 03 2017 14:38

As soon as I'd like to be a little more generic and add more than one python commands to the menu, this gets a little messy. As far as I can tell, there are two ways to solve this:

  1. Allow to add menu items based on existing objects rather than classes (I don't know if this is feasible)
  2. Make a call to javac and compile a new class as needed and add to the classpath.

If (1.) is not an option, I will look into 2

Philipp Hanslovsky
@hanslovsky
May 03 2017 16:14
I implemented 2, and it works. I create a (temporary) directory and add that directory to the classpath. The temporary .class files will be stored in that directory. Strangely enough, I do not need to compile the .java files into .class files before I start the jvm. Will the jvm look for .class files in directories specified by the class path dynamically? I.e. I can add .class files at run time and the jvm finds them?
Philipp Hanslovsky
@hanslovsky
May 03 2017 16:20
It seems to work that way for me but that is only based on my observation running my python code.
Philipp Hanslovsky
@hanslovsky
May 03 2017 16:30
@ctrueden @dietzc Just confirming that I can start a QTConsole in ImageJ2 now
Philipp Hanslovsky
@hanslovsky
May 03 2017 16:36
This is how it works:
def compile_class( javac, path, classpath, *javac_args ):
    subprocess.call( ( javac, '-cp', classpath ) + javac_args + ( path, ) )
    class_file = re.sub( 'java$', 'class', path )
    return class_file

def compile_command_class( javac, class_name, directory, classpath, *javac_args ):
    code = """public class {class_name} implements org.scijava.command.Command
{{

    private static Runnable runnable;

    public {class_name}()
    {{

    }}

    public {class_name}( final Runnable runnable )
    {{
        super();
        setRunnable( runnable );
    }}

    public static void setRunnable( final Runnable runnable )
    {{
        {class_name}.runnable = runnable;
    }}

    @Override
    public void run()
    {{
        runnable.run();
    }}

}}
""".format( class_name=class_name )
    path = '{directory}/{class_name}.java'.format( directory=directory, class_name=class_name )
    with open( path, 'w' ) as f:
        f.write( code )
And then I call it like this:
compile_command_class( javac, class_name='ToggleQTConsoleWrapper', directory=pth, classpath=[ lib for lib in libs if re.search( 'scijava-common', lib ) ][ 0 ] )
PythonCommand = autoclass( 'ToggleQTConsoleWrapper' )
pr = PythonRunnable( lambda : QtWidgets.QApplication.postEvent( widget, QtGui.QShowEvent() ) )
PythonCommand().setRunnable( pr )
And then I can just add a menu entry for ToggleQTConsoleWrapper as @ctrueden had described earlier
Curtis Rueden
@ctrueden
May 03 2017 18:53
@hanslovsky Man, that sucks...
I think we can avoid needing to call javac here.
The method moduleService.addModule(myModuleInfo) takes an object which implements ModuleInfo. In our case, this should be a CommandInfo instance.
We could make a subclass of CommandInfo which is useful for you here. The idea is: every time a module is run, its ModuleInfo is told to create a new Module instance.
If you have a Runnable instance and want it to run every time the menu item is hit, that is "swimming upstream" a bit from the current paradigm.
However, you can do it: just have the CommandInfo object's createInstance() method return myRunnable every time, or whatnot. But beware of state which persists between executions.
Officially, the SciJava framework does not support this way of doing things.
I'll try to whip up a quick test for you.
Philipp Hanslovsky
@hanslovsky
May 03 2017 18:57
Ok, thanks!
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:25
@ctrueden I managed to get it to run without javac now, following your idea of subclassing CommandInfo
That's essentially it:
package net.imglib2.python;

import org.scijava.command.Command;
import org.scijava.command.CommandInfo;

public class PythonCommandInfo extends CommandInfo
{

    private final Command command;

    public PythonCommandInfo( final String name, final Command command )
    {
        super( name );
        this.command = command;
    }

    @Override
    public Command createInstance()
    {
        return command;
    }


}
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:40
@ctrueden Is there a way to access an ImageJ instance when I don't have a handle
like in the ImageJ1 world:
IJ.getInstance()
Curtis Rueden
@ctrueden
May 03 2017 19:45
@hanslovsky You mean an instance of net.imagej.ImageJ? Intentionally there is not.
You need to keep a handle on that, and pass it to things that need it.
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:46
Yes
Curtis Rueden
@ctrueden
May 03 2017 19:46
Really, it is the org.scijava.Context that needs to be kept, though. The net.imagej.ImageJ is just a lightweight convenience wrapper around the Context. It can be recreated at will.
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:47
Ok, then I will need to figure out to initialize the qt console with variables, which should be possible, as it is started from wihin the python process
Curtis Rueden
@ctrueden
May 03 2017 19:47
If you want to have one true net.imagej.ImageJ, you can make your own static field in your own code.
@hanslovsky I got stuck with my example, and then got distracted by community stuff.
Everything works, except that for unknown reason, the UI does not actually have a Greetings menu as intended.
In imglyb or similar component, you could make a static reference to an org.scijava.Context I guess. You understand the problems with doing that, though, right?
There is actually a huge hack to get the Context statically, but it is part of ImageJ Legacy.
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:51
I am not aware of the problems, no
Curtis Rueden
@ctrueden
May 03 2017 19:51
So what it really gives you is "the SciJava context linked to this ImageJ 1.x instance"
As soon as you make it static, you can only have one at a time.
If you want to have two different contexts simultaneously in the same JVM, you cannot do it without serious class loading trickery.
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:52
I see
Curtis Rueden
@ctrueden
May 03 2017 19:52
I mean, you can, but your static variable will only point at one of them.
ImageJ 1.x is rife with problems caused by static proliferation. Just today, I fixed a huge memory leak in Coloc 2 caused by keeping static references to things.
The garbage collector can never do its job as long as there is a static field somewhere holding a reference to your thing, you know?
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:54
My current plan is to feed the ImageJ/Context instance to the qtconsole and expose it as variable in there.
I have to figure out how, though
Curtis Rueden
@ctrueden
May 03 2017 19:54
Yeah, that makes sense.
This is how we do it in the Script Interpreter: make the context a variable in the bindings.
Call it ij or whatever, and then it's fun to code against.
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:55
yep
Curtis Rueden
@ctrueden
May 03 2017 19:55
I'm sure there is a way to do it for the Qt console.
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:56
There must be, it'd be ridiculous if not
Just haven't found it yet
Curtis Rueden
@ctrueden
May 03 2017 19:56
Can you pass statements to the console to programmatically evaluate?
Just start with something like ij = new net.imagej.ImageJ() and you are gold, right?
Yeah?
Oh, I thought you meant you did find it now.
If you press the Up arrow, you can fix your previous post.
Actually, you have 10 minutes to edit any previous post in Gitter.
Philipp Hanslovsky
@hanslovsky
May 03 2017 19:57
Cool, thanks
Curtis Rueden
@ctrueden
May 03 2017 19:57
:+1:
Philipp Hanslovsky
@hanslovsky
May 03 2017 20:01
Found it!
kernel.shell.push( {'ij2' : ij2 } )
So now I can just reference the ImageJ instance as ij2 from within the Qtconsole
Curtis Rueden
@ctrueden
May 03 2017 20:04
Oh, nice.
Is there something wrong with just calling it ij?
I am against using the term IJ2 gratuitously.
I only use it to differentiate from IJ1, in cases where it is really confusing otherwise.
Because it won't be called "ImageJ2" forever. At least I really hope not.
Philipp Hanslovsky
@hanslovsky
May 03 2017 20:09
Nothing wrong about that. I just had another ij variable within the code that I don't use anymore now, so I planned to rename it to ij anyway.
The cool thing about ImageJ2: I can now actually share memory between numpy and imagej (not possible with ImageProcessors in general). I just tested it like this:
  • Create np array, e.g. img = np.random.rand( *shape )
  • Wrap into imglib2: rai = imglyb.util.to_imglib( img )
  • Display image: ij2.ui().show( rai )
  • Set image to zero using numpy: img[...] = 0
  • Compare with displayed image
Philipp Hanslovsky
@hanslovsky
May 03 2017 20:15
With autocomplete, even for Java methods (only on named objects, though, and no documentation)
Curtis Rueden
@ctrueden
May 03 2017 20:27
Awesome!
Hadrien Mary
@hadim
May 03 2017 21:43
This is great @hanslovsky !
So np array modification are automatically synced to IJ display ?
Philipp Hanslovsky
@hanslovsky
May 03 2017 21:56
@hadim The modifications are registered, e.g. in the mouse-over you will see the correct value, but they are not reflected in the display right away. If you update the display, e.g. scroll through a 3D stack, the changes will show up, as well. I am not familiar with ImageJ2 yet, but i am sure there is an equivalent to ImageJ1's ImagePlus.updateAndDraw in ImageJ2. So you could just call that after modifications of numpy arrays.
Philipp Hanslovsky
@hanslovsky
May 03 2017 22:10

If you create a display like this:

display = ij2.display().createDisplay( name, source )

You can just call display.update() to redraw

Hadrien Mary
@hadim
May 03 2017 22:40
Ok nice.
Last thing, I saw you have a conda package. Did you consider making a conda forge package ? It's a community driven project that use CI services (Travis, CircleCI, Appveyor) to automatically build and distribute conda packages. It's widely used in the Python community.