diff --git a/pom.xml b/pom.xml
index e7890aef8c13dfcb63b2c73fb297b3068209b44b..50f16a9020b6df3044be50d4dfe6283208ab4c4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,11 +79,15 @@
 			<artifactId>vecmath</artifactId>
 			<version>1.3.1</version>
 		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-server</artifactId>
+			<version>9.1.3.v20140225</version>
+		</dependency>
 		<dependency>
 			<groupId>com.google.code.gson</groupId>
 			<artifactId>gson</artifactId>
 			<version>2.2.4</version>
-			<scope>compile</scope>
 		</dependency>
 	</dependencies>
 
diff --git a/src/main/java/bdv/AbstractViewerImgLoader.java b/src/main/java/bdv/AbstractViewerImgLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..fb1aad336b80a9cee1e5dac4462f3fe940aab85a
--- /dev/null
+++ b/src/main/java/bdv/AbstractViewerImgLoader.java
@@ -0,0 +1,53 @@
+package bdv;
+
+import java.io.File;
+
+import mpicbg.spim.data.View;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.Volatile;
+import net.imglib2.type.numeric.real.FloatType;
+
+import org.jdom2.Element;
+
+public abstract class AbstractViewerImgLoader< T, V extends Volatile< T > > implements ViewerImgLoader< T, V >
+{
+	protected final T type;
+
+	protected final V volatileType;
+
+	public AbstractViewerImgLoader( final T type, final V volatileType )
+	{
+		this.type = type;
+		this.volatileType = volatileType;
+	}
+
+	@Override
+	public T getImageType()
+	{
+		return type;
+	}
+
+	@Override
+	public V getVolatileImageType()
+	{
+		return volatileType;
+	}
+
+	@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< T > getImage( final View view )
+	{
+		return getImage( view, 0 );
+	}
+}
diff --git a/src/main/java/bdv/SpimSource.java b/src/main/java/bdv/SpimSource.java
index f5c8e6c89b8c1d1511367f7cdb9428cb0f90d89b..70b34d0655c43308680cca8e9f692e06d4004e18 100644
--- a/src/main/java/bdv/SpimSource.java
+++ b/src/main/java/bdv/SpimSource.java
@@ -29,15 +29,9 @@ public class SpimSource< T extends NumericType< T > > extends AbstractSpimSource
 			zero.setZero();
 			final View view = sequenceViews.getView( timepoint, setup );
 			final AffineTransform3D reg = view.getModel();
