From 0babe091c2d5cb7086f7e416d09800cec018f2ad Mon Sep 17 00:00:00 2001 From: tpietzsch Date: Thu, 7 Mar 2024 15:56:51 +0100 Subject: [PATCH] Add support for arbitrary net.imglib2.converter.Converter in Convert --- .../algorithm/blocks/convert/Convert.java | 13 + .../convert/ConverterBlockProcessor.java | 243 ++++++++++++++++++ .../convert/PrimitiveTypeProperties.java | 140 ++++++++++ 3 files changed, 396 insertions(+) create mode 100644 src/main/java/net/imglib2/algorithm/blocks/convert/ConverterBlockProcessor.java create mode 100644 src/main/java/net/imglib2/algorithm/blocks/convert/PrimitiveTypeProperties.java diff --git a/src/main/java/net/imglib2/algorithm/blocks/convert/Convert.java b/src/main/java/net/imglib2/algorithm/blocks/convert/Convert.java index 5b838345f..03ed2e88b 100644 --- a/src/main/java/net/imglib2/algorithm/blocks/convert/Convert.java +++ b/src/main/java/net/imglib2/algorithm/blocks/convert/Convert.java @@ -33,8 +33,11 @@ */ package net.imglib2.algorithm.blocks.convert; +import java.util.function.Supplier; + import net.imglib2.algorithm.blocks.DefaultUnaryBlockOperator; import net.imglib2.algorithm.blocks.UnaryBlockOperator; +import net.imglib2.converter.Converter; import net.imglib2.type.NativeType; /** @@ -77,4 +80,14 @@ UnaryBlockOperator< S, T > convert( final S sourceType, final T targetType, fina { return new DefaultUnaryBlockOperator<>( sourceType, targetType, new ConvertBlockProcessor<>( sourceType, targetType, clamp ) ); } + + /** + * Create {@link UnaryBlockOperator} to convert blocks between {@code + * sourceType} and {@code targetType} with the specified {@code Converter}. + */ + public static < S extends NativeType< S >, T extends NativeType< T > > + UnaryBlockOperator< S, T > convert( final S sourceType, final T targetType, Supplier< Converter< ? super S, T > > converterSupplier ) + { + return new DefaultUnaryBlockOperator<>( sourceType, targetType, new ConverterBlockProcessor<>( sourceType, targetType, converterSupplier ) ); + } } diff --git a/src/main/java/net/imglib2/algorithm/blocks/convert/ConverterBlockProcessor.java b/src/main/java/net/imglib2/algorithm/blocks/convert/ConverterBlockProcessor.java new file mode 100644 index 000000000..b737cd965 --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/blocks/convert/ConverterBlockProcessor.java @@ -0,0 +1,243 @@ +/*- + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2023 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.algorithm.blocks.convert; + +import java.util.Arrays; +import java.util.function.Supplier; + +import net.imglib2.Cursor; +import net.imglib2.Interval; +import net.imglib2.RandomAccess; +import net.imglib2.algorithm.blocks.BlockProcessor; +import net.imglib2.algorithm.blocks.util.BlockProcessorSourceInterval; +import net.imglib2.blocks.TempArray; +import net.imglib2.converter.Converter; +import net.imglib2.img.AbstractImg; +import net.imglib2.img.Img; +import net.imglib2.img.ImgFactory; +import net.imglib2.img.NativeImg; +import net.imglib2.type.NativeType; +import net.imglib2.type.NativeTypeFactory; +import net.imglib2.util.CloseableThreadLocal; +import net.imglib2.util.Intervals; + +/** + * Convert primitive arrays between ImgLib2 {@code NativeType}s using a {@link }Converter}. + * + * @param + * source type + * @param + * target type + * @param + * input primitive array type, e.g., float[]. Must correspond to S. + * @param + * output primitive array type, e.g., float[]. Must correspond to T. + */ +class ConverterBlockProcessor< S extends NativeType< S >, T extends NativeType< T >, I, O > implements BlockProcessor< I, O > +{ + private final S sourceType; + + private final T targetType; + + private final Supplier< Converter< ? super S, T > > converterSupplier; + + private final TempArray< I > tempArray; + + private Supplier< ConverterBlockProcessor< S, T, I, O > > threadSafeSupplier; + + private long[] sourcePos; + + private int[] sourceSize; + + private int sourceLength; + + private final BlockProcessorSourceInterval sourceInterval; + + private final Converter< ? super S, T > converter; + + private final Wrapper< S > wrapSource; + + private final Wrapper< T > wrapTarget; + + public ConverterBlockProcessor( final S sourceType, final T targetType, final Supplier< Converter< ? super S, T > > converterSupplier ) + { + this.sourceType = sourceType; + this.targetType = targetType; + this.converterSupplier = converterSupplier; + + tempArray = TempArray.forPrimitiveType( sourceType.getNativeTypeFactory().getPrimitiveType() ); + sourceInterval = new BlockProcessorSourceInterval( this ); + converter = converterSupplier.get(); + wrapSource = Wrapper.of( sourceType ); + wrapTarget = Wrapper.of( targetType ); + } + + private ConverterBlockProcessor( ConverterBlockProcessor< S, T, I, O > convert ) + { + sourceType = convert.sourceType; + targetType = convert.targetType; + converterSupplier = convert.converterSupplier; + + threadSafeSupplier = convert.threadSafeSupplier; + + tempArray = convert.tempArray.newInstance(); + sourceInterval = new BlockProcessorSourceInterval( this ); + converter = converterSupplier.get(); + wrapSource = Wrapper.of( sourceType ); + wrapTarget = Wrapper.of( targetType ); + } + + private ConverterBlockProcessor< S, T, I, O > newInstance() + { + return new ConverterBlockProcessor<>( this ); + } + + @Override + public Supplier< ? extends BlockProcessor< I, O > > threadSafeSupplier() + { + if ( threadSafeSupplier == null ) + threadSafeSupplier = CloseableThreadLocal.withInitial( this::newInstance )::get; + return threadSafeSupplier; + } + + @Override + public void setTargetInterval( final Interval interval ) + { + final int n = interval.numDimensions(); + if ( sourcePos == null || sourcePos.length != n ) + { + sourcePos = new long[ n ]; + sourceSize = new int[ n ]; + } + interval.min( sourcePos ); + Arrays.setAll( sourceSize, d -> safeInt( interval.dimension( d ) ) ); + sourceLength = safeInt( Intervals.numElements( sourceSize ) ); + } + + private static int safeInt( final long value ) + { + if ( value > Integer.MAX_VALUE ) + throw new IllegalArgumentException( "value too large" ); + return ( int ) value; + } + + @Override + public long[] getSourcePos() + { + return sourcePos; + } + + @Override + public int[] getSourceSize() + { + return sourceSize; + } + + @Override + public Interval getSourceInterval() + { + return sourceInterval; + } + + @Override + public I getSourceBuffer() + { + return tempArray.get( sourceLength ); + } + + @Override + public void compute( final I src, final O dest ) + { + final S in = wrapSource.wrap( src ); + final T out = wrapTarget.wrap( dest ); + for ( int i = 0; i < sourceLength; i++ ) + { + in.index().set( i ); + out.index().set( i ); + converter.convert( in, out ); + } + } + + // ------------------------------------------------------------------------ + // + // Wrap primitive array into a Type that can be passed to the Converter + // + + private interface Wrapper< T extends NativeType< T > > + { + T wrap( final Object array ); + + static < T extends NativeType< T > > Wrapper of( T type ) + { + return new WrapperImpl<>( type ); + } + } + + private static class WrapperImpl< T extends NativeType< T >, A > extends AbstractImg< T > implements NativeImg< T, A >, Wrapper< T > + { + private final PrimitiveTypeProperties< ?, A > props; + private Object array; + private final T wrapper; + + WrapperImpl( T type ) + { + super( new long[ 0 ] ); + final NativeTypeFactory< T, A > nativeTypeFactory = ( NativeTypeFactory< T, A > ) type.getNativeTypeFactory(); + props = ( PrimitiveTypeProperties< ?, A > ) PrimitiveTypeProperties.get( nativeTypeFactory.getPrimitiveType() ); + wrapper = nativeTypeFactory.createLinkedType( this ); + } + + @Override + public T wrap( final Object array ) + { + this.array = array; + wrapper.updateContainer( null ); + return wrapper; + } + + @Override + public A update( final Object updater ) + { + return props.wrap( array ); + } + + @Override public void setLinkedType( final T type ) {throw new UnsupportedOperationException();} + @Override public T createLinkedType() {throw new UnsupportedOperationException();} + @Override public Cursor< T > cursor() {throw new UnsupportedOperationException();} + @Override public Cursor< T > localizingCursor() {throw new UnsupportedOperationException();} + @Override public Object iterationOrder() {throw new UnsupportedOperationException();} + @Override public RandomAccess< T > randomAccess() {throw new UnsupportedOperationException();} + @Override public ImgFactory< T > factory() {throw new UnsupportedOperationException();}@Override public Img< T > copy() {throw new UnsupportedOperationException();} + } +} diff --git a/src/main/java/net/imglib2/algorithm/blocks/convert/PrimitiveTypeProperties.java b/src/main/java/net/imglib2/algorithm/blocks/convert/PrimitiveTypeProperties.java new file mode 100644 index 000000000..ba7015b6e --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/blocks/convert/PrimitiveTypeProperties.java @@ -0,0 +1,140 @@ +/*- + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2023 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.algorithm.blocks.convert; + +import static net.imglib2.type.PrimitiveType.BOOLEAN; +import static net.imglib2.type.PrimitiveType.BYTE; +import static net.imglib2.type.PrimitiveType.CHAR; +import static net.imglib2.type.PrimitiveType.DOUBLE; +import static net.imglib2.type.PrimitiveType.FLOAT; +import static net.imglib2.type.PrimitiveType.INT; +import static net.imglib2.type.PrimitiveType.LONG; +import static net.imglib2.type.PrimitiveType.SHORT; + +import java.util.EnumMap; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.ToIntFunction; + +import net.imglib2.img.basictypeaccess.array.BooleanArray; +import net.imglib2.img.basictypeaccess.array.ByteArray; +import net.imglib2.img.basictypeaccess.array.CharArray; +import net.imglib2.img.basictypeaccess.array.DoubleArray; +import net.imglib2.img.basictypeaccess.array.FloatArray; +import net.imglib2.img.basictypeaccess.array.IntArray; +import net.imglib2.img.basictypeaccess.array.LongArray; +import net.imglib2.img.basictypeaccess.array.ShortArray; +import net.imglib2.type.PrimitiveType; + +// For now, this class is copied from imglib2 core. I don't want to make it public just yet. + +/** + * @param

a primitive array type, e.g., {@code byte[]}. + * @param the corresponding {@code ArrayDataAccess} type. + */ +class PrimitiveTypeProperties< P, A > +{ + final Class< P > primitiveArrayClass; + + final IntFunction< P > createPrimitiveArray; + + final ToIntFunction< P > primitiveArrayLength; + + final Function< P, A > wrapAsAccess; + + static PrimitiveTypeProperties< ?, ? > get( final PrimitiveType primitiveType ) + { + final PrimitiveTypeProperties< ?, ? > props = creators.get( primitiveType ); + if ( props == null ) + throw new IllegalArgumentException(); + return props; + } + + /** + * Wrap a primitive array {@code data} into a corresponding {@code ArrayDataAccess}. + * + * @param data primitive array to wrap (actually type {@code P} instead of {@code Object}, but its easier to use this way) + * @return {@code ArrayDataAccess} wrapping {@code data} + */ + A wrap( Object data ) + { + if ( data == null ) + throw new NullPointerException(); + if ( !primitiveArrayClass.isInstance( data ) ) + throw new IllegalArgumentException( "expected " + primitiveArrayClass.getSimpleName() + " argument" ); + return wrapAsAccess.apply( ( P ) data ); + } + + /** + * Allocate a primitive array (type {@code P}) with {@code length} elements. + */ + P allocate( int length ) + { + return createPrimitiveArray.apply( length ); + } + + /** + * Get the length of a primitive array (type {@code P}). + */ + int length( P array ) + { + return primitiveArrayLength.applyAsInt( array ); + } + + private PrimitiveTypeProperties( + final Class< P > primitiveArrayClass, + final IntFunction< P > createPrimitiveArray, + final ToIntFunction< P > primitiveArrayLength, + final Function< P, A > wrapAsAccess ) + { + this.primitiveArrayClass = primitiveArrayClass; + this.createPrimitiveArray = createPrimitiveArray; + this.primitiveArrayLength = primitiveArrayLength; + this.wrapAsAccess = wrapAsAccess; + } + + private static final EnumMap< PrimitiveType, PrimitiveTypeProperties< ?, ? > > creators = new EnumMap<>( PrimitiveType.class ); + + static + { + creators.put( BOOLEAN, new PrimitiveTypeProperties<>( boolean[].class, boolean[]::new, a -> a.length, BooleanArray::new ) ); + creators.put( BYTE, new PrimitiveTypeProperties<>( byte[].class, byte[]::new, a -> a.length, ByteArray::new ) ); + creators.put( CHAR, new PrimitiveTypeProperties<>( char[].class, char[]::new, a -> a.length, CharArray::new ) ); + creators.put( SHORT, new PrimitiveTypeProperties<>( short[].class, short[]::new, a -> a.length, ShortArray::new ) ); + creators.put( INT, new PrimitiveTypeProperties<>( int[].class, int[]::new, a -> a.length, IntArray::new ) ); + creators.put( LONG, new PrimitiveTypeProperties<>( long[].class, long[]::new, a -> a.length, LongArray::new ) ); + creators.put( FLOAT, new PrimitiveTypeProperties<>( float[].class, float[]::new, a -> a.length, FloatArray::new ) ); + creators.put( DOUBLE, new PrimitiveTypeProperties<>( double[].class, double[]::new, a -> a.length, DoubleArray::new ) ); + } +}