diff --git a/src/main/java/bdv/ij/export/imgloader/ImagePlusImgLoader.java b/src/main/java/bdv/ij/export/imgloader/ImagePlusImgLoader.java
index fbefc4ad89bcf786835780ca1350b1ac408e9a93..77ecf62a16609e4f816f6a772671ee7c663de842 100644
--- a/src/main/java/bdv/ij/export/imgloader/ImagePlusImgLoader.java
+++ b/src/main/java/bdv/ij/export/imgloader/ImagePlusImgLoader.java
@@ -1,7 +1,11 @@
 package bdv.ij.export.imgloader;
 
+import java.util.ArrayList;
+
 import ij.ImagePlus;
 import mpicbg.spim.data.generic.sequence.BasicImgLoader;
+import mpicbg.spim.data.generic.sequence.BasicSetupImgLoader;
+import mpicbg.spim.data.generic.sequence.TypedBasicImgLoader;
 import mpicbg.spim.data.sequence.ViewId;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.algorithm.stats.ComputeMinMax;
@@ -9,9 +13,7 @@ import net.imglib2.converter.Converters;
 import net.imglib2.converter.RealUnsignedShortConverter;
 import net.imglib2.type.NativeType;
 import net.imglib2.type.numeric.RealType;
-import net.imglib2.type.numeric.integer.UnsignedByteType;
 import net.imglib2.type.numeric.integer.UnsignedShortType;
-import net.imglib2.type.numeric.real.FloatType;
 import bdv.img.cache.VolatileGlobalCellCache;
 import bdv.img.imagestack.ImageStackImageLoader;
 import bdv.img.virtualstack.VirtualStackImageLoader;
@@ -29,7 +31,7 @@ import bdv.img.virtualstack.VirtualStackImageLoader;
  *
  * @author Tobias Pietzsch <tobias.pietzsch@gmail.com>
  */
-public class ImagePlusImgLoader< T extends RealType< T > & NativeType< T > > implements BasicImgLoader< UnsignedShortType >
+public class ImagePlusImgLoader implements TypedBasicImgLoader< UnsignedShortType >
 {
 	public static enum MinMaxOption
 	{
@@ -38,49 +40,51 @@ public class ImagePlusImgLoader< T extends RealType< T > & NativeType< T > > imp
 		TAKE_FROM_IMAGEPROCESSOR
 	}
 
-	public static ImagePlusImgLoader< UnsignedByteType > createGray8( final ImagePlus imp, final MinMaxOption minMaxOption, final double min, final double max )
+	public static ImagePlusImgLoader createGray8( final ImagePlus imp, final MinMaxOption minMaxOption, final double min, final double max )
 	{
 		if( imp.getType() != ImagePlus.GRAY8 )
 			throw new RuntimeException( "expected ImagePlus type GRAY8" );
 		if ( imp.getStack() != null && imp.getStack().isVirtual() )
-			return new ImagePlusImgLoader< UnsignedByteType >( imp, VirtualStackImageLoader.createUnsignedByteInstance( imp ), minMaxOption, min, max );
+			return new ImagePlusImgLoader( imp, VirtualStackImageLoader.createUnsignedByteInstance( imp ), minMaxOption, min, max );
 		else
-			return new ImagePlusImgLoader< UnsignedByteType >( imp, ImageStackImageLoader.createUnsignedByteInstance( imp ), minMaxOption, min, max );
+			return new ImagePlusImgLoader( imp, ImageStackImageLoader.createUnsignedByteInstance( imp ), minMaxOption, min, max );
 	}
 
-	public static ImagePlusImgLoader< UnsignedShortType > createGray16( final ImagePlus imp, final MinMaxOption minMaxOption, final double min, final double max )
+	public static ImagePlusImgLoader createGray16( final ImagePlus imp, final MinMaxOption minMaxOption, final double min, final double max )
 	{
 		if( imp.getType() != ImagePlus.GRAY16 )
 			throw new RuntimeException( "expected ImagePlus type GRAY16" );
 		if ( imp.getStack() != null && imp.getStack().isVirtual() )
-			return new ImagePlusImgLoader< UnsignedShortType >( imp, VirtualStackImageLoader.createUnsignedShortInstance( imp ), minMaxOption, min, max );
+			return new ImagePlusImgLoader( imp, VirtualStackImageLoader.createUnsignedShortInstance( imp ), minMaxOption, min, max );
 		else
-			return new ImagePlusImgLoader< UnsignedShortType >( imp, ImageStackImageLoader.createUnsignedShortInstance( imp ), minMaxOption, min, max );
+			return new ImagePlusImgLoader( imp, ImageStackImageLoader.createUnsignedShortInstance( imp ), minMaxOption, min, max );
 	}
 
-	public static ImagePlusImgLoader< FloatType > createGray32( final ImagePlus imp, final MinMaxOption minMaxOption, final double min, final double max )
+	public static ImagePlusImgLoader createGray32( final ImagePlus imp, final MinMaxOption minMaxOption, final double min, final double max )
 	{
 		if( imp.getType() != ImagePlus.GRAY32 )
 			throw new RuntimeException( "expected ImagePlus type GRAY32" );
 		if ( imp.getStack() != null && imp.getStack().isVirtual() )
-			return new ImagePlusImgLoader< FloatType >( imp, VirtualStackImageLoader.createFloatInstance( imp ), minMaxOption, min, max );
+			return new ImagePlusImgLoader( imp, VirtualStackImageLoader.createFloatInstance( imp ), minMaxOption, min, max );
 		else
-			return new ImagePlusImgLoader< FloatType >( imp, ImageStackImageLoader.createFloatInstance( imp ), minMaxOption, min, max );
+			return new ImagePlusImgLoader( imp, ImageStackImageLoader.createFloatInstance( imp ), minMaxOption, min, max );
 	}
 
 	protected final ImagePlus imp;
 
-	protected final BasicImgLoader< T > loader;
+	protected final BasicImgLoader loader;
+
+	protected VolatileGlobalCellCache loadercache;
 
-	protected VolatileGlobalCellCache< ? > loadercache;
+	protected final ArrayList< SetupImgLoader< ? > > setupImgLoaders;
 
 	protected double impMin;
 
 	protected double impMax;
 
 	@SuppressWarnings( "unchecked" )
-	protected ImagePlusImgLoader( final ImagePlus imp,
-			final BasicImgLoader< T > loader,
+	protected< T extends RealType< T > & NativeType< T > > ImagePlusImgLoader( final ImagePlus imp,
+			final TypedBasicImgLoader< T > loader,
 			final MinMaxOption minMaxOption,
 			final double min,
 			final double max )
@@ -88,8 +92,13 @@ public class ImagePlusImgLoader< T extends RealType< T > & NativeType< T > > imp
 		this.imp = imp;
 		this.loader = loader;
 
+		final int numSetups = imp.getNChannels();
+		setupImgLoaders = new ArrayList< SetupImgLoader< ? > >();
+		for ( int setupId = 0; setupId < numSetups; ++setupId )
+			setupImgLoaders.add( new SetupImgLoader< T >( loader.getSetupImgLoader( setupId ) ) );
+
 		if ( loader instanceof VirtualStackImageLoader )
-			this.loadercache = ( ( VirtualStackImageLoader< T, ?, ? > ) loader ).getCache();
+			this.loadercache = ( ( VirtualStackImageLoader< ?, ?, ? > ) loader ).getCache();
 		else
 			this.loadercache = null;
 
@@ -97,14 +106,13 @@ public class ImagePlusImgLoader< T extends RealType< T > & NativeType< T > > imp
 		{
 			impMin = Double.POSITIVE_INFINITY;
 			impMax = Double.NEGATIVE_INFINITY;
-			final T minT = loader.getImageType().createVariable();
-			final T maxT = loader.getImageType().createVariable();
-			final int numSetups = imp.getNChannels();
+			final T minT = loader.getSetupImgLoader( 0 ).getImageType().createVariable();
+			final T maxT = minT.createVariable();
 			final int numTimepoints = imp.getNFrames();
 			for ( int t = 0; t < numTimepoints; t++ )
 				for ( int s = 0; s < numSetups; ++s )
 				{
-					ComputeMinMax.computeMinMax( loader.getImage( new ViewId( t, s ) ), minT, maxT );
+					ComputeMinMax.computeMinMax( loader.getSetupImgLoader( s ).getImage( t ), minT, maxT );
 					impMin = Math.min( minT.getRealDouble(), impMin );
 					impMax = Math.max( maxT.getRealDouble(), impMax );
 					if ( loadercache != null )
@@ -139,18 +147,34 @@ public class ImagePlusImgLoader< T extends RealType< T > & NativeType< T > > imp
 		}
 	}
 
-	@Override
-	public RandomAccessibleInterval< UnsignedShortType > getImage( final ViewId view )
+	public class SetupImgLoader< T extends RealType< T > & NativeType< T > > implements BasicSetupImgLoader< UnsignedShortType >
 	{
-		if ( loadercache != null )
-			loadercache.clearCache();
-		final RandomAccessibleInterval< T > img = loader.getImage( view );
-		return Converters.convert( img, new RealUnsignedShortConverter< T >( impMin, impMax ), new UnsignedShortType() );
+		final BasicSetupImgLoader< T > loader;
+
+		protected SetupImgLoader( final BasicSetupImgLoader< T > loader )
+		{
+			this.loader = loader;
+		}
+
+		@Override
+		public RandomAccessibleInterval< UnsignedShortType > getImage( final int timepointId )
+		{
+			if ( loadercache != null )
+				loadercache.clearCache();
+			final RandomAccessibleInterval< T > img = loader.getImage( timepointId );
+			return Converters.convert( img, new RealUnsignedShortConverter< T >( impMin, impMax ), new UnsignedShortType() );
+		}
+
+		@Override
+		public UnsignedShortType getImageType()
+		{
+			return new UnsignedShortType();
+		}
 	}
 
 	@Override
-	public UnsignedShortType getImageType()
+	public SetupImgLoader< ? > getSetupImgLoader( final int setupId )
 	{
-		return new UnsignedShortType();
+		return setupImgLoaders.get( setupId );
 	}
 }
diff --git a/src/main/java/bdv/img/imagestack/ImageStackImageLoader.java b/src/main/java/bdv/img/imagestack/ImageStackImageLoader.java
index c3014bb52f50eef2cf73fd2f70e94a59598d4026..6c5c813d6500e4e03c949610844a1378047db564 100644
--- a/src/main/java/bdv/img/imagestack/ImageStackImageLoader.java
+++ b/src/main/java/bdv/img/imagestack/ImageStackImageLoader.java
@@ -1,8 +1,11 @@
 package bdv.img.imagestack;
 
+import java.util.ArrayList;
+
 import ij.ImagePlus;
 import mpicbg.spim.data.generic.sequence.BasicImgLoader;
-import mpicbg.spim.data.sequence.ViewId;
+import mpicbg.spim.data.generic.sequence.BasicSetupImgLoader;
+import mpicbg.spim.data.generic.sequence.TypedBasicImgLoader;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.img.basictypeaccess.array.ArrayDataAccess;
 import net.imglib2.img.basictypeaccess.array.ByteArray;
@@ -17,7 +20,7 @@ import net.imglib2.type.numeric.integer.UnsignedByteType;
 import net.imglib2.type.numeric.integer.UnsignedShortType;
 import net.imglib2.type.numeric.real.FloatType;
 
-public abstract class ImageStackImageLoader< T extends NumericType< T > & NativeType< T >, A extends ArrayDataAccess< A > > implements BasicImgLoader< T >
+public abstract class ImageStackImageLoader< T extends NumericType< T > & NativeType< T >, A extends ArrayDataAccess< A > > implements TypedBasicImgLoader< T >, BasicImgLoader
 {
 	public static ImageStackImageLoader< UnsignedByteType, ByteArray > createUnsignedByteInstance( final ImagePlus imp )
 	{
@@ -97,37 +100,59 @@ public abstract class ImageStackImageLoader< T extends NumericType< T > & Native
 
 	private final long[] dim;
 
+	private final ArrayList< SetupImgLoader > setupImgLoaders;
+
 	public ImageStackImageLoader( final T type, final ImagePlus imp )
 	{
 		this.type = type;
 		this.imp = imp;
 		this.dim = new long[] { imp.getWidth(), imp.getHeight(), imp.getNSlices() };
+		final int numSetups = imp.getNChannels();
+		setupImgLoaders = new ArrayList< SetupImgLoader >();
+		for ( int setupId = 0; setupId < numSetups; ++setupId )
+			setupImgLoaders.add( new SetupImgLoader( setupId ) );
 	}
 
 	protected abstract A wrapPixels( Object array );
 
 	protected abstract void linkType( PlanarImg< T, A > img );
 
-	@Override
-	public RandomAccessibleInterval< T > getImage( final ViewId view )
+	public class SetupImgLoader implements BasicSetupImgLoader< T >
 	{
-		return new PlanarImg< T, A >( dim, type.getEntitiesPerPixel() )
+		private final int setupId;
+
+		public SetupImgLoader( final int setupId )
 		{
-			private PlanarImg< T, A > init()
+			this.setupId = setupId;
+		}
+
+		@Override
+		public RandomAccessibleInterval< T > getImage( final int timepointId )
+		{
+			return new PlanarImg< T, A >( dim, type.getEntitiesPerPixel() )
 			{
-				final int channel = view.getViewSetupId() + 1;
-				final int frame = view.getTimePointId() + 1;
-				for ( int slice = 1; slice <= dim[ 2 ]; ++slice )
-					mirror.set( slice - 1, wrapPixels( imp.getStack().getPixels( imp.getStackIndex( channel, slice, frame ) ) ) );
-				linkType( this );
-				return this;
-			}
-		}.init();
+				private PlanarImg< T, A > init()
+				{
+					final int channel = setupId + 1;
+					final int frame = timepointId + 1;
+					for ( int slice = 1; slice <= dim[ 2 ]; ++slice )
+						mirror.set( slice - 1, wrapPixels( imp.getStack().getPixels( imp.getStackIndex( channel, slice, frame ) ) ) );
+					linkType( this );
+					return this;
+				}
+			}.init();
+		}
+
+		@Override
+		public T getImageType()
+		{
+			return type;
+		}
 	}
 
 	@Override
-	public T getImageType()
+	public SetupImgLoader getSetupImgLoader( final int setupId )
 	{
-		return type;
+		return setupImgLoaders.get( setupId );
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/bdv/img/virtualstack/VirtualStackImageLoader.java b/src/main/java/bdv/img/virtualstack/VirtualStackImageLoader.java
index 4d2f27000a020b97176f735b4f1d13119fccd483..f159c4845925f354d89f3f273a9809e21b570a62 100644
--- a/src/main/java/bdv/img/virtualstack/VirtualStackImageLoader.java
+++ b/src/main/java/bdv/img/virtualstack/VirtualStackImageLoader.java
@@ -1,6 +1,9 @@
 package bdv.img.virtualstack;
 
+import java.util.ArrayList;
+
 import ij.ImagePlus;
+import mpicbg.spim.data.generic.sequence.TypedBasicImgLoader;
 import mpicbg.spim.data.sequence.ViewId;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.Volatile;
@@ -22,6 +25,7 @@ import net.imglib2.type.volatiles.VolatileUnsignedByteType;
 import net.imglib2.type.volatiles.VolatileUnsignedShortType;
 import net.imglib2.util.Fraction;
 import bdv.AbstractViewerImgLoader;
+import bdv.AbstractViewerSetupImgLoader;
 import bdv.img.cache.CacheArrayLoader;
 import bdv.img.cache.CacheHints;
 import bdv.img.cache.CachedCellImg;
@@ -54,7 +58,7 @@ import bdv.img.cache.VolatileImgCells.CellCache;
  * @author Tobias Pietzsch <tobias.pietzsch@gmail.com>
  */
 public abstract class VirtualStackImageLoader< T extends NativeType< T >, V extends Volatile< T > & NativeType< V >, A extends VolatileAccess >
-		extends AbstractViewerImgLoader< T, V >
+		extends AbstractViewerImgLoader< T, V > implements TypedBasicImgLoader< T >
 {
 	public static VirtualStackImageLoader< FloatType, VolatileFloatType, VolatileFloatArray > createFloatInstance( final ImagePlus imp )
 	{
@@ -136,77 +140,102 @@ public abstract class VirtualStackImageLoader< T extends NativeType< T >, V exte
 
 	private static AffineTransform3D[] mipmapTransforms = new AffineTransform3D[] { new AffineTransform3D() };
 
-	private final VolatileGlobalCellCache< A > cache;
+	private final VolatileGlobalCellCache cache;
 
 	private final long[] dimensions;
 
 	private final int[] cellDimensions;
 
+	private final CacheArrayLoader< A > loader;
+
+	private final ArrayList< SetupImgLoader > setupImgLoaders;
+
 	protected VirtualStackImageLoader( final ImagePlus imp, final CacheArrayLoader< A > loader, final T type, final V volatileType )
 	{
 		super( type, volatileType );
+		this.loader = loader;
 		dimensions = new long[] { imp.getWidth(), imp.getHeight(), imp.getNSlices() };
 		cellDimensions = new int[] { imp.getWidth(), imp.getHeight(), 1 };
 		final int numTimepoints = imp.getNFrames();
 		final int numSetups = imp.getNChannels();
-		cache = new VolatileGlobalCellCache< A >( loader, numTimepoints, numSetups, 1, 1 );
+		cache = new VolatileGlobalCellCache( numTimepoints, numSetups, 1, 1 );
+		setupImgLoaders = new ArrayList< SetupImgLoader >();
+		for ( int setupId = 0; setupId < numSetups; ++setupId )
+			setupImgLoaders.add( new SetupImgLoader( setupId ) );
 	}
 
-	protected abstract void linkType( CachedCellImg< T, A > img );
+	public class SetupImgLoader extends AbstractViewerSetupImgLoader< T, V >
+	{
+		private final int setupId;
 
-	protected abstract void linkVolatileType( CachedCellImg< V, A > img );
+		public SetupImgLoader( final int setupId )
+		{
+			super( VirtualStackImageLoader.this.type, VirtualStackImageLoader.this.volatileType );
+			this.setupId = setupId;
+		}
 
-	@Override
-	public RandomAccessibleInterval< T > getImage( final ViewId view, final int level )
-	{
-		final CachedCellImg< T, A > img = prepareCachedImage( view, level, LoadingStrategy.BLOCKING );
-		linkType( img );
-		return img;
-	}
+		@Override
+		public RandomAccessibleInterval< T > getImage( final int timepointId, final int level )
+		{
+			final CachedCellImg< T, A > img = prepareCachedImage( timepointId, setupId, level, LoadingStrategy.BLOCKING );
+			linkType( img );
+			return img;
+		}
 
-	@Override
-	public RandomAccessibleInterval< V > getVolatileImage( final ViewId view, final int level )
-	{
-		final CachedCellImg< V, A > img = prepareCachedImage( view, level, LoadingStrategy.BUDGETED );
-		linkVolatileType( img );
-		return img;
-	}
+		@Override
+		public RandomAccessibleInterval< V > getVolatileImage( final int timepointId, final int level )
+		{
+			final CachedCellImg< V, A > img = prepareCachedImage( timepointId, setupId, level, LoadingStrategy.BUDGETED );
+			linkVolatileType( img );
+			return img;
+		}
 
-	@Override
-	public double[][] getMipmapResolutions( final int setup )
-	{
-		return mipmapResolutions;
-	}
+		@Override
+		public double[][] getMipmapResolutions()
+		{
+			return mipmapResolutions;
+		}
 
-	@Override
-	public AffineTransform3D[] getMipmapTransforms( final int setup )
-	{
-		return mipmapTransforms;
+		@Override
+		public AffineTransform3D[] getMipmapTransforms()
+		{
+			return mipmapTransforms;
+		}
+
+		@Override
+		public int numMipmapLevels()
+		{
+			return 1;
+		}
 	}
 
 	@Override
-	public int numMipmapLevels( final int setup )
+	public SetupImgLoader getSetupImgLoader( final int setupId )
 	{
-		return 1;
+		return setupImgLoaders.get( setupId );
 	}
 
 	@Override
-	public VolatileGlobalCellCache< A > getCache()
+	public VolatileGlobalCellCache getCache()
 	{
 		return cache;
 	}
 
+	protected abstract void linkType( CachedCellImg< T, A > img );
+
+	protected abstract void linkVolatileType( CachedCellImg< V, A > img );
+
 	/**
 	 * (Almost) create a {@link CachedCellImg} 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 ARGBType} and {@link VolatileARGBType}.
 	 */
-	protected < T extends NativeType< T > > CachedCellImg< T, A > prepareCachedImage( final ViewId view, final int level, final LoadingStrategy loadingStrategy )
+	protected < T extends NativeType< T > > CachedCellImg< T, A > prepareCachedImage( final int timepointId, final int setupId, final int level, final LoadingStrategy loadingStrategy )
 	{
 		final int priority = 0;
 		final CacheHints cacheHints = new CacheHints( loadingStrategy, priority, false );
-		final CellCache< A > c = cache.new VolatileCellCache( view.getTimePointId(), view.getViewSetupId(), level, cacheHints );
+		final CellCache< A > c = cache.new VolatileCellCache< A >( timepointId, setupId, level, cacheHints, loader );
 		final VolatileImgCells< A > cells = new VolatileImgCells< A >( c, new Fraction(), dimensions, cellDimensions );
 		final CachedCellImg< T, A > img = new CachedCellImg< T, A >( cells );
 		return img;