How to convert FloatType Img to UnsignedByteType Img with ImageJ Ops?

After filtering, the result Img has type FloatType, and its min and max values could be in the range, for example, [-22.76, 18.41].

How to convert this FloatType Img to UnsignedByte Img with ImageJ Ops, and after conversion, the result Img min and max values will be in the range [0, 255]?

Hi @panovr,

you should be able to pass an Ops.Convert.NormalizeScale to Ops.Convert.ImageType. That said, I have tried myself and wasn’t able to properly initialize the construct.

I guess you have stumbled over an issue here: NormalizeScaleRealTypes.conforms() checks for factor to be set. factor, however, is only set in ConvertIIs.initialize() which requires the RealTypeConverter to be initialized.

You can work around that with (in Groovy)

// @OpService ops
// @Dataset input

import net.imagej.ops.Ops;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imagej.ops.convert.normalizeScale.NormalizeScaleRealTypes;

converter = new NormalizeScaleRealTypes();
converter.setEnvironment(ops);
converter.initialize();
converter.checkInput(input);

out = ops.create().img(input, new UnsignedByteType());
ops.run(Ops.Convert.ImageType.class, out, input, converter);

for now.

Best,
Stefan

@stelfrich the above code works for UnsignedByteType. My original code like these:

ij = new ImageJ()
input = ij.scifio().datasetIO().open("http://samples.fiji.sc/tutorials/DrosophilaWing.tif").getImgPlus()
converted = ij.op().convert().float32(input)
dog = ij.op().filter().dog(converted, 1.0, 1.25)
convert_op = ij.op().op("convert.normalizeScale", input.firstElement(), dog.firstElement())
output = ij.op().create().img(input, new UnsignedByteType());
ij.op().convert.imageType(output, dog, convert_op)

However, there is an error:

+java.lang.IllegalArgumentException: No matching ‘convert.normalizeScale’ op

Request:

  • convert.normalizeScale(
    UnsignedByteType,
    FloatType)

Candidates:

  1. (RealType out) =
    net.imagej.ops.convert.normalizeScale.NormalizeScaleRealTypes(
    RealType out,
    RealType in)
    Inputs do not conform to op rules
    out = 250
    in = -0.1973114013671875

During matching, the conforms() method is called which checks for factor to be set. For factor to be set, the checkInput(IterableInterval) has to be called, which I am doing explicitly in my code. It is supposed to be called by the Ops.Convert.ImageType op, which you can’t initialize without the converter…

The IllegalArgumentException is thrown because the conforms() method returns false per default…

@stelfrich Thanks for the explanation.

By the way, there is another op named scale, and it seems that this op doesn’t need to call checkInput(IterableInterval) explicitly like normalizeScale op.

However, my code uses scale op seems not work properly:

ij = new ImageJ()
input = ij.scifio().datasetIO().open("http://samples.fiji.sc/tutorials/DrosophilaWing.tif").getImgPlus()
converted = ij.op().convert().float32(input)
dog = ij.op().filter().dog(converted, 1.0, 1.25)
scale_op = ij.op().op("convert.scale", input.firstElement(), dog.firstElement())
output = ij.op().create().img(input, new UnsignedByteType());
ij.op().convert().imageType(output, dog, scale_op)

It is a bit strange that the output image has all the same pixel value. I think even the the output isn’t corect, the pixel value of the output should be different.

What’s the different between scale op and normalizeScale op?

Scale uses only type information for a conversion from A to B. That is, the minimum value of type A will be mapped to the minimum value of type B, and the same for the maxima. NormalizeScale, however, uses the minimal set value of the input image (hence the requirement for the complete IterableInterval) and maps it to the minimum value of type B, and the same for maxima.

I will check again, but in the case of your data, the issue with Scale is the low dynamic range of your input image. You are mapping the whole FloatType range to the UnsignedByteType range. The image however only has values in [-12, 15] which maps all of them to 128 given the huge, complete input range of FloatType.

There was an issue in how NormalizeScale computed the conversion factor. I have fixed it at https://github.com/imagej/imagej-ops/commit/097e524719108a61d72cd2a77d9ce50b886d7963.

If you check that branch out

scale_op = new NormalizeScaleRealTypes();
scale_op.setEnvironment(ops);
scale_op.initialize();

//scale_op = ij.op().op("convert.scale", dog.firstElement(), output.firstElement())
ij.op().convert().imageType(output, dog, scale_op)

should do the trick for you @panovr.

I will look into the matching issue shortly.

https://github.com/imagej/imagej-ops/pull/466 enables

scale_op = ij.op().op("convert.normalizeScale", dog.firstElement(), output.firstElement())
ij.op().convert().imageType(output, dog, scale_op)

BTW, what does that mean exactly? The range from -(Float.MAX_VALUE) to Float.MAX_VALUE?

IMHO, although that might be the only sensible default, it rarely fits what you want to achieve when converting from float to any other type. But I guess you should not be using normalize on FloatType with these defaults anyways, but rather use the actual image min and max, or provide your own values, right?

Exactly. That is what Scale uses, @imagejan .

That is what NormalizeScale does when converting.

1 Like

@stelfrich your explanation really helps me, thanks!

I’m writing an ImageJ Ops example of the imagej-tutorials in dev-manual branch. So is it possible that I just update my pom.xml to a new ImageJ version and use these fixes?

<parent>
    <groupId>net.imagej</groupId>
    <artifactId>pom-imagej</artifactId>
    <version>16.6.0</version>
    <relativePath />
</parent>

I would postpone that tutorial if I were you. Hopefully those fixes get merged sooner than later and are urgent enough for a new version of Ops to be released :smiley:

The problem is that I don’t know how the Beaker-Maven integration works in detail for SNAPSHOT releases.

@stelfrich I use the latest snapshot version of imagej-ops (which Jenkins server built automatically) in my example project, and the fix works for me. Thanks!