-			final AffineTransform3D mipmapTransform = new AffineTransform3D();
 			for ( int level = 0; level < currentSources.length; level++ )
 			{
-				final double[] resolution = imgLoader.getMipmapResolutions( setup )[ level ];
-				for ( int d = 0; d < 3; ++d )
-				{
-					mipmapTransform.set( resolution[ d ], d, d );
-					mipmapTransform.set( 0.5 * ( resolution[ d ] - 1 ), d, 3 );
-				}
+				final AffineTransform3D mipmapTransform = imgLoader.getMipmapTransforms( setup )[ level ];
 				currentSourceTransforms[ level ].set( reg );
 				currentSourceTransforms[ level ].concatenate( mipmapTransform );
 				currentSources[ level ] = imgLoader.getImage( view, level );
diff --git a/src/main/java/bdv/ViewerImgLoader.java b/src/main/java/bdv/ViewerImgLoader.java
index 61634d081d28c38d0804f1f79df40875fba84537..c3d24bd667d36e1337a0c81568b50cc1690d5d84 100644
--- a/src/main/java/bdv/ViewerImgLoader.java
+++ b/src/main/java/bdv/ViewerImgLoader.java
@@ -4,6 +4,8 @@ import mpicbg.spim.data.ImgLoader;
 import mpicbg.spim.data.View;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.Volatile;
+import net.imglib2.realtransform.AffineTransform3D;
+import bdv.img.cache.Cache;
 
 public interface ViewerImgLoader< T, V extends Volatile< T > > extends ImgLoader< T >
 {
@@ -17,5 +19,9 @@ public interface ViewerImgLoader< T, V extends Volatile< T > > extends ImgLoader
 
 	public double[][] getMipmapResolutions( final int setup );
 
+	public AffineTransform3D[] getMipmapTransforms( final int setup );
+
 	public int numMipmapLevels( final int setup );
+
+	public Cache getCache();
 }
diff --git a/src/main/java/bdv/VolatileSpimSource.java b/src/main/java/bdv/VolatileSpimSource.java
index 696847f75c71444a76fcc07dfdea0853ed3af877..d2dfb10d279230c222817119966157e6332ee331 100644
--- a/src/main/java/bdv/VolatileSpimSource.java
+++ b/src/main/java/bdv/VolatileSpimSource.java
@@ -34,15 +34,9 @@ public class VolatileSpimSource< T extends NumericType< T >, V extends Volatile<
 			zero.setZero();
 			final View view = sequenceViews.getView( timepoint, setup );
 			final AffineTransform3D reg = view.getModel();
-			final AffineTransform3D mipmapTransform = new AffineTransform3D();
 			for ( int level = 0; level < currentSources.length; level++ )
 			{
-				final double[] resolution = imgLoader.getMipmapResolutions( setup )[ level ];
-				for ( int d = 0; d < 3; ++d )
-				{
-					mipmapTransform.set( resolution[ d ], d, d );
-					mipmapTransform.set( 0.5 * ( resolution[ d ] - 1 ), d, 3 );
-				}
+				final AffineTransform3D mipmapTransform = imgLoader.getMipmapTransforms( setup )[ level ];
 				currentSourceTransforms[ level ].set( reg );
 				currentSourceTransforms[ level ].concatenate( mipmapTransform );
 				currentSources[ level ] = imgLoader.getVolatileImage( view, level );
diff --git a/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java b/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java
index 08a7c956c4e2b3096006b21006f505ef4e208be1..6d509aaf99e13d1b10d6e54a6f209247dd3b0fa9 100644
--- a/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java
+++ b/src/main/java/bdv/img/cache/VolatileGlobalCellCache.java
@@ -217,6 +217,7 @@ public class VolatileGlobalCellCache< A extends VolatileAccess > implements Cach
 		{
 			final Fetcher f = new Fetcher();
 			f.setDaemon( true );
+			f.setName( "Fetcher-" + i );
 			fetchers.add( f );
 			f.start();
 		}
diff --git a/src/main/java/bdv/img/catmaid/CatmaidImageLoader.java b/src/main/java/bdv/img/catmaid/CatmaidImageLoader.java
index 4467b86d63c564e701bee3bceace96ec6a54a0b8..f7b839f0e384667fa62859ae40f160e3d3ccde28 100644
--- a/src/main/java/bdv/img/catmaid/CatmaidImageLoader.java
+++ b/src/main/java/bdv/img/catmaid/CatmaidImageLoader.java
@@ -9,20 +9,18 @@ import net.imglib2.img.basictypeaccess.volatiles.array.VolatileIntArray;
 import net.imglib2.img.cell.CellImg;
 import net.imglib2.type.NativeType;
 import net.imglib2.type.numeric.ARGBType;
-import net.imglib2.type.numeric.real.FloatType;
 import net.imglib2.type.volatiles.VolatileARGBType;
 
 import org.jdom2.Element;
 
-import bdv.ViewerImgLoader;
-import bdv.img.cache.Cache;
+import bdv.AbstractViewerImgLoader;
 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;
 
-public class CatmaidImageLoader implements ViewerImgLoader< ARGBType, VolatileARGBType >
+public class CatmaidImageLoader extends AbstractViewerImgLoader< ARGBType, VolatileARGBType >
 {
 	private long width;
 
@@ -50,6 +48,11 @@ public class CatmaidImageLoader implements ViewerImgLoader< ARGBType, VolatileAR
 
 	protected VolatileGlobalCellCache< VolatileIntArray > cache;
 
+	public CatmaidImageLoader()
+	{
+		super( new ARGBType(), new VolatileARGBType() );
+	}
+
 	@Override
 	public void init( final Element elem, final File basePath )
 	{
@@ -61,7 +64,7 @@ public class CatmaidImageLoader implements ViewerImgLoader< ARGBType, VolatileAR
 		resZ = Double.parseDouble( elem.getChildText( "resZ" ) );
 
 		urlFormat = elem.getChildText( "urlFormat" );
-		
+
 		tileWidth = Integer.parseInt( elem.getChildText( "tileWidth" ) );
 		tileHeight = Integer.parseInt( elem.getChildText( "tileHeight" ) );
 
@@ -76,7 +79,6 @@ public class CatmaidImageLoader implements ViewerImgLoader< ARGBType, VolatileAR
 		blockDimensions = new int[ numScales ][];
 		for ( int l = 0; l < numScales; ++l )
 		{
-
 			mipmapResolutions[ l ] = new double[] { 1 << l, 1 << l, 1 };
 			imageDimensions[ l ] = new long[] { width >> l, height >> l, depth };
 			blockDimensions[ l ] = new int[] { tileWidth, tileHeight, 1 };
@@ -97,24 +99,6 @@ public class CatmaidImageLoader implements ViewerImgLoader< ARGBType, VolatileAR
 		return i;
 	}
 
-	@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< ARGBType > getImage( final View view )
-	{
-		return getImage( view, 0 );
-	}
-
 	@Override
 	public RandomAccessibleInterval< ARGBType > getImage( final View view, final int level )
 	{
@@ -161,25 +145,9 @@ public class CatmaidImageLoader implements ViewerImgLoader< ARGBType, VolatileAR
 		return img;
 	}
 
-	public Cache getCache()
-	{
-		return cache;
-	}
-
-	private final ARGBType type = new ARGBType();
-
-	private final VolatileARGBType volatileType = new VolatileARGBType();
-
-	@Override
-	public ARGBType getImageType()
-	{
-		return type;
-	}
-
 	@Override
-	public VolatileARGBType getVolatileImageType()
+	public VolatileGlobalCellCache< VolatileIntArray > getCache()
 	{
-		return volatileType;
+		return cache;
 	}
-
 }
diff --git a/src/main/java/bdv/img/catmaid/CatmaidVolatileIntArrayLoader.java b/src/main/java/bdv/img/catmaid/CatmaidVolatileIntArrayLoader.java
index c9dba0152173148ef56b7141486c0b71eda478d2..762693a9db71ccad96e39508fdb3d3c29ac4608d 100644
--- a/src/main/java/bdv/img/catmaid/CatmaidVolatileIntArrayLoader.java
+++ b/src/main/java/bdv/img/catmaid/CatmaidVolatileIntArrayLoader.java
@@ -61,7 +61,7 @@ public class CatmaidVolatileIntArrayLoader implements CacheArrayLoader< Volatile
 	@Override
 	public int getBytesPerElement()
 	{
-		return 1;
+		return 4;
 	}
 
 	@Override
diff --git a/src/main/java/bdv/img/catmaid/CatmaidVolatileShortArrayLoader.java b/src/main/java/bdv/img/catmaid/CatmaidVolatileShortArrayLoader.java
index 483f45b925053128ef76f7525a6799e61c356ebf..e6d1d6141093b18830c5cb79c5fc64760227c768 100644
--- a/src/main/java/bdv/img/catmaid/CatmaidVolatileShortArrayLoader.java
+++ b/src/main/java/bdv/img/catmaid/CatmaidVolatileShortArrayLoader.java
@@ -31,7 +31,7 @@ public class CatmaidVolatileShortArrayLoader implements CacheArrayLoader< Volati
 	@Override
 	public int getBytesPerElement()
 	{
-		return 1;
+		return 2;
 	}
 
 	@Override
diff --git a/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java b/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
index 1b6e87a1390a00635aa649d27631159ff55547d2..526891d2fc8af7b4694f6886620071d11d7161f0 100644
--- a/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
+++ b/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
@@ -15,16 +15,16 @@ 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.realtransform.AffineTransform3D;
 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.view.Views;
 
 import org.jdom2.Element;
 
-import bdv.ViewerImgLoader;
+import bdv.AbstractViewerImgLoader;
 import bdv.img.cache.VolatileCell;
 import bdv.img.cache.VolatileGlobalCellCache;
 import bdv.img.cache.VolatileGlobalCellCache.LoadingStrategy;
@@ -34,7 +34,7 @@ import ch.systemsx.cisd.hdf5.HDF5DataSetInformation;
 import ch.systemsx.cisd.hdf5.HDF5Factory;
 import ch.systemsx.cisd.hdf5.IHDF5Reader;
 
-public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, VolatileUnsignedShortType >
+public class Hdf5ImageLoader extends AbstractViewerImgLoader< UnsignedShortType, VolatileUnsignedShortType >
 {
 	protected File hdf5File;
 
@@ -44,6 +44,8 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 
 	protected final ArrayList< double[][] > perSetupMipmapResolutions;
 
+	protected final ArrayList< AffineTransform3D[] > perSetupMipmapTransforms;
+
 	protected final ArrayList< int[][] > perSetupSubdivisions;
 
 	/**
@@ -77,8 +79,6 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 	 */
 	protected Boolean[] cachedExistence;
 
-	protected final boolean isCoarsestLevelBlocking = true;
-
 	public Hdf5ImageLoader()
 	{
 		this( null );
@@ -86,10 +86,12 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 
 	public Hdf5ImageLoader( final ArrayList< Partition > hdf5Partitions )
 	{
+		super( new UnsignedShortType(), new VolatileUnsignedShortType() );
 		hdf5File = null;
 		hdf5Reader = null;
 		cache = null;
 		perSetupMipmapResolutions = new ArrayList< double[][] >();
+		perSetupMipmapTransforms = new ArrayList< AffineTransform3D[] >();
 		perSetupSubdivisions = new ArrayList< int[][] >();
 		partitions = new ArrayList< Partition >();
 		if ( hdf5Partitions != null )
@@ -106,7 +108,9 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 
 	public Hdf5ImageLoader( final File hdf5File, final ArrayList< Partition > hdf5Partitions, final boolean doOpen )
 	{
+		super( new UnsignedShortType(), new VolatileUnsignedShortType() );
 		this.hdf5File = hdf5File;
+		perSetupMipmapTransforms = new ArrayList< AffineTransform3D[] >();
 		perSetupMipmapResolutions = new ArrayList< double[][] >();
 		perSetupSubdivisions = new ArrayList< int[][] >();
 		partitions = new ArrayList< Partition >();
@@ -128,13 +132,27 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 		perSetupSubdivisions.clear();
 		for ( int setup = 0; setup < numSetups; ++setup )
 		{
-			final double [][] mipmapResolutions = hdf5Reader.readDoubleMatrix( getResolutionsPath( setup ) );
+			final double[][] mipmapResolutions = hdf5Reader.readDoubleMatrix( getResolutionsPath( setup ) );
 			perSetupMipmapResolutions.add( mipmapResolutions );
 			if ( mipmapResolutions.length > maxNumLevels )
 				maxNumLevels = mipmapResolutions.length;
 			maxLevels[ setup ] = mipmapResolutions.length - 1;
 
-			final int [][] subdivisions = hdf5Reader.readIntMatrix( getSubdivisionsPath( setup ) );
+			final AffineTransform3D[] mipmapTransforms = new AffineTransform3D[ mipmapResolutions.length ];
+			for ( int level = 0; level < mipmapResolutions.length; level++ )
+			{
+				final AffineTransform3D mipmapTransform = new AffineTransform3D();
+				final double[] resolution = mipmapResolutions[ level ];
+				for ( int d = 0; d < 3; ++d )
+				{
+					mipmapTransform.set( resolution[ d ], d, d );
+					mipmapTransform.set( 0.5 * ( resolution[ d ] - 1 ), d, 3 );
+				}
+				mipmapTransforms[ level ] = mipmapTransform;
+			}
+			perSetupMipmapTransforms.add( mipmapTransforms );
+
+			final int[][] subdivisions = hdf5Reader.readIntMatrix( getSubdivisionsPath( setup ) );
 			perSetupSubdivisions.add( subdivisions );
 		}
 
@@ -221,18 +239,6 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 		return partitions;
 	}
 
-	@Override
-	public RandomAccessibleInterval< FloatType > getFloatImage( final View view )
-	{
-		throw new UnsupportedOperationException( "currently not used" );
-	}
-
-	@Override
-	public RandomAccessibleInterval< UnsignedShortType > getImage( final View view )
-	{
-		return getImage( view, 0 );
-	}
-
 	@Override
 	public RandomAccessibleInterval< UnsignedShortType > getImage( final View view, final int level )
 	{
@@ -261,6 +267,7 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 		return img;
 	}
 
+	@Override
 	public VolatileGlobalCellCache< VolatileShortArray > getCache()
 	{
 		return cache;
@@ -272,6 +279,12 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 		return perSetupMipmapResolutions.get( setup );
 	}
 
+	@Override
+	public AffineTransform3D[] getMipmapTransforms( final int setup )
+	{
+		return perSetupMipmapTransforms.get( setup );
+	}
+
 	public int[][] getSubdivisions( final int setup )
 	{
 		return perSetupSubdivisions.get( setup );
@@ -289,10 +302,21 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 	 *
 	 * @return true, if the given image data is present.
 	 */
-	protected boolean existsImageData( final View view, final int level )
+	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 in the hdf5. Missing data
+	 * may be caused by missing partition files
+	 *
+	 * @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 );
 		if ( cachedExistence[ index ] == null )
 			// will set cachedExistence[ index ] as a side effect
@@ -316,7 +340,7 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 		return Views.interval( new ConstantRandomAccessible< T >( constant, 3 ), new FinalInterval( d ) );
 	}
 
-	protected long[] getImageDimension( final int timepoint, final int setup, final int level )
+	public long[] getImageDimension( final int timepoint, final int setup, final int level )
 	{
 		final int index = getViewInfoCacheIndex( timepoint, setup, level );
 		if ( cachedDimensions[ index ] == null )
@@ -392,20 +416,4 @@ public class Hdf5ImageLoader implements ViewerImgLoader< UnsignedShortType, Vola
 			}
 		}
 	}
-
-	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/RemoteImageLoader.java b/src/main/java/bdv/img/remote/RemoteImageLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6d937387e3911be48c38ff2b2fcc87c5f21ed89
--- /dev/null
+++ b/src/main/java/bdv/img/remote/RemoteImageLoader.java
@@ -0,0 +1,238 @@
+package bdv.img.remote;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+
+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.realtransform.AffineTransform3D;
+import net.imglib2.sampler.special.ConstantRandomAccessible;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.integer.UnsignedShortType;
+import net.imglib2.type.volatiles.VolatileUnsignedShortType;
+import net.imglib2.util.IntervalIndexer;
+import net.imglib2.view.Views;
+
+import org.jdom2.Element;
+
+import bdv.AbstractViewerImgLoader;
+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 extends AbstractViewerImgLoader< UnsignedShortType, VolatileUnsignedShortType >
+{
+	protected String baseUrl;
+
+	protected RemoteImageLoaderMetaData metadata;
+
+	protected final ArrayList< AffineTransform3D[] > perSetupMipmapTransforms;
+
+	protected int[][] cellsDimensions;
+
+	protected VolatileGlobalCellCache< VolatileShortArray > cache;
+
+	public RemoteImageLoader()
+	{
+		super( new UnsignedShortType(), new VolatileUnsignedShortType() );
+		perSetupMipmapTransforms = new ArrayList< AffineTransform3D[] >();
+	}
+
+	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 );
+		for ( int setup = 0; setup < metadata.numSetups; ++setup )
+		{
+			final double[][] mipmapResolutions = metadata.perSetupMipmapResolutions.get( setup );
+			final AffineTransform3D[] mipmapTransforms = new AffineTransform3D[ mipmapResolutions.length ];
+			for ( int level = 0; level < mipmapResolutions.length; level++ )
+			{
+				final AffineTransform3D mipmapTransform = new AffineTransform3D();
+				final double[] resolution = mipmapResolutions[ level ];
+				for ( int d = 0; d < 3; ++d )
+				{
+					mipmapTransform.set( resolution[ d ], d, d );
+					mipmapTransform.set( 0.5 * ( resolution[ d ] - 1 ), d, 3 );
+				}
+				mipmapTransforms[ level ] = mipmapTransform;
+			}
+			perSetupMipmapTransforms.add( mipmapTransforms );
+		}
+		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 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;
+	}
+
+	@Override
+	public VolatileGlobalCellCache< VolatileShortArray > getCache()
+	{
+		return cache;
+	}
+
+	@Override
+	public double[][] getMipmapResolutions( final int setup )
+	{
+		return metadata.perSetupMipmapResolutions.get( setup );
+	}
+
+	@Override
+	public AffineTransform3D[] getMipmapTransforms( final int setup )
+	{
+		return perSetupMipmapTransforms.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;
+	}
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..3356227d6978879ce9560a6ae4ae7f85fe717a42
--- /dev/null
+++ b/src/main/java/bdv/server/BigDataServer.java
@@ -0,0 +1,112 @@
+package bdv.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import mpicbg.spim.data.SequenceDescription;
+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.jdom2.JDOMException;
+
+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;
+
+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/data/fibsem.xml";
+		final Server server = new Server( 8080 );
+		server.setHandler( new CellHandler( fn ) );
+		server.start();
+		server.join();
+	}
+
+	static class CellHandler extends AbstractHandler
+	{
+		private final VolatileGlobalCellCache< VolatileShortArray > cache;
+
+		private final String metadataJson;
+
+		private final RemoteImageLoaderMetaData metadata;
+
+		public CellHandler( final String xmlFilename ) throws JDOMException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException
+		{
+			final SequenceViewsLoader loader = new SequenceViewsLoader( xmlFilename );
+			final SequenceDescription seq = loader.getSequenceDescription();
+			final Hdf5ImageLoader imgLoader = ( Hdf5ImageLoader ) seq.imgLoader;
+			cache = imgLoader.getCache();
+			metadata = new RemoteImageLoaderMetaData( imgLoader, seq.numTimepoints(), seq.numViewSetups() );
+			metadataJson = new Gson().toJson( metadata );
+		}
+
+		@Override
+		public void handle( final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response ) throws IOException, ServletException
+		{
+			final String cellString = request.getParameter( "p" );
+			if ( cellString == null )
+				return;
+			final String[] parts = cellString.split( "/" );
+			if ( parts[ 0 ].equals( "cell" ) )
+			{
+				final int index = Integer.parseInt( parts[ 1 ] );
+				final int timepoint = Integer.parseInt( parts[ 2 ] );
+				final int setup = Integer.parseInt( parts[ 3 ] );
+				final int level = Integer.parseInt( parts[ 4 ] );
+				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.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++ )
+				{
+					final short s = data[ i ];
+					buf[ j++ ] = ( byte ) ( ( s >> 8 ) & 0xff );
+					buf[ j++ ] = ( byte ) ( s & 0xff );
+				}
+
+				response.setContentType( "application/octet-stream" );
+				response.setContentLength( buf.length );
+				response.setStatus( HttpServletResponse.SC_OK );
+				baseRequest.setHandled( true );
+				final OutputStream os = response.getOutputStream();
+				os.write( buf );
+				os.close();
+			}
+			else if ( parts[ 0 ].equals( "init" ) )
+			{
+				response.setContentType( "application/octet-stream" );
+				response.setStatus( HttpServletResponse.SC_OK );
+				baseRequest.setHandled( true );
+				final PrintWriter ow = response.getWriter();
+				ow.write( metadataJson );
+				ow.close();
+			}
+		}
+	}
+}
diff --git a/src/main/java/bdv/viewer/render/AccumulateProjector.java b/src/main/java/bdv/viewer/render/AccumulateProjector.java
index 980125cae767e75fc1926aa6bf60bfa83a0db365..c184991cc236c8dd4cac58a34946f1bf92ff012f 100644
--- a/src/main/java/bdv/viewer/render/AccumulateProjector.java
+++ b/src/main/java/bdv/viewer/render/AccumulateProjector.java
@@ -1,9 +1,9 @@
 package bdv.viewer.render;
 
 import java.util.ArrayList;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import net.imglib2.Cursor;
