diff --git a/core/src/main/java/bdv/img/hdf5/HDF5Access.java b/core/src/main/java/bdv/img/hdf5/HDF5Access.java
index a6bc46169b0d27b6c0bf2f65e459855ac1daa1ae..bcd44780ae4cfc58b890767cba3056bffc20cde4 100644
--- a/core/src/main/java/bdv/img/hdf5/HDF5Access.java
+++ b/core/src/main/java/bdv/img/hdf5/HDF5Access.java
@@ -1,6 +1,7 @@
 package bdv.img.hdf5;
 
 import static bdv.img.hdf5.Util.reorder;
+import ch.systemsx.cisd.base.mdarray.MDFloatArray;
 import ch.systemsx.cisd.base.mdarray.MDShortArray;
 import ch.systemsx.cisd.hdf5.HDF5DataSetInformation;
 import ch.systemsx.cisd.hdf5.IHDF5Reader;
@@ -42,7 +43,6 @@ public class HDF5Access implements IHDF5Access
 			throw new InterruptedException();
 		Util.reorder( dimensions, reorderedDimensions );
 		Util.reorder( min, reorderedMin );
-		hdf5Reader.int16().readMDArray( Util.getCellsPath( timepoint, setup, level ) );
 		final MDShortArray array = hdf5Reader.int16().readMDArrayBlockWithOffset( Util.getCellsPath( timepoint, setup, level ), reorderedDimensions, reorderedMin );
 		return array.getAsFlatArray();
 	}
@@ -53,4 +53,22 @@ public class HDF5Access implements IHDF5Access
 		System.arraycopy( readShortMDArrayBlockWithOffset( timepoint, setup, level, dimensions, min ), 0, dataBlock, 0, dataBlock.length );
 		return dataBlock;
 	}
+
+	@Override
+	public float[] readShortMDArrayBlockWithOffsetAsFloat( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException
+	{
+		if ( Thread.interrupted() )
+			throw new InterruptedException();
+		Util.reorder( dimensions, reorderedDimensions );
+		Util.reorder( min, reorderedMin );
+		final MDFloatArray array = hdf5Reader.float32().readMDArrayBlockWithOffset( Util.getCellsPath( timepoint, setup, level ), reorderedDimensions, reorderedMin );
+		return array.getAsFlatArray();
+	}
+
+	@Override
+	public float[] readShortMDArrayBlockWithOffsetAsFloat( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min, final float[] dataBlock ) throws InterruptedException
+	{
+		System.arraycopy( readShortMDArrayBlockWithOffsetAsFloat( timepoint, setup, level, dimensions, min ), 0, dataBlock, 0, dataBlock.length );
+		return dataBlock;
+	}
 }
diff --git a/core/src/main/java/bdv/img/hdf5/HDF5AccessHack.java b/core/src/main/java/bdv/img/hdf5/HDF5AccessHack.java
index ef965c9382fa6e684573cb6f51af650f5f32a0c6..f763621311d276d7860ed7f8f4c7b993ff9afc81 100644
--- a/core/src/main/java/bdv/img/hdf5/HDF5AccessHack.java
+++ b/core/src/main/java/bdv/img/hdf5/HDF5AccessHack.java
@@ -12,6 +12,7 @@ import static ch.systemsx.cisd.hdf5.hdf5lib.H5S.H5Sselect_hyperslab;
 import static ch.systemsx.cisd.hdf5.hdf5lib.HDF5Constants.H5P_DEFAULT;
 import static ch.systemsx.cisd.hdf5.hdf5lib.HDF5Constants.H5S_MAX_RANK;
 import static ch.systemsx.cisd.hdf5.hdf5lib.HDF5Constants.H5S_SELECT_SET;
+import static ch.systemsx.cisd.hdf5.hdf5lib.HDF5Constants.H5T_NATIVE_FLOAT;
 import static ch.systemsx.cisd.hdf5.hdf5lib.HDF5Constants.H5T_NATIVE_INT16;
 
 import java.lang.reflect.Field;
@@ -171,6 +172,32 @@ public class HDF5AccessHack implements IHDF5Access
 		return dataBlock;
 	}
 
+
+	@Override
+	public float[] readShortMDArrayBlockWithOffsetAsFloat( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException
+	{
+		final float[] dataBlock = new float[ dimensions[ 0 ] * dimensions[ 1 ] * dimensions[ 2 ] ];
+		readShortMDArrayBlockWithOffsetAsFloat( timepoint, setup, level, dimensions, min, dataBlock );
+		return dataBlock;
+	}
+
+	@Override
+	public float[] readShortMDArrayBlockWithOffsetAsFloat( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min, final float[] dataBlock ) throws InterruptedException
+	{
+		if ( Thread.interrupted() )
+			throw new InterruptedException();
+		Util.reorder( dimensions, reorderedDimensions );
+		Util.reorder( min, reorderedMin );
+
+		final OpenDataSet dataset = openDataSetCache.getDataSet( new ViewLevelId( timepoint, setup, level ) );
+		final int memorySpaceId = H5Screate_simple( reorderedDimensions.length, reorderedDimensions, null );
+		H5Sselect_hyperslab( dataset.fileSpaceId, H5S_SELECT_SET, reorderedMin, null, reorderedDimensions, null );
+		H5Dread( dataset.dataSetId, H5T_NATIVE_FLOAT, memorySpaceId, dataset.fileSpaceId, numericConversionXferPropertyListID, dataBlock );
+		H5Sclose( memorySpaceId );
+
+		return dataBlock;
+	}
+
 	@Override
 	protected void finalize() throws Throwable
 	{
diff --git a/core/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java b/core/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
index 6d6f779fb89bee1cc00ef46536563fcc75f2cfe4..4db8274c110e1a9aa8df0d18588c6cf867e74f70 100644
--- a/core/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
+++ b/core/src/main/java/bdv/img/hdf5/Hdf5ImageLoader.java
@@ -22,12 +22,13 @@ import net.imglib2.Cursor;
 import net.imglib2.Dimensions;
 import net.imglib2.FinalDimensions;
 import net.imglib2.FinalInterval;
+import net.imglib2.IterableInterval;
 import net.imglib2.RandomAccess;
 import net.imglib2.RandomAccessibleInterval;
-import net.imglib2.algorithm.stats.Normalize;
 import net.imglib2.img.Img;
 import net.imglib2.img.NativeImg;
 import net.imglib2.img.array.ArrayImgs;
+import net.imglib2.img.basictypeaccess.array.FloatArray;
 import net.imglib2.img.basictypeaccess.array.ShortArray;
 import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray;
 import net.imglib2.img.cell.CellImg;
@@ -375,10 +376,10 @@ public class Hdf5ImageLoader extends AbstractViewerImgLoader< UnsignedShortType,
 
 	public class MonolithicImageLoader implements ImgLoader< UnsignedShortType >
 	{
-
 		@Override
 		public RandomAccessibleInterval< UnsignedShortType > getImage( final ViewId view )
 		{
+			Img< UnsignedShortType > img = null;
 			final int timepoint = view.getTimePointId();
 			final int setup = view.getViewSetupId();
 			final int level = 0;
@@ -400,31 +401,18 @@ public class Hdf5ImageLoader extends AbstractViewerImgLoader< UnsignedShortType,
 				}
 				catch ( final InterruptedException e )
 				{}
-				return ArrayImgs.unsignedShorts( data, dimsLong );
+				img = ArrayImgs.unsignedShorts( data, dimsLong );
 			}
 			else
 			{
-				// TODO
-				final int[] cellDimensions = new int[ n ];
-				long s = Integer.MAX_VALUE;
-				for ( int d = 0; d < n; ++d )
-				{
-					final long ns = s / dimsLong[ d ];
-					if ( ns > 0 )
-						cellDimensions[ d ] = ( int ) dimsLong[ d ];
-					else
-					{
-						cellDimensions[ d ] = ( int ) ( s % dimsLong[ d ] );
-						for ( ++d; d < n; ++d )
-							cellDimensions[ d ] = 1;
-					}
-					s = ns;
-				}
+				final int[] cellDimensions = computeCellDimensions(
+						dimsLong,
+						perSetupMipmapInfo.get( setup ).getSubdivisions()[ level ] );
 				final CellImgFactory< UnsignedShortType > factory = new CellImgFactory< UnsignedShortType >( cellDimensions );
 				@SuppressWarnings( "unchecked" )
-				final CellImg< UnsignedShortType, ShortArray, DefaultCell< ShortArray > > img =
+				final CellImg< UnsignedShortType, ShortArray, DefaultCell< ShortArray > > cellImg =
 					( CellImg< UnsignedShortType, ShortArray, DefaultCell< ShortArray > > ) factory.create( dimsLong, new UnsignedShortType() );
-				final Cursor< DefaultCell< ShortArray > > cursor = img.getCells().cursor();
+				final Cursor< DefaultCell< ShortArray > > cursor = cellImg.getCells().cursor();
 				while ( cursor.hasNext() )
 				{
 					final DefaultCell< ShortArray > cell = cursor.next();
@@ -438,75 +426,99 @@ public class Hdf5ImageLoader extends AbstractViewerImgLoader< UnsignedShortType,
 					catch ( final InterruptedException e )
 					{}
 				}
-				return img;
+				img = cellImg;
 			}
+			return img;
 		}
 
 		@Override
 		public RandomAccessibleInterval< FloatType > getFloatImage( final ViewId view, final boolean normalize )
 		{
-			final RandomAccessibleInterval< UnsignedShortType > ushortImg = getImage( view );
-
-			// copy unsigned short img to float img
-			final FloatType f = new FloatType();
-			final Img< FloatType > floatImg = net.imglib2.util.Util.getArrayOrCellImgFactory( ushortImg, f ).create( ushortImg, f );
-
-			// set up executor service
-			final int numProcessors = Runtime.getRuntime().availableProcessors();
-			final ExecutorService taskExecutor = Executors.newFixedThreadPool( numProcessors );
-			final ArrayList< Callable< Void > > tasks = new ArrayList< Callable< Void > >();
-
-			// set up all tasks
-			final int numPortions = numProcessors * 2;
-			final long threadChunkSize = floatImg.size() / numPortions;
-			final long threadChunkMod = floatImg.size() % numPortions;
-
-			for ( int portionID = 0; portionID < numPortions; ++portionID )
+			Img< FloatType > img = null;
+			final int timepoint = view.getTimePointId();
+			final int setup = view.getViewSetupId();
+			final int level = 0;
+			final Dimensions dims = getImageSize( view );
+			final int n = dims.numDimensions();
+			final long[] dimsLong = new long[ n ];
+			dims.dimensions( dimsLong );
+			final int[] dimsInt = new int[ n ];
+			final long[] min = new long[ n ];
+			if ( Intervals.numElements( dims ) <= Integer.MAX_VALUE )
 			{
-				// move to the starting position of the current thread
-				final long startPosition = portionID * threadChunkSize;
-
-				// the last thread may has to run longer if the number of pixels cannot be divided by the number of threads
-				final long loopSize = ( portionID == numPortions - 1 ) ? threadChunkSize + threadChunkMod : threadChunkSize;
-
-				tasks.add( new Callable< Void >()
+				// use ArrayImg
+				for ( int d = 0; d < dimsInt.length; ++d )
+					dimsInt[ d ] = ( int ) dimsLong[ d ];
+				float[] data = null;
+				try
 				{
-					@Override
-					public Void call() throws Exception
+					data = hdf5Access.readShortMDArrayBlockWithOffsetAsFloat( timepoint, setup, level, dimsInt, min );
+				}
+				catch ( final InterruptedException e )
+				{}
+				img = ArrayImgs.floats( data, dimsLong );
+			}
+			else
+			{
+				final int[] cellDimensions = computeCellDimensions(
+						dimsLong,
+						perSetupMipmapInfo.get( setup ).getSubdivisions()[ level ] );
+				final CellImgFactory< FloatType > factory = new CellImgFactory< FloatType >( cellDimensions );
+				@SuppressWarnings( "unchecked" )
+				final CellImg< FloatType, FloatArray, DefaultCell< FloatArray > > cellImg =
+					( CellImg< FloatType, FloatArray, DefaultCell< FloatArray > > ) factory.create( dimsLong, new FloatType() );
+				final Cursor< DefaultCell< FloatArray > > cursor = cellImg.getCells().cursor();
+				while ( cursor.hasNext() )
+				{
+					final DefaultCell< FloatArray > cell = cursor.next();
+					final float[] dataBlock = cell.getData().getCurrentStorageArray();
+					cell.dimensions( dimsInt );
+					cell.min( min );
+					try
 					{
-						final Cursor< UnsignedShortType > in = Views.iterable( ushortImg ).localizingCursor();
-						final RandomAccess< FloatType > out = floatImg.randomAccess();
+						hdf5Access.readShortMDArrayBlockWithOffsetAsFloat( timepoint, setup, level, dimsInt, min, dataBlock );
+					}
+					catch ( final InterruptedException e )
+					{}
+				}
+				img = cellImg;
+			}
 
-						in.jumpFwd( startPosition );
+			if ( normalize )
+				// normalize the image to 0...1
+				normalize( img );
 
-						for ( long j = 0; j < loopSize; ++j )
-						{
-							final UnsignedShortType vin = in.next();
-							out.setPosition( in );
-							out.get().set( vin.getRealFloat() );
-						}
+			return img;
+		}
 
-						return null;
-					}
-				});
-			}
+		private int[] computeCellDimensions( final long[] dimsLong, final int[] chunkSize )
+		{
+			final int n = dimsLong.length;
 
-			try
+			final long[] dimsInChunks = new long[ n ];
+			int elementsPerChunk = 1;
+			for ( int d = 0; d < n; ++d )
 			{
-				// invokeAll() returns when all tasks are complete
-				taskExecutor.invokeAll( tasks );
-				taskExecutor.shutdown();
+				dimsInChunks[ d ] = ( dimsLong[ d ] + chunkSize[ d ] - 1 ) / chunkSize[ d ];
+				elementsPerChunk *= chunkSize[ d ];
 			}
-			catch ( final InterruptedException e )
+
+			final int[] cellDimensions = new int[ n ];
+			long s = Integer.MAX_VALUE / elementsPerChunk;
+			for ( int d = 0; d < n; ++d )
 			{
-				return null;
+				final long ns = s / dimsInChunks[ d ];
+				if ( ns > 0 )
+					cellDimensions[ d ] = chunkSize[ d ] * ( int ) ( dimsInChunks[ d ] );
+				else
+				{
+					cellDimensions[ d ] = chunkSize[ d ] * ( int ) ( s % dimsInChunks[ d ] );
+					for ( ++d; d < n; ++d )
+						cellDimensions[ d ] = chunkSize[ d ];
+				}
+				s = ns;
 			}
-
-			if ( normalize )
-				// normalize the image to 0...1
-				Normalize.normalize( floatImg, new FloatType( 0 ), new FloatType( 1 ) );
-
-			return floatImg;
+			return cellDimensions;
 		}
 
 		@Override
@@ -593,11 +605,32 @@ public class Hdf5ImageLoader extends AbstractViewerImgLoader< UnsignedShortType,
 
 		if ( normalize )
 			// normalize the image to 0...1
-			Normalize.normalize( floatImg, new FloatType( 0 ), new FloatType( 1 ) );
+			normalize( floatImg );
 
 		return floatImg;
 	}
 
+	/**
+	 * normalize img to 0...1
+	 */
+	protected static void normalize( final IterableInterval< FloatType > img )
+	{
+		float currentMax = img.firstElement().get();
+		float currentMin = currentMax;
+		for ( final FloatType t : img )
+		{
+			final float f = t.get();
+			if ( f > currentMax )
+				currentMax = f;
+			else if ( f < currentMin )
+				currentMin = f;
+		}
+
+		final float scale = ( float ) ( 1.0 / ( currentMax - currentMin ) );
+		for ( final FloatType t : img )
+			t.set( ( t.get() - currentMin ) * scale );
+	}
+
 	@Override
 	public Dimensions getImageSize( final ViewId view )
 	{
diff --git a/core/src/main/java/bdv/img/hdf5/IHDF5Access.java b/core/src/main/java/bdv/img/hdf5/IHDF5Access.java
index a08d814c9c61351d819e173e8e9c5a3bc62b9a63..aa10ae2e83126a9b6b7e4d2290e4f5dc51ff60a8 100644
--- a/core/src/main/java/bdv/img/hdf5/IHDF5Access.java
+++ b/core/src/main/java/bdv/img/hdf5/IHDF5Access.java
@@ -7,4 +7,8 @@ public interface IHDF5Access
 	public short[] readShortMDArrayBlockWithOffset( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException;
 
 	public short[] readShortMDArrayBlockWithOffset( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min, final short[] dataBlock ) throws InterruptedException;
+
+	public float[] readShortMDArrayBlockWithOffsetAsFloat( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min ) throws InterruptedException;
+
+	public float[] readShortMDArrayBlockWithOffsetAsFloat( final int timepoint, final int setup, final int level, final int[] dimensions, final long[] min, final float[] dataBlock ) throws InterruptedException;
 }