imagej / imagej-ops

ImageJ Ops: "Write once, run anywhere" image processing
https://imagej.net/libs/imagej-ops
BSD 2-Clause "Simplified" License
89 stars 42 forks source link

Improve prime OPS example script #69

Open ctrueden opened 10 years ago

ctrueden commented 10 years ago

The OPS library is focused on image processing, and as such it would be best if the front-facing example script did more than gimmicky operations. Rather, it should perform a really common and useful—but simple—image analysis workflow. We want something succinct that elegantly performs some powerful image processing.

We need to update both the README.md as well as the using-ops tutorial.

bnorthan commented 10 years ago

Hi Curtis

Eventually it would be nice to have something as below. Still need Rolling-ball and a better convolver design. By the way, are you planning to put the ops in namespaces eventually?? (like ops.threshold.otsu(...) ).

(Farther down below is where I am at as of today, not as pretty but it works.)

# @OpService ops
# @Dataset input
# @OUTPUT ImgPlus thresholded

# convert to float
converted=ops.convert.toFloat(input)

# background subtraction
backgroundsubtracted=ops.backgroundsubtraction.rollingball(converted, 50)

# Laplacian of Gaussian
log=ops.filter.log(backgroundsubtracted, 3.0)

# threshold
thresholded=ops.threshold.otsu(log)

And here is what it looks like today

# @DatasetService data
# @DisplayService display
# @IOService io
# @OpService ops
# @net.imagej.Dataset inputData
# @OUTPUT net.imglib2.meta.ImgPlus thresholded

from net.imglib2.meta import ImgPlus
from net.imglib2.type.numeric.real import FloatType
from net.imglib2.img.display.imagej import ImageJFunctions

from ij import ImagePlus
from ij.plugin.filter import BackgroundSubtracter

from jarray import array

from fiji.plugin.trackmate.detection import DetectionUtils

from net.imagej.ops.convert import ConvertPixCopy

###############################################################
# Step 1:  Rolling ball background subtraction (still uses IJ1)
###############################################################

# wrap as ImagePlus
imp=ImageJFunctions.wrap(inputData, "wrapped")

# create and call background subtractor
bgs=BackgroundSubtracter()
bgs.rollingBallBackground(imp.getProcessor(), 50.0, False, False, True, True, True) 

# wrap the result of background subtraction as Img
iplus=ImagePlus("bgs", imp.getProcessor())
imgBgs=ImageJFunctions.wrapShort(iplus)

###############################################################
# Step 2:  Laplacian of Gaussian Filtering
###############################################################

# convert to 32 bit
imgBgs32=ops.run("createimg", imgBgs, FloatType())
ops.convert(imgBgs32, imgBgs, ConvertPixCopy() )

# create the Laplacian of Gaussian filter
kernel = DetectionUtils.createLoGKernel( 3.0, 2, array([1.0, 1.0], 'd' ) )

# create the output Img for convolution
log=ImgPlus( ops.run("createimg", inputData.getImgPlus(), FloatType() ) )

# apply the log filter
ops.convolve(log, imgBgs32, kernel)

###############################################################
# Step 3:  Threshold
###############################################################

# apply the threshold operation
thresholded = ops.run("triangle", log)
display.createDisplay("thresholded", data.create(ImgPlus(thresholded)))
ctrueden commented 10 years ago

Thanks for the update, @bnorthan. I think there are some great targets for the framework embedded in your "ideal" and "current" scripts above. It would be good to make an explicit list of those and file them as issues, so we can make concrete progress toward it.

Regarding namespaces: I actually thought about that during the last hackathon, but tentatively was against it (in my own head) since it would potentially complicate things. They can certainly be simulated using names like threshold_otsu, threshold_triangle, etc. If you really want the dot though, it makes things more complicated, because while you could write threshold.otsu for the op name, you cannot embed a dot into a Java method name. So writing ops.threshold.otsu wouldn't work. I guess I could tackle that as part of issue #19, though.

I do think namespaces are a perfect fit for things like the threshold methods. @dietzc? @dscho? @hinerm? Any opinion?

dietzc commented 10 years ago

just my 2 cents: I think addressing namespaces as part of #19 makes sense. But this is rather a nice to have (priority = low). Anyway, targeting the scripts mentioned by @bnorthan makes completely sense.

bnorthan commented 9 years ago

Hi

Quick update on this. Once I get these pull requests done it should be possible to concisely write a spot detector using ops.

https://github.com/imagej/imagej-ops/pull/94 https://github.com/imagej/imagej-ops/pull/76 https://github.com/imagej/imagej-ops/pull/48

This code also uses IJ1 to generate a result table so it also demonstrates interoperability.

# @DatasetService data
# @DisplayService display
# @OpService ops
# @net.imagej.Dataset inputData

from net.imglib2.meta import ImgPlus
from net.imglib2.img.display.imagej import ImageJFunctions

from jarray import array
from ij import IJ

# create a log kernel
logKernel=ops.logKernel(2, 1.0);

# convolve with log kernel
logFiltered=ops.convolve(inputData, logKernel);

# display log filter result
display.createDisplay("log", ImgPlus(logFiltered));

# otsu threshold and display
thresholded = ops.run("threshold.otsu", logFiltered)
display.createDisplay("thresholded", ImgPlus(thresholded));

# convert to imagej1 imageplus so we can run analyze particles
impThresholded=ImageJFunctions.wrap(thresholded, "wrapped")

