diff --git a/src/main/java/bdv/img/remote/RemoteImageLoader.java b/src/main/java/bdv/img/remote/RemoteImageLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..a0a2965e7c65c432b3c5f1bcba83d1ff3cddda66 --- /dev/null +++ b/src/main/java/bdv/img/remote/RemoteImageLoader.java @@ -0,0 +1,239 @@ +package bdv.img.remote; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; + +import mpicbg.spim.data.View; +import net.imglib2.FinalInterval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.NativeImg; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import net.imglib2.img.cell.CellImg; +import net.imglib2.sampler.special.ConstantRandomAccessible; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.type.volatiles.VolatileUnsignedShortType; +import net.imglib2.util.IntervalIndexer; +import net.imglib2.view.Views; + +import org.jdom2.Element; + +import bdv.ViewerImgLoader; +import bdv.img.cache.VolatileCell; +import bdv.img.cache.VolatileGlobalCellCache; +import bdv.img.cache.VolatileGlobalCellCache.LoadingStrategy; +import bdv.img.cache.VolatileImgCells; +import bdv.img.cache.VolatileImgCells.CellCache; + +import com.google.gson.Gson; + +public class RemoteImageLoader implements ViewerImgLoader< UnsignedShortType, VolatileUnsignedShortType > +{ + protected String baseUrl; + + protected RemoteImageLoaderMetaData metadata; + + protected int[][] cellsDimensions; + + protected VolatileGlobalCellCache< VolatileShortArray > cache; + + private void open() throws IOException + { + final URL url = new URL( baseUrl + "?p=init" ); + metadata = new Gson().fromJson( + new InputStreamReader( url.openStream() ), + RemoteImageLoaderMetaData.class ); + cache = new VolatileGlobalCellCache< VolatileShortArray >( + new RemoteVolatileShortArrayLoader( this ), + metadata.numTimepoints, + metadata.numSetups, + metadata.maxNumLevels, + metadata.maxLevels, + 10 ); + cellsDimensions = metadata.createCellsDimensions(); + } + + @Override + public void init( final Element elem, final File basePath ) + { + try + { + baseUrl = elem.getChildText( "baseUrl" ); + open(); + } + catch ( final Exception e ) + { + throw new RuntimeException( e ); + } + } + + @Override + public Element toXml( final File basePath ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public RandomAccessibleInterval< FloatType > getFloatImage( final View view ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public RandomAccessibleInterval< UnsignedShortType > getImage( final View view ) + { + return getImage( view, 0 ); + } + + @Override + public RandomAccessibleInterval< UnsignedShortType > getImage( final View view, final int level ) + { + if ( ! existsImageData( view, level ) ) + { + System.err.println( "image data for " + view.getBasename() + " level " + level + " could not be found. Partition file missing?" ); + return getMissingDataImage( view, level, new UnsignedShortType() ); + } + final CellImg< UnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.BLOCKING ); + final UnsignedShortType linkedType = new UnsignedShortType( img ); + img.setLinkedType( linkedType ); + return img; + } + + @Override + public RandomAccessibleInterval< VolatileUnsignedShortType > getVolatileImage( final View view, final int level ) + { + if ( ! existsImageData( view, level ) ) + { + System.err.println( "image data for " + view.getBasename() + " level " + level + " could not be found." ); + return getMissingDataImage( view, level, new VolatileUnsignedShortType() ); + } + final CellImg< VolatileUnsignedShortType, VolatileShortArray, VolatileCell< VolatileShortArray > > img = prepareCachedImage( view, level, LoadingStrategy.BUDGETED ); + final VolatileUnsignedShortType linkedType = new VolatileUnsignedShortType( img ); + img.setLinkedType( linkedType ); + return img; + } + + public VolatileGlobalCellCache< VolatileShortArray > getCache() + { + return cache; + } + + @Override + public double[][] getMipmapResolutions( final int setup ) + { + return metadata.perSetupMipmapResolutions.get( setup ); + } + + public int[][] getSubdivisions( final int setup ) + { + return metadata.perSetupSubdivisions.get( setup ); + } + + @Override + public int numMipmapLevels( final int setup ) + { + return getMipmapResolutions( setup ).length; + } + + /** + * Checks whether the given image data is present on the server. + * + * @return true, if the given image data is present. + */ + public boolean existsImageData( final View view, final int level ) + { + final int timepoint = view.getTimepointIndex(); + final int setup = view.getSetupIndex(); + return existsImageData( timepoint, setup, level ); + } + + /** + * Checks whether the given image data is present on the server. + * + * @return true, if the given image data is present. + */ + public boolean existsImageData( final int timepoint, final int setup, final int level ) + { + final int index = getViewInfoCacheIndex( timepoint, setup, level ); + return metadata.existence[ index ]; + } + + /** + * For images that are missing on the server, a constant image is created. + * If the dimension of the missing image is present in + * {@link RemoteImageLoaderMetaData#dimensions} then use that. Otherwise + * create a 1x1x1 image. + */ + protected < T > RandomAccessibleInterval< T > getMissingDataImage( final View view, final int level, final T constant ) + { + final int t = view.getTimepointIndex(); + final int s = view.getSetupIndex(); + final int index = getViewInfoCacheIndex( t, s, level ); + long[] d = metadata.dimensions[ index ]; + if ( d == null ) + d = new long[] { 1, 1, 1 }; + return Views.interval( new ConstantRandomAccessible< T >( constant, 3 ), new FinalInterval( d ) ); + } + + public long[] getImageDimension( final int timepoint, final int setup, final int level ) + { + final int index = getViewInfoCacheIndex( timepoint, setup, level ); + return metadata.dimensions[ index ]; + } + + private int getViewInfoCacheIndex( final int timepoint, final int setup, final int level ) + { + return level + metadata.maxNumLevels * ( setup + metadata.numSetups * timepoint ); + } + + int getCellIndex( final int timepoint, final int setup, final int level, final long[] globalPosition ) + { + final int index = getViewInfoCacheIndex( timepoint, setup, level ); + final int[] cellSize = getSubdivisions( setup )[ level ]; + final int[] cellPos = new int[] { + ( int ) globalPosition[ 0 ] / cellSize[ 0 ], + ( int ) globalPosition[ 1 ] / cellSize[ 1 ], + ( int ) globalPosition[ 2 ] / cellSize[ 2 ] }; + return IntervalIndexer.positionToIndex( cellPos, cellsDimensions[ index ] ); + } + + /** + * (Almost) create a {@link CellImg} backed by the cache. + * The created image needs a {@link NativeImg#setLinkedType(net.imglib2.type.Type) linked type} before it can be used. + * The type should be either {@link UnsignedShortType} and {@link VolatileUnsignedShortType}. + */ + protected < T extends NativeType< T > > CellImg< T, VolatileShortArray, VolatileCell< VolatileShortArray > > prepareCachedImage( final View view, final int level, final LoadingStrategy loadingStrategy ) + { + if ( cache == null ) + throw new RuntimeException( "no connection open" ); + + final int timepoint = view.getTimepointIndex(); + final int setup = view.getSetupIndex(); + final long[] dimensions = getImageDimension( timepoint, setup, level ); + final int[] cellDimensions = metadata.perSetupSubdivisions.get( setup )[ level ]; + + final CellCache< VolatileShortArray > c = cache.new VolatileCellCache( timepoint, setup, level, loadingStrategy ); + final VolatileImgCells< VolatileShortArray > cells = new VolatileImgCells< VolatileShortArray >( c, 1, dimensions, cellDimensions ); + final CellImg< T, VolatileShortArray, VolatileCell< VolatileShortArray > > img = new CellImg< T, VolatileShortArray, VolatileCell< VolatileShortArray > >( null, cells ); + return img; + } + + private final UnsignedShortType type = new UnsignedShortType(); + + private final VolatileUnsignedShortType volatileType = new VolatileUnsignedShortType(); + + @Override + public UnsignedShortType getImageType() + { + return type; + } + + @Override + public VolatileUnsignedShortType getVolatileImageType() + { + return volatileType; + } +} diff --git a/src/main/java/bdv/img/remote/RemoteImageLoaderMetaData.java b/src/main/java/bdv/img/remote/RemoteImageLoaderMetaData.java new file mode 100644 index 0000000000000000000000000000000000000000..4283ecf3ed92e6bcbe6a5de21b6fb41708bc506b --- /dev/null +++ b/src/main/java/bdv/img/remote/RemoteImageLoaderMetaData.java @@ -0,0 +1,97 @@ +package bdv.img.remote; + +import java.util.ArrayList; + +import bdv.img.hdf5.Hdf5ImageLoader; + +public class RemoteImageLoaderMetaData +{ + protected final ArrayList< double[][] > perSetupMipmapResolutions; + + protected final ArrayList< int[][] > perSetupSubdivisions; + + protected int numTimepoints; + + protected int numSetups; + + protected int[] maxLevels; + + protected int maxNumLevels; + + /** + * An array of long[] arrays with {@link #numTimepoints} * + * {@link #numSetups} * {@link #maxNumLevels} entries. Every entry is the + * dimensions of one image (identified by flattened index of level, setup, + * and timepoint). + */ + protected long[][] dimensions; + + /** + * An array of Booleans with {@link #numTimepoints} * {@link #numSetups} * + * {@link #maxNumLevels} entries. Every entry is the existence of one image + * (identified by flattened index of level, setup, and timepoint). + */ + protected boolean[] existence; + + public RemoteImageLoaderMetaData() + { + perSetupMipmapResolutions = new ArrayList< double[][] >(); + perSetupSubdivisions = new ArrayList< int[][] >(); + } + + public RemoteImageLoaderMetaData( final Hdf5ImageLoader imgLoader, final int numTimepoints, final int numSetups ) + { + perSetupMipmapResolutions = new ArrayList< double[][] >(); + perSetupSubdivisions = new ArrayList< int[][] >(); + this.numTimepoints = numTimepoints; + this.numSetups = numSetups; + maxLevels = new int[ numSetups ]; + maxNumLevels = 0; + for ( int setup = 0; setup < numSetups; ++setup ) + { + perSetupMipmapResolutions.add( imgLoader.getMipmapResolutions( setup ) ); + perSetupSubdivisions.add( imgLoader.getSubdivisions( setup ) ); + final int numLevels = imgLoader.numMipmapLevels( setup ); + maxLevels[ setup ] = numLevels - 1; + if ( numLevels > maxNumLevels ) + maxNumLevels = numLevels; + } + final int numImages = numTimepoints * numSetups * maxNumLevels; + dimensions = new long[ numImages ][]; + existence = new boolean[ numImages ]; + + for ( int t = 0; t < numTimepoints; ++t ) + for ( int s = 0; s < numSetups; ++s ) + for ( int l = 0; l <= maxLevels[ s ]; ++l ) + { + final int i = l + maxNumLevels * ( s + numSetups * t ); + existence[ i ] = imgLoader.existsImageData( t, s, l ); + dimensions[ i ] = imgLoader.getImageDimension( t, s, l ); + } + } + + /** + * Create an array of int[] with {@link #numTimepoints} * {@link #numSetups} + * * {@link #maxNumLevels} entries. Every entry is the dimensions in cells + * (instead of pixels) of one image (identified by flattened index of level, + * setup, and timepoint). + */ + protected int[][] createCellsDimensions() + { + final int numImages = numTimepoints * numSetups * maxNumLevels; + final int[][] cellsDimensions = new int[ numImages ][]; + for ( int t = 0; t < numTimepoints; ++t ) + for ( int s = 0; s < numSetups; ++s ) + for ( int l = 0; l <= maxLevels[ s ]; ++l ) + { + final int i = l + maxNumLevels * ( s + numSetups * t ); + final long[] imageDimensions = dimensions[ i ]; + final int[] cellSize = perSetupSubdivisions.get( s )[ l ]; + cellsDimensions[ i ] = new int[] { + ( int ) imageDimensions[ 0 ] / cellSize[ 0 ], + ( int ) imageDimensions[ 1 ] / cellSize[ 1 ], + ( int ) imageDimensions[ 2 ] / cellSize[ 2 ] }; + } + return cellsDimensions; + } +} diff --git a/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..6db202a2bae109f96471e0a140995930257f52a9 --- /dev/null +++ b/src/main/java/bdv/img/remote/RemoteVolatileShortArrayLoader.java @@ -0,0 +1,76 @@ +package bdv.img.remote; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import bdv.img.cache.CacheArrayLoader; + +public class RemoteVolatileShortArrayLoader implements CacheArrayLoader< VolatileShortArray > +{ + private VolatileShortArray theEmptyArray; + + private final RemoteImageLoader imgLoader; + + public RemoteVolatileShortArrayLoader( final RemoteImageLoader imgLoader ) + { + theEmptyArray = new VolatileShortArray( 32 * 32 * 32, false ); + this.imgLoader = imgLoader; + } + + @Override + public VolatileShortArray loadArray( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException + { + final int index = imgLoader.getCellIndex( timepoint, setup, level, min ); + final short[] data = new short[ dimensions[ 0 ] * dimensions[ 1 ] * dimensions[ 2 ] ]; + try + { + final URL url = new URL( String.format( "%s?p=cell/%d/%d/%d/%d/%d/%d/%d/%d/%d/%d", + imgLoader.baseUrl, + index, + timepoint, + setup, + level, + dimensions[ 0 ], + dimensions[ 1 ], + dimensions[ 2 ], + min[ 0 ], + min[ 1 ], + min[ 2 ] ) ); + final InputStream s = url.openStream(); + final byte[] buf = new byte[ data.length * 2 ]; + for ( int i = 0, l = s.read( buf, 0, buf.length ); l != -1; i += l, l = s.read( buf, i, buf.length - i ) ); + for ( int i = 0, j = 0; i < data.length; ++i, j += 2 ) + data[ i ] = ( short ) ( ( ( buf[ j ] & 0xff ) << 8 ) | ( buf[ j + 1 ] & 0xff ) ); + s.close(); + } + catch ( final MalformedURLException e ) + { + e.printStackTrace(); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + return new VolatileShortArray( data, true ); + } + + @Override + public VolatileShortArray emptyArray( final int[] dimensions ) + { + int numEntities = 1; + for ( int i = 0; i < dimensions.length; ++i ) + numEntities *= dimensions[ i ]; + if ( theEmptyArray.getCurrentStorageArray().length < numEntities ) + theEmptyArray = new VolatileShortArray( numEntities, false ); + return theEmptyArray; + } + + @Override + public int getBytesPerElement() { + return 2; + } + +} diff --git a/src/main/java/bdv/server/BigDataServer.java b/src/main/java/bdv/server/BigDataServer.java index afe3a4cb4a6f32b86609c79fbee7d431769b5bbd..3356227d6978879ce9560a6ae4ae7f85fe717a42 100644 --- a/src/main/java/bdv/server/BigDataServer.java +++ b/src/main/java/bdv/server/BigDataServer.java @@ -1,35 +1,35 @@ -package server; +package bdv.server; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.ObjectOutputStream; import java.io.OutputStream; -import java.util.ArrayList; +import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.xml.parsers.ParserConfigurationException; import mpicbg.spim.data.SequenceDescription; -import net.imglib2.img.basictypeaccess.array.ShortArray; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; -import org.xml.sax.SAXException; +import org.jdom2.JDOMException; -import viewer.SequenceViewsLoader; -import viewer.hdf5.Hdf5ImageLoader; -import viewer.hdf5.Hdf5ImageLoader.DimensionsInfo; -import viewer.hdf5.img.Hdf5Cell; -import viewer.hdf5.img.Hdf5GlobalCellCache; +import bdv.SequenceViewsLoader; +import bdv.img.cache.VolatileCell; +import bdv.img.cache.VolatileGlobalCellCache; +import bdv.img.cache.VolatileGlobalCellCache.LoadingStrategy; +import bdv.img.hdf5.Hdf5ImageLoader; +import bdv.img.remote.RemoteImageLoaderMetaData; -public class CellServer +import com.google.gson.Gson; + +public class BigDataServer { public static void main( final String[] args ) throws Exception { - final String fn = args.length > 0 ? args[ 0 ] : "/Users/pietzsch/Desktop/Valia/valia.xml"; + final String fn = args.length > 0 ? args[ 0 ] : "/Users/pietzsch/Desktop/data/fibsem.xml"; final Server server = new Server( 8080 ); server.setHandler( new CellHandler( fn ) ); server.start(); @@ -38,28 +38,20 @@ public class CellServer static class CellHandler extends AbstractHandler { - final Hdf5ImageLoader imgLoader; + private final VolatileGlobalCellCache< VolatileShortArray > cache; - final Hdf5GlobalCellCache< ShortArray > cache; + private final String metadataJson; - final byte[] initObjects; + private final RemoteImageLoaderMetaData metadata; - public CellHandler( final String xmlFilename ) throws ParserConfigurationException, SAXException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException + public CellHandler( final String xmlFilename ) throws JDOMException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException { final SequenceViewsLoader loader = new SequenceViewsLoader( xmlFilename ); final SequenceDescription seq = loader.getSequenceDescription(); - imgLoader = ( Hdf5ImageLoader ) seq.imgLoader; + final Hdf5ImageLoader imgLoader = ( Hdf5ImageLoader ) seq.imgLoader; cache = imgLoader.getCache(); - - final ArrayList< double[][] > perSetupResolutions = new ArrayList< double[][] >(); - for ( int setup = 0; setup < seq.numViewSetups(); ++setup ) - perSetupResolutions.add( imgLoader.getMipmapResolutions( setup ) ); - final ByteArrayOutputStream bs = new ByteArrayOutputStream(); - final ObjectOutputStream os = new ObjectOutputStream( bs ); - os.writeInt( seq.numTimepoints() ); - os.writeObject( perSetupResolutions ); - os.close(); - initObjects = bs.toByteArray(); + metadata = new RemoteImageLoaderMetaData( imgLoader, seq.numTimepoints(), seq.numViewSetups() ); + metadataJson = new Gson().toJson( metadata ); } @Override @@ -75,13 +67,20 @@ public class CellServer final int timepoint = Integer.parseInt( parts[ 2 ] ); final int setup = Integer.parseInt( parts[ 3 ] ); final int level = Integer.parseInt( parts[ 4 ] ); - Hdf5Cell< ShortArray > cell = cache.getGlobalIfCached( timepoint, setup, level, index ); + VolatileCell< VolatileShortArray > cell = cache.getGlobalIfCached( timepoint, setup, level, index, LoadingStrategy.BLOCKING ); if ( cell == null ) { - final int[] cellDims = new int[] { Integer.parseInt( parts[ 5 ] ), Integer.parseInt( parts[ 6 ] ), Integer.parseInt( parts[ 7 ] ) }; - final long[] cellMin = new long[] { Long.parseLong( parts[ 8 ] ), Long.parseLong( parts[ 9 ] ), Long.parseLong( parts[ 10 ] ) }; - cell = cache.loadGlobal( cellDims, cellMin, timepoint, setup, level, index ); + final int[] cellDims = new int[] { + Integer.parseInt( parts[ 5 ] ), + Integer.parseInt( parts[ 6 ] ), + Integer.parseInt( parts[ 7 ] ) }; + final long[] cellMin = new long[] { + Long.parseLong( parts[ 8 ] ), + Long.parseLong( parts[ 9 ] ), + Long.parseLong( parts[ 10 ] ) }; + cell = cache.createGlobal( cellDims, cellMin, timepoint, setup, level, index, LoadingStrategy.BLOCKING ); } + final short[] data = cell.getData().getCurrentStorageArray(); final byte[] buf = new byte[ 2 * data.length ]; for ( int i = 0, j = 0; i < data.length; i++ ) @@ -98,37 +97,15 @@ public class CellServer final OutputStream os = response.getOutputStream(); os.write( buf ); os.close(); - -// final byte[] buf = new byte[ 4096 ]; -// for ( int i = 0, l = Math.min( buf.length / 2, data.length - i ); i < data.length; i += l ) -// { -// ByteBuffer.wrap( buf ).asShortBuffer().put( data, i, l ); -// os.write( buf, 0, 2 * l ); -// } -// os.close(); - } - else if ( parts[ 0 ].equals( "dim" ) ) - { - final int timepoint = Integer.parseInt( parts[ 1 ] ); - final int setup = Integer.parseInt( parts[ 2 ] ); - final int level = Integer.parseInt( parts[ 3 ] ); - final DimensionsInfo info = imgLoader.getDimensionsInfo( timepoint, setup, level ); - - response.setContentType( "application/octet-stream" ); - response.setStatus( HttpServletResponse.SC_OK ); - baseRequest.setHandled( true ); - final ObjectOutputStream os = new ObjectOutputStream( response.getOutputStream() ); - os.writeObject( info ); - os.close(); } else if ( parts[ 0 ].equals( "init" ) ) { response.setContentType( "application/octet-stream" ); response.setStatus( HttpServletResponse.SC_OK ); baseRequest.setHandled( true ); - final OutputStream os = response.getOutputStream(); - os.write( initObjects ); - os.close(); + final PrintWriter ow = response.getWriter(); + ow.write( metadataJson ); + ow.close(); } } }