Subtract Images using ImageJ Ops

I am trying to use IJ Ops to subtract the first image of a stack to the entire stack. The way I do this on IJ1 is using the Process > Image Calculator plugin which work well.

Now using the following script :

# @ImageJ ij
# @Dataset data

from net.imglib2.util import Intervals
from net.imagej.axis import Axes
from net.imagej.ops import Ops
from net.imglib2.type.numeric.integer import UnsignedByteType, ByteType

# Get the first frame
 (ugly code)
t_dim = data.dimensionIndex(Axes.TIME)
interval_start = []
interval_end = []
for d in range(0, data.numDimensions()):
	if d != t_dim:
		interval_start.append(0)
		interval_end.append(data.dimension(d) - 1)
	else:
		interval_start.append(0)
		interval_end.append(0)
intervals = interval_start + interval_end
intervals = Intervals.createMinMax(*intervals)
first_frame = ij.op().transform().crop(data.getImgPlus(), intervals)

# Get the type of the image
pixel_type = data.getImgPlus().firstElement().__class__

# Allocate memory for the output
subtracted = ij.op().create().img(data.getImgPlus(), pixel_type())

# Setup the op
sub_op =  ij.op().op("math.subtract", first_frame, first_frame)

# Calculate the fixed axes
fixed_axis = [d for d in range(0, data.numDimensions()) if d != t_dim]

# Do the subtraction
ij.op().slice(subtracted, data.getImgPlus(), sub_op, fixed_axis)

# Display image
ij.ui().show("subtracted", subtracted)

The resulting image is black for the first one (it’s ok since I am subtracting an image to itself) but the next ones are just noise.

This is how the histograms of the entire stack look :

Note that I am using the same Type for the output image. Now if I used the signed version as Type for the output image the result is a 32 bit image (for a 8 bit input).

Ideally I would like to get something identical to the Image Calculator output.

Dear @hadim,

I just remembered a recent discussion between @cmongis and @dietzc on Gitter on a similar topic, starting here.

The gist is that you are working with UnsignedByteTypes and the subtraction generates negative values. Correct behavior for unsigned types is, however, only guaranteed for positive values. As you have stated in Gitter, Image Calculator/ByteBlitter sets those cases where x-y < 0 manually to 0.

Maybe @dietzc has an idea on how to handle that?

Best,
Stefan

I didn’t test the script on my own, but it seems that the problem is, that ops doesn’t automatically handle underflows or overflows. This means if you have an UnsignedByteType (i.e. values from 0-255) and a subtraction leads to a value < 0 (or for addition > 255), then the behaviour is not clearly defined. In ImageJ, as far as I understand, the handling of underflows / overflows is handled implicitly, which is dangerous, because you don’t really know what happens.

Besides working around it and adding a Converter handling the under-/overflow, what about just making sure that you use an Signed type for the result of the subtraction?

subtracted = ij.op().create().img(data.getImgPlus(), pixel_type()) and pixel_type() would just be FloatType.class or ShortType.class or …?!

I hope this helps!

Christian

Thank you for the help guys.

If I use ShortType on my image (UnsignedShortType). The resulting image is black for the first one and uniform noise between -128 and +127 for the next ones.

So I guess the only way to workaround for me would be to use this Converter ? Could you guide me on how to implement it ?

Can you could quickly try with IntegerType output-type to see if it’s really type related or something else is going on?

Got the following error TypeError: No visible constructors for class (net.imglib2.type.numeric.IntegerType)

I guess this class can’t be implemented ?

sorry, try IntType.

Casting error :

java.lang.RuntimeException: java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.ClassCastException: net.imglib2.type.numeric.integer.UnsignedShortType cannot be cast to net.imglib2.type.numeric.integer.GenericIntType

I see. Try the following with your input image:

converted = ij.op().convert().int32(data.getImgPlus())

or float32, depending on the use-case!

Best,

Christian

Here also I am getting closer but I am still missing something related to type :frowning:

# @ImageJ ij
# @Dataset data

from net.imglib2.util import Intervals
from net.imagej.axis import Axes
from net.imagej.ops import Ops
from net.imglib2.type.numeric.integer import UnsignedShortType
from net.imglib2.type.numeric.integer import ShortType
from net.imglib2.type.numeric.integer import IntType

# Convert input
converted = ij.op().convert().int32(data.getImgPlus())

# Get the first frame (TODO: find a faser way !)
t_dim = data.dimensionIndex(Axes.TIME)
interval_start = []
interval_end = []
for d in range(0, data.numDimensions()):
	if d != t_dim:
		interval_start.append(0)
		interval_end.append(data.dimension(d) - 1)
	else:
		interval_start.append(0)
		interval_end.append(0)
		
intervals = interval_start + interval_end
intervals = Intervals.createMinMax(*intervals)

first_frame = ij.op().transform().crop(converted, intervals)

# Allocate output memory (wait for hybrid CF version of slice)
subtracted = ij.op().create().img(converted)

# Create the op
sub_op =  ij.op().op("math.subtract", first_frame, first_frame)

# Setup the fixed axis
fixed_axis = [d for d in range(0, data.numDimensions()) if d != t_dim]

# Run the op
ij.op().slice(subtracted, converted, sub_op, fixed_axis)

# Show it
#subtracted = ij.op().convert().uint8(subtracted)
ij.ui().show("subtracted", subtracted)

Ideally I would like to have the same output type as the input type.

PS: Are ij.op().convert just raw conversion ops without any scaling ?

The issue is again with histogram (an type I guess).

From left to right :

  1. original image
  2. Stack subtracted “manually” with Image Calculator
  3. Stack subtracted using the above script

Ok I think I understand your problem now: Tryuse Map in combination with ClipRealTypes after subtraction. It seems that the IJ1 does the clipping (setting values under 0 to 0) for you.

After the subtraction I am using the following :

clip_op = ij.op().op("convert.clip", pixel_type(), subtracted.firstElement().class())
clipped = ij.op().create().img(subtracted)
ij.op().map(clipped, subtracted, clip_op)

But the output is 0 everywhere. Am I doing something wrong ?

You have to feed the convert.clip op to the convert.imagetype op instead of a Map:

clipped = ij.op().create().img(subtracted, data.getImgPlus().firstElement())
clip_op = ij.op().op("convert.clip", data.getImgPlus().firstElement(), subtracted.firstElement())
ij.op().convert().imageType(clipped, subtracted, clip_op)
ij.ui().show("clipped", clipped)

This is a bit misleading, I fear, and the ConvertNamepsace.clip() method is even more misleading since calling it on its own doesn’t even do any clipping…

3 Likes

Indeed it wasn’t clear how to use this guy ! But it’s very clear now. Thank you all for the help.

For the record : http://imagej.net/ImageJ2_Python_Scripts#Subtract_stack_to_its_first_image