# convert to mask and analyze particles
IJ.run(impThresholded, "Convert to Mask", "")
IJ.run(impThresholded, "Analyze Particles...", "display add");
IJ.run("Close"); '''
ctrueden commented 9 years ago

@bnorthan That is beautiful!

bnorthan commented 9 years ago

@ctrueden, @dscho, @dietzc

Looks like you guys are getting lots done. Thanks for merging those pull requests. I tested this script this morning using the new 'master' and using the hela cells-image from 'sample images'.

  1. It seems to be working for 2D images. A good test image is the red channel of hela-cells. That image has lots of spots.
  2. For nd images. one small change is needed logKernel=ops.logKernel(2, 1.0) --> logKernel=ops.logKernel(inputData.numDimensions(), 1.0); Though even with this change the script fails on the "Convert to mask" step with the message "This command does not work with virtual stacks".
  3. I think 'inputData' ends up being the most recently opened image instead of the active image. Things got a bit confusing with multiple images open. If I recall correctly this issue is known and being worked on.
  4. For nd images I still need to read and handle the axis meta data. For example for x, y and channels we would need to "create a 2D filter and apply to each channel", for x,y,z we would need to "create a 3D filter and apply"... I am guessing this is something to revisit once ImgPlus is finalized.
ctrueden commented 9 years ago

I'll to try polish off the loose ends needed to make this script really work out of the box. In particular, we need Dataset :left_right_arrow: ImagePlus converters in SciJava so that the "Enable ImageJ2 data structures" option can go away.

ctrueden commented 9 years ago

Today, @hinerm looked a bit at the imagej-legacy improvements that will be needed to improve this. But I just wanted to comment quickly that I was not able to get the script fully working while at the hackathon (even with "Enable ImageJ2 data structures" enabled). And I haven't had time to play with it again since then. Will try again as time allows...

bnorthan commented 9 years ago

I made a couple of small changes to make it run with the latest imagej release. Though the result may not make sense for ND images. Is there a repository we can put these kind of example scripts in??

# @DatasetService data
# @DisplayService display
# @OpService ops
# @net.imagej.Dataset inputData

from net.imglib2.meta import ImgPlus
from net.imglib2.img.display.imagej import ImageJFunctions

from jarray import array
from ij import IJ

# create a log kernel
logKernel=ops.logKernel(inputData.numDimensions(), 1.0);

# convolve with log kernel
logFiltered=ops.convolve(inputData, logKernel);

# display log filter result
display.createDisplay("log", ImgPlus(logFiltered));

# otsu threshold and display
thresholded = ops.run("threshold.otsu",logFiltered)
display.createDisplay("thresholded", ImgPlus(thresholded));

# convert to imagej1 imageplus so we can run analyze particles
impThresholded=ImageJFunctions.wrap(thresholded, "wrapped")

# convert to mask and analyze particles
IJ.run(impThresholded, "Convert to Mask", "")
IJ.run(impThresholded, "Analyze Particles...", "display add");
IJ.run("Close");```
hinerm commented 9 years ago

@bnorthan Absolutely - all the SciJava scripting components have example scripts in a src/main/resources/script-templates/${LANGUAGE} directory, (e.g. beanshell).

Any component can follow this same pattern and I believe the scripts will be automatically detected as examples in the script editor.

Since your script uses ImageJ classes and is demonstrating ops use I think it makes sense to keep it in the imagej-ops component, e.g. in a new src/main/resources/script-templates/Ops directory?

bnorthan commented 9 years ago

Hi @hinerm

Since my script is Jython I assume I should add it to the scripting-jython repo in the below directory?? Is that correct? Thanks for letting me know.

https://github.com/scijava/scripting-jython/tree/master/src/main/resources/script-templates/

hinerm commented 9 years ago

@bnorthan Script templates can go in any component's src/main/resources/script-templates directory, but they should go in a component that makes sense for their dependencies. Since your script uses ij.IJ, it actually makes the most sense to put it in imagej-legacy since that's our IJ 1.x/IJ2 bridge component.

We're about to do a pass through our scripting repos to rename script-templates to script_templates as the former interferes with javadoc generation... so let's just put your script in imagej-legacy/src/main/resources/script_templates/

hinerm commented 9 years ago

just kidding.. we want to put it here:

https://github.com/imagej/imagej-legacy/tree/master/src/main/resources/script_templates/Python

bnorthan commented 9 years ago

Hi Mark

I'm having trouble building/testing imagej-legacy with maven. The LegacyOpenerTest is failing in "TestPaulsMacro" on 'assertEquals(3, (int) nResults)'. Any ideas why?? Anything I could do differently to fix it?? Thanks --

hinerm commented 9 years ago

@bnorthan because it is the worst unit test ever. It shares options with your Fiji installation, and requires ImageJ2 to be disabled. So open up Fiji and run Edit>Options>ImageJ2 and disable everything. Then run the test. Awesome!

dscho commented 9 years ago

I agree that it is the worst unit test ever – I wrote it. But then, it tests something very crucial: that the macro you run works no matter what your defaults are. And what it showed is that there is a problem in that ImageJ2 modifies the results of that one test depending on global settings. That is just an undesirable situation for users because it means that their macros might fail depending on something outside their control, or even worse: not fail, but instead yield irreproducible results.

hinerm commented 9 years ago

Certainly one of the best things I could do would be to fix the bug in SCIFIO (I assume) causing the value discrepancy. But we should probably also temporarily set and then re-set the Imagej2 options values to ensure a consistent test (and even test once with and without IJ2 enabled)

ctrueden commented 9 years ago

@hinerm Agreed on all counts. Furthermore, we should add a global setting (maybe via system property) to disable IJ options persistence completely, for the purposes of unit tests. I.e.: all unit tests should be testing with the default options, unless they explicitly want to do otherwise.

ctrueden commented 9 years ago

https://github.com/scijava/scijava-common/issues/150

bnorthan commented 9 years ago

Thanks @hinerm. The tests work now. I pushed a new branch (very simple, I just added the one script).

https://github.com/imagej/imagej-legacy/blob/ops_ij1_example/src/main/resources/script_templates/Python/OpsThresholdIJ1Analyze.py

Let me know if anyone has any comments on the script. If it looks OK I can merge it. Thanks

hinerm commented 9 years ago

Looks fantastic to me @bnorthan :+1: