diff --git a/pom.xml b/pom.xml index 63d1325..0037bd1 100644 --- a/pom.xml +++ b/pom.xml @@ -181,5 +181,6 @@ imglib2-ij test + diff --git a/src/main/java/tpietzsch/blocks/ByteUtils.java b/src/main/java/tpietzsch/blocks/ByteUtils.java index 4541d9e..e9f2ed1 100644 --- a/src/main/java/tpietzsch/blocks/ByteUtils.java +++ b/src/main/java/tpietzsch/blocks/ByteUtils.java @@ -95,7 +95,11 @@ public static void setBytes( final byte src, final long dst, final long csx ) UNSAFE.setMemory( dst, csx, src ); } - public interface Address + public static void copyBytes( final byte[] src, final long dst, final long sox, final long csx) { + UNSAFE.copyMemory( src, BYTE_ARRAY_OFFSET + sox, null, dst, csx ); + } + + public interface Address { long getAddress(); } diff --git a/src/main/java/tpietzsch/blocks/CopySubArrayImp.java b/src/main/java/tpietzsch/blocks/CopySubArrayImp.java index 29e68c1..874f0af 100644 --- a/src/main/java/tpietzsch/blocks/CopySubArrayImp.java +++ b/src/main/java/tpietzsch/blocks/CopySubArrayImp.java @@ -52,6 +52,23 @@ public void copysubarray3d( final short[] src, final int sox, final int soy, fin } } + public static class ByteToAddress implements CopySubArray< byte[], Address > + { + @Override + public void clearsubarray3d( final Address dst, final int dox, final int doy, final int doz, final int dsx, final int dsy, final int csx, final int csy, final int csz ) + { + final ArrayFill fill = ( o, l ) -> ByteUtils.setBytes( ( byte ) 0, dst.getAddress() + o, l ); + fillsubarray3dn( fill, dox, doy, doz, dsx, dsy, csx, csy, csz ); + } + + @Override + public void copysubarray3d( final byte[] src, final int sox, final int soy, final int soz, final int ssx, final int ssy, final Address dst, final int dox, final int doy, final int doz, final int dsx, final int dsy, final int csx, final int csy, final int csz ) + { + final ArrayCopy copy = ( so, o, l ) -> ByteUtils.copyBytes( src, dst.getAddress() + o, so, l ); + copysubarray3dn( copy, sox, soy, soz, ssx, ssy, dox, doy, doz, dsx, dsy, csx, csy, csz ); + } + } + static void copysubarray3dn( ArrayCopy copysubarray1dn, final int sox, diff --git a/src/main/java/tpietzsch/blocks/TileAccess.java b/src/main/java/tpietzsch/blocks/TileAccess.java index f24d7e0..73b9519 100644 --- a/src/main/java/tpietzsch/blocks/TileAccess.java +++ b/src/main/java/tpietzsch/blocks/TileAccess.java @@ -41,8 +41,10 @@ import tpietzsch.cache.UploadBuffer; import tpietzsch.multires.ResolutionLevel3D; +import static net.imglib2.type.PrimitiveType.BYTE; import static net.imglib2.type.PrimitiveType.SHORT; import static tpietzsch.backend.Texture.InternalFormat.R16; +import static tpietzsch.backend.Texture.InternalFormat.R8; /** * Copy blocks from a {@link ResolutionLevel3D} source to an {@link UploadBuffer}. @@ -124,6 +126,16 @@ public boolean loadTile( final int[] gridPos, final UploadBuffer buffer ) new CopySubArrayImp.ShortToAddress(), cacheSpec ); + } else if ( cacheSpec.format() == R8 && cellimg ) + { + final boolean volatil = type instanceof Volatile; + return new TileAccess<>( + volatil + ? new GridDataAccessImp.VolatileCells<>( ( AbstractCellImg ) img ) + : new GridDataAccessImp.Cells<>( ( AbstractCellImg ) img ), + new CopySubArrayImp.ByteToAddress(), + cacheSpec + ); } } @@ -137,7 +149,7 @@ public static boolean isSupportedType( final Object type ) { final PrimitiveType primitive = ( ( NativeType ) type ).getNativeTypeFactory().getPrimitiveType(); final Fraction epp = ( ( NativeType ) type ).getEntitiesPerPixel(); - if ( primitive == SHORT && epp.getNumerator() == epp.getDenominator() ) + if (( primitive == SHORT && epp.getNumerator() == epp.getDenominator() )|| (primitive == BYTE && epp.getNumerator() == epp.getDenominator())) return true; } diff --git a/src/main/java/tpietzsch/example2/VolumeRenderer.java b/src/main/java/tpietzsch/example2/VolumeRenderer.java index cbabafd..35e7ded 100644 --- a/src/main/java/tpietzsch/example2/VolumeRenderer.java +++ b/src/main/java/tpietzsch/example2/VolumeRenderer.java @@ -35,6 +35,7 @@ import static com.jogamp.opengl.GL.GL_SRC_ALPHA; import static com.jogamp.opengl.GL.GL_UNPACK_ALIGNMENT; import static tpietzsch.backend.Texture.InternalFormat.R16; +import static tpietzsch.backend.Texture.InternalFormat.R8; import static tpietzsch.example2.VolumeRenderer.RepaintType.DITHER; import static tpietzsch.example2.VolumeRenderer.RepaintType.FULL; import static tpietzsch.example2.VolumeRenderer.RepaintType.LOAD; @@ -160,9 +161,6 @@ void request( final RepaintType type ) private final DefaultQuad quad; - - - public VolumeRenderer( final int renderWidth, final int renderHeight, @@ -177,7 +175,7 @@ public VolumeRenderer( // set up gpu cache // TODO This could be packaged into one class and potentially shared between renderers? - cacheSpec = new CacheSpec( R16, cacheBlockSize ); + cacheSpec = new CacheSpec( R8, cacheBlockSize ); //new CacheSpec( R16, cacheBlockSize ); final int[] cacheGridDimensions = TextureCache.findSuitableGridSize( cacheSpec, maxCacheSizeInMB ); textureCache = new TextureCache( cacheGridDimensions, cacheSpec ); pboChain = new PboChain( 5, 100, textureCache ); @@ -436,7 +434,8 @@ private void updateBlocks( try { - ProcessFillTasks.parallel( textureCache, pboChain, context, forkJoinPool, fillTasks ); + //ProcessFillTasks.parallel( textureCache, pboChain, context, forkJoinPool, fillTasks ); + ProcessFillTasks.sequential( textureCache, pboChain, context, fillTasks ); } catch ( final InterruptedException e ) { diff --git a/src/test/java/bvv/examples/Example08.java b/src/test/java/bvv/examples/Example08.java new file mode 100644 index 0000000..0dc3aad --- /dev/null +++ b/src/test/java/bvv/examples/Example08.java @@ -0,0 +1,346 @@ +package bvv.examples; + +import bdv.util.BdvFunctions; +import bdv.util.BdvOptions; +import bdv.util.BdvSource; +import bdv.util.volatiles.SharedQueue; +import bdv.util.volatiles.VolatileViews; +import bvv.util.BvvFunctions; +import bvv.util.BvvOptions; +import bvv.util.BvvStackSource; +import net.imglib2.Cursor; +import net.imglib2.FinalInterval; +import net.imglib2.IterableInterval; +import net.imglib2.IterableRealInterval; +import net.imglib2.KDTree; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealInterval; +import net.imglib2.RealPoint; +import net.imglib2.RealPointSampleList; +import net.imglib2.RealRandomAccessible; +import net.imglib2.Volatile; +import net.imglib2.algorithm.util.Grids; +import net.imglib2.cache.img.CachedCellImg; +import net.imglib2.cache.img.DiskCachedCellImgFactory; +import net.imglib2.cache.img.DiskCachedCellImgOptions; +import net.imglib2.cache.img.RandomAccessibleCacheLoader; +import net.imglib2.cache.img.ReadOnlyCachedCellImgFactory; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgFactory; +import net.imglib2.img.basictypeaccess.AccessFlags; +import net.imglib2.img.basictypeaccess.array.ArrayDataAccess; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.interpolation.neighborsearch.NearestNeighborSearchInterpolatorFactory; +import net.imglib2.neighborsearch.NearestNeighborSearch; +import net.imglib2.neighborsearch.NearestNeighborSearchOnKDTree; +import net.imglib2.type.NativeType; +import net.imglib2.type.Type; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Intervals; +import net.imglib2.util.Util; +import net.imglib2.view.Views; + +import java.io.IOException; +import java.util.Random; + +public class Example08 { + + public static void main(String... args) { + + long[] imageVoxelSize = new long[] { 512, 512, 32 }; + int numberOfPoints = 800; + + //RandomAccessibleInterval labelImage = getFloatLabelImage(imageVoxelSize, numberOfPoints); + //RandomAccessibleInterval labelImage = get8BitsLabelImage(imageVoxelSize, numberOfPoints); + RandomAccessibleInterval labelImage = get16BitsLabelImage(imageVoxelSize, numberOfPoints); + + final BdvSource source = BdvFunctions.show(labelImage,"Label Image" ); + source.setColor(new ARGBType(0xFF00FF00)); + //source.setDisplayRange(0,1); for Float + source.setDisplayRange(0,256); + + final SharedQueue queue = new SharedQueue( 7 ); + RandomAccessibleInterval> rai = VolatileViews.wrapAsVolatile(get3DBorderLabelImage(labelImage), queue); + BdvFunctions.show(rai, "Borders", BdvOptions.options().addTo(source)); + + final BvvStackSource< ? > bvvSourceBorders = BvvFunctions.show( rai, "Borders"); + bvvSourceBorders.setDisplayRange( 0, 256*256 ); + + final BvvStackSource< ? > bvvSourceLabels = BvvFunctions.show( labelImage, "Labels", BvvOptions.options().addTo(bvvSourceBorders)); + //bvvSourceLabels.setDisplayRange( 0, 256*256 ); + bvvSourceLabels.setColor(new ARGBType(0xFF00FF00)); + + } + + public static RandomAccessibleInterval getFloatLabelImage(long[] imageVoxelSize, int numberOfPoints) { + FinalInterval interval = new FinalInterval( imageVoxelSize ); + IterableRealInterval< FloatType > iterableFloat = createFloatRandomPoints( interval, numberOfPoints ); + // Get test label image + RandomAccessibleInterval< FloatType > labelImage = getTestLabelImage(iterableFloat, imageVoxelSize); + return labelImage; + } + + public static RandomAccessibleInterval get8BitsLabelImage(long[] imageVoxelSize, int numberOfPoints) { + FinalInterval interval = new FinalInterval( imageVoxelSize ); + IterableRealInterval< UnsignedByteType > iterableFloat = createByteRandomPoints( interval, numberOfPoints ); + // Get test label image + RandomAccessibleInterval< UnsignedByteType > labelImage = getTestLabelImage(iterableFloat, imageVoxelSize); + return labelImage; + } + + public static RandomAccessibleInterval get16BitsLabelImage(long[] imageVoxelSize, int numberOfPoints) { + FinalInterval interval = new FinalInterval( imageVoxelSize ); + IterableRealInterval< UnsignedShortType > iterableFloat = createShortRandomPoints( interval, numberOfPoints ); + // Get test label image + RandomAccessibleInterval< UnsignedShortType > labelImage = getTestLabelImage(iterableFloat, imageVoxelSize); + return labelImage; + } + + public static & Comparable > Img get3DBorderLabelImage(RandomAccessibleInterval lblImg) { + // Make edge display on demand + final int[] cellDimensions = new int[] { 32, 32, 32 }; + + // Cached Image Factory Options + final DiskCachedCellImgOptions factoryOptions = DiskCachedCellImgOptions.options() + .cellDimensions( cellDimensions ) + .cacheType( DiskCachedCellImgOptions.CacheType.BOUNDED ) + .maxCacheSize( 100 ); + + // Expand label image by one pixel to avoid out of bounds exception + final RandomAccessibleInterval lblImgWithBorder = Views.expandBorder(lblImg, new long[] {1,1,1}); + + // Creates cached image factory of Type Byte + final DiskCachedCellImgFactory< UnsignedByteType > factory = new DiskCachedCellImgFactory<>( new UnsignedByteType(), factoryOptions ); + + // Creates shifted views by one pixel in each dimension + RandomAccessibleInterval lblImgXShift = Views.translate(lblImgWithBorder, new long[] {1,0,0}); + RandomAccessibleInterval lblImgYShift = Views.translate(lblImgWithBorder,new long[] {0,1,0}); + RandomAccessibleInterval lblImgZShift = Views.translate(lblImgWithBorder,new long[] {0,0,1}); + + // Creates border image, with cell Consumer method, which creates the image + final Img borderLabel = factory.create( lblImg, cell -> { + + // Cursor on the source image + final Cursor inNS = Views.flatIterable( Views.interval( lblImg, cell ) ).cursor(); + + // Cursor on shifted source image + final Cursor inXS = Views.flatIterable( Views.interval( lblImgXShift, cell ) ).cursor(); + final Cursor inYS = Views.flatIterable( Views.interval( lblImgYShift, cell ) ).cursor(); + final Cursor inZS = Views.flatIterable( Views.interval( lblImgZShift, cell ) ).cursor(); + + // Cursor on output image + final Cursor out = Views.flatIterable( cell ).cursor(); + + // Loops through voxels + while ( out.hasNext() ) { + T v = inNS.next(); + if (v.compareTo(inXS.next())!=0) { + out.next().set( (byte) 126 ); + inYS.next(); + inZS.next(); + } else { + if (v.compareTo(inYS.next())!=0) { + out.next().set( (byte) 126 ); + inZS.next(); + } else { + if (v.compareTo(inZS.next())!=0) { + out.next().set( (byte) 126 ); + } else { + out.next(); + } + } + } + } + }, factoryOptions.options().initializeCellsAsDirty( true ) ); + + return borderLabel; + } + + + //------------------------------------- METHODS TO CREATE TEST LABEL IMAGE + + public static &NativeType> RandomAccessibleInterval< T > getTestLabelImage(IterableRealInterval< T > realInterval, final long[] imgTestSize) { + + // the interval in which to create random points + + // create an IterableRealInterval + // using nearest neighbor search we will be able to return a value an any position in space + NearestNeighborSearch< T > search = + new NearestNeighborSearchOnKDTree<>( + new KDTree<>( realInterval ) ); + + // make it into RealRandomAccessible using nearest neighbor search + RealRandomAccessible< T > realRandomAccessible = + Views.interpolate( search, new NearestNeighborSearchInterpolatorFactory<>() ); + + // convert it into a RandomAccessible which can be displayed + RandomAccessible< T > randomAccessible = Views.raster( realRandomAccessible ); + + // set the initial interval as area to view + RandomAccessibleInterval< T > labelImage = Views.interval( randomAccessible, new FinalInterval(imgTestSize) ); + + final RandomAccessibleInterval< T > labelImageCopy = new ArrayImgFactory( Util.getTypeFromInterval( labelImage ) ).create( labelImage ); + + // Image copied to avoid computing it on the fly + // https://github.com/imglib/imglib2-algorithm/blob/47cd6ed5c97cca4b316c92d4d3260086a335544d/src/main/java/net/imglib2/algorithm/util/Grids.java#L221 used for parallel copy + Grids.collectAllContainedIntervals(imgTestSize, new int[] {128,128,32}).parallelStream().forEach( blockinterval-> + copy(labelImage, Views.interval(labelImageCopy, blockinterval)) + ); + + // Returning it + return labelImageCopy; + } + + + /** + * Copy from a source that is just RandomAccessible to an IterableInterval. Latter one defines + * size and location of the copy operation. It will query the same pixel locations of the + * IterableInterval in the RandomAccessible. It is up to the developer to ensure that these + * coordinates match. + * + * Note that both, input and output could be Views, Img or anything that implements + * those interfaces. + * + * @param source - a RandomAccess as source that can be infinite + * @param target - an IterableInterval as target + */ + public static < T extends Type< T >> void copy(final RandomAccessible< T > source, + final IterableInterval< T > target ) + { + // create a cursor that automatically localizes itself on every move + Cursor< T > targetCursor = target.localizingCursor(); + RandomAccess< T > sourceRandomAccess = source.randomAccess(); + + // iterate over the input cursor + while ( targetCursor.hasNext()) + { + // move input cursor forward + targetCursor.fwd(); + + // set the output cursor to the position of the input cursor + sourceRandomAccess.setPosition( targetCursor ); + + // set the value of this pixel of the output image, every Type supports T.set( T type ) + targetCursor.get().set( sourceRandomAccess.get() ); + } + + } + + + /** + * Create a number of n-dimensional random points in a certain interval + * having a random intensity 0...1 + * + * @param interval - the interval in which points are created + * @param numPoints - the amount of points + * + * @return a RealPointSampleList (which is an IterableRealInterval) + */ + public static RealPointSampleList createFloatRandomPoints( + RealInterval interval, int numPoints ) + { + // the number of dimensions + int numDimensions = interval.numDimensions(); + + // a random number generator + Random rnd = new Random( 2001);//System.currentTimeMillis() ); + + // a list of Samples with coordinates + RealPointSampleList< FloatType > elements = + new RealPointSampleList<>( numDimensions ); + + for ( int i = 0; i < numPoints; ++i ) + { + RealPoint point = new RealPoint( numDimensions ); + + for ( int d = 0; d < numDimensions; ++d ) + point.setPosition( rnd.nextDouble() * + ( interval.realMax( d ) - interval.realMin( d ) ) + interval.realMin( d ), d ); + + // add a new element with a random intensity in the range 0...1 + elements.add( point, new FloatType( rnd.nextFloat() ) ); + } + + return elements; + } + + public static IterableRealInterval createShortRandomPoints(RealInterval interval, int numPoints) { + // the number of dimensions + int numDimensions = interval.numDimensions(); + + // a random number generator + Random rnd = new Random( 2001);//System.currentTimeMillis() ); + + // a list of Samples with coordinates + RealPointSampleList< UnsignedShortType > elements = + new RealPointSampleList<>( numDimensions ); + + for ( int i = 0; i < numPoints; ++i ) + { + RealPoint point = new RealPoint( numDimensions ); + + for ( int d = 0; d < numDimensions; ++d ) + point.setPosition( rnd.nextDouble() * + ( interval.realMax( d ) - interval.realMin( d ) ) + interval.realMin( d ), d ); + + // add a new element with a random intensity in the range 0...1 + elements.add( point, new UnsignedShortType( rnd.nextInt(65535) ) ); + } + + return elements; + } + + /** + * Create a number of n-dimensional random points in a certain interval + * having a random intensity 0...1 + * + * @param interval - the interval in which points are created + * @param numPoints - the amount of points + * + * @return a RealPointSampleList (which is an IterableRealInterval) + */ + public static RealPointSampleList createByteRandomPoints( + RealInterval interval, int numPoints ) + { + // the number of dimensions + int numDimensions = interval.numDimensions(); + + // a random number generator + Random rnd = new Random( 2001);//System.currentTimeMillis() ); + + // a list of Samples with coordinates + RealPointSampleList< UnsignedByteType > elements = + new RealPointSampleList<>( numDimensions ); + + for ( int i = 0; i < numPoints; ++i ) + { + RealPoint point = new RealPoint( numDimensions ); + + for ( int d = 0; d < numDimensions; ++d ) + point.setPosition( rnd.nextDouble() * + ( interval.realMax( d ) - interval.realMin( d ) ) + interval.realMin( d ), d ); + + // add a new element with a random intensity in the range 0...1 + elements.add( point, new UnsignedByteType( rnd.nextInt(255) ) ); + } + + return elements; + } + + public static final , A extends ArrayDataAccess, CA extends ArrayDataAccess> CachedCellImg wrapAsCachedCellImg( + final RandomAccessibleInterval source, + final int[] blockSize) throws IOException { + + final long[] dimensions = Intervals.dimensionsAsLongArray(source); + final CellGrid grid = new CellGrid(dimensions, blockSize); + + final RandomAccessibleCacheLoader loader = RandomAccessibleCacheLoader.get(grid, Views.zeroMin(source), AccessFlags.setOf()); + return new ReadOnlyCachedCellImgFactory().createWithCacheLoader(dimensions, source.randomAccess().get(), loader); + } +}