Skip to content
Snippets Groups Projects
Commit b6b39d6f authored by Tobias Pietzsch's avatar Tobias Pietzsch
Browse files

finished prefetching strategy:

* clean up Prefetcher (previously PlayWithGeometry)
* wait/notify instead of letting Fetcher threads sleep
* remove debug prints
* clean up IoTimeBudget
* keep references to entries accessed in current frame
  such that they cannot be cleared from the cache prematurely.
parent 9b770f74
No related branches found
No related tags found
No related merge requests found
......@@ -26,5 +26,5 @@ public interface Cache
/**
* (Re-)initialize the IO time budget.
*/
public void initIoTimeBudget( final long[] partialBudget, final boolean reinitialize );
public void initIoTimeBudget( final long[] partialBudget );
}
......@@ -20,22 +20,31 @@ public class CacheIoTiming
*/
public static class IoTimeBudget
{
private final long[] initialBudget;
private final long[] budget;
public IoTimeBudget( final long[] initBudget )
public IoTimeBudget( final int numLevels )
{
budget = new long[ numLevels ];
}
public synchronized void reset( final long[] partialBudget )
{
if ( partialBudget == null )
clear();
else
{
initialBudget = initBudget.clone();
for ( int l = 1; l < initialBudget.length; ++l )
if ( initialBudget[ l ] > initialBudget[ l - 1 ] )
initialBudget[ l ] = initialBudget[ l - 1 ];
budget = initialBudget.clone();
for ( int i = 0; i < budget.length; ++i )
budget[ i ] = partialBudget.length > i ? partialBudget[ i ] : partialBudget[ partialBudget.length - 1 ];
for ( int i = 1; i < budget.length; ++i )
if ( budget[ i ] > budget[ i - 1 ] )
budget[ i ] = budget[ i - 1 ];
}
}
public void reset()
public synchronized void clear()
{
System.arraycopy( initialBudget, 0, budget, 0, budget.length );
for ( int i = 0; i < budget.length; ++i )
budget[ i ] = 0;
}
public synchronized long timeLeft( final int level )
......
......@@ -4,6 +4,8 @@ import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import net.imglib2.img.basictypeaccess.volatiles.VolatileAccess;
......@@ -99,6 +101,12 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
protected final ConcurrentHashMap< Key, Reference< Entry > > softReferenceCache = new ConcurrentHashMap< Key, Reference< Entry > >();
/**
* Keeps references to the {@link Entry entries} accessed in the current
* frame, such that they cannot be cleared from the cache prematurely.
*/
protected final List< Entry > currentFrameEntries = Collections.synchronizedList( new ArrayList< Entry >() );
protected final BlockingFetchQueues< Key > queue;
protected volatile long currentQueueFrame = 0;
......@@ -123,7 +131,10 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
{
try
{
Thread.sleep( 1 );
synchronized ( lock )
{
lock.wait( waitMillis );
}
}
catch ( final InterruptedException e )
{}
......@@ -139,6 +150,8 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
}
}
private final Object lock = new Object();
private volatile long pauseUntilTimeMillis = 0;
public void pauseUntil( final long timeMillis )
......@@ -150,25 +163,36 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
public void wakeUp()
{
pauseUntilTimeMillis = 0;
synchronized ( lock )
{
lock.notify();
}
}
public void pauseFetcherThreads()
{
pauseFetcherThreadsUntil( System.currentTimeMillis() + 5 );
}
public void pauseFetcherThreads( final long ms )
/**
* pause all {@link Fetcher} threads for the specified number of milliseconds.
*/
public void pauseFetcherThreadsFor( final long ms )
{
pauseFetcherThreadsUntil( System.currentTimeMillis() + ms );
}
/**
* pause all {@link Fetcher} threads until the given time (see
* {@link System#currentTimeMillis()}).
*/
public void pauseFetcherThreadsUntil( final long timeMillis )
{
for ( final Fetcher f : fetchers )
f.pauseUntil( timeMillis );
}
/**
* Wake up all Fetcher threads immediately. This ends any
* {@link #pauseFetcherThreadsFor(long)} and
* {@link #pauseFetcherThreadsUntil(long)} set earlier.
*/
public void wakeFetcherThreads()
{
for ( final Fetcher f : fetchers )
......@@ -179,7 +203,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
private final CacheArrayLoader< A > loader;
public VolatileGlobalCellCache( final CacheArrayLoader< A > loader, final int numTimepoints, final int numSetups, final int maxNumLevels, final int[] maxLevels )
public VolatileGlobalCellCache( final CacheArrayLoader< A > loader, final int numTimepoints, final int numSetups, final int maxNumLevels, final int[] maxLevels, final int numFetcherThreads )
{
this.loader = loader;
this.numTimepoints = numTimepoints;
......@@ -189,7 +213,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
queue = new BlockingFetchQueues< Key >( maxNumLevels );
fetchers = new ArrayList< Fetcher >();
for ( int i = 0; i < 1; ++i ) // TODO: add numFetcherThreads parameter
for ( int i = 0; i < numFetcherThreads; ++i )
{
final Fetcher f = new Fetcher();
f.setDaemon( true );
......@@ -240,7 +264,9 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
{
final VolatileCell< A > cell = new VolatileCell< A >( cellDims, cellMin, loader.loadArray( timepoint, setup, level, cellDims, cellMin ) );
entry.data = cell;
entry.enqueueFrame = Long.MAX_VALUE;
softReferenceCache.put( entry.key, new SoftReference< Entry >( entry ) );
entry.notifyAll();
}
}
}
......@@ -258,6 +284,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
final Key k = entry.key;
final int priority = maxLevels[ k.setup ] - k.level;
queue.put( k, priority );
currentFrameEntries.add( entry );
}
}
......@@ -272,26 +299,26 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
final IoTimeBudget budget = stats.getIoTimeBudget();
final Key k = entry.key;
final int priority = maxLevels[ k.setup ] - k.level;
if ( budget.timeLeft( priority ) > 0 )
final long timeLeft = budget.timeLeft( priority );
if ( timeLeft > 0 )
{
synchronized ( entry )
{
pauseFetcherThreads( 1000 );
if ( entry.data.getData().isValid() )
return;
enqueueEntry( entry );
final long t0 = stats.getIoNanoTime();
stats.start();
while( true )
try
{
loadEntryIfNotValid( entry );
break;
entry.wait( timeLeft / 1000000l, 1 );
}
catch ( final InterruptedException e )
{}
stats.stop();
final long t = stats.getIoNanoTime() - t0;
budget.use( t, priority );
if ( budget.timeLeft( 0 ) <= 0 )
wakeFetcherThreads();
else
pauseFetcherThreads( 3 );
}
}
else
enqueueEntry( entry );
......@@ -432,6 +459,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
public void prepareNextFrame()
{
queue.clear();
currentFrameEntries.clear();
++currentQueueFrame;
}
......@@ -445,23 +473,14 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
* smaller-equal the budget for level <em>j</em>. If <em>n</em>
* is smaller than the maximum number of mipmap levels, the
* remaining priority levels are filled up with budget[n].
* @param reinitialize
* If true, the IO time budget is initialized to the given
* partial budget. If false, the IO time budget is reset to the
* previous initial values.
*/
@Override
public void initIoTimeBudget( final long[] partialBudget, final boolean reinitialize )
public void initIoTimeBudget( final long[] partialBudget )
{
final IoStatistics stats = CacheIoTiming.getThreadGroupIoStatistics();
if ( reinitialize || stats.getIoTimeBudget() == null )
{
final long[] budget = new long[ maxNumLevels ];
for ( int i = 0; i < budget.length; ++i )
budget[ i ] = partialBudget.length > i ? partialBudget[ i ] : partialBudget[ partialBudget.length - 1 ];
stats.setIoTimeBudget( new IoTimeBudget( budget ) );
}
stats.getIoTimeBudget().reset();
if ( stats.getIoTimeBudget() == null )
stats.setIoTimeBudget( new IoTimeBudget( maxNumLevels ) );
stats.getIoTimeBudget().reset( partialBudget );
}
public class Hdf5CellCache implements CellCache< A >
......
......@@ -141,7 +141,7 @@ public class Hdf5ImageLoader implements ViewerImgLoader
cachedDimensions = new long[ numTimepoints * numSetups * maxNumLevels ][];
cachedExistence = new Boolean[ numTimepoints * numSetups * maxNumLevels ];
cache = new VolatileGlobalCellCache< VolatileShortArray >( new Hdf5VolatileShortArrayLoader( hdf5Reader ), numTimepoints, numSetups, maxNumLevels, maxLevels );
cache = new VolatileGlobalCellCache< VolatileShortArray >( new Hdf5VolatileShortArrayLoader( hdf5Reader ), numTimepoints, numSetups, maxNumLevels, maxLevels, 1 );
}
@Override
......@@ -255,8 +255,7 @@ public class Hdf5ImageLoader implements ViewerImgLoader
System.err.println( "image data for " + view.getBasename() + " level " + level + " could not be found. Partition file missing?" );
return getMissingDataImage( view, level, new VolatileUnsignedShortType() );
}
final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.VOLATILE );
// final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.BUDGETED );
final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.BUDGETED );
final VolatileUnsignedShortType linkedType = new VolatileUnsignedShortType( img );
img.setLinkedType( linkedType );
return img;
......@@ -325,7 +324,11 @@ public class Hdf5ImageLoader implements ViewerImgLoader
final String cellsPath = Util.getCellsPath( timepoint, setup, level );
HDF5DataSetInformation info = null;
boolean exists = false;
cache.pauseFetcherThreads();
// pause Fetcher threads for 5 ms. There will be more calls to
// getImageDimension() because this happens when a timepoint is
// loaded, and all setups for the timepoint are loaded then. We
// don't want to interleave this with block loading operations.
cache.pauseFetcherThreadsFor( 5 );
synchronized ( hdf5Reader )
{
try {
......
......@@ -2,9 +2,6 @@ package bdv.img.hdf5;
import static bdv.img.hdf5.Util.getCellsPath;
import static bdv.img.hdf5.Util.reorder;
import java.io.PrintStream;
import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray;
import bdv.img.cache.CacheArrayLoader;
import ch.systemsx.cisd.base.mdarray.MDShortArray;
......@@ -16,74 +13,27 @@ public class Hdf5VolatileShortArrayLoader implements CacheArrayLoader< VolatileS
private VolatileShortArray theEmptyArray;
PrintStream log = System.out;
public Hdf5VolatileShortArrayLoader( final IHDF5Reader hdf5Reader )
{
this.hdf5Reader = hdf5Reader;
theEmptyArray = new VolatileShortArray( 32 * 32 * 32, false );
}
// @Override
// public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException
// {
// final MDShortArray array;
// synchronized ( hdf5Reader )
// {
// if ( Thread.interrupted() )
// throw new InterruptedException();
// array = hdf5Reader.readShortMDArrayBlockWithOffset( getCellsPath( timepoint, setup, level ), reorder( dimensions ), reorder( min ) );
// }
// return new VolatileShortArray( array.getAsFlatArray(), true );
// }
public static volatile long pStart = System.currentTimeMillis();
public static volatile long pEnd = System.currentTimeMillis();
public static volatile long tLoad = 0;
public static volatile long sLoad = 0;
private final int[] reorderedDimensions = new int[ 3 ];
private final long[] reorderedMin = new long[ 3 ];
@Override
public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min )
public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException
{
final MDShortArray array;
synchronized ( hdf5Reader )
{
pStart = System.currentTimeMillis();
// log.println( String.format( "%3d %d %d { %2d, %2d, %2d } { %4d, %4d, %4d }", timepoint, setup, level,
// dimensions[0], dimensions[1], dimensions[2],
// min[0], min[1], min[2] ) );
final long msBetweenLoads = pStart - pEnd;
if ( msBetweenLoads > 2 )
{
log.println( msBetweenLoads + " ms pause before this load." );
// final StringWriter sw = new StringWriter();
// final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
// for ( final StackTraceElement elem : trace )
// sw.write( elem.getClassName() + "." + elem.getMethodName() + "\n" );
// log.println( sw.toString() );
}
final long t0 = System.currentTimeMillis();
if ( Thread.interrupted() )
throw new InterruptedException();
reorder( dimensions, reorderedDimensions );
reorder( min, reorderedMin );
array = hdf5Reader.readShortMDArrayBlockWithOffset( getCellsPath( timepoint, setup, level ), reorderedDimensions, reorderedMin );
pEnd = System.currentTimeMillis();
final long t = pEnd - t0;
final long size = array.size();
tLoad += t;
sLoad += size;
if ( sLoad > 1000000 )
{
log.println( String.format( "%.0f k shorts/sec ", ( ( double ) sLoad / tLoad ) ) );
tLoad = 1;
sLoad = 1;
}
}
return new VolatileShortArray( array.getAsFlatArray(), true );
}
......@@ -103,4 +53,46 @@ public class Hdf5VolatileShortArrayLoader implements CacheArrayLoader< VolatileS
public int getBytesPerElement() {
return 2;
}
// PrintStream log = System.out;
// public static volatile long pStart = System.currentTimeMillis();
// public static volatile long pEnd = System.currentTimeMillis();
// public static volatile long tLoad = 0;
// public static volatile long sLoad = 0;
//
// @Override
// public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min )
// {
// final MDShortArray array;
// synchronized ( hdf5Reader )
// {
// pStart = System.currentTimeMillis();
// final long msBetweenLoads = pStart - pEnd;
// if ( msBetweenLoads > 2 )
// {
// log.println( msBetweenLoads + " ms pause before this load." );
// final StringWriter sw = new StringWriter();
// final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
// for ( final StackTraceElement elem : trace )
// sw.write( elem.getClassName() + "." + elem.getMethodName() + "\n" );
// log.println( sw.toString() );
// }
// final long t0 = System.currentTimeMillis();
// reorder( dimensions, reorderedDimensions );
// reorder( min, reorderedMin );
// array = hdf5Reader.readShortMDArrayBlockWithOffset( getCellsPath( timepoint, setup, level ), reorderedDimensions, reorderedMin );
// pEnd = System.currentTimeMillis();
// final long t = System.currentTimeMillis() - t0;
// final long size = array.size();
// tLoad += t;
// sLoad += size;
// if ( sLoad > 1000000 )
// {
// log.println( String.format( "%.0f k shorts/sec ", ( ( double ) sLoad / tLoad ) ) );
// tLoad = 1;
// sLoad = 1;
// }
// }
// return new VolatileShortArray( array.getAsFlatArray(), true );
// }
}
......@@ -262,7 +262,7 @@ public class RecordMovieDialog extends JDialog implements OverlayRenderer
final MultiResolutionRenderer renderer = new MultiResolutionRenderer( target, new PainterThread( null ), new double[] { 1 }, 0, false, 1, null, false, new Cache()
{
@Override
public void initIoTimeBudget( final long[] partialBudget, final boolean reinitialize )
public void initIoTimeBudget( final long[] partialBudget )
{}
@Override
......
......@@ -237,7 +237,10 @@ public class MultiResolutionRenderer
protected int previousTimepoint;
// TODO: should be settable
protected long[] iobudget = new long[] { 500l * 1000000l, 0l * 1000000l };
protected long[] iobudget = new long[] { 100l * 1000000l, 10l * 1000000l };
// TODO: should be settable
protected boolean prefetchCells = true;
/**
* @param display
......@@ -453,19 +456,6 @@ public class MultiResolutionRenderer
}
// try rendering
// if ( clearQueue )
// cache.prepareNextFrame();
// if ( clearQueue )
// try
// {
// Thread.sleep( 100 );
// }
// catch ( final InterruptedException e1 )
// {
// // TODO Auto-generated catch block
// e1.printStackTrace();
// }
final boolean success = p.map( createProjector );
final long rendertime = p.getLastFrameRenderNanoTime();
......@@ -497,11 +487,9 @@ public class MultiResolutionRenderer
if ( rendertime < targetRenderNanos && maxScreenScaleIndex > 0 )
maxScreenScaleIndex--;
}
// System.out.println( "created projector" );
// System.out.println( String.format( "rendering:%4d ms", rendertime / 1000000 ) );
// System.out.println( "scale = " + currentScreenScaleIndex );
// System.out.println( "maxScreenScaleIndex = " + maxScreenScaleIndex + " (" + screenImages[ maxScreenScaleIndex ][ 0 ].dimension( 0 ) + " x " + screenImages[ maxScreenScaleIndex ][ 0 ].dimension( 1 ) + ")" );
// System.out.println();
}
if ( currentScreenScaleIndex > 0 )
......@@ -517,8 +505,6 @@ public class MultiResolutionRenderer
requestRepaint( currentScreenScaleIndex );
}
}
// else
// System.out.println("! success");
}
return success;
......@@ -530,7 +516,6 @@ public class MultiResolutionRenderer
*/
public synchronized void requestRepaint()
{
// System.out.println("requestRepaint()");
newFrameRequest = true;
requestRepaint( maxScreenScaleIndex );
}
......@@ -556,7 +541,7 @@ public class MultiResolutionRenderer
{
synchronized ( viewerState )
{
cache.initIoTimeBudget( iobudget, false );
cache.initIoTimeBudget( null ); // clear time budget such that prefetching doesn't wait for loading blocks.
final List< SourceState< ? > > sources = viewerState.getSources();
final List< Integer > visibleSourceIndices = viewerState.getVisibleSourceIndices();
VolatileProjector projector;
......@@ -586,6 +571,7 @@ public class MultiResolutionRenderer
projector = new AccumulateProjectorARGB( sourceProjectors, sourceImages, screenImage, numRenderingThreads );
}
previousTimepoint = viewerState.getCurrentTimepoint();
cache.initIoTimeBudget( iobudget );
return projector;
}
}
......@@ -667,12 +653,14 @@ public class MultiResolutionRenderer
levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, bestLevel ) );
if ( nLevels - 1 != bestLevel )
levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, nLevels - 1 ) );
final long t0 = System.currentTimeMillis();
if ( prefetchCells )
{
if ( nLevels - 1 != bestLevel )
prefetch( viewerState, spimSource, screenScaleTransform, nLevels - 1, screenImage );
prefetch( viewerState, spimSource, screenScaleTransform, bestLevel, screenImage );
final long t1 = System.currentTimeMillis() - t0;
System.out.println( "prefetch: " + t1 + " ms" );
}
// slight abuse of newFrameRequest: we only want this two-pass
// rendering to happen once then switch to normal multi-pass
// rendering if we remain longer on this frame.
......@@ -682,11 +670,10 @@ public class MultiResolutionRenderer
{
for ( int i = bestLevel; i < nLevels; ++i )
levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, i ) );
final long t0 = System.currentTimeMillis();
if ( prefetchCells )
for ( int i = nLevels - 1; i >= bestLevel; --i )
prefetch( viewerState, spimSource, screenScaleTransform, i, screenImage );
final long t1 = System.currentTimeMillis() - t0;
System.out.println( "prefetch: " + t1 + " ms" );
}
// for ( int i = bestLevel - 1; i >= 0; --i )
// levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, i ) );
......@@ -714,24 +701,17 @@ public class MultiResolutionRenderer
final int mipmapIndex,
final Dimensions screenInterval )
{
// if ( mipmapIndex != source.getNumMipmapLevels() - 1 )
// return;
final int timepoint = viewerState.getCurrentTimepoint();
final RandomAccessibleInterval< T > img = source.getSource( timepoint, mipmapIndex );
if ( CellImg.class.isInstance( img ) )
{
// System.out.println( "is CellImg" );
final CellImg< ?, ?, ? > cellImg = ( CellImg ) img;
final CellImg< ?, ?, ? > cellImg = ( CellImg< ?, ?, ? > ) img;
final int[] cellDimensions = new int[ 3 ];
cellImg.getCells().cellDimensions( cellDimensions );
final long[] dimensions = new long[ 3 ];
cellImg.dimensions( dimensions );
final RandomAccess< ? > cellsRandomAccess = cellImg.getCells().randomAccess();
// System.out.println( net.imglib2.util.Util.printCoordinates( cellDimensions ) );
// System.out.println( net.imglib2.util.Util.printCoordinates( dimensions ) );
// System.out.println();
final Interpolation interpolation = viewerState.getInterpolation();
final AffineTransform3D sourceToScreen = new AffineTransform3D();
......@@ -739,7 +719,7 @@ public class MultiResolutionRenderer
sourceToScreen.concatenate( source.getSourceTransform( timepoint, mipmapIndex ) );
sourceToScreen.preConcatenate( screenScaleTransform );
PlayWithGeometry.scan( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
Prefetcher.fetchCells( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
}
}
}
package bdv.viewer.render;
import net.imglib2.Dimensions;
import net.imglib2.RandomAccess;
import net.imglib2.RealPoint;
import net.imglib2.realtransform.AffineTransform3D;
import bdv.viewer.Interpolation;
public class PlayWithGeometry
{
public static void scan( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
{
new Prefetcher().scan( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
}
static class Prefetcher
{
private final double[] xStep = new double[ 3 ];
private final double[] offsetNeg = new double[ 3 ];
private final double[] offsetPos = new double[ 3 ];
private static final double eps = 0.0000001;
public void scan( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
{
final RealPoint pSource = new RealPoint( 3 );
final RealPoint pScreen = new RealPoint( 3 );
final int[] minCell = new int[ 3 ];
final int[] maxCell = new int[ 3 ];
final int w = ( int ) screenInterval.dimension( 0 );
final int h = ( int ) screenInterval.dimension( 1 );
for ( int d = 0; d < 3; ++d )
maxCell[ d ] = ( int ) ( ( dimensions[ d ] - 1 ) / cellDimensions[ d ] );
// compute bounding box
final RealPoint[] screenCorners = new RealPoint[ 4 ];
screenCorners[ 0 ] = new RealPoint( 0, 0, 0 );
screenCorners[ 1 ] = new RealPoint( w, 0, 0 );
screenCorners[ 2 ] = new RealPoint( w, h, 0 );
screenCorners[ 3 ] = new RealPoint( 0, h, 0 );
final RealPoint sourceCorner = new RealPoint( 3 );
final double[] bbMin = new double[] { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY };
final double[] bbMax = new double[] { Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
for ( int i = 0; i < 4; ++i )
{
sourceToScreen.applyInverse( sourceCorner, screenCorners[ i ] );
for ( int d = 0; d < 3; ++d )
{
final double p = sourceCorner.getDoublePosition( d );
if ( p < bbMin[ d ] )
bbMin[ d ] = p;
if ( p > bbMax[ d ] )
bbMax[ d ] = p;
}
}
for ( int d = 0; d < 3; ++d )
{
minCell[ d ] = Math.min( maxCell[ d ], Math.max( ( int ) bbMin[ d ] / cellDimensions[ d ] - 1, 0 ) );
maxCell[ d ] = Math.max( 0, Math.min( ( int ) bbMax[ d ] / cellDimensions[ d ] + 1, maxCell[ d ] ) );
}
checkProtoCell( cellDimensions, sourceToScreen, interpolation );
getStep( cellDimensions, sourceToScreen );
pSource.setPosition( ( minCell[ 2 ] - 1 ) * cellDimensions[ 2 ], 2 );
for ( cellsRandomAccess.setPosition( minCell[ 2 ], 2 ); cellsRandomAccess.getIntPosition( 2 ) <= maxCell[ 2 ]; cellsRandomAccess.fwd( 2 ) )
{
pSource.move( cellDimensions[ 2 ], 2 );
pSource.setPosition( ( minCell[ 1 ] - 1 ) * cellDimensions[ 1 ], 1 );
for ( cellsRandomAccess.setPosition( minCell[ 1 ], 1 ); cellsRandomAccess.getIntPosition( 1 ) <= maxCell[ 1 ]; cellsRandomAccess.fwd( 1 ) )
{
pSource.move( cellDimensions[ 1 ], 1 );
// find first and last cell that hits z
pSource.setPosition( minCell[ 0 ] * cellDimensions[ 0 ], 0 );
sourceToScreen.apply( pSource, pScreen );
final double z0 = pScreen.getDoublePosition( 2 );
int nStart = 0;
int nStop = 0;
if ( xStep[ 2 ] > eps )
{
nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
if ( nStart > maxCell[ 0 ] )
continue;
nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
if ( nStop < minCell[ 0 ] )
continue;
}
else if ( xStep[ 2 ] < - eps )
{
nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
if ( nStart > maxCell[ 0 ] )
continue;
nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
if ( nStop < minCell[ 0 ] )
continue;
}
else
{
if ( z0 + offsetNeg[ 2 ] > 0 || z0 + offsetPos[ 2 ] < 0 )
continue;
nStart = minCell[ 0 ];
nStop = maxCell[ 0 ];
}
pSource.setPosition( nStart * cellDimensions[ 0 ], 0 );
for ( cellsRandomAccess.setPosition( nStart, 0 ); cellsRandomAccess.getIntPosition( 0 ) <= nStop; cellsRandomAccess.fwd( 0 ) )
{
sourceToScreen.apply( pSource, pScreen );
final double x = pScreen.getDoublePosition( 0 );
final double y = pScreen.getDoublePosition( 1 );
if ( ( x + offsetPos[ 0 ] >= 0 ) &&
( x + offsetNeg[ 0 ] < w ) &&
( y + offsetPos[ 1 ] >= 0 ) &&
( y + offsetNeg[ 1 ] < h ) )
{
cellsRandomAccess.get();
}
pSource.move( cellDimensions[ 0 ], 0 );
}
}
}
}
void getStep( final int[] cellStep, final AffineTransform3D sourceToScreen )
{
final RealPoint p0 = new RealPoint( 3 );
final RealPoint p1 = new RealPoint( 3 );
p1.setPosition( cellStep[ 0 ], 0 );
final RealPoint s0 = new RealPoint( 3 );
final RealPoint s1 = new RealPoint( 3 );
sourceToScreen.apply( p0, s0 );
sourceToScreen.apply( p1, s1 );
for ( int d = 0; d < 3; ++d )
xStep[ d ] = s1.getDoublePosition( d ) - s0.getDoublePosition( d );
}
void checkProtoCell( final int[] cellDims, final AffineTransform3D sourceToScreen, final Interpolation interpolation )
{
final RealPoint pSource = new RealPoint( 3 );
final RealPoint pScreenAnchor = new RealPoint( 3 );
sourceToScreen.apply( pSource, pScreenAnchor );
final RealPoint[] pScreen = new RealPoint[ 8 ];
final double[] cellMin = new double[ 3 ];
final double[] cellSize = new double[] { cellDims[ 0 ], cellDims[ 1 ], cellDims[ 2 ] };
if ( interpolation == Interpolation.NEARESTNEIGHBOR )
{
for ( int d = 0; d < 3; ++d )
{
cellMin[ d ] -= 0.5;
cellSize[ d ] -= 0.5;
}
}
else // Interpolation.NLINEAR
{
for ( int d = 0; d < 3; ++d )
cellMin[ d ] -= 1;
}
int i = 0;
for ( int z = 0; z < 2; ++z )
{
pSource.setPosition( ( z == 0 ) ? cellMin[ 2 ] : cellSize[ 2 ], 2 );
for ( int y = 0; y < 2; ++y )
{
pSource.setPosition( ( y == 0 ) ? cellMin[ 1 ] : cellSize[ 1 ], 1 );
for ( int x = 0; x < 2; ++x )
{
pSource.setPosition( ( x == 0 ) ? cellMin[ 0 ] : cellSize[ 0 ], 0 );
pScreen[ i ] = new RealPoint( 3 );
sourceToScreen.apply( pSource, pScreen[ i++ ] );
}
}
}
for ( int d = 0; d < 3; ++d )
{
double min = pScreen[ 0 ].getDoublePosition( d );
double max = pScreen[ 0 ].getDoublePosition( d );
for ( i = 1; i < 8; ++i )
{
final double p = pScreen[ i ].getDoublePosition( d );
if ( p < min )
min = p;
if ( p > max )
max = p;
}
offsetNeg[ d ] = min - pScreenAnchor.getDoublePosition( d );
offsetPos[ d ] = max - pScreenAnchor.getDoublePosition( d );
}
}
}
}
package bdv.viewer.render;
import net.imglib2.Dimensions;
import net.imglib2.RandomAccess;
import net.imglib2.RealPoint;
import net.imglib2.img.cell.CellImg;
import net.imglib2.realtransform.AffineTransform3D;
import bdv.viewer.Interpolation;
public class Prefetcher
{
/**
* Access cells that will be needed for rendering to the screen.
*
* @param sourceToScreen
* source-to-screen transform
* @param cellDimensions
* standard size of a source cell
* @param dimensions
* dimensions of the source {@link CellImg}
* @param screenInterval
* the interval of the screen that will be rendered
* @param interpolation
* the interpolation method
* @param cellsRandomAccess
* access to the source cells
*/
public static void fetchCells( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
{
new Prefetcher().scan( sourceToScreen, cellDimensions, dimensions, screenInterval, interpolation, cellsRandomAccess );
}
private Prefetcher()
{}
/**
* The transformed vector in screen coordinate when moving by by one cell in
* X direction.
*/
private final double[] xStep = new double[ 3 ];
/**
* The min of the bounding box of a cell under the current transform and
* interpolation method, as seen from the min corner of the cell.
*/
private final double[] offsetNeg = new double[ 3 ];
/**
* The max of the bounding box of a cell under the current transform and
* interpolation method, as seen from the min corner of the cell.
*/
private final double[] offsetPos = new double[ 3 ];
private static final double eps = 0.0000001;
/**
* Access cells that will be needed for rendering to the screen.
*
* @param sourceToScreen
* source-to-screen transform
* @param cellDimensions
* standard size of a source cell
* @param dimensions
* dimensions of the source {@link CellImg}
* @param screenInterval
* the interval of the screen that will be rendered
* @param interpolation
* the interpolation method
* @param cellsRandomAccess
* access to the source cells
*/
private void scan( final AffineTransform3D sourceToScreen, final int[] cellDimensions, final long[] dimensions, final Dimensions screenInterval, final Interpolation interpolation, final RandomAccess< ? > cellsRandomAccess )
{
final RealPoint pSource = new RealPoint( 3 );
final RealPoint pScreen = new RealPoint( 3 );
final int[] minCell = new int[ 3 ];
final int[] maxCell = new int[ 3 ];
final int w = ( int ) screenInterval.dimension( 0 );
final int h = ( int ) screenInterval.dimension( 1 );
for ( int d = 0; d < 3; ++d )
maxCell[ d ] = ( int ) ( ( dimensions[ d ] - 1 ) / cellDimensions[ d ] );
// compute bounding box
final RealPoint[] screenCorners = new RealPoint[ 4 ];
screenCorners[ 0 ] = new RealPoint( 0, 0, 0 );
screenCorners[ 1 ] = new RealPoint( w, 0, 0 );
screenCorners[ 2 ] = new RealPoint( w, h, 0 );
screenCorners[ 3 ] = new RealPoint( 0, h, 0 );
final RealPoint sourceCorner = new RealPoint( 3 );
final double[] bbMin = new double[] { Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY };
final double[] bbMax = new double[] { Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY };
for ( int i = 0; i < 4; ++i )
{
sourceToScreen.applyInverse( sourceCorner, screenCorners[ i ] );
for ( int d = 0; d < 3; ++d )
{
final double p = sourceCorner.getDoublePosition( d );
if ( p < bbMin[ d ] )
bbMin[ d ] = p;
if ( p > bbMax[ d ] )
bbMax[ d ] = p;
}
}
for ( int d = 0; d < 3; ++d )
{
minCell[ d ] = Math.min( maxCell[ d ], Math.max( ( int ) bbMin[ d ] / cellDimensions[ d ] - 1, 0 ) );
maxCell[ d ] = Math.max( 0, Math.min( ( int ) bbMax[ d ] / cellDimensions[ d ] + 1, maxCell[ d ] ) );
}
checkProtoCell( cellDimensions, sourceToScreen, interpolation );
getXStep( cellDimensions, sourceToScreen );
pSource.setPosition( ( minCell[ 2 ] - 1 ) * cellDimensions[ 2 ], 2 );
for ( cellsRandomAccess.setPosition( minCell[ 2 ], 2 ); cellsRandomAccess.getIntPosition( 2 ) <= maxCell[ 2 ]; cellsRandomAccess.fwd( 2 ) )
{
pSource.move( cellDimensions[ 2 ], 2 );
pSource.setPosition( ( minCell[ 1 ] - 1 ) * cellDimensions[ 1 ], 1 );
for ( cellsRandomAccess.setPosition( minCell[ 1 ], 1 ); cellsRandomAccess.getIntPosition( 1 ) <= maxCell[ 1 ]; cellsRandomAccess.fwd( 1 ) )
{
pSource.move( cellDimensions[ 1 ], 1 );
// find first and last cell that hits z
pSource.setPosition( minCell[ 0 ] * cellDimensions[ 0 ], 0 );
sourceToScreen.apply( pSource, pScreen );
final double z0 = pScreen.getDoublePosition( 2 );
int nStart = 0;
int nStop = 0;
if ( xStep[ 2 ] > eps )
{
nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
if ( nStart > maxCell[ 0 ] )
continue;
nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
if ( nStop < minCell[ 0 ] )
continue;
}
else if ( xStep[ 2 ] < - eps )
{
nStart = minCell[ 0 ] + Math.max( 0, ( int ) Math.ceil( - ( z0 + offsetNeg[ 2 ] ) / xStep[ 2 ] ) );
if ( nStart > maxCell[ 0 ] )
continue;
nStop = Math.min( maxCell[ 0 ], minCell[ 0 ] + ( int ) Math.floor( - ( z0 + offsetPos[ 2 ] ) / xStep[ 2 ] ) );
if ( nStop < minCell[ 0 ] )
continue;
}
else
{
if ( z0 + offsetNeg[ 2 ] > 0 || z0 + offsetPos[ 2 ] < 0 )
continue;
nStart = minCell[ 0 ];
nStop = maxCell[ 0 ];
}
pSource.setPosition( nStart * cellDimensions[ 0 ], 0 );
for ( cellsRandomAccess.setPosition( nStart, 0 ); cellsRandomAccess.getIntPosition( 0 ) <= nStop; cellsRandomAccess.fwd( 0 ) )
{
sourceToScreen.apply( pSource, pScreen );
final double x = pScreen.getDoublePosition( 0 );
final double y = pScreen.getDoublePosition( 1 );
if ( ( x + offsetPos[ 0 ] >= 0 ) &&
( x + offsetNeg[ 0 ] < w ) &&
( y + offsetPos[ 1 ] >= 0 ) &&
( y + offsetNeg[ 1 ] < h ) )
{
cellsRandomAccess.get();
}
pSource.move( cellDimensions[ 0 ], 0 );
}
}
}
}
/**
* Get the transformed vector in screen coordinate when moving by
* cellStep[0] in X direction.
*/
private void getXStep( final int[] cellStep, final AffineTransform3D sourceToScreen )
{
final RealPoint p0 = new RealPoint( 3 );
final RealPoint p1 = new RealPoint( 3 );
p1.setPosition( cellStep[ 0 ], 0 );
final RealPoint s0 = new RealPoint( 3 );
final RealPoint s1 = new RealPoint( 3 );
sourceToScreen.apply( p0, s0 );
sourceToScreen.apply( p1, s1 );
for ( int d = 0; d < 3; ++d )
xStep[ d ] = s1.getDoublePosition( d ) - s0.getDoublePosition( d );
}
/**
* Get the bounding box of a cell under the current transform and
* interpolation method. Set {@link #offsetNeg} and {@link #offsetPos} as
* seen from the min corner of the cell.
*/
private void checkProtoCell( final int[] cellDims, final AffineTransform3D sourceToScreen, final Interpolation interpolation )
{
final RealPoint pSource = new RealPoint( 3 );
final RealPoint pScreenAnchor = new RealPoint( 3 );
sourceToScreen.apply( pSource, pScreenAnchor );
final RealPoint[] pScreen = new RealPoint[ 8 ];
final double[] cellMin = new double[ 3 ];
final double[] cellSize = new double[] { cellDims[ 0 ], cellDims[ 1 ], cellDims[ 2 ] };
if ( interpolation == Interpolation.NEARESTNEIGHBOR )
{
for ( int d = 0; d < 3; ++d )
{
cellMin[ d ] -= 0.5;
cellSize[ d ] -= 0.5;
}
}
else // Interpolation.NLINEAR
{
for ( int d = 0; d < 3; ++d )
cellMin[ d ] -= 1;
}
int i = 0;
for ( int z = 0; z < 2; ++z )
{
pSource.setPosition( ( z == 0 ) ? cellMin[ 2 ] : cellSize[ 2 ], 2 );
for ( int y = 0; y < 2; ++y )
{
pSource.setPosition( ( y == 0 ) ? cellMin[ 1 ] : cellSize[ 1 ], 1 );
for ( int x = 0; x < 2; ++x )
{
pSource.setPosition( ( x == 0 ) ? cellMin[ 0 ] : cellSize[ 0 ], 0 );
pScreen[ i ] = new RealPoint( 3 );
sourceToScreen.apply( pSource, pScreen[ i++ ] );
}
}
}
for ( int d = 0; d < 3; ++d )
{
double min = pScreen[ 0 ].getDoublePosition( d );
double max = pScreen[ 0 ].getDoublePosition( d );
for ( i = 1; i < 8; ++i )
{
final double p = pScreen[ i ].getDoublePosition( d );
if ( p < min )
min = p;
if ( p > max )
max = p;
}
offsetNeg[ d ] = min - pScreenAnchor.getDoublePosition( d );
offsetPos[ d ] = max - pScreenAnchor.getDoublePosition( d );
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment