diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java
index 9cb341b526d8501b6393265c84510eea21ba120b..7e90bc48f80298bc4e9500f79810d9895ff77bf2 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java
@@ -24,11 +24,15 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import javax.imageio.ImageIO;
@@ -40,11 +44,14 @@ import ar.com.hjg.pngj.ImageInfo;
 import ar.com.hjg.pngj.ImageLine;
 import ar.com.hjg.pngj.PngFilterType;
 import ar.com.hjg.pngj.PngWriter;
+
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked;
 import ch.systemsx.cisd.base.io.IRandomAccessFile;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.image.ImageHistogram;
 import ch.systemsx.cisd.common.image.IntensityRescaling;
+import ch.systemsx.cisd.common.image.IntensityRescaling.Channel;
 import ch.systemsx.cisd.common.image.IntensityRescaling.GrayscalePixels;
 import ch.systemsx.cisd.common.image.IntensityRescaling.Levels;
 import ch.systemsx.cisd.common.logging.LogCategory;
@@ -931,6 +938,49 @@ public class ImageUtil
     {
         return image.getColorModel().getColorSpace().getNumComponents() == 1;
     }
+    
+    /**
+     * Checks whether the specified image is actually a (colored) gray image. 
+     * 
+     * @return the representative color channel which can be used to extract the gray image. 
+     *          <code>null</code> if the image isn't a gray image.
+     */
+    public static Channel getRepresentativeChannelIfEffectiveGray(BufferedImage image)
+    {
+        ImageHistogram imageHistogram = ImageHistogram.calculateHistogram(image);
+        Map<Channel, int[]> histogramsByChannels = new LinkedHashMap<Channel, int[]>();
+        int numberOfPixels = image.getWidth() * image.getHeight();
+        checkIfChannelIsUsed(histogramsByChannels, numberOfPixels, Channel.RED, imageHistogram.getRedHistogram());
+        checkIfChannelIsUsed(histogramsByChannels, numberOfPixels, Channel.GREEN, imageHistogram.getGreenHistogram());
+        checkIfChannelIsUsed(histogramsByChannels, numberOfPixels, Channel.BLUE, imageHistogram.getBlueHistogram());
+        List<Entry<Channel, int[]>> usedChannels = new ArrayList<Map.Entry<Channel,int[]>>(histogramsByChannels.entrySet());
+        if (usedChannels.isEmpty())
+        {
+            return Channel.RED; // Black image is a gray image, doesn't matter which channel to return.
+        }
+        Entry<Channel, int[]> representativeChannel = usedChannels.get(0);
+        int[] representativeHistogram = representativeChannel.getValue();
+        for (int i = 1; i < usedChannels.size(); i++)
+        {
+            int[] histogram = usedChannels.get(i).getValue();
+            for (int j = 0; j < histogram.length; j++)
+            {
+                if (histogram[j] != representativeHistogram[j])
+                {
+                    return null;
+                }
+            }
+        }
+        return representativeChannel.getKey();
+    }
+
+    private static void checkIfChannelIsUsed(Map<Channel, int[]> usedChannels, int numberOfPixels, Channel channel, int[] histogram)
+    {
+        if (histogram[0] < numberOfPixels)
+        {
+            usedChannels.put(channel, histogram);
+        }
+    }
 
     /**
      * Re-scales the image to be the biggest one which fits into a (0,0,maxWidth, maxHeight)
@@ -1011,9 +1061,6 @@ public class ImageUtil
             imageType =
                     imageType == BufferedImage.TYPE_USHORT_GRAY ? BufferedImage.TYPE_BYTE_GRAY
                             : BufferedImage.TYPE_INT_RGB;
-        } else if (imageType == BufferedImage.TYPE_CUSTOM)
-        {
-            imageType = isTransparent ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB;
         } else if (imageType == BufferedImage.TYPE_BYTE_INDEXED)
         {
             imageType = BufferedImage.TYPE_INT_RGB;
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java
index d0952b083f7faac854f6555a114be10908d968c0..16f8790de14065579de07ca0a400bf3417f67da2 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java
@@ -16,6 +16,9 @@
 
 package ch.systemsx.cisd.openbis.dss.generic.shared.utils;
 
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.InputStream;
@@ -30,6 +33,7 @@ import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked;
 import ch.systemsx.cisd.base.io.ByteBufferRandomAccessFile;
 import ch.systemsx.cisd.base.io.IRandomAccessFile;
 import ch.systemsx.cisd.base.io.RandomAccessFileImpl;
+import ch.systemsx.cisd.common.image.IntensityRescaling.Channel;
 import ch.systemsx.cisd.imagereaders.ImageReaderConstants;
 import ch.systemsx.cisd.imagereaders.ImageReadersTestHelper;
 import ch.systemsx.cisd.openbis.common.io.FileBasedContentNode;
@@ -304,6 +308,74 @@ public class ImageUtilTest extends AssertJUnit
         assertEquals(0, buffer.getFilePointer());
     }
     
+    @Test
+    public void testGetRepresentaticChannelIfBlack()
+    {
+        BufferedImage image = new BufferedImage(20, 10, BufferedImage.TYPE_INT_RGB);
+        
+        Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+        
+        assertEquals(Channel.RED, channel);
+    }
+    
+    @Test
+    public void testGetRepresentaticChannelIfReallyGray()
+    {
+        BufferedImage image = new BufferedImage(20, 10, BufferedImage.TYPE_INT_RGB);
+        Graphics2D graphics = (Graphics2D) image.getGraphics();
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        graphics.setColor(new Color(100, 100, 100));
+        graphics.drawOval(0, 0, 5, 7);
+        
+        Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+        
+        assertEquals(Channel.RED, channel);
+    }
+    
+    @Test
+    public void testGetRepresentaticChannelIfColoredGrayGreen()
+    {
+        BufferedImage image = new BufferedImage(20, 10, BufferedImage.TYPE_INT_RGB);
+        Graphics2D graphics = (Graphics2D) image.getGraphics();
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        graphics.setColor(new Color(0, 255, 0));
+        graphics.drawOval(0, 0, 5, 7);
+        
+        Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+        
+        assertEquals(Channel.GREEN, channel);
+    }
+    
+    @Test
+    public void testGetRepresentaticChannelIfColoredGrayGreenAndBlue()
+    {
+        BufferedImage image = new BufferedImage(20, 10, BufferedImage.TYPE_INT_RGB);
+        Graphics2D graphics = (Graphics2D) image.getGraphics();
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        graphics.setColor(new Color(0, 255, 255));
+        graphics.drawOval(0, 0, 5, 7);
+        
+        Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+        
+        assertEquals(Channel.GREEN, channel);
+    }
+    
+    @Test
+    public void testGetRepresentaticChannelIfColored()
+    {
+        BufferedImage image = new BufferedImage(20, 10, BufferedImage.TYPE_INT_RGB);
+        Graphics2D graphics = (Graphics2D) image.getGraphics();
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        graphics.setColor(Color.GREEN);
+        graphics.drawOval(0, 0, 5, 7);
+        graphics.setColor(Color.BLUE);
+        graphics.fillOval(3, 3, 3, 4);
+        
+        Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+        
+        assertEquals(null, channel);
+    }
+    
     private void assertFileType(String expectedFileType, String fileName) throws Exception
     {
         RandomAccessFileImpl handle = null;
diff --git a/screening/resource/test-data/MicroscopyImageDropboxTest/Merged_256x256_C0_0_20_C4_2_15.png b/screening/resource/test-data/MicroscopyImageDropboxTest/Merged_256x256_C0_0_20_C4_2_15.png
new file mode 100644
index 0000000000000000000000000000000000000000..81c735e05c9337d3566b0cacd4410009a4df5fc2
Binary files /dev/null and b/screening/resource/test-data/MicroscopyImageDropboxTest/Merged_256x256_C0_0_20_C4_2_15.png differ
diff --git a/screening/resource/test-data/SimpleImageDropboxTest/1_1_DAPI_Default.png b/screening/resource/test-data/SimpleImageDropboxTest/1_1_DAPI_Default.png
index 38e8bbd20a411bcd2436fdc3c41b075f920bebb8..5eaeb36b545a0f6aa138620126b2434779ad6e42 100644
Binary files a/screening/resource/test-data/SimpleImageDropboxTest/1_1_DAPI_Default.png and b/screening/resource/test-data/SimpleImageDropboxTest/1_1_DAPI_Default.png differ
diff --git a/screening/resource/test-data/SimpleImageDropboxTest/1_1_Merged_Default.png b/screening/resource/test-data/SimpleImageDropboxTest/1_1_Merged_Default.png
index 45479ae5be23afa3bf347c4bfc22efccbf52b3c5..5ef6b073c71901c960688be344ece94bf707ce4c 100644
Binary files a/screening/resource/test-data/SimpleImageDropboxTest/1_1_Merged_Default.png and b/screening/resource/test-data/SimpleImageDropboxTest/1_1_Merged_Default.png differ
diff --git a/screening/resource/test-data/SimpleImageDropboxTest/1_1_Merged_Default_GFP_0_100.png b/screening/resource/test-data/SimpleImageDropboxTest/1_1_Merged_Default_GFP_0_100.png
new file mode 100644
index 0000000000000000000000000000000000000000..68655e43c38560c42ecd17c8beaec838498e1414
Binary files /dev/null and b/screening/resource/test-data/SimpleImageDropboxTest/1_1_Merged_Default_GFP_0_100.png differ
diff --git a/screening/resource/test-data/SimpleImageDropboxTest/1_2_Merged_1392x1040.png b/screening/resource/test-data/SimpleImageDropboxTest/1_2_Merged_1392x1040.png
new file mode 100644
index 0000000000000000000000000000000000000000..ae35bbb850ad3cc6598e9c5a5d95b2032329a8c8
Binary files /dev/null and b/screening/resource/test-data/SimpleImageDropboxTest/1_2_Merged_1392x1040.png differ
diff --git a/screening/resource/test-data/SimpleImageDropboxTest/1_3_DAPI_CY3_256x191.png b/screening/resource/test-data/SimpleImageDropboxTest/1_3_DAPI_CY3_256x191.png
index 706b65693c749749475aec9ed869c846d3eb2b60..1dbe050dae9b9b58aa0d245179754120294f07fc 100644
Binary files a/screening/resource/test-data/SimpleImageDropboxTest/1_3_DAPI_CY3_256x191.png and b/screening/resource/test-data/SimpleImageDropboxTest/1_3_DAPI_CY3_256x191.png differ
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetLoaderFactory.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetLoaderFactory.java
index a8eda14b07185f0087b4fbe6fcea5250e9523b0d..0b4f1d7c7647afca8ec415e4a2613504f9ca3dba 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetLoaderFactory.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetLoaderFactory.java
@@ -35,4 +35,15 @@ public class HCSImageDatasetLoaderFactory
     {
         return ImagingDatasetLoader.tryCreate(DssScreeningUtils.getQuery(), datasetCode, content);
     }
+
+    public static IImagingDatasetLoader create(IHierarchicalContent content, String datasetCode)
+    {
+        IImagingDatasetLoader loader = tryCreate(content, datasetCode);
+        if (loader == null)
+        {
+            throw new IllegalStateException(String.format(
+                    "Dataset '%s' not found in the imaging database.", datasetCode));
+        }
+        return loader;
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Utils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Utils.java
index ae7cf5806e370c7d8e7316e5ddb7a27939b2f361..af991b3333fcd68f6f3f534d3939759f40bc700f 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Utils.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Utils.java
@@ -49,8 +49,7 @@ public class Utils
         }
         BufferedImage image = ImageUtil.loadUnchangedImage(contentNode, imageIdOrNull, imageLibraryNameOrNull,
                 imageLibraryReaderNameOrNull, null);
-
-        return ImageUtil.convertToRGB(image);
+        return image;
     }
 
     public static Size loadUnchangedImageSize(IHierarchicalContentNode contentNode,
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/transformations/AutoRescaleIntensityImageTransformerFactory.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/transformations/AutoRescaleIntensityImageTransformerFactory.java
index 125699044527f5ad28b232f50bb9c26044dc0bda..94df6986d01827c64a4902e48eb7b0a5699f8e26 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/transformations/AutoRescaleIntensityImageTransformerFactory.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/transformations/AutoRescaleIntensityImageTransformerFactory.java
@@ -18,14 +18,18 @@ package ch.systemsx.cisd.openbis.dss.etl.dto.api.transformations;
 
 import java.awt.image.BufferedImage;
 import java.awt.image.WritableRaster;
+import java.util.Arrays;
 import java.util.EnumSet;
 
 import ch.systemsx.cisd.base.annotation.JsonObject;
 import ch.systemsx.cisd.base.image.IImageTransformer;
 import ch.systemsx.cisd.base.image.IImageTransformerFactory;
+import ch.systemsx.cisd.common.image.ImageHistogram;
 import ch.systemsx.cisd.common.image.IntensityRescaling;
 import ch.systemsx.cisd.common.image.IntensityRescaling.Channel;
 import ch.systemsx.cisd.common.image.IntensityRescaling.Levels;
+import ch.systemsx.cisd.common.test.ImageDebugViewer;
+import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil;
 
 /**
  * Transformation performed by
@@ -60,22 +64,17 @@ public class AutoRescaleIntensityImageTransformerFactory implements IImageTransf
                 {
                     if (IntensityRescaling.isNotGrayscale(image))
                     {
-                        EnumSet<Channel> channels = IntensityRescaling.getUsedRgbChannels(image);
-                        if (channels.size() != 1)
+                        Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+                        if (channel == null)
                         {
                             return image;
-                        } else
-                        {
-                            Channel channel = channels.iterator().next();
-                            Levels levels =
-                                    IntensityRescaling.computeLevels(toGrayScale(image, channel),
-                                            threshold);
-                            return IntensityRescaling.rescaleIntensityLevelTo8Bits(image, levels,
-                                    channel);
                         }
+                        BufferedImage grayScaleImage = toGrayScale(image, channel);
+                        Levels levels = IntensityRescaling.computeLevels(grayScaleImage, threshold);
+                        return IntensityRescaling.rescaleIntensityLevelTo8Bits(image, levels);
                     }
                     Levels levels = IntensityRescaling.computeLevels(image, threshold);
-                    return IntensityRescaling.rescaleIntensityLevelTo8Bits(image, levels);
+                    return IntensityRescaling.rescaleIntensityLevelTo8Bits(new IntensityRescaling.GrayscalePixels(image), levels);
                 }
             };
     }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java
index adc2c89882cf2a47d45851cebe5899601281f1d2..c81abae54547ebb0ddbfaa214be0d0b2b7c134b1 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java
@@ -19,10 +19,8 @@ package ch.systemsx.cisd.openbis.dss.generic.server.images;
 import java.awt.AlphaComposite;
 import java.awt.Color;
 import java.awt.Graphics2D;
-import java.awt.color.ColorSpace;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
-import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -31,10 +29,11 @@ import java.util.Map;
 import org.apache.log4j.Logger;
 
 import ch.rinn.restrictions.Private;
+import ch.systemsx.cisd.base.image.IImageTransformer;
 import ch.systemsx.cisd.base.image.IImageTransformerFactory;
-import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
-import ch.systemsx.cisd.common.image.IntensityRescaling;
+import ch.systemsx.cisd.common.image.ImageHistogram;
+import ch.systemsx.cisd.common.image.IntensityRescaling.Channel;
 import ch.systemsx.cisd.common.image.MixColors;
 import ch.systemsx.cisd.common.image.MixColors.MixedImageWithWhitePoint;
 import ch.systemsx.cisd.common.logging.LogCategory;
@@ -80,34 +79,43 @@ public class ImageChannelsUtils
 
     // MIME type of the images which are produced by this class
     public static final String IMAGES_CONTENT_TYPE = "image/png";
+    
+    private static interface IColorTransformation
+    {
+        int transform(int rgb);
+    }
 
-    public static interface IDatasetDirectoryProvider
+    private static interface IImageCalculator
     {
-        /** directory where dataset can be found in DSS store */
-        File getDatasetRoot(String datasetCode);
+        public BufferedImage create(AbsoluteImageReference imageContent);
     }
 
-    private final IImagingLoaderStrategy imageLoaderStrategy;
+    private static class ImageWithReference
+    {
+        private BufferedImage image;
 
-    private final RequestedImageSize imageSizeLimit;
+        private final AbsoluteImageReference reference;
 
-    private final String singleChannelTransformationCodeOrNull;
+        public ImageWithReference(BufferedImage image, AbsoluteImageReference reference)
+        {
+            this.image = image;
+            this.reference = reference;
+        }
 
-    @Private
-    ImageChannelsUtils(IImagingLoaderStrategy imageLoaderStrategy,
-            RequestedImageSize imageSizeLimit, String singleChannelTransformationCodeOrNull)
-    {
-        this.imageLoaderStrategy = imageLoaderStrategy;
-        this.imageSizeLimit = imageSizeLimit;
-        this.singleChannelTransformationCodeOrNull = singleChannelTransformationCodeOrNull;
-    }
+        public BufferedImage getBufferedImage()
+        {
+            return image;
+        }
+        
+        public void setImage(BufferedImage image)
+        {
+            this.image = image;
+        }
 
-    @Private
-    ImageChannelsUtils(IImagingLoaderStrategy imageLoaderStrategy, Size imageSizeLimitOrNull,
-            String singleChannelTransformationCodeOrNull)
-    {
-        this(imageLoaderStrategy, new RequestedImageSize(imageSizeLimitOrNull, false),
-                singleChannelTransformationCodeOrNull);
+        public AbsoluteImageReference getReference()
+        {
+            return reference;
+        }
     }
 
     /**
@@ -147,7 +155,7 @@ public class ImageChannelsUtils
 
     /**
      * Returns content of image for the specified tile in the specified size and for the requested
-     * channel or with all channels merged.
+     * channel or with all channels merged. This method is called by the servlets which delivers images.
      * 
      * @param params
      * @param contentProvider
@@ -155,8 +163,6 @@ public class ImageChannelsUtils
     public static ResponseContentStream getImageStream(ImageGenerationDescription params,
             IHierarchicalContentProvider contentProvider)
     {
-        Size thumbnailSizeOrNull = params.tryGetThumbnailSize();
-
         ImageRepresentationFormat existingRepresentationFormat =
                 RepresentationUtil.tryGetRepresentationFormat(params);
 
@@ -167,45 +173,63 @@ public class ImageChannelsUtils
                     tryGetRawContentOfExistingThumbnail(params, existingRepresentationFormat);
             if (content != null)
             {
-                return asResponseContentStream(content);
+//                return asResponseContentStream(content);
             }
         }
 
-        BufferedImage image = null;
+        BufferedImage image = calculateImage(params, contentProvider);
+        image = drawOverlays(image, params, contentProvider);
+        if (image == null)
+        {
+            throw new UserFailureException("No image is available for parameters: " + params);
+        }
+        return createResponseContentStream(image, null);
+    }
+    
+    private static BufferedImage calculateImage(ImageGenerationDescription params, 
+            IHierarchicalContentProvider contentProvider)
+    {
         DatasetAcquiredImagesReference imageChannels = params.tryGetImageChannels();
-        if (imageChannels != null)
+        if (imageChannels == null)
         {
-            RequestedImageSize imageSize = new RequestedImageSize(thumbnailSizeOrNull, false);
-            image =
-                    calculateBufferedImage(imageChannels,
-                            params.tryGetSingleChannelTransformationCode(),
-                            params.tryGetTransformationsPerChannel(), contentProvider, imageSize);
+            return null;
         }
+        RequestedImageSize imageSize = new RequestedImageSize(params.tryGetThumbnailSize(), false);
+        String transformationCode = params.tryGetSingleChannelTransformationCode();
+        Map<String, String> transformationsPerChannel = params.tryGetTransformationsPerChannel();
+        ImageLoadingHelper imageLoadingHelper = new ImageLoadingHelper(imageChannels, contentProvider, imageSize, transformationCode);
+        boolean mergeAllChannels = imageLoadingHelper.isMergeAllChannels(imageChannels);
+        ImageTransformationParams transformationInfo = new ImageTransformationParams(true, mergeAllChannels,
+                        transformationCode, transformationsPerChannel);
+        List<AbsoluteImageReference> imageContents =
+                imageLoadingHelper.fetchImageContents(imageChannels, mergeAllChannels, false, transformationInfo);
+        return calculateBufferedImage(imageContents, transformationInfo);
+    }
 
-        RequestedImageSize overlaySize = calcOverlaySize(image, thumbnailSizeOrNull);
+    private static BufferedImage drawOverlays(BufferedImage imageOrNull, ImageGenerationDescription params, 
+            IHierarchicalContentProvider contentProvider)
+    {
+        RequestedImageSize overlaySize = calcOverlaySize(imageOrNull, params.tryGetThumbnailSize());
+        BufferedImage imageWithOverlays = imageOrNull;
         for (DatasetAcquiredImagesReference overlayChannels : params.getOverlayChannels())
         {
             // NOTE: never merges the overlays, draws each channel separately (merging looses
             // transparency and is slower)
-            List<ImageWithReference> overlayImages =
-                    getSingleImagesSkipNonExisting(overlayChannels, overlaySize,
-                            params.tryGetSingleChannelTransformationCode(), contentProvider);
+            String transformationCode = params.tryGetSingleChannelTransformationCode();
+            List<ImageWithReference> overlayImages = getSingleImagesSkipNonExisting(overlayChannels, 
+                    overlaySize, transformationCode, contentProvider);
             for (ImageWithReference overlayImage : overlayImages)
             {
-                if (image != null)
+                if (imageWithOverlays != null)
                 {
-                    drawOverlay(image, overlayImage);
+                    drawOverlay(imageWithOverlays, overlayImage);
                 } else
                 {
-                    image = overlayImage.getBufferedImage();
+                    imageWithOverlays = overlayImage.getBufferedImage();
                 }
             }
         }
-        if (image == null)
-        {
-            throw new UserFailureException("No image is available for parameters: " + params);
-        }
-        return createResponseContentStream(image, null);
+        return imageWithOverlays;
     }
 
     private static List<ImageWithReference> getSingleImagesSkipNonExisting(
@@ -213,17 +237,22 @@ public class ImageChannelsUtils
             String singleChannelTransformationCodeOrNull,
             IHierarchicalContentProvider contentProvider)
     {
-        ImageChannelsUtils utils =
-                createImageChannelsUtils(imagesReference, contentProvider, imageSize,
-                        singleChannelTransformationCodeOrNull);
-        boolean mergeAllChannels = utils.isMergeAllChannels(imagesReference);
-        ImageTransformationParams transformationInfo =
-                new ImageTransformationParams(true, mergeAllChannels, null,
-                        new HashMap<String, String>());
+        ImageLoadingHelper imageLoadingHelper = new ImageLoadingHelper(imagesReference,
+                contentProvider, imageSize, singleChannelTransformationCodeOrNull);
+        boolean mergeAllChannels = imageLoadingHelper.isMergeAllChannels(imagesReference);
+        final ImageTransformationParams transformationInfo =
+                new ImageTransformationParams(true, mergeAllChannels, null, new HashMap<String, String>());
         List<AbsoluteImageReference> imageContents =
-                utils.fetchImageContents(imagesReference, mergeAllChannels, true,
+                imageLoadingHelper.fetchImageContents(imagesReference, mergeAllChannels, true,
                         transformationInfo);
-        return calculateSingleImagesForDisplay(imageContents, transformationInfo, 0.0f, null);
+        return calculateSingleImages(imageContents, new IImageCalculator()
+            {
+                @Override
+                public BufferedImage create(AbsoluteImageReference imageContent)
+                {
+                    return calculateAndTransformSingleImageForDisplay(imageContent, transformationInfo, 0f);
+                }
+            });
     }
 
     private static RequestedImageSize getSize(BufferedImage img, boolean highQuality)
@@ -261,116 +290,6 @@ public class ImageChannelsUtils
         return asResponseContentStream(imageContent);
     }
 
-    private static BufferedImage calculateBufferedImage(
-            DatasetAcquiredImagesReference imageChannels,
-            String singleChannelTransformationCodeOrNull,
-            Map<String, String> transformationsPerChannels,
-            IHierarchicalContentProvider contentProvider, RequestedImageSize imageSizeLimit)
-    {
-        ImageChannelsUtils imageChannelsUtils =
-                createImageChannelsUtils(imageChannels, contentProvider, imageSizeLimit,
-                        singleChannelTransformationCodeOrNull);
-        boolean useMergedChannelsTransformation =
-                imageChannelsUtils.isMergeAllChannels(imageChannels);
-        ImageTransformationParams transformationInfo =
-                new ImageTransformationParams(true, useMergedChannelsTransformation,
-                        singleChannelTransformationCodeOrNull, transformationsPerChannels);
-
-        return imageChannelsUtils.calculateBufferedImage(imageChannels, transformationInfo);
-    }
-
-    private static ImageChannelsUtils createImageChannelsUtils(
-            DatasetAcquiredImagesReference imageChannels,
-            IHierarchicalContentProvider contentProvider, RequestedImageSize imageSizeLimit,
-            String singleChannelTransformationCodeOrNull)
-    {
-        IImagingDatasetLoader imageAccessor = createImageAccessor(imageChannels, contentProvider);
-        return new ImageChannelsUtils(
-                ImagingLoaderStrategyFactory.createImageLoaderStrategy(imageAccessor),
-                imageSizeLimit, singleChannelTransformationCodeOrNull);
-    }
-
-    @Private
-    BufferedImage calculateBufferedImage(DatasetAcquiredImagesReference imageChannels,
-            ImageTransformationParams transformationInfo)
-    {
-        boolean mergeAllChannels = isMergeAllChannels(imageChannels);
-        List<AbsoluteImageReference> imageContents =
-                fetchImageContents(imageChannels, mergeAllChannels, false, transformationInfo);
-        return calculateBufferedImage(imageContents, transformationInfo);
-    }
-
-    private boolean isMergeAllChannels(DatasetAcquiredImagesReference imageChannels)
-    {
-        return imageChannels.isMergeAllChannels(getAllChannelCodes());
-    }
-
-    /**
-     * @param skipNonExisting if true references to non-existing images are ignored, otherwise an
-     *            exception is thrown
-     * @param mergeAllChannels true if all existing channel images should be merged
-     * @param transformationInfo
-     */
-    private List<AbsoluteImageReference> fetchImageContents(
-            DatasetAcquiredImagesReference imagesReference, boolean mergeAllChannels,
-            boolean skipNonExisting, ImageTransformationParams transformationInfo)
-    {
-        List<String> channelCodes = imagesReference.getChannelCodes(getAllChannelCodes());
-        List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>();
-        for (String channelCode : channelCodes)
-        {
-            ImageChannelStackReference channelStackReference =
-                    imagesReference.getChannelStackReference();
-            AbsoluteImageReference image =
-                    imageLoaderStrategy.tryGetImage(channelCode, channelStackReference,
-                            imageSizeLimit, singleChannelTransformationCodeOrNull);
-            if (image == null && skipNonExisting == false)
-            {
-                throw createImageNotFoundException(channelStackReference, channelCode);
-            }
-            if (image != null)
-            {
-                images.add(image);
-            }
-        }
-
-        // Optimization for a case where all channels are on one image
-        if (mergeAllChannels
-                && (false == shouldApplySingleChannelsTransformations(transformationInfo)))
-        {
-            AbsoluteImageReference allChannelsImageReference =
-                    tryCreateAllChannelsImageReference(images);
-            if (allChannelsImageReference != null)
-            {
-                images.clear();
-                images.add(allChannelsImageReference);
-            }
-        }
-        return images;
-    }
-
-    private boolean shouldApplySingleChannelsTransformations(
-            ImageTransformationParams transformationInfo)
-    {
-        if (transformationInfo == null
-                || transformationInfo.tryGetTransformationCodeForChannels() == null
-                || transformationInfo.tryGetTransformationCodeForChannels().size() == 0)
-        {
-            return false;
-        }
-
-        return true;
-    }
-
-    private static IImagingDatasetLoader createImageAccessor(
-            DatasetAcquiredImagesReference imagesReference,
-            IHierarchicalContentProvider contentProvider)
-    {
-        String datasetCode = imagesReference.getDatasetCode();
-        IHierarchicalContent dataSetRoot = contentProvider.asContent(datasetCode);
-        return createDatasetLoader(dataSetRoot, datasetCode);
-    }
-
     /**
      * Returns content of the image which is representative for the given dataset.
      */
@@ -378,32 +297,18 @@ public class ImageChannelsUtils
             IHierarchicalContent dataSetRoot, String datasetCode, Location wellLocationOrNull,
             Size imageSizeLimitOrNull, String singleChannelTransformationCodeOrNull)
     {
-        IImagingDatasetLoader imageAccessor = createDatasetLoader(dataSetRoot, datasetCode);
-        List<AbsoluteImageReference> imageReferences =
-                new ImageChannelsUtils(
-                        ImagingLoaderStrategyFactory.createImageLoaderStrategy(imageAccessor),
-                        imageSizeLimitOrNull, singleChannelTransformationCodeOrNull)
-                        .getRepresentativeImageReferences(wellLocationOrNull);
-        BufferedImage image =
-                calculateBufferedImage(imageReferences, new ImageTransformationParams(true, true,
-                        null, new HashMap<String, String>()));
+        IImagingDatasetLoader imageAccessor = HCSImageDatasetLoaderFactory.create(dataSetRoot, datasetCode);
+        IImagingLoaderStrategy loaderStrategy = ImagingLoaderStrategyFactory.createImageLoaderStrategy(imageAccessor);
+        ImageLoadingHelper imageLoadingHelper = 
+                new ImageLoadingHelper(loaderStrategy, imageSizeLimitOrNull, singleChannelTransformationCodeOrNull);
+        List<AbsoluteImageReference> imageReferences = imageLoadingHelper.getRepresentativeImageReferences(wellLocationOrNull);
+        ImageTransformationParams transformationParams = new ImageTransformationParams(true, true,
+                        null, new HashMap<String, String>());
+        BufferedImage image = calculateBufferedImage(imageReferences, transformationParams);
         String name = createFileName(datasetCode, wellLocationOrNull, imageSizeLimitOrNull);
         return createResponseContentStream(image, name);
     }
 
-    private static IImagingDatasetLoader createDatasetLoader(IHierarchicalContent dataSetRoot,
-            String datasetCode)
-    {
-        IImagingDatasetLoader loader =
-                HCSImageDatasetLoaderFactory.tryCreate(dataSetRoot, datasetCode);
-        if (loader == null)
-        {
-            throw new IllegalStateException(String.format(
-                    "Dataset '%s' not found in the imaging database.", datasetCode));
-        }
-        return loader;
-    }
-
     private static String createFileName(String datasetCode, Location wellLocationOrNull,
             Size imageSizeLimitOrNull)
     {
@@ -442,16 +347,16 @@ public class ImageChannelsUtils
                         chosenChannelCode == null ? ScreeningConstants.MERGED_CHANNELS
                                 : chosenChannelCode);
 
-        ImageChannelsUtils imageChannelsUtils =
-                new ImageChannelsUtils(imageLoaderStrategy, imageSizeLimitOrNull,
+        ImageLoadingHelper imageLoadingHelper =
+                new ImageLoadingHelper(imageLoaderStrategy, imageSizeLimitOrNull,
                         singleChannelImageTransformationCodeOrNull);
-        boolean mergeAllChannels = imageChannelsUtils.isMergeAllChannels(imagesReference);
+        boolean mergeAllChannels = imageLoadingHelper.isMergeAllChannels(imagesReference);
         ImageTransformationParams transformationInfo =
                 new ImageTransformationParams(transform, mergeAllChannels,
                         singleChannelImageTransformationCodeOrNull, new HashMap<String, String>());
 
         List<AbsoluteImageReference> imageContents =
-                imageChannelsUtils.fetchImageContents(imagesReference, mergeAllChannels, false,
+                imageLoadingHelper.fetchImageContents(imagesReference, mergeAllChannels, false,
                         transformationInfo);
 
         IHierarchicalContentNode contentNode = tryGetRawContent(convertToPng, imageContents);
@@ -493,47 +398,6 @@ public class ImageChannelsUtils
         }
         return null;
     }
-
-    private List<AbsoluteImageReference> getRepresentativeImageReferences(
-            Location wellLocationOrNull)
-    {
-        List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>();
-
-        for (String chosenChannel : getAllChannelCodes())
-        {
-            AbsoluteImageReference image =
-                    getRepresentativeImageReference(chosenChannel, wellLocationOrNull);
-            images.add(image);
-        }
-        return images;
-    }
-
-    private List<String> getAllChannelCodes()
-    {
-        return imageLoaderStrategy.getImageParameters().getChannelsCodes();
-    }
-
-    /**
-     * @throw {@link EnvironmentFailureException} when image does not exist
-     */
-    private AbsoluteImageReference getRepresentativeImageReference(String channelCode,
-            Location wellLocationOrNull)
-    {
-        AbsoluteImageReference image =
-                imageLoaderStrategy.tryGetRepresentativeImage(channelCode, wellLocationOrNull,
-                        imageSizeLimit, singleChannelTransformationCodeOrNull);
-        if (image != null)
-        {
-            return image;
-        } else
-        {
-            throw EnvironmentFailureException.fromTemplate(
-                    "No representative "
-                            + (imageSizeLimit.isThumbnailRequired() ? "thumbnail" : "image")
-                            + " found for well %s and channel %s", wellLocationOrNull, channelCode);
-        }
-    }
-
     /**
      * @param threshold
      * @param useMergedChannelsTransformation sometimes we can have a single image which contain all
@@ -546,13 +410,20 @@ public class ImageChannelsUtils
         BufferedImage image = calculateSingleImage(imageReference);
         image = transform(image, imageReference, transformationInfo);
         image = ImageUtil.convertForDisplayIfNecessary(image, threshold);
+        Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+        if (channel != null)
+        {
+            final ChannelColorRGB channelColor = imageReference.getChannelColor();
+            image = transformColor(image, createColorTransformation(channel, channelColor));
+        }
         return image;
     }
-
+    
     private static BufferedImage calculateSingleImage(AbsoluteImageReference imageReference)
     {
         long start = operationLog.isDebugEnabled() ? System.currentTimeMillis() : 0;
         BufferedImage image = imageReference.getUnchangedImage();
+        
         if (operationLog.isDebugEnabled())
         {
             operationLog.debug("Load original image: " + (System.currentTimeMillis() - start));
@@ -565,14 +436,14 @@ public class ImageChannelsUtils
         if (size != null)
         {
             start = operationLog.isDebugEnabled() ? System.currentTimeMillis() : 0;
-            image =
-                    ImageUtil.rescale(image, size.getWidth(), size.getHeight(),
-                            requestedSize.enlargeIfNecessary(),
-                            requestedSize.isHighQualityRescalingRequired());
+            boolean enlarge = requestedSize.enlargeIfNecessary();
+            boolean highQuality8Bit = requestedSize.isHighQualityRescalingRequired();
+            image = ImageUtil.rescale(image, size.getWidth(), size.getHeight(), enlarge, highQuality8Bit);
             if (operationLog.isDebugEnabled())
             {
                 operationLog.debug("Create thumbnail: " + (System.currentTimeMillis() - start));
             }
+
         }
 
         // choose color component if necessary
@@ -590,25 +461,41 @@ public class ImageChannelsUtils
         return image;
     }
 
-    /**
-     * @param allChannelsMerged if true then we use one special transformation on the merged images
-     *            instead of transforming every single image.
-     */
-    private static BufferedImage calculateBufferedImage(
-            List<AbsoluteImageReference> imageReferences,
+    private static IColorTransformation createColorTransformation(final Channel channel, final ChannelColorRGB channelColor)
+    {
+        return new IColorTransformation()
+            {
+                
+                @Override
+                public int transform(int rgb)
+                {
+                    int gray = (rgb >> channel.getShift()) & 0xff;
+                    int red = scale(gray, channelColor.getR());
+                    int green = scale(gray, channelColor.getG());
+                    int blue = scale(gray, channelColor.getB());
+                    return (red << 16) + (green << 8) + blue;
+                }
+                
+                private int scale(int value, int maxValue)
+                {
+                    return maxValue == 0 ? 0 : Math.min(255, (value * 255) / maxValue);
+                }
+            };
+    }
+
+    @Private
+    static BufferedImage calculateBufferedImage(List<AbsoluteImageReference> imageReferences,
             ImageTransformationParams transformationInfo)
     {
         AbsoluteImageReference singleImageReference = imageReferences.get(0);
         if (imageReferences.size() == 1)
         {
-            return calculateAndTransformSingleImageForDisplay(singleImageReference,
-                    transformationInfo, null);
+            return calculateAndTransformSingleImageForDisplay(singleImageReference, transformationInfo, null);
         } else
         {
             IImageTransformerFactory mergedChannelTransformationOrNull =
-                    singleImageReference.getImageTransfomationFactories().tryGetForMerged();
-            return mergeChannels(imageReferences, transformationInfo,
-                    mergedChannelTransformationOrNull);
+                    singleImageReference.getImageTransformationFactories().tryGetForMerged();
+            return mergeChannels(imageReferences, transformationInfo, mergedChannelTransformationOrNull);
         }
     }
 
@@ -618,38 +505,85 @@ public class ImageChannelsUtils
     {
         // We do not transform single images here.
         List<ImageWithReference> images =
-                calculateSingleImagesForDisplay(imageReferences, null, null, transformationInfo);
+            calculateSingleImages(imageReferences, createCalculator(transformationInfo));
+        for (int i = 0; i < images.size(); i++)
+        {
+            ImageWithReference imageWithReference = images.get(i);
+            BufferedImage image = imageWithReference.getBufferedImage();
+            AbsoluteImageReference imageReference = imageWithReference.getReference();
+            Channel channel = ImageUtil.getRepresentativeChannelIfEffectiveGray(image);
+            if (channel != null)
+            {
+                final ChannelColorRGB channelColor = imageReference.getChannelColor();
+                image = transformColor(image, createColorTransformation(channel, channelColor));
+                imageWithReference.setImage(image);
+            }
+        }
         
-		MixedImageWithWhitePoint mergedImageWithWhitePoint = mergeImages(images);
-		BufferedImage mergedImage = mergedImageWithWhitePoint.getImage();
+        MixedImageWithWhitePoint mergedImageWithWhitePoint = mergeImages(images);
+        BufferedImage mergedImage = mergedImageWithWhitePoint.getImage();
         
-		// non-user transformation - apply color range fix after mixing
+        // non-user transformation - apply color range fix after mixing
         Map<String, String> transMap = transformationInfo.tryGetTransformationCodeForChannels();
-    	Color whitePointColor = mergedImageWithWhitePoint.getWhitePoint();
-        if ((transMap == null || transMap.size() == 0) && mergedChannelTransformationOrNull == null && whitePointColor != null) 
+        Color whitePointColor = mergedImageWithWhitePoint.getWhitePoint();
+        IImageTransformerFactory channelTransformation = mergedChannelTransformationOrNull;
+        if ((transMap == null || transMap.size() == 0) && channelTransformation == null && whitePointColor != null) 
         {
-        	mergedChannelTransformationOrNull = new IntensityRangeImageTransformerFactory(0, Math.max(Math.max(whitePointColor.getRed(), whitePointColor.getGreen()), whitePointColor.getBlue()));
+            int red = whitePointColor.getRed();
+            int green = whitePointColor.getGreen();
+            int blue = whitePointColor.getBlue();
+            int whitePoint = Math.max(Math.max(red, green), blue);
+            channelTransformation = new IntensityRangeImageTransformerFactory(0, whitePoint);
         }
         
         // NOTE: even if we are not merging all the channels but just few of them we use the
         // merged-channel transformation
         if (transformationInfo.isApplyNonImageLevelTransformation())
         {
-            mergedImage = applyImageTransformation(mergedImage, mergedChannelTransformationOrNull);
+            mergedImage = applyImageTransformation(mergedImage, channelTransformation);
         }
         return mergedImage;
     }
 
+    private static IImageCalculator createCalculator(final ImageTransformationParams transformationInfo)
+    {
+        return new IImageCalculator()
+            {
+                @Override
+                public BufferedImage create(AbsoluteImageReference imageContent)
+                {
+                    String channelCode = imageContent.tryGetChannelCode();
+                    if (transformationInfo != null
+                            && transformationInfo.tryGetTransformationCodeForChannel(channelCode) != null)
+                    {
+                        String transformationCode = 
+                                transformationInfo.tryGetTransformationCodeForChannel(channelCode);
+                        boolean applyNonImageLevelTransformation = 
+                                transformationInfo.isApplyNonImageLevelTransformation();
+                        ImageTransformationParams info = 
+                                new ImageTransformationParams(applyNonImageLevelTransformation, false, 
+                                        transformationCode, null);
+                        return calculateAndTransformSingleImageForDisplay(imageContent, info, null);
+                    } else
+                    {
+                        // NOTE: here we skip image level transformations as well
+                        BufferedImage image = calculateSingleImage(imageContent);
+                        BufferedImage image2 = ImageUtil.convertForDisplayIfNecessary(image, null);
+                        return image2;
+                    }
+                }
+            };
+    }
+
 	private static BufferedImage transform(BufferedImage image,
             AbsoluteImageReference imageReference, ImageTransformationParams transformationInfo)
     {
         BufferedImage resultImage = image;
-        ImageTransfomationFactories transfomations =
-                imageReference.getImageTransfomationFactories();
+        ImageTransfomationFactories transformations = imageReference.getImageTransformationFactories();
         // image level transformation is applied always, as it cannot be applied or changed in
         // external image viewer
 
-        resultImage = applyImageTransformation(resultImage, transfomations.tryGetForImage());
+        resultImage = applyImageTransformation(resultImage, transformations.tryGetForImage());
 
         if (transformationInfo.isApplyNonImageLevelTransformation() == false)
         {
@@ -658,28 +592,24 @@ public class ImageChannelsUtils
         IImageTransformerFactory channelLevelTransformationOrNull = null;
         if (transformationInfo.isUseMergedChannelsTransformation())
         {
-            channelLevelTransformationOrNull = transfomations.tryGetForMerged();
+            channelLevelTransformationOrNull = transformations.tryGetForMerged();
         } else
         {
-            String channelTransformationCode =
-                    transformationInfo.tryGetSingleChannelTransformationCode() == null ? transfomations
-                            .tryGetDefaultTransformationCode()
-                            : transformationInfo
-                                    .tryGetSingleChannelTransformationCode();
-
+            String transformationCode = transformationInfo.tryGetSingleChannelTransformationCode();
+            String channelTransformationCode = transformationCode;
+            if (transformationCode == null)
+            {
+                channelTransformationCode = transformations.tryGetDefaultTransformationCode();
+            }
             if (channelTransformationCode != null
                     && (false == channelTransformationCode.equals(imageReference
                             .tryGetSingleChannelTransformationCode())))
             {
-                channelLevelTransformationOrNull =
-                        transfomations.tryGetForChannel(transformationInfo
-                                .tryGetSingleChannelTransformationCode());
+                channelLevelTransformationOrNull = transformations.tryGetForChannel(transformationCode);
             }
-
             if (channelLevelTransformationOrNull == null)
             {
-                channelLevelTransformationOrNull =
-                        new AutoRescaleIntensityImageTransformerFactory(
+                channelLevelTransformationOrNull = new AutoRescaleIntensityImageTransformerFactory(
                                 ImageUtil.DEFAULT_IMAGE_OPTIMAL_RESCALING_FACTOR);
             }
         }
@@ -694,109 +624,23 @@ public class ImageChannelsUtils
         {
             return image;
         }
-        return transformerFactoryOrNull.createTransformer().transform(image);
+        IImageTransformer transformer = transformerFactoryOrNull.createTransformer();
+        BufferedImage transformImage = transformer.transform(image);
+        return transformImage;
     }
 
-    private static class ImageWithReference
-    {
-        private final BufferedImage image;
-
-        private final AbsoluteImageReference reference;
-
-        public ImageWithReference(BufferedImage image, AbsoluteImageReference reference)
-        {
-            this.image = image;
-            this.reference = reference;
-        }
-
-        public BufferedImage getBufferedImage()
-        {
-            return image;
-        }
-
-        public AbsoluteImageReference getReference()
-        {
-            return reference;
-        }
-    }
-
-    /**
-     * @param transformationInfoOrNull if null all transformations (including image-level) will be
-     *            skipped
-     * @param transformationInfo
-     */
-    private static List<ImageWithReference> calculateSingleImagesForDisplay(
-            List<AbsoluteImageReference> imageReferences,
-            ImageTransformationParams transformationInfoOrNull, Float threshold,
-            ImageTransformationParams transformationInfoForMergingOrNull)
+    private static List<ImageWithReference> calculateSingleImages(List<AbsoluteImageReference> imageContents, 
+            IImageCalculator imageCalculator)
     {
         List<ImageWithReference> images = new ArrayList<ImageWithReference>();
-        for (AbsoluteImageReference imageRef : imageReferences)
+        for (AbsoluteImageReference imageContent : imageContents)
         {
-            BufferedImage image;
-            if (transformationInfoOrNull != null)
-            {
-                image =
-                        calculateAndTransformSingleImageForDisplay(imageRef,
-                                transformationInfoOrNull, threshold);
-            } else if (transformationInfoForMergingOrNull != null
-                    && null != transformationInfoForMergingOrNull
-                            .tryGetTransformationCodeForChannel(imageRef.tryGetChannelCode()))
-            {
-                String transformationCode =
-                        transformationInfoForMergingOrNull
-                                .tryGetTransformationCodeForChannel(imageRef.tryGetChannelCode());
-                image =
-                        calculateAndTransformSingleImageForDisplay(
-                                imageRef,
-                                new ImageTransformationParams(transformationInfoForMergingOrNull
-                                        .isApplyNonImageLevelTransformation(), false,
-                                        transformationCode, null), threshold);
-            } else
-            {
-                // NOTE: here we skip image level transformations as well
-                image = calculateSingleImage(imageRef);
-                image = ImageUtil.convertForDisplayIfNecessary(image, threshold);
-            }
-            images.add(new ImageWithReference(image, imageRef));
+            BufferedImage image = imageCalculator.create(imageContent);
+            images.add(new ImageWithReference(image, imageContent));
         }
         return images;
     }
-
-    // Checks if all images differ only at the color component level and stem from the same page
-    // of the same file. If that's the case any image from the collection contains the merged
-    // channels image (if we erase the color component).
-    private static AbsoluteImageReference tryCreateAllChannelsImageReference(
-            List<AbsoluteImageReference> imageReferences)
-    {
-        AbsoluteImageReference lastFound = null;
-        for (AbsoluteImageReference image : imageReferences)
-        {
-            if (lastFound == null)
-            {
-                lastFound = image;
-            } else
-            {
-                if (equals(image.tryGetImageID(), lastFound.tryGetImageID()) == false
-                        || image.getUniqueId().equals(lastFound.getUniqueId()) == false)
-                {
-                    return null;
-                }
-            }
-        }
-        if (lastFound != null)
-        {
-            return lastFound.createWithoutColorComponent();
-        } else
-        {
-            return null;
-        }
-    }
-
-    private static boolean equals(String i1OrNull, String i2OrNull)
-    {
-        return (i1OrNull == null) ? (i2OrNull == null) : i1OrNull.equals(i2OrNull);
-    }
+    
 
     // this method always returns RGB images, even if the input was in grayscale
     private static MixedImageWithWhitePoint mergeImages(List<ImageWithReference> images)
@@ -920,22 +764,24 @@ public class ImageChannelsUtils
         return image.getBufferedImage().getColorModel().hasAlpha();
     }
 
-    // --------- common
-
-    private EnvironmentFailureException createImageNotFoundException(
-            ImageChannelStackReference channelStackReference, String chosenChannelCode)
-    {
-        return EnvironmentFailureException.fromTemplate(
-                "No " + (imageSizeLimit.isThumbnailRequired() ? "thumbnail" : "image")
-                        + " found for channel stack %s and channel %s", channelStackReference,
-                chosenChannelCode);
-    }
-
     /**
      * Transforms the given <var>bufferedImage</var> by selecting a single channel from it.
      */
-    public static BufferedImage transformToChannel(BufferedImage bufferedImage,
-            ColorComponent colorComponent)
+    public static BufferedImage transformToChannel(BufferedImage bufferedImage, final ColorComponent colorComponent)
+    {
+        return transformColor(bufferedImage, new IColorTransformation()
+            {
+                @Override
+                public int transform(int rgb)
+                {
+                    // We reset all ingredients besides the one which is specified by color component.
+                    // The result is the rgb value with only one component which is non-zero.
+                    return colorComponent.extractSingleComponent(rgb).getRGB();
+                }
+            });
+    }
+    
+    public static BufferedImage transformColor(BufferedImage bufferedImage, IColorTransformation transformation)
     {
         BufferedImage newImage = createNewRGBImage(bufferedImage);
         int width = bufferedImage.getWidth();
@@ -945,20 +791,12 @@ public class ImageChannelsUtils
             for (int x = 0; x < width; x++)
             {
                 int rgb = bufferedImage.getRGB(x, y);
-                int channelColor = extractSingleComponent(rgb, colorComponent);
-                newImage.setRGB(x, y, channelColor);
+                newImage.setRGB(x, y, transformation.transform(rgb));
             }
         }
         return newImage;
     }
-
-    // We reset all ingredients besides the one which is specified by color component.
-    // The result is the rgb value with only one component which is non-zero.
-    private static int extractSingleComponent(int rgb, ColorComponent colorComponent)
-    {
-        return colorComponent.extractSingleComponent(rgb).getRGB();
-    }
-
+    
     // NOTE: drawing on this image will not preserve transparency - but we do not need it and the
     // image is smaller
     private static BufferedImage createNewRGBImage(RenderedImage bufferedImage)
@@ -975,4 +813,10 @@ public class ImageChannelsUtils
         return new ByteArrayBasedContentNode(output, nameOrNull);
     }
 
+    private static void logImage(String title, BufferedImage image)
+    {
+        ImageHistogram calculateHistogram = ImageHistogram.calculateHistogram(image);
+        System.err.println(title+": "+image+"\n" + calculateHistogram);
+    }
+
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageLoadingHelper.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageLoadingHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ed25e31b9420446c124bd81a0c34efff81bee79
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageLoadingHelper.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2014 ETH Zuerich, SIS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.dss.generic.server.images;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ch.rinn.restrictions.Private;
+import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.hcs.Location;
+import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent;
+import ch.systemsx.cisd.openbis.dss.etl.AbsoluteImageReference;
+import ch.systemsx.cisd.openbis.dss.etl.HCSImageDatasetLoaderFactory;
+import ch.systemsx.cisd.openbis.dss.etl.IImagingDatasetLoader;
+import ch.systemsx.cisd.openbis.dss.etl.IImagingLoaderStrategy;
+import ch.systemsx.cisd.openbis.dss.etl.ImagingLoaderStrategyFactory;
+import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.DatasetAcquiredImagesReference;
+import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageChannelStackReference;
+import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageTransformationParams;
+import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.RequestedImageSize;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider;
+import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size;
+
+/**
+ * Helper class for loading images using an {@link IImagingLoaderStrategy}.
+ *
+ * @author Franz-Josef Elmer
+ */
+class ImageLoadingHelper
+{
+    private static IImagingLoaderStrategy createLoaderStrategy(DatasetAcquiredImagesReference imageChannels,
+            IHierarchicalContentProvider contentProvider)
+    {
+        String datasetCode = imageChannels.getDatasetCode();
+        IHierarchicalContent dataSetRoot = contentProvider.asContent(datasetCode);
+        IImagingDatasetLoader loader = HCSImageDatasetLoaderFactory.create(dataSetRoot, datasetCode);
+        return ImagingLoaderStrategyFactory.createImageLoaderStrategy(loader);
+    }
+    
+    private final IImagingLoaderStrategy imageLoaderStrategy;
+    
+    private final RequestedImageSize imageSizeLimit;
+    
+    private final String singleChannelTransformationCodeOrNull;
+    
+    @Private
+    ImageLoadingHelper(IImagingLoaderStrategy imageLoaderStrategy,
+            RequestedImageSize imageSizeLimit, String singleChannelTransformationCodeOrNull)
+    {
+        this.imageLoaderStrategy = imageLoaderStrategy;
+        this.imageSizeLimit = imageSizeLimit;
+        this.singleChannelTransformationCodeOrNull = singleChannelTransformationCodeOrNull;
+    }
+    
+    ImageLoadingHelper(IImagingLoaderStrategy imageLoaderStrategy, Size imageSizeLimitOrNull,
+            String singleChannelTransformationCodeOrNull)
+    {
+        this(imageLoaderStrategy, new RequestedImageSize(imageSizeLimitOrNull, false),
+                singleChannelTransformationCodeOrNull);
+    }
+    
+    ImageLoadingHelper(DatasetAcquiredImagesReference imageChannels, IHierarchicalContentProvider contentProvider, 
+            RequestedImageSize imageSizeLimit, String singleChannelTransformationCodeOrNull)
+    {
+        this(createLoaderStrategy(imageChannels, contentProvider), imageSizeLimit, singleChannelTransformationCodeOrNull);
+    }
+    
+    boolean isMergeAllChannels(DatasetAcquiredImagesReference imageChannels)
+    {
+        return imageChannels.isMergeAllChannels(getAllChannelCodes());
+    }
+
+    /**
+     * @param skipNonExisting if true references to non-existing images are ignored, otherwise an
+     *            exception is thrown
+     * @param mergeAllChannels true if all existing channel images should be merged
+     * @param transformationInfo
+     */
+    List<AbsoluteImageReference> fetchImageContents(
+            DatasetAcquiredImagesReference imagesReference, boolean mergeAllChannels,
+            boolean skipNonExisting, ImageTransformationParams transformationInfo)
+    {
+        List<String> channelCodes = imagesReference.getChannelCodes(getAllChannelCodes());
+        List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>();
+        for (String channelCode : channelCodes)
+        {
+            ImageChannelStackReference channelStackReference = imagesReference.getChannelStackReference();
+            AbsoluteImageReference image = imageLoaderStrategy.tryGetImage(channelCode, channelStackReference,
+                            imageSizeLimit, singleChannelTransformationCodeOrNull);
+            if (image == null && skipNonExisting == false)
+            {
+                String item = imageSizeLimit.isThumbnailRequired() ? "thumbnail" : "image";
+                throw EnvironmentFailureException.fromTemplate("No %s found for channel stack %s and channel %s", 
+                        item, channelStackReference, channelCode);
+            }
+            if (image != null)
+            {
+                images.add(image);
+            }
+        }
+
+        // Optimization for a case where all channels are on one image
+        if (mergeAllChannels && (false == shouldApplySingleChannelsTransformations(transformationInfo)))
+        {
+            AbsoluteImageReference allChannelsImageReference = tryCreateAllChannelsImageReference(images);
+            if (allChannelsImageReference != null)
+            {
+                images.clear();
+                images.add(allChannelsImageReference);
+            }
+        }
+        return images;
+    }
+
+    List<AbsoluteImageReference> getRepresentativeImageReferences(Location wellLocationOrNull)
+    {
+        List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>();
+
+        for (String chosenChannel : getAllChannelCodes())
+        {
+            images.add(getRepresentativeImageReference(chosenChannel, wellLocationOrNull));
+        }
+        return images;
+    }
+
+    private List<String> getAllChannelCodes()
+    {
+        return imageLoaderStrategy.getImageParameters().getChannelsCodes();
+    }
+
+    /**
+     * @throw {@link EnvironmentFailureException} when image does not exist
+     */
+    private AbsoluteImageReference getRepresentativeImageReference(String channelCode,
+            Location wellLocationOrNull)
+    {
+        AbsoluteImageReference image = imageLoaderStrategy.tryGetRepresentativeImage(channelCode, 
+                wellLocationOrNull, imageSizeLimit, singleChannelTransformationCodeOrNull);
+        if (image != null)
+        {
+            return image;
+        }
+        String item = imageSizeLimit.isThumbnailRequired() ? "thumbnail" : "image";
+        throw EnvironmentFailureException.fromTemplate("No representative %s found for well %s and channel %s", 
+                item, wellLocationOrNull, channelCode);
+    }
+
+    private boolean shouldApplySingleChannelsTransformations(ImageTransformationParams transformationInfo)
+    {
+        return transformationInfo != null
+                && transformationInfo.tryGetTransformationCodeForChannels() != null
+                && transformationInfo.tryGetTransformationCodeForChannels().size() > 0;
+    }
+
+    // Checks if all images differ only at the color component level and stem from the same page
+    // of the same file. If that's the case any image from the collection contains the merged
+    // channels image (if we erase the color component).
+    private AbsoluteImageReference tryCreateAllChannelsImageReference(
+            List<AbsoluteImageReference> imageReferences)
+    {
+        AbsoluteImageReference lastFound = null;
+        for (AbsoluteImageReference image : imageReferences)
+        {
+            if (lastFound == null)
+            {
+                lastFound = image;
+            } else
+            {
+                if (equals(image.tryGetImageID(), lastFound.tryGetImageID()) == false
+                        || image.getUniqueId().equals(lastFound.getUniqueId()) == false)
+                {
+                    return null;
+                }
+            }
+        }
+        if (lastFound != null)
+        {
+            return lastFound.createWithoutColorComponent();
+        } else
+        {
+            return null;
+        }
+    }
+
+    private static boolean equals(String i1OrNull, String i2OrNull)
+    {
+        return (i1OrNull == null) ? (i2OrNull == null) : i1OrNull.equals(i2OrNull);
+    }
+}
\ No newline at end of file
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java
index eec32f3828b790984c618d7eae91ffd591bae173..e01e0b66f82525f3940f582c132f9079a0dcb405 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.io.IOUtils;
@@ -60,7 +61,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.InternalImageT
 /**
  * @author Franz-Josef Elmer
  */
-@Friend(toClasses = ImageChannelsUtils.class)
+@Friend(toClasses = {ImageLoadingHelper.class, ImageChannelsUtils.class})
 public class ImageChannelsUtilsTest extends AssertJUnit
 {
     public static final File TEST_IMAGE_FOLDER = new File("../screening/sourceTest/java/"
@@ -83,6 +84,7 @@ public class ImageChannelsUtilsTest extends AssertJUnit
                     for (int y = 0; y < height; y++)
                     {
                         int rgb = image.getRGB(x, y);
+                        System.out.println(x+" "+y+" "+Integer.toHexString(rgb));
                         output.setRGB(x, y, (rgb & 0xff) << 16);
                     }
                 }
@@ -122,8 +124,7 @@ public class ImageChannelsUtilsTest extends AssertJUnit
 
         try
         {
-            createImageChannelsUtils(null).calculateBufferedImage(imageRef,
-                    createSingleChannelTransformationParams());
+            createImage(imageRef, createSingleChannelTransformationParams(), null);
             fail("EnvironmentFailureException expected");
         } catch (EnvironmentFailureException ex)
         {
@@ -164,14 +165,25 @@ public class ImageChannelsUtilsTest extends AssertJUnit
         final DatasetAcquiredImagesReference imageRef = createDatasetAcquiredImagesReference();
         prepareExpectations(absoluteImageReference, imageRef);
 
+        ImageTransformationParams transformationParams = createSingleChannelTransformationParams();
         BufferedImage image =
-                createImageChannelsUtils(thumbnailSizeOrNull).calculateBufferedImage(imageRef,
-                        createSingleChannelTransformationParams());
+                createImage(imageRef, transformationParams, thumbnailSizeOrNull);
         assertEquals(expectedImageContentDescription, getImageContentDescription(image));
 
         context.assertIsSatisfied();
     }
 
+    private BufferedImage createImage(final DatasetAcquiredImagesReference imageRef, 
+            ImageTransformationParams transformationParams, Size thumbnailSizeOrNull)
+    {
+        ImageLoadingHelper imageHelper = new ImageLoadingHelper(ImagingLoaderStrategyFactory.createImageLoaderStrategy(loader),
+        new RequestedImageSize(thumbnailSizeOrNull, false), null);
+        boolean mergeAllChannels = imageHelper.isMergeAllChannels(imageRef);
+        List<AbsoluteImageReference> imageContents =
+                imageHelper.fetchImageContents(imageRef, mergeAllChannels, false, transformationParams);
+        return ImageChannelsUtils.calculateBufferedImage(imageContents, transformationParams);
+    }
+
     private void prepareExpectations(final AbsoluteImageReference absoluteImageReferenceOrNull,
             final DatasetAcquiredImagesReference imageRef)
     {
@@ -214,7 +226,7 @@ public class ImageChannelsUtilsTest extends AssertJUnit
         String transformationCode = "MY_TRANSFORMATION";
         Map<String, IImageTransformerFactory> transformations =
                 createImageTransformationsMap(transformationCode, transformerFactory);
-        imgRef.getImageTransfomationFactories().setForChannel(transformations);
+        imgRef.getImageTransformationFactories().setForChannel(transformations);
 
         prepareExpectations(imgRef, imageRef);
         context.checking(new Expectations()
@@ -225,13 +237,11 @@ public class ImageChannelsUtilsTest extends AssertJUnit
                 }
             });
 
-        BufferedImage image =
-                createImageChannelsUtils(null).calculateBufferedImage(
-                        imageRef,
-                        new ImageTransformationParams(true, false, transformationCode,
-                                new HashMap<String, String>()));
-        assertEquals("e00 f00 f00 e00\n" + "f00 c00 c00 f00\n" + "f00 c00 c00 f00\n"
-                + "e00 f00 f00 e00\n", getImageContentDescription(image));
+        ImageTransformationParams transformationParams = new ImageTransformationParams(true, false, transformationCode,
+                new HashMap<String, String>());
+        BufferedImage image = createImage(imageRef, transformationParams, null);
+        assertEquals("0e0 0f0 0f0 0e0\n" + "0f0 0c0 0c0 0f0\n" + "0f0 0c0 0c0 0f0\n"
+                + "0e0 0f0 0f0 0e0\n", getImageContentDescription(image));
 
         context.assertIsSatisfied();
     }
@@ -249,17 +259,10 @@ public class ImageChannelsUtilsTest extends AssertJUnit
             RequestedImageSize imageSize)
     {
         return new AbsoluteImageReference(image(fileName), "id42", null, null, imageSize,
-                new ChannelColorRGB(0, 0, 255), new ImageTransfomationFactories(), null, null,
+                new ChannelColorRGB(0, 255, 0), new ImageTransfomationFactories(), null, null,
                 "ch2");
     }
 
