Using imglib2 converters

imglib2
converters

#1

@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

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


#3

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

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?


#5

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)


#6

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?


#7

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