@@ -43,6 +43,8 @@ public abstract class AccumulateProjector< A, B > implements VolatileProjector
      */
     protected final int numThreads;
 
+	protected final ExecutorService executorService;
+
     /**
      * Time needed for rendering the last frame, in nano-seconds.
      */
@@ -57,7 +59,8 @@ public abstract class AccumulateProjector< A, B > implements VolatileProjector
 			final ArrayList< ? extends RandomAccessible< A > > sources,
 			final Converter< ? super A, B > converter,
 			final RandomAccessibleInterval< B > target,
-			final int numThreads )
+			final int numThreads,
+			final ExecutorService executorService )
 	{
 		this.sourceProjectors = sourceProjectors;
 		this.sources = new ArrayList< IterableInterval< A > >();
@@ -67,6 +70,7 @@ public abstract class AccumulateProjector< A, B > implements VolatileProjector
 		this.target = target;
 		this.iterableTarget = Views.flatIterable( target );
 		this.numThreads = numThreads;
+		this.executorService = executorService;
 		lastFrameRenderNanoTime = -1;
 	}
 
@@ -96,23 +100,25 @@ public abstract class AccumulateProjector< A, B > implements VolatileProjector
 		final int height = ( int ) target.dimension( 1 );
 		final int length = width * height;
 
-		final ExecutorService ex = Executors.newFixedThreadPool( numThreads );
-		final int numTasks = Math.max( numThreads * 10, height );
+		final boolean createExecutor = ( executorService == null );
+		final ExecutorService ex = createExecutor ? Executors.newFixedThreadPool( numThreads ) : executorService;
+		final int numTasks = Math.min( numThreads * 10, height );
 		final double taskLength = ( double ) length / numTasks;
 		final int numSources = sources.size();
+		final ArrayList< Callable< Void > > tasks = new ArrayList< Callable< Void > >( numTasks );
 		for ( int taskNum = 0; taskNum < numTasks; ++taskNum )
 		{
 			final int myOffset = ( int ) ( taskNum * taskLength );
 			final int myLength = ( (taskNum == numTasks - 1 ) ? length : ( int ) ( ( taskNum + 1 ) * taskLength ) ) - myOffset;
 
-			final Runnable r = new Runnable()
+			final Callable< Void > r = new Callable< Void >()
 			{
 				@SuppressWarnings( "unchecked" )
 				@Override
-				public void run()
+				public Void call()
 				{
 					if ( interrupted.get() )
-						return;
+						return null;
 
 					final Cursor< A >[] sourceCursors = new Cursor[ numSources ];
 					for ( int s = 0; s < numSources; ++s )
@@ -130,19 +136,21 @@ public abstract class AccumulateProjector< A, B > implements VolatileProjector
 							sourceCursors[ s ].fwd();
 						accumulate( sourceCursors, targetCursor.next() );
 					}
+					return null;
 				}
 			};
-			ex.execute( r );
+			tasks.add( r );
 		}
-		ex.shutdown();
 		try
 		{
-			ex.awaitTermination( 1, TimeUnit.HOURS );
+			ex.invokeAll( tasks );
 		}
 		catch ( final InterruptedException e )
 		{
 			e.printStackTrace();
 		}
+		if ( createExecutor )
+			ex.shutdown();
 
 		lastFrameRenderNanoTime = stopWatch.nanoTime();
 
diff --git a/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java b/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java
index 481a9c4189619def08de1b2ab290a385a15db7a2..4f54315de4ae4160822f8568dad5bee1d866b0f9 100644
--- a/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java
+++ b/src/main/java/bdv/viewer/render/AccumulateProjectorARGB.java
@@ -1,6 +1,7 @@
 package bdv.viewer.render;
 
 import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
 
 import net.imglib2.Cursor;
 import net.imglib2.RandomAccessible;
@@ -14,9 +15,10 @@ public class AccumulateProjectorARGB extends AccumulateProjector< ARGBType, ARGB
 			final ArrayList< VolatileProjector > sourceProjectors,
 			final ArrayList< ? extends RandomAccessible< ARGBType > > sources,
 			final RandomAccessibleInterval< ARGBType > target,
-			final int numThreads )
+			final int numThreads,
+			final ExecutorService executorService )
 	{
-		super( sourceProjectors, sources, null, target, numThreads );
+		super( sourceProjectors, sources, null, target, numThreads, executorService );
 	}
 
 	@Override
diff --git a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
index b9ca971cd43d7ab3f69e62e1787c0f23e4d09a3f..e794a060b02b0cb7f7099aa9843ec723806f6c24 100644
--- a/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
+++ b/src/main/java/bdv/viewer/render/MultiResolutionRenderer.java
@@ -571,7 +571,7 @@ public class MultiResolutionRenderer
 					sourceProjectors.add( p );
 					sourceImages.add( renderImage );
 				}
-				projector = new AccumulateProjectorARGB( sourceProjectors, sourceImages, screenImage, numRenderingThreads );
+				projector = new AccumulateProjectorARGB( sourceProjectors, sourceImages, screenImage, numRenderingThreads, renderingExecutorService );
 			}
 			previousTimepoint = viewerState.getCurrentTimepoint();
 			cache.initIoTimeBudget( iobudget );
@@ -680,7 +680,7 @@ public class MultiResolutionRenderer
 		}
 //		for ( int i = bestLevel - 1; i >= 0; --i )
 //			levels.add( getTransformedSource( viewerState, spimSource, screenScaleTransform, i ) );
-		return new VolatileHierarchyProjector< T, ARGBType >( levels, source.getConverter(), screenImage, numRenderingThreads, renderingExecutorService );
+		return new VolatileHierarchyProjector< T, ARGBType >( levels, source.getConverter(), screenImage, maskArray, numRenderingThreads, renderingExecutorService );
 	}
 
 	private static < T > RandomAccessible< T > getTransformedSource( final ViewerState viewerState, final Source< T > source, final AffineTransform3D screenScaleTransform, final int mipmapIndex )