-    private ImageChannelsUtils createImageChannelsUtils(Size thumbnailSizeOrNull)
-    {
-        return new ImageChannelsUtils(
-                ImagingLoaderStrategyFactory.createImageLoaderStrategy(loader),
-                new RequestedImageSize(thumbnailSizeOrNull, false), null);
-    }
-
     public void assertPNG(IHierarchicalContentNode image)
     {
         InputStream inputStream = image.getInputStream();
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java
index a3cb97ab6bc3ac2e4c2ad12526c13282d613be02..b1931c71ecb682ecef159913552e358bbe817a15 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java
@@ -24,7 +24,10 @@ import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import javax.imageio.ImageIO;
 
@@ -84,7 +87,7 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
         public String toString()
         {
             StringBuilder builder = new StringBuilder();
-            builder.append("/===== Failure Report for image ").append(referenceImage).append('\n');
+            builder.append("/===== Failure Report for image ").append(referenceImage.getAbsolutePath()).append('\n');
             builder.append(failureReport).append("\\============================\n");
             return builder.toString();
         }
@@ -100,6 +103,7 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
     {
         private final URLMethodWithParameters url;
         private final List<String> channels = new ArrayList<String>();
+        private final Map<String, String> transformationsByChannel = new HashMap<String, String>();
         private boolean mergeChannels = true;
         private boolean microscopy = false;
         private int wellRow = 1;
@@ -146,6 +150,11 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
             }
             url.addParameter(ImageServletUrlParameters.TILE_ROW_PARAM, Integer.toString(tileRow));
             url.addParameter(ImageServletUrlParameters.TILE_COL_PARAM, Integer.toString(tileColumn));
+            for (Entry<String, String> entry : transformationsByChannel.entrySet())
+            {
+                url.addParameter(ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM + entry.getKey(), 
+                        entry.getValue());
+            }
             url.addParameter("mode", mode);
             try
             {
@@ -198,6 +207,13 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
             channels.add(channel);
             return this;
         }
+        
+        public ImageLoader rescaling(String channel, int low, int high)
+        {
+            transformationsByChannel.put(channel, 
+                    ScreeningConstants.USER_DEFINED_RESCALING_CODE + "(" + low + "," + high + ")");
+            return this;
+        }
     }
     
     /**
@@ -208,6 +224,8 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
      */
     protected static final class ImageChecker
     {
+        private static final int HISTOGRAM_HEIGHT = 19;
+        private static final int HISTOGRAM_WIDTH = 151;
         private final StringBuilder failureReport = new StringBuilder();
         private final File folderForWrongImages;
         
@@ -230,8 +248,8 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
         {
             if (assertNoFailuresAlreadyInvoked == false)
             {
-                AssertJUnit.assertEquals("", failureReport.toString());
                 assertNoFailuresAlreadyInvoked = true;
+                AssertJUnit.assertEquals("", failureReport.toString());
             }
         }
         
@@ -244,13 +262,12 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
             FailureReport report = new FailureReport(referenceImage);
             try
             {
-                BufferedImage expectedImage = ImageIO.read(referenceImage);
                 BufferedImage actualImage = imageLoader.load();
-                checkEquals(report, expectedImage, actualImage);
+                checkEquals(report, referenceImage, actualImage);
                 saveActualImageIfDifferent(actualImage, referenceImage, report);
             } catch (IOException ex)
             {
-                report.addFailureMessage("Couldn't load image: " + ex);
+                report.addFailureMessage("Couldn't save image: " + ex);
             } finally
             {
                 if (report.isFailure())
@@ -260,6 +277,17 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
             }
         }
 
+        private BufferedImage load(File referenceImage)
+        {
+            try
+            {
+                return ImageIO.read(referenceImage);
+            } catch (IOException ex)
+            {
+                return null;
+            }
+        }
+
         private void saveActualImageIfDifferent(BufferedImage actualImage, File referenceImage, FailureReport report) throws IOException
         {
             if (report.isFailure() == false || actualImage == null)
@@ -278,12 +306,17 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
             }
         }
         
-        private void checkEquals(FailureReport report, BufferedImage expectedImage, BufferedImage actualImage)
+        private void checkEquals(FailureReport report, File referenceImage, BufferedImage actualImage)
         {
-            if (expectedImage == null || actualImage == null)
+            BufferedImage expectedImage = load(referenceImage);
+            if (expectedImage == null)
+            {
+                report.addFailureMessage("Expected and image is undefined");
+                return;
+            }
+            if (actualImage == null)
             {
-                report.addFailureMessage("Expected and/or actual image is undefined: Expected image: <" + expectedImage 
-                        + ">, actual image: <" + actualImage + ">");
+                report.addFailureMessage("Actual image is undefined");
                 return;
             }
             checkEquals(report, "Image height", expectedImage.getHeight(), actualImage.getHeight());
@@ -297,23 +330,32 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase
             checkEquals(report, "Color space type", expectedColorSpace.getType(), actualColorSpace.getType());
             ImageHistogram expectedHistogram = ImageHistogram.calculateHistogram(expectedImage);
             ImageHistogram actualHistogram = ImageHistogram.calculateHistogram(actualImage);
-            String expectedRenderedHistogram = expectedHistogram.renderAsASCIIChart(49, 9);
-            String actualRenderedHistogram = actualHistogram.renderAsASCIIChart(49, 9);
+            String expectedRenderedHistogram = expectedHistogram.renderAsASCIIChart(HISTOGRAM_WIDTH, HISTOGRAM_HEIGHT);
+            String actualRenderedHistogram = actualHistogram.renderAsASCIIChart(HISTOGRAM_WIDTH, HISTOGRAM_HEIGHT);
             if (expectedRenderedHistogram.equals(actualRenderedHistogram))
             {
+                printComparisonMessage(referenceImage, expectedRenderedHistogram);
                 return;
             }
             // difference can be caused by a value close to discretization border. Thus try another height.
-            expectedRenderedHistogram = expectedHistogram.renderAsASCIIChart(49, 10);
-            actualRenderedHistogram = actualHistogram.renderAsASCIIChart(49, 10);
+            expectedRenderedHistogram = expectedHistogram.renderAsASCIIChart(HISTOGRAM_WIDTH, HISTOGRAM_HEIGHT + 1);
+            actualRenderedHistogram = actualHistogram.renderAsASCIIChart(HISTOGRAM_WIDTH, HISTOGRAM_HEIGHT + 1);
             if (expectedRenderedHistogram.equals(actualRenderedHistogram))
             {
+                printComparisonMessage(referenceImage, expectedRenderedHistogram);
                 return;
             }
-            report.addFailureMessage("expected histogram of " + expectedImage + ":\n" + expectedRenderedHistogram 
+            report.addFailureMessage("expected histogram of " + expectedImage + ":\n" + expectedRenderedHistogram
                     + "actual histogram of " + actualImage + ":\n" + actualRenderedHistogram);
         }
-        
+
+        private void printComparisonMessage(File referenceImage, 
+                String expectedRenderedHistogram)
+        {
+            System.out.println("Histogram of reference image (" + referenceImage 
+                    + ") equals actual histogram:\n" + expectedRenderedHistogram);
+        }
+
         private void checkEquals(FailureReport report, String message, Object expected, Object actual)
         {
             if (expected == null ? expected == actual : expected.equals(actual))
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java
index 7530bb282620a0ce2d99915b4ba2a1cc5f99bdf0..132e6918a6b1acdb14c489a95c84d7479909d028 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java
@@ -107,6 +107,10 @@ public class MicroscopyImageDropboxTest extends AbstractImageDropboxTestCase
         imageChecker.check(new File(getTestDataFolder(), "C01_512x512.png"), 
                 new ImageLoader(dataSet, sessionToken).microscopy().channel("SERIES-0_CHANNEL-0")
                 .channel("SERIES-0_CHANNEL-1").mode("thumbnail512x512"));
+        imageChecker.check(new File(getTestDataFolder(), "Merged_256x256_C0_0_20_C4_2_15.png"), 
+                new ImageLoader(dataSet, sessionToken).microscopy().rescaling("SERIES-0_CHANNEL-0", 0, 20)
+                .rescaling("SERIES-0_CHANNEL-4", 2, 15).mode("thumbnail256x256"));
+
         imageChecker.assertNoFailures();
     }
     
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/SimpleImageDropboxTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/SimpleImageDropboxTest.java
index 7e82c70dfe40254e6535ecba11b3cbc5ad40bc44..adf74e7430184618e780133f1bcc8521c9e7d992 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/SimpleImageDropboxTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/SimpleImageDropboxTest.java
@@ -54,6 +54,10 @@ public class SimpleImageDropboxTest extends AbstractImageDropboxTestCase
                 new ImageLoader(dataSet, sessionToken).channel("DAPI"));
         imageChecker.check(new File(getTestDataFolder(), "1_3_DAPI_CY3_256x191.png"), 
                 new ImageLoader(dataSet, sessionToken).tileColumn(3).channel("DAPI").channel("CY3").mode("thumbnail256x191"));
+        imageChecker.check(new File(getTestDataFolder(), "1_2_Merged_1392x1040.png"), 
+                new ImageLoader(dataSet, sessionToken).tileColumn(2).mode("thumbnail1392x1040"));
+        imageChecker.check(new File(getTestDataFolder(), "1_1_Merged_Default_GFP_0_100.png"), 
+                new ImageLoader(dataSet, sessionToken).rescaling("GFP", 0, 100));
         imageChecker.assertNoFailures();
     }
 }