Using imglib2 converters

@tpietzsch @imagejan @bogovicj

Hello,

There is one thing in imglib2 where I regularly “shoot myself in the foot”, citing Tobias :slight_smile: .
It is this pattern:

RandomAccessibleInterval< BitType > mask = Converters.convert( rai, ( i, o ) -> o.set( i.getRealDouble() > threshold ? true : false ), new BitType() ) );
mask.randomAccess().get().set( false ); // this line has no effect

Basically, modifying pixel values in an image that has been obtained through a converter has no effect.
The problem is that it also does not crash and is thus very hard to debug.
Is there any generic way of avoiding this?

2 Likes

Hi @Christian_Tischer,

that’s an issue, indeed. I shoot myself in the foot as well, until I implemented a copy method which copies the view-image of BoolType to an actual image of BitType. The BitType image can then be modified as you suggest. You find the snippet and a discussion about it here:

Afterwards, @dietzc suggested to put this method as a ops().burnIn(...) somewhere into Ops, but I’m not sure where it finally landed.

Cheers,
Robert

1 Like

The problem here is that there is no differentiation between read and write direction for imglib2 Types. This could be solved by a gigantic refactoring effort but that is just not realistic, e.g. add interfaces DoubleGet and DoubleSet, then compose the existing types of that as appropriate. Converted RAIs would then just expose the Get part of the type.

For your use case, you will have to create a copy as @haesleinhuepf suggested (it is super easy to do that with @maarzt’s LoobBuilder). For similar use-cases, where writing into the underlying RAI (rai in your example) would be meaningful, you could also use a SamplerConverter.

4 Likes

Thanks for the answers!
I understand that it would be too much work to change this!
In fact, I am copying already (but sometimes I forget).

Below is my code, which also addresses the issue of copying RAIs with non-zero coordinate origin:

@hanslovsky That’s the first time I hear of the SamplerConverter ; would it be possible to post a link to some code where it is used?

1 Like

I do not have an example in my code, unfortunately.

I suggest, instead of

orig.randomAccess().get()

you use

Util.getTypeFromInterval(orig);

That ensures that the RandomAccess is at a defined position of the RAI.

Also, afaik LoopBuilder should already support non-zero (and different per each RAI) min.

This is how I would write your method:

public static < T extends NativeType< T > >
RandomAccessibleInterval< T > copyAsArrayImg( RandomAccessibleInterval< T > orig )
{
	final RandomAccessibleInterval< T > copy = new ArrayImgFactory<>( Util.getTypeFromInterval( orig ) ).create( orig );
	LoopBuilder.setImages( copy, orig ).forEachPixel( Type::set );
	return Views.translate( copy, Intervals.minAsLongArray( orig ) );
}

(Assuming that Transforms.getWithAdjustedOrigin does the same as Views.translate)

2 Likes

Thanks a lot!

Just to be on the safe said, and because I find it more logical when reading the code, I rather left the translation before the LoopBuilder:

public static < T extends RealType< T > & NativeType< T > >
	RandomAccessibleInterval< T > copyAsArrayImg( RandomAccessibleInterval< T > orig )
	{
		RandomAccessibleInterval< T > copy = new ArrayImgFactory( Util.getTypeFromInterval( orig ) ).create( orig );

		copy = Views.translate( copy, Intervals.minAsLongArray( orig ) );

		LoopBuilder.setImages( copy, orig ).forEachPixel( Type::set );

		return copy;
	}

Or do you think that would have performance issues?

The performance benefit is probably negligble. I personally just prefer to have final variables wherever possible.

1 Like

Hi all,

I was wondering, in the same line of questioning, what would be the best way to convert a RAI to a RAI without passing through ops…

That is, how to pass a min and max scale for the normalization, and to instantiate a converter once so I can reuse it many times…

I’ve been looking at the Converter class, but I admit got quite lost…

I know this one from the DoG example

final RealTypeConverter< FloatType, T > scale_op = ( RealTypeConverter< FloatType, T > ) ops
				.op( "convert.normalizeScale", dog_img.firstElement(), image.firstElement() );
3 Likes

I personally do not use ops for conversion but go through the imglib2 API directly.

Maybe this pseudo code helps (U extends Type<U> but T can be anything. In practice, both are probably RealType or at least NumericType):

final RandomAccessibleInterval<T> source = ...
final Converter<T, U> converter = (sourceT, targetU) -> { /* converter impelementation here or use named class instead*/ };
final U u = // get a U from somewhere
RandomAccessibleInterval<U> target = Converters.convert(source, converter, u);

This is read-only conversion, i.e. target cannot be modified.

For your concrete example of min max scale conversion, I would probably create a named class like this (not tested):

class ScalingConverter<T extends RealType<T>, U extends RealType<U>> implements Converter<T, U> {
    private final double min;
    private final double max;
    private final double factor;
    public ScalingConverter(double min, double max) {
        this.min = min;
        this.max = max;
        this.factor = 1.0 / (max - min);
    }

    @Override
    public void convert(T input, U output) {
        final double inputDouble = input.getRealDouble() - this.min;
        output.setReal(inputDouble * this.factor);
    }
}

Possibly relevant classes to avoid code duplication:

3 Likes