diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java
index 28502c54b29212f20f2e6ce902cebc7f1eb3a713..fcf3ed43780f4a651f44ea1251596239eb75872a 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java
@@ -295,7 +295,7 @@ abstract public class AbstractDatasetDownloadServlet extends HttpServlet
     protected static final BufferedImage createThumbnail(IHierarchicalContentNode fileNode,
             Size thumbnailSize)
     {
-        BufferedImage image = ImageUtil.loadImage(fileNode);
+        BufferedImage image = ImageUtil.loadImageForDisplay(fileNode);
         return createThumbnail(image, thumbnailSize);
     }
 
@@ -303,7 +303,7 @@ abstract public class AbstractDatasetDownloadServlet extends HttpServlet
     {
         int width = thumbnailSize.getWidth();
         int height = thumbnailSize.getHeight();
-        return ImageUtil.createThumbnail(image, width, height);
+        return ImageUtil.createThumbnailForDisplay(image, width, height);
     }
 
     // if display mode describes a thumbnail return its expected size
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 b1b09962833dbbc500870db6e8845393f6a05664..10abc68c5ae0f6bd111902cab13f63383dece687 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,6 +24,7 @@ import static ch.systemsx.cisd.common.utilities.DataTypeUtil.TIFF_FILE;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.OutputStream;
@@ -47,6 +48,8 @@ import ar.com.hjg.pngj.PngWriter;
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.base.io.IRandomAccessFile;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.image.IntensityRescaling;
+import ch.systemsx.cisd.common.image.IntensityRescaling.Levels;
 import ch.systemsx.cisd.common.io.FileBasedContent;
 import ch.systemsx.cisd.common.io.IContent;
 import ch.systemsx.cisd.common.io.hierarchical_content.HierarchicalNodeBasedContent;
@@ -57,7 +60,6 @@ import ch.systemsx.cisd.imagereaders.IReadParams;
 import ch.systemsx.cisd.imagereaders.ImageID;
 import ch.systemsx.cisd.imagereaders.ImageReaderConstants;
 import ch.systemsx.cisd.imagereaders.ImageReaderFactory;
-import ch.systemsx.cisd.imagereaders.ReadParams;
 
 /**
  * Utility function on images.
@@ -83,7 +85,7 @@ public class ImageUtil
             handle.mark(MAX_READ_AHEAD);
             try
             {
-                return loadJavaAdvancedImagingTiff(handle, imageID, false);
+                return loadJavaAdvancedImagingTiff(handle, imageID);
             } catch (RuntimeException ex)
             {
                 if (imageID.equals(ImageID.NULL))
@@ -110,13 +112,9 @@ public class ImageUtil
 
     /**
      * For experts only! Loads some kinds of TIFF images handled by JAI library.
-     * 
-     * @param allow16BitGrayscaleModel if true and the image is 16 bit grayscale, then the
-     *            appropriate buffered imaged type will be used, otherwise the image will be
-     *            converted to 24 bits RGB. Useful if access to original pixel values is needed.
      */
     public static BufferedImage loadJavaAdvancedImagingTiff(IRandomAccessFile handle,
-            ImageID imageID, boolean allow16BitGrayscaleModel) throws EnvironmentFailureException
+            ImageID imageID) throws EnvironmentFailureException
     {
         IImageReader imageReader =
                 ImageReaderFactory.tryGetReader(ImageReaderConstants.JAI_LIBRARY, "tiff");
@@ -126,11 +124,9 @@ public class ImageUtil
                     .fromTemplate("Cannot find JAI image decoder for TIFF files.");
         }
 
-        ReadParams readParams = new ReadParams();
-        readParams.setAllow16BitGrayscaleModel(allow16BitGrayscaleModel);
         try
         {
-            return imageReader.readImage(handle, imageID, readParams);
+            return imageReader.readImage(handle, imageID, null);
         } catch (Exception ex)
         {
             throw EnvironmentFailureException.fromTemplate("Cannot decode image.", ex);
@@ -194,11 +190,14 @@ public class ImageUtil
      * Loads the image specified by <var>imageIdOrNull</var> from the given </var>inputStream</var>.
      * Supported images formats are GIF, JPG, PNG, and TIFF. The input stream will be closed after
      * loading.
+     * <p>
+     * Note that the original color depth will be kept, so e.g. 12 or 16 bit grayscale images will
+     * not be converted to RGB.
      * 
      * @throws IllegalArgumentException if the input stream doesn't start with a magic number
      *             identifying supported image format.
      */
-    public static BufferedImage loadImage(IContent content, String imageIdOrNull,
+    public static BufferedImage loadUnchangedImage(IContent content, String imageIdOrNull,
             String imageLibraryNameOrNull, String imageLibraryReaderNameOrNull, IReadParams params)
     {
         assert (imageLibraryReaderNameOrNull == null || imageLibraryNameOrNull != null) : "if image reader "
@@ -416,7 +415,7 @@ public class ImageUtil
     @Private
     static BufferedImage loadImage(IContent content)
     {
-        return loadImage(content, null, null, null, null);
+        return loadUnchangedImage(content, null, null, null, null);
     }
 
     /**
@@ -424,26 +423,54 @@ public class ImageUtil
      * 
      * @throws IllegalArgumentException if the file isn't a valid image file.
      */
-    public static BufferedImage loadImage(IHierarchicalContentNode fileNode)
+    public static BufferedImage loadImageForDisplay(IHierarchicalContentNode fileNode)
     {
         if (fileNode.exists() == false)
         {
             throw new IllegalArgumentException("File does not exist: " + fileNode.getRelativePath());
         }
-        return loadImage(new HierarchicalNodeBasedContent(fileNode));
+        BufferedImage result = loadImage(new HierarchicalNodeBasedContent(fileNode));
+        result = convertForDisplayIfNecessary(result);
+        return result;
     }
 
     /**
      * Re-scales the image to be the biggest one which fits into a (0,0,maxWidth, maxHeight)
      * rectangle. Preserves the aspect ratio. If the rectangle is bigger than the image does
-     * nothing. Ignores alpha channel of the original image.
+     * nothing.
+     * <p>
+     * If the specified image uses grayscale with color depth larger then 8 bits, conversion to 8
+     * bits grayscale is done.
+     * </p>
      * 
      * @param maxWidth Maximum width of the result image.
      * @param maxHeight Maximum height of the result image.
      */
-    public static BufferedImage createThumbnail(BufferedImage image, int maxWidth, int maxHeight)
+    public static BufferedImage createThumbnailForDisplay(BufferedImage image, int maxWidth,
+            int maxHeight)
+    {
+        BufferedImage result = rescale(image, maxWidth, maxHeight, true, false);
+        result = convertForDisplayIfNecessary(result);
+        return result;
+    }
+
+    /**
+     * If the specified image uses grayscale with color depth larger then 8 bits, conversion to 8
+     * bits grayscale is done. Otherwise the original image is returned.
+     */
+    public static BufferedImage convertForDisplayIfNecessary(BufferedImage image)
     {
-        return rescale(image, maxWidth, maxHeight, true, false);
+        ColorModel colorModel = image.getColorModel();
+        // is grayscale?
+        if (colorModel.getColorSpace().getNumComponents() == 1)
+        {
+            if (colorModel.getPixelSize() > 8)
+            {
+                Levels intensityRange = IntensityRescaling.computeLevels(image, 0);
+                return IntensityRescaling.rescaleIntensityLevelTo8Bits(image, intensityRange);
+            }
+        }
+        return image;
     }
 
     /**
@@ -480,10 +507,7 @@ public class ImageUtil
         int thumbnailWidth = (int) (scale * width + 0.5);
         int thumbnailHeight = (int) (scale * height + 0.5);
 
-        // preserve alpha channel if it was present before
-        int imageType =
-                image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB
-                        : BufferedImage.TYPE_INT_RGB;
+        int imageType = image.getType();
         BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight, imageType);
         Graphics2D graphics2D = thumbnail.createGraphics();
         Object renderingHint =
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 ae4865d5369d9a47543874578d2c856f39fc27a7..49159712a497645324ae3649b03014f47b756936 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
@@ -128,8 +128,8 @@ public class ImageUtilTest extends AssertJUnit
     public void testCreateThumbnail()
     {
         BufferedImage image = loadImageByFile("gif-example.gif");
-        assertImageSize(79, 100, ImageUtil.createThumbnail(image, 200, 100));
-        assertImageSize(100, 127, ImageUtil.createThumbnail(image, 100, 200));
+        assertImageSize(79, 100, ImageUtil.createThumbnailForDisplay(image, 200, 100));
+        assertImageSize(100, 127, ImageUtil.createThumbnailForDisplay(image, 100, 200));
     }
 
     @Test
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java
index 9ebff8850e35dcd1469527c31888171c1a39b1b9..07c9f28131713496a42f6fd4ef849977f0cc8f41 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java
@@ -43,7 +43,7 @@ public class ThumbnailTiming
         BufferedImage image = ImageUtil.loadImage(file);
         stopWatch.stop();
         stopWatch.start("Convert");
-        BufferedImage thumbnail = ImageUtil.createThumbnail(image, 120, 60);
+        BufferedImage thumbnail = ImageUtil.createThumbnailForDisplay(image, 120, 60);
         stopWatch.stop();
         stopWatch.start("Write");
         try
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java
index 507bc8cf4cbf53db4b7a605d0043ed6b21ebf275..8163116a76861b48a653d4d183cc224f7ac4221c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java
@@ -95,16 +95,16 @@ public class AbsoluteImageReference extends AbstractImageReference
         }
     }
 
-    public BufferedImage getImage()
+    public BufferedImage getUnchangedImage()
     {
         if (image == null)
         {
-            image = loadImage(content, tryGetImageID(), imageLibraryOrNull);
+            image = loadUnchangedImage(content, tryGetImageID(), imageLibraryOrNull);
         }
         return image;
     }
 
-    static BufferedImage loadImage(IContent content, String imageIdOrNull,
+    static BufferedImage loadUnchangedImage(IContent content, String imageIdOrNull,
             ImageLibraryInfo imageLibraryOrNull)
     {
         String imageLibraryNameOrNull = null;
@@ -114,7 +114,7 @@ public class AbsoluteImageReference extends AbstractImageReference
             imageLibraryNameOrNull = imageLibraryOrNull.getName();
             imageLibraryReaderNameOrNull = imageLibraryOrNull.getReaderName();
         }
-        return ImageUtil.loadImage(content, imageIdOrNull, imageLibraryNameOrNull,
+        return ImageUtil.loadUnchangedImage(content, imageIdOrNull, imageLibraryNameOrNull,
                 imageLibraryReaderNameOrNull, null);
     }
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java
index bb018f9b2bda0fa57842afea16180e90c00e86e1..f3e8e45a8c45c92011ebb432b9bfcb8da5111ce8 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java
@@ -201,8 +201,7 @@ class Hdf5ThumbnailGenerator implements IHDF5WriterClient
 
     private BufferedImage loadImage(File imageFile, String imageIdOrNull)
     {
-        // NOTE 2011-04-20, Tomasz Pylak: support paged tiffs when generating thumbnails
-        return AbsoluteImageReference.loadImage(new FileBasedContent(imageFile), imageIdOrNull,
+        return AbsoluteImageReference.loadUnchangedImage(new FileBasedContent(imageFile), imageIdOrNull,
                 imageLibraryOrNull);
     }
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java
index 7fd1e6a062fcd0cfad90a5ba1642e2aa8d26a9dc..b9c9b8c825601b718f42cad6870b72738535e581 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.openbis.dss.etl;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -27,10 +28,12 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO;
 import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.Channel;
 import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.ChannelColor;
+import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.ImageTransformation;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgExperimentDTO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO;
 
 /**
  * Helper class for retrieving and/or creating entities associated with the imaging database:
@@ -409,9 +412,33 @@ public class ImagingDatabaseHelper
             ImgChannelDTO channelDTO = makeChannelDTO(channel, channelOwner);
             long channelId = dao.addChannel(channelDTO);
             channelDTO.setId(channelId);
+            saveChannelImageTransformations(channelId, channel.getAvailableTransformations());
             return channelDTO;
         }
 
+        private void saveChannelImageTransformations(long channelId,
+                ImageTransformation[] transformations)
+        {
+            if (transformations.length == 0)
+            {
+                return;
+            }
+            List<ImgImageTransformationDTO> transformationDTOs =
+                    new ArrayList<ImgImageTransformationDTO>();
+            for (ImageTransformation transformation : transformations)
+            {
+                transformationDTOs.add(createTransformationDTO(transformation, channelId));
+            }
+            dao.addImageTransformations(transformationDTOs);
+        }
+
+        private ImgImageTransformationDTO createTransformationDTO(ImageTransformation tr,
+                long channelId)
+        {
+            return new ImgImageTransformationDTO(tr.getCode(), tr.getLabel(), tr.getDescription(),
+                    tr.isEditable(), channelId, tr.getImageTransformerFactory());
+        }
+
         private static ImgChannelDTO makeChannelDTO(Channel channel, ChannelOwner channelOwner)
         {
             ChannelColor channelColor = channel.tryGetChannelColor();
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java
index c1eb6f00962cf4db90fdc295ace4aa6ce7f34246..af00232405943884ebbc81fd2d2e35a40191a390 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java
@@ -28,7 +28,7 @@ public class ImagingDatabaseVersionHolder implements IDatabaseVersionHolder
 
     public String getDatabaseVersion()
     {
-        return "013"; // changed in S108
+        return "014"; // changed in S115
     }
 
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java
index 98ad698efd25155442c1e9c5e702562c66164f69..946d40f0b3e458731d65ce778ffd90b6c10a8a68 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java
@@ -33,6 +33,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFe
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureValuesDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureVocabularyTermDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDTO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgSpotDTO;
 
 /**
@@ -69,8 +70,8 @@ public interface IImagingQueryDAO extends TransactionQuery, IImagingReadonlyQuer
     @Select("insert into EXPERIMENTS (PERM_ID) values (?{1}) returning ID")
     public long addExperiment(String experimentPermId);
 
-    @Select("insert into CHANNELS (LABEL, CODE, DESCRIPTION, WAVELENGTH, DS_ID, EXP_ID, COLOR) values "
-            + "(?{1.label}, ?{1.code}, ?{1.description}, ?{1.wavelength}, ?{1.datasetId}, ?{1.experimentId}, ?{1.dbChannelColor}) returning ID")
+    @Select("insert into CHANNELS (CODE, LABEL, DESCRIPTION, WAVELENGTH, DS_ID, EXP_ID, COLOR) values "
+            + "(?{1.code}, ?{1.label}, ?{1.description}, ?{1.wavelength}, ?{1.datasetId}, ?{1.experimentId}, ?{1.dbChannelColor}) returning ID")
     public long addChannel(ImgChannelDTO channel);
 
     @Select("insert into CONTAINERS (PERM_ID, SPOTS_WIDTH, SPOTS_HEIGHT, EXPE_ID) values "
@@ -85,6 +86,13 @@ public interface IImagingQueryDAO extends TransactionQuery, IImagingReadonlyQuer
             + "?{1.imageLibraryName}, ?{1.imageReaderName}) returning ID")
     public long addDataset(ImgDatasetDTO dataset);
 
+    @Update(sql = "insert into IMAGE_TRANSFORMATIONS(CODE, LABEL, DESCRIPTION, IMAGE_TRANSFORMER_FACTORY, IS_EDITABLE, CHANNEL_ID) values "
+            + "(?{1.code}, ?{1.label}, ?{1.description}, ?{1.serializedImageTransformerFactory}, ?{1.isEditable}, ?{1.channelId})", batchUpdate = true)
+    public void addImageTransformations(List<ImgImageTransformationDTO> imageTransformations);
+
+    @Update(sql = "delete from IMAGE_TRANSFORMATIONS where CODE = ?{1} and CHANNEL_ID = ?{2}")
+    public void removeImageTransformation(String transformationCode, long channelId);
+
     @Select("insert into SPOTS (X, Y, CONT_ID) values "
             + "(?{1.column}, ?{1.row}, ?{1.containerId}) returning ID")
     public long addSpot(ImgSpotDTO spot);
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java
index 402a58583c8b888d3e266bc774201e9a4b43519d..28b691b58551846f8e6cd77fe8fc277567ebea4c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java
@@ -17,6 +17,9 @@
 package ch.systemsx.cisd.openbis.dss.etl.dataaccess;
 
 import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
 
@@ -42,6 +45,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgCh
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDTO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO;
 
 /**
  * {@link HCSDatasetLoader} extension with code for handling images.
@@ -98,7 +102,7 @@ public class ImagingDatasetLoader extends HCSDatasetLoader implements IImagingDa
         }
         AbsoluteImageReference imgRef =
                 createAbsoluteImageReference(imageDTO, channel, imageSize, thumbnailFetched);
-        if (thumbnailFetched && isThumbnailsTooSmall(imageSize, imgRef.getImage()))
+        if (thumbnailFetched && isThumbnailsTooSmall(imageSize, imgRef.getUnchangedImage()))
         {
             imageDTO = tryGetOriginalImage(channel.getId(), channelStackReference, datasetId);
             if (imageDTO != null)
@@ -143,11 +147,33 @@ public class ImagingDatasetLoader extends HCSDatasetLoader implements IImagingDa
         ImageTransfomationFactories imageTransfomationFactories = new ImageTransfomationFactories();
         imageTransfomationFactories
                 .setForMergedChannels(tryGetImageTransformerFactoryForMergedChannels());
-        imageTransfomationFactories.setForChannel(channel.tryGetImageTransformerFactory());
+
+        Map<String, IImageTransformerFactory> singleChannelMap =
+                getAvailableImageTransformationsForChannel(channel);
+        imageTransfomationFactories.setForChannel(singleChannelMap);
+
         imageTransfomationFactories.setForImage(imageDTO.tryGetImageTransformerFactory());
         return imageTransfomationFactories;
     }
 
+    private Map<String, IImageTransformerFactory> getAvailableImageTransformationsForChannel(
+            ImgChannelDTO channel)
+    {
+        List<ImgImageTransformationDTO> availableImageTransformations =
+                availableImageTransformationsMap.get(channel.getId());
+        Map<String, IImageTransformerFactory> singleChannelMap =
+                new HashMap<String, IImageTransformerFactory>();
+        if (availableImageTransformations != null)
+        {
+            for (ImgImageTransformationDTO transformation : availableImageTransformations)
+            {
+                singleChannelMap.put(transformation.getCode(),
+                        transformation.getImageTransformerFactory());
+            }
+        }
+        return singleChannelMap;
+    }
+
     private IImageTransformerFactory tryGetImageTransformerFactoryForMergedChannels()
     {
         IImageTransformerFactory imageTransformerFactory = dataset.tryGetImageTransformerFactory();
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java
index 3a6aa9f26c661afaf8029fc3fc5d1546514ec10d..43caf51acac04c165122c4d6b98aed47fb8e098f 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.openbis.dss.etl.dto;
 
+import java.util.Map;
+
 import ch.systemsx.cisd.base.image.IImageTransformerFactory;
 
 /**
@@ -25,8 +27,8 @@ import ch.systemsx.cisd.base.image.IImageTransformerFactory;
  */
 public class ImageTransfomationFactories
 {
-    // applied when a single channel is displayed
-    private IImageTransformerFactory singleChannel;
+    // transformation with a specified code can be applied when a single channel is displayed
+    private Map<String/* transformation code */, IImageTransformerFactory> singleChannelMap;
 
     // applied when all channels of the image are merged
     private IImageTransformerFactory mergedChannels;
@@ -34,14 +36,24 @@ public class ImageTransfomationFactories
     // individual transformation for the image
     private IImageTransformerFactory image;
 
-    public IImageTransformerFactory tryGetForChannel(boolean areChannelsMerged)
+    public IImageTransformerFactory tryGetForChannel(String transformationCodeOrNull)
+    {
+        if (transformationCodeOrNull == null)
+        {
+            return null;
+        }
+        return singleChannelMap.get(transformationCodeOrNull);
+    }
+
+    public IImageTransformerFactory tryGetForMerged()
     {
-        return areChannelsMerged ? mergedChannels : singleChannel;
+        return mergedChannels;
     }
 
-    public void setForChannel(IImageTransformerFactory transformerFactoryForChannel)
+    public void setForChannel(
+            Map<String/* transformation code */, IImageTransformerFactory> singleChannelMap)
     {
-        this.singleChannel = transformerFactoryForChannel;
+        this.singleChannelMap = singleChannelMap;
     }
 
     public void setForMergedChannels(IImageTransformerFactory transformerFactoryForMergedChannels)
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java
index 10c6c9ac0aa0b95a0f27ebdf2c4f434c06035356..e2d4450e71e33806b1dd9091117482a567f34dca 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java
@@ -24,6 +24,8 @@ import ch.systemsx.cisd.base.image.IImageTransformerFactory;
 import ch.systemsx.cisd.common.shared.basic.utils.StringUtils;
 import ch.systemsx.cisd.openbis.dss.etl.dto.ImageLibraryInfo;
 import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.transformations.ConvertToolImageTransformerFactory;
+import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.transformations.ImageTransformationBuffer;
+import ch.systemsx.cisd.openbis.generic.shared.basic.CodeNormalizer;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 
@@ -34,14 +36,18 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConst
  */
 abstract public class SimpleImageDataConfig
 {
-    // --- one of the following two methods have to be overridden -----------------
+    // --- one of the following two methods has to be overridden -----------------
 
     /**
-     * Extracts tile number, channel code and well code for a given relative path to an image.
+     * Extracts tile number, channel code and well code for a given relative path to a single image.
+     * This method should overridden to deal with files containing single images. It is ignored if
+     * {@link #extractImagesMetadata(String, List)} is overridden as well.
      * <p>
-     * Will be called for each file found in the incoming directory which has the extension returned
-     * by {@link #getRecognizedImageExtensions()}.
+     * It will be called for each file found in the incoming directory which has the extension
+     * returned by {@link #getRecognizedImageExtensions()}.
      * </p>
+     * To deal with image containers (like multi-page TIFF files) override
+     * {@link #extractImagesMetadata(String, List)} instead, otherwise leave that method unchanged.
      */
     public ImageMetadata extractImageMetadata(String imagePath)
     {
@@ -50,12 +56,14 @@ abstract public class SimpleImageDataConfig
     }
 
     /**
-     * Returns meta-data for each image contained in specified image file path. This method returns
-     * just the {@link ImageMetadata} object returned by {@link #extractImageMetadata(String)}.
+     * Returns meta-data for each image in the specified image container. This method should
+     * overridden to deal with container images (like multi-page TIFF files).
      * <p>
-     * In case of a image container file format (like multi-page TIFF) this method should
-     * overridden.
+     * This implementation returns the result of {@link #extractImageMetadata(String)} wrapped in an
+     * array, image identifiers are ignored.
+     * </p>
      * 
+     * @param imagePath path to the single image or container of many images
      * @param imageIdentifiers Identifiers of all images contained in the image file.
      */
     public ImageMetadata[] extractImagesMetadata(String imagePath,
@@ -103,13 +111,33 @@ abstract public class SimpleImageDataConfig
      * <p>
      * Creates channel description for a given code. Can be overridden in subclasses.
      * </p>
-     * By default rhe channel label will be equal to the code. Channel color returned by
+     * By default the channel label will be equal to the code. Channel color returned by
      * {@link #getChannelColor(String)} will be used.
      */
     public Channel createChannel(String channelCode)
     {
-        ChannelColor channelColor = getChannelColor(channelCode.toUpperCase());
-        return new Channel(channelCode, channelCode, channelColor);
+        ChannelColor channelColor = getChannelColor(channelCode);
+        ImageTransformation[] availableTransformations =
+                getAvailableChannelTransformations(channelCode);
+        String label = channelCode;
+        String normalizedChannelCode = CodeNormalizer.normalize(channelCode);
+        Channel channel = new Channel(normalizedChannelCode, label, channelColor);
+        channel.setAvailableTransformations(availableTransformations);
+        return channel;
+    }
+
+    /**
+     * Sets available transformations which can be applied to images of the specified channel (on
+     * user's request).
+     * <p>
+     * Can be overridden in subclasses. The easiest way to create transformations is to use
+     * {@link ImageTransformationBuffer} class.
+     * <p>
+     * By default returns null.
+     */
+    public ImageTransformation[] getAvailableChannelTransformations(String channelCode)
+    {
+        return null;
     }
 
     /**
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java
index 75edc13c1b6cf2bb88162f79d0b84f7b8989b84e..b63e6071c2506e5a2e7bdb98ccd735afddb1aca7 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java
@@ -33,7 +33,7 @@ import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.ImageTransformation;
  */
 public class ImageTransformationBuffer
 {
-    private static final String PREDEFINED_TRANSFORMATIONS_CODE_PREFIX = "PREDEFINED_";
+    private static final String PREDEFINED_TRANSFORMATIONS_CODE_PREFIX = "_";
 
     private final List<ImageTransformation> imageTransformations;
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java
index 27a0b7f970d7af7b6b3c1d8a2b6d87d0ec3f8dea..38c3aee6c83de4a8be39351c27b2189d01b4b0fd 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java
@@ -80,7 +80,11 @@ class ImageGenerationDescriptionFactory
             throw new UserFailureException(
                     "Neither channels nor segmentation objects have been specified!");
         }
-        return new ImageGenerationDescription(channelsToMerge, overlayChannels, sessionId,
+
+        String singleChannelTransformationCodeOrNull =
+                request.getParameter(ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM);
+        return new ImageGenerationDescription(channelsToMerge,
+                singleChannelTransformationCodeOrNull, overlayChannels, sessionId,
                 thumbnailSizeOrNull);
     }
 
@@ -95,7 +99,7 @@ class ImageGenerationDescriptionFactory
         {
             String datasetCode = entry.getKey();
             List<String> channels = entry.getValue();
-            overlayChannels.add(new DatasetAcquiredImagesReference(datasetCode,
+            overlayChannels.add(DatasetAcquiredImagesReference.createForManyChannels(datasetCode,
                     channelStackReference, channels));
         }
         return overlayChannels;
@@ -143,17 +147,29 @@ class ImageGenerationDescriptionFactory
         {
             return null;
         }
-        List<String> channelsOrNull = null;
         if (isMergedChannels == false)
         {
-            channelsOrNull = tryGetParams(request, ImageServletUrlParameters.CHANNEL_PARAM);
+            List<String> channelsOrNull =
+                    tryGetParams(request, ImageServletUrlParameters.CHANNEL_PARAM);
             if (channelsOrNull == null)
             {
                 return null; // no channels to merge at all
             }
+            if (channelsOrNull.size() > 1)
+            {
+                return DatasetAcquiredImagesReference.createForManyChannels(datasetCode,
+                        channelStackReference, channelsOrNull);
+            } else
+            {
+                String channelCode = channelsOrNull.get(0);
+                return DatasetAcquiredImagesReference.createForSingleChannel(datasetCode,
+                        channelStackReference, channelCode);
+            }
+        } else
+        {
+            return DatasetAcquiredImagesReference.createForMergedChannels(datasetCode,
+                    channelStackReference);
         }
-        return new DatasetAcquiredImagesReference(datasetCode, channelStackReference,
-                channelsOrNull);
     }
 
     private static boolean isMergedChannels(HttpServletRequest request)
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 4bef593d90d88f3833144d992085db663daf346b..5902a3a4dc78fe94ca865a6137ad2974b6356d08 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
@@ -21,15 +21,11 @@ import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.image.BufferedImage;
 import java.awt.image.RenderedImage;
-import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import javax.imageio.ImageIO;
-
 import org.apache.log4j.Logger;
 
 import ch.rinn.restrictions.Private;
@@ -49,6 +45,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ResponseContentStream;
 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.ImageGenerationDescription;
+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.dto.Size;
 import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil;
@@ -109,7 +106,10 @@ public class ImageChannelsUtils
         if (imageChannels != null)
         {
             RequestedImageSize imageSize = new RequestedImageSize(thumbnailSizeOrNull, false);
-            image = calculateBufferedImage(imageChannels, datasetDirectoryProvider, imageSize);
+            image =
+                    calculateBufferedImage(imageChannels,
+                            params.tryGetSingleChannelTransformationCode(),
+                            datasetDirectoryProvider, imageSize);
         }
 
         RequestedImageSize overlaySize = calcOverlaySize(image, thumbnailSizeOrNull);
@@ -147,7 +147,9 @@ public class ImageChannelsUtils
         boolean mergeAllChannels = utils.isMergeAllChannels(imagesReference);
         List<AbsoluteImageReference> imageContents =
                 utils.fetchImageContents(imagesReference, mergeAllChannels, true);
-        return calculateSingleImages(imageContents, true, mergeAllChannels);
+        ImageTransformationParams transformationInfo =
+                new ImageTransformationParams(true, mergeAllChannels, null);
+        return calculateSingleImagesForDisplay(imageContents, transformationInfo);
     }
 
     private static RequestedImageSize getSize(BufferedImage img, boolean highQuality)
@@ -187,11 +189,19 @@ public class ImageChannelsUtils
 
     private static BufferedImage calculateBufferedImage(
             DatasetAcquiredImagesReference imageChannels,
+            String singleChannelTransformationCodeOrNull,
             IDatasetDirectoryProvider datasetDirectoryProvider, RequestedImageSize imageSizeLimit)
     {
         ImageChannelsUtils imageChannelsUtils =
                 createImageChannelsUtils(imageChannels, datasetDirectoryProvider, imageSizeLimit);
-        return imageChannelsUtils.calculateBufferedImage(imageChannels, true);
+
+        boolean useMergedChannelsTransformation =
+                imageChannelsUtils.isMergeAllChannels(imageChannels);
+        ImageTransformationParams transformationInfo =
+                new ImageTransformationParams(true, useMergedChannelsTransformation,
+                        singleChannelTransformationCodeOrNull);
+
+        return imageChannelsUtils.calculateBufferedImage(imageChannels, transformationInfo);
     }
 
     private static ImageChannelsUtils createImageChannelsUtils(
@@ -205,50 +215,29 @@ public class ImageChannelsUtils
 
     @Private
     BufferedImage calculateBufferedImage(DatasetAcquiredImagesReference imageChannels,
-            boolean transform)
+            ImageTransformationParams transformationInfo)
     {
         boolean mergeAllChannels = isMergeAllChannels(imageChannels);
         List<AbsoluteImageReference> imageContents =
                 fetchImageContents(imageChannels, mergeAllChannels, false);
-        return mergeChannels(imageContents, transform, mergeAllChannels);
+        return calculateBufferedImage(imageContents, transformationInfo);
     }
 
-    // Check if all exiting channels of the image should be merged (and not a single one or a
-    // subset).
-    // We want to treat the case where merged channels were requested in the same way as the case
-    // where all channel names have been enumerated.
     private boolean isMergeAllChannels(DatasetAcquiredImagesReference imageChannels)
     {
-        if (imageChannels.isMergeAllChannels())
-        {
-            return true;
-        }
-        List<String> wantedChannelCodes = imageChannels.getChannelCodes();
-        List<String> allChannelsCodes = imageAccessor.getImageParameters().getChannelsCodes();
-        if (allChannelsCodes.size() == 1)
-        {
-            return false; // there is only one channel in total, single channel transformation
-                          // should be used
-        }
-        for (String existingChannelCode : allChannelsCodes)
-        {
-            if (wantedChannelCodes.indexOf(existingChannelCode) == -1)
-            {
-                return false;
-            }
-        }
-        return true;
+        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
      */
     private List<AbsoluteImageReference> fetchImageContents(
             DatasetAcquiredImagesReference imagesReference, boolean mergeAllChannels,
             boolean skipNonExisting)
     {
-        List<String> channelCodes = getChannelCodes(imagesReference);
+        List<String> channelCodes = imagesReference.getChannelCodes(getAllChannelCodes());
         List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>();
         for (String channelCode : channelCodes)
         {
@@ -289,17 +278,6 @@ public class ImageChannelsUtils
         return HCSImageDatasetLoaderFactory.create(datasetRoot, datasetCode);
     }
 
-    private List<String> getChannelCodes(DatasetAcquiredImagesReference imagesReference)
-    {
-        if (imagesReference.isMergeAllChannels())
-        {
-            return imageAccessor.getImageParameters().getChannelsCodes();
-        } else
-        {
-            return imagesReference.getChannelCodes();
-        }
-    }
-
     /**
      * Returns content of the image which is representative for the given dataset.
      */
@@ -311,7 +289,9 @@ public class ImageChannelsUtils
         List<AbsoluteImageReference> imageReferences =
                 new ImageChannelsUtils(imageAccessor, imageSizeLimitOrNull)
                         .getRepresentativeImageReferences(wellLocationOrNull);
-        BufferedImage image = mergeChannels(imageReferences, true, true);
+        BufferedImage image =
+                calculateBufferedImage(imageReferences, new ImageTransformationParams(true, true,
+                        null));
         String name = createFileName(datasetCode, wellLocationOrNull, imageSizeLimitOrNull);
         return createResponseContentStream(image, name);
     }
@@ -344,15 +324,12 @@ public class ImageChannelsUtils
      */
     public static IContent getImage(IImagingDatasetLoader imageAccessor,
             ImageChannelStackReference channelStackReference, String chosenChannelCode,
-            Size imageSizeLimitOrNull, boolean convertToPng)
+            Size imageSizeLimitOrNull, String singleChannelImageTransformationCodeOrNull,
+            boolean convertToPng, boolean transform)
     {
-        String datasetCode = imageAccessor.getImageParameters().getDatasetCode();
-        boolean isMergedChannels =
-                ScreeningConstants.MERGED_CHANNELS.equalsIgnoreCase(chosenChannelCode);
-        List<String> channelCodes = isMergedChannels ? null : Arrays.asList(chosenChannelCode);
-
         DatasetAcquiredImagesReference imagesReference =
-                new DatasetAcquiredImagesReference(datasetCode, channelStackReference, channelCodes);
+                createDatasetAcquiredImagesReference(imageAccessor, channelStackReference,
+                        chosenChannelCode);
 
         ImageChannelsUtils imageChannelsUtils =
                 new ImageChannelsUtils(imageAccessor, imageSizeLimitOrNull);
@@ -365,38 +342,29 @@ public class ImageChannelsUtils
         {
             return rawContent;
         }
-        BufferedImage image = mergeChannels(imageContents, false, mergeAllChannels);
+        ImageTransformationParams transformationInfo =
+                new ImageTransformationParams(transform, mergeAllChannels,
+                        singleChannelImageTransformationCodeOrNull);
+        BufferedImage image = calculateBufferedImage(imageContents, transformationInfo);
         return createPngContent(image, null);
     }
 
-    /**
-     * @return an image for the specified tile in the specified size and for the requested channel.
-     */
-    public static IContent getImage(IImagingDatasetLoader imageAccessor,
-            ImageChannelStackReference channelStackReference, String chosenChannelCode,
-            Size imageSizeLimitOrNull, boolean convertToPng, boolean transform)
+    private static DatasetAcquiredImagesReference createDatasetAcquiredImagesReference(
+            IImagingDatasetLoader imageAccessor, ImageChannelStackReference channelStackReference,
+            String chosenChannelCode)
     {
         String datasetCode = imageAccessor.getImageParameters().getDatasetCode();
         boolean isMergedChannels =
                 ScreeningConstants.MERGED_CHANNELS.equalsIgnoreCase(chosenChannelCode);
-        List<String> channelCodes = isMergedChannels ? null : Arrays.asList(chosenChannelCode);
-
-        DatasetAcquiredImagesReference imagesReference =
-                new DatasetAcquiredImagesReference(datasetCode, channelStackReference, channelCodes);
-
-        ImageChannelsUtils imageChannelsUtils =
-                new ImageChannelsUtils(imageAccessor, imageSizeLimitOrNull);
-        boolean mergeAllChannels = imageChannelsUtils.isMergeAllChannels(imagesReference);
-        List<AbsoluteImageReference> imageContents =
-                imageChannelsUtils.fetchImageContents(imagesReference, mergeAllChannels, false);
-
-        IContent rawContent = tryGetRawContent(convertToPng, imageContents);
-        if (rawContent != null)
+        if (isMergedChannels)
         {
-            return rawContent;
+            return DatasetAcquiredImagesReference.createForMergedChannels(datasetCode,
+                    channelStackReference);
+        } else
+        {
+            return DatasetAcquiredImagesReference.createForSingleChannel(datasetCode,
+                    channelStackReference, chosenChannelCode);
         }
-        BufferedImage image = mergeChannels(imageContents, transform, mergeAllChannels);
-        return createPngContent(image, null);
     }
 
     // optimization: if there is exactly one image reference, maybe its original raw content is the
@@ -417,7 +385,7 @@ public class ImageChannelsUtils
     {
         List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>();
 
-        for (String chosenChannel : imageAccessor.getImageParameters().getChannelsCodes())
+        for (String chosenChannel : getAllChannelCodes())
         {
             AbsoluteImageReference image =
                     getRepresentativeImageReference(chosenChannel, wellLocationOrNull);
@@ -426,6 +394,11 @@ public class ImageChannelsUtils
         return images;
     }
 
+    private List<String> getAllChannelCodes()
+    {
+        return imageAccessor.getImageParameters().getChannelsCodes();
+    }
+
     /**
      * @throw {@link EnvironmentFailureException} when image does not exist
      */
@@ -451,18 +424,19 @@ public class ImageChannelsUtils
      * @param useMergedChannelsTransformation sometimes we can have a single image which contain all
      *            channels merged. In this case a different transformation will be applied to it.
      */
-    private static BufferedImage calculateAndTransformSingleImage(
-            AbsoluteImageReference imageReference, boolean transform,
-            boolean useMergedChannelsTransformation)
+    private static BufferedImage calculateAndTransformSingleImageForDisplay(
+            AbsoluteImageReference imageReference, ImageTransformationParams transformationInfo)
     {
         BufferedImage image = calculateSingleImage(imageReference);
-        return transform(image, imageReference, transform, useMergedChannelsTransformation);
+        image = transform(image, imageReference, transformationInfo);
+        image = ImageUtil.convertForDisplayIfNecessary(image);
+        return image;
     }
 
     private static BufferedImage calculateSingleImage(AbsoluteImageReference imageReference)
     {
         long start = operationLog.isDebugEnabled() ? System.currentTimeMillis() : 0;
-        BufferedImage image = imageReference.getImage();
+        BufferedImage image = imageReference.getUnchangedImage();
         if (operationLog.isDebugEnabled())
         {
             operationLog.debug("Load original image: " + (System.currentTimeMillis() - start));
@@ -504,29 +478,51 @@ public class ImageChannelsUtils
      * @param allChannelsMerged if true then we use one special transformation on the merged images
      *            instead of transforming every single image.
      */
-    private static BufferedImage mergeChannels(List<AbsoluteImageReference> imageReferences,
-            boolean transform, boolean allChannelsMerged)
+    private static BufferedImage calculateBufferedImage(
+            List<AbsoluteImageReference> imageReferences,
+            ImageTransformationParams transformationInfo)
     {
         AbsoluteImageReference singleImageReference = imageReferences.get(0);
         if (imageReferences.size() == 1)
         {
-            return calculateAndTransformSingleImage(singleImageReference, transform,
-                    allChannelsMerged);
+            return calculateAndTransformSingleImageForDisplay(singleImageReference,
+                    transformationInfo);
         } else
         {
-            // We do not transform single images here.
-            // The 'merged channels' transformation will be applied later.
-            List<ImageWithReference> images =
-                    calculateSingleImages(imageReferences, false, allChannelsMerged);
-            BufferedImage mergedImage = mergeImages(images);
-            // NOTE: even if we are not merging all the channels but just few of them we use the
-            // merged-channel transformation
-            return transform(mergedImage, singleImageReference, transform, true);
+            return mergeChannels(imageReferences, transformationInfo, singleImageReference);
         }
     }
 
+    private static BufferedImage mergeChannels(List<AbsoluteImageReference> imageReferences,
+            ImageTransformationParams transformationInfo,
+            AbsoluteImageReference singleImageReference)
+    {
+        // We do not transform single images here.
+        // The 'merged channels' transformation will be applied later.
+        List<ImageWithReference> images = calculateSingleImagesForDisplay(imageReferences, null);
+        BufferedImage mergedImage = mergeImages(images);
+        // NOTE: even if we are not merging all the channels but just few of them we use the
+        // merged-channel transformation
+
+        // TODO 2011-09-13, Tomasz Pylak: it looks like we are applying image level
+        // transformation from a random single channel to merged images. Replace with following
+        // code after testing:
+
+        // if (transformationInfo.isApplyNonImageLevelTransformation())
+        // {
+        // IImageTransformerFactory transformationOrNull =
+        // singleImageReference.getImageTransfomationFactories().tryGetForMerged();
+        // mergedImage =
+        // applyImageTransformation(mergedImage, transformationOrNull);
+        // }
+        // return mergedImage;
+
+        return transform(mergedImage, singleImageReference,
+                transformationInfo.cloneAndSetUseMergedChannelsTransformation());
+    }
+
     private static BufferedImage transform(BufferedImage image,
-            AbsoluteImageReference imageReference, boolean transform, boolean allChannelsMerged)
+            AbsoluteImageReference imageReference, ImageTransformationParams transformationInfo)
     {
         BufferedImage resultImage = image;
         ImageTransfomationFactories transfomations =
@@ -535,13 +531,21 @@ public class ImageChannelsUtils
         // external image viewer
         resultImage = applyImageTransformation(resultImage, transfomations.tryGetForImage());
 
-        if (transform == false)
+        if (transformationInfo.isApplyNonImageLevelTransformation() == false)
         {
             return resultImage;
         }
-        IImageTransformerFactory channelLevelTransformation =
-                transfomations.tryGetForChannel(allChannelsMerged);
-        return applyImageTransformation(resultImage, channelLevelTransformation);
+        IImageTransformerFactory channelLevelTransformationOrNull;
+        if (transformationInfo.isUseMergedChannelsTransformation())
+        {
+            channelLevelTransformationOrNull = transfomations.tryGetForMerged();
+        } else
+        {
+            channelLevelTransformationOrNull =
+                    transfomations.tryGetForChannel(transformationInfo
+                            .tryGetSingleChannelTransformationCode());
+        }
+        return applyImageTransformation(resultImage, channelLevelTransformationOrNull);
     }
 
     private static BufferedImage applyImageTransformation(BufferedImage image,
@@ -577,24 +581,28 @@ public class ImageChannelsUtils
         }
     }
 
-    /** @param useMergedChannelsTransformation used only if transformEachImage is true */
-    private static List<ImageWithReference> calculateSingleImages(
-            List<AbsoluteImageReference> imageReferences, boolean transformEachImage,
-            boolean useMergedChannelsTransformation)
+    /**
+     * @param transformationInfoOrNull if null all transformations (including image-level) will be
+     *            skipped
+     */
+    private static List<ImageWithReference> calculateSingleImagesForDisplay(
+            List<AbsoluteImageReference> imageReferences,
+            ImageTransformationParams transformationInfoOrNull)
     {
         List<ImageWithReference> images = new ArrayList<ImageWithReference>();
         for (AbsoluteImageReference imageRef : imageReferences)
         {
             BufferedImage image;
-            if (transformEachImage)
+            if (transformationInfoOrNull != null)
             {
                 image =
-                        calculateAndTransformSingleImage(imageRef, true,
-                                useMergedChannelsTransformation);
+                        calculateAndTransformSingleImageForDisplay(imageRef,
+                                transformationInfoOrNull);
             } else
             {
+                // NOTE: here we skip image level transformations as well
                 image = calculateSingleImage(imageRef);
-
+                image = ImageUtil.convertForDisplayIfNecessary(image);
             }
             images.add(new ImageWithReference(image, imageRef));
         }
@@ -636,11 +644,12 @@ public class ImageChannelsUtils
         return (i1OrNull == null) ? (i2OrNull == null) : i1OrNull.equals(i2OrNull);
     }
 
+    // this method always returns RGB images, even if the input was in grayscale
     private static BufferedImage mergeImages(List<ImageWithReference> images)
     {
         assert images.size() > 1 : "more than 1 image expected, but found: " + images.size();
 
-        BufferedImage newImage = createNewImage(images.get(0).getBufferedImage());
+        BufferedImage newImage = createNewRGBImage(images.get(0).getBufferedImage());
         int width = newImage.getWidth();
         int height = newImage.getHeight();
         int colorBuffer[] = new int[4];
@@ -795,7 +804,7 @@ public class ImageChannelsUtils
     private static BufferedImage transformToChannel(BufferedImage bufferedImage,
             ColorComponent colorComponent)
     {
-        BufferedImage newImage = createNewImage(bufferedImage);
+        BufferedImage newImage = createNewRGBImage(bufferedImage);
         int width = bufferedImage.getWidth();
         int height = bufferedImage.getHeight();
         for (int x = 0; x < width; x++)
@@ -803,7 +812,7 @@ public class ImageChannelsUtils
             for (int y = 0; y < height; y++)
             {
                 int rgb = bufferedImage.getRGB(x, y);
-                int channelColor = getGrayscaleAsChannel(rgb, colorComponent);
+                int channelColor = extractSingleComponent(rgb, colorComponent);
                 newImage.setRGB(x, y, channelColor);
             }
         }
@@ -812,7 +821,7 @@ public class ImageChannelsUtils
 
     // NOTE: drawing on this image will not preserve transparency - but we do not need it and the
     // image is smaller
-    private static BufferedImage createNewImage(RenderedImage bufferedImage)
+    private static BufferedImage createNewRGBImage(RenderedImage bufferedImage)
     {
         BufferedImage newImage =
                 new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(),
@@ -848,9 +857,9 @@ public class ImageChannelsUtils
         }
     }
 
-    // we assume that the color was in a grayscale
-    // we reset all ingredients besides the one which should be shown
-    private static int getGrayscaleAsChannel(int rgb, ColorComponent colorComponent)
+    // 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();
     }
@@ -866,10 +875,10 @@ public class ImageChannelsUtils
         return new Color(rgb[0], rgb[1], rgb[2], rgb[3]).getRGB();
     }
 
-    
     private static IContent createPngContent(BufferedImage image, String nameOrNull)
     {
         final byte[] output = ImageUtil.imageToPngFast(image);
         return new ByteArrayBasedContent(output, nameOrNull);
     }
+
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java
index 10d3e10d786fa058913a412fd0fa2c2d15715116..4113176606cb05b9d0d3a051b19af3210ede4f2b 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.openbis.dss.generic.server.images.dto;
 
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -29,10 +30,30 @@ public class DatasetAcquiredImagesReference
 
     private final ImageChannelStackReference channelStackReference;
 
+    /** Can be null if merged channels are requested. */
     private final List<String> channelCodesOrNull;
 
+    public static DatasetAcquiredImagesReference createForMergedChannels(String datasetCode,
+            ImageChannelStackReference channelStackReference)
+    {
+        return new DatasetAcquiredImagesReference(datasetCode, channelStackReference, null);
+    }
+
+    public static DatasetAcquiredImagesReference createForSingleChannel(String datasetCode,
+            ImageChannelStackReference channelStackReference, String channelCode)
+    {
+        return new DatasetAcquiredImagesReference(datasetCode, channelStackReference,
+                Arrays.asList(channelCode));
+    }
+
+    public static DatasetAcquiredImagesReference createForManyChannels(String datasetCode,
+            ImageChannelStackReference channelStackReference, List<String> channelCodes)
+    {
+        return new DatasetAcquiredImagesReference(datasetCode, channelStackReference, channelCodes);
+    }
+
     /** @param channelCodesOrNull null if channels should be merged */
-    public DatasetAcquiredImagesReference(String datasetCode,
+    private DatasetAcquiredImagesReference(String datasetCode,
             ImageChannelStackReference channelStackReference, List<String> channelCodesOrNull)
     {
         assert datasetCode != null;
@@ -53,14 +74,42 @@ public class DatasetAcquiredImagesReference
         return channelStackReference;
     }
 
-    public boolean isMergeAllChannels()
+    /**
+     * Check if all exiting channels of the image should be merged (and not a single one or a
+     * subset). We want to treat the case where merged channels were requested in the same way as
+     * the case where all channel names have been enumerated.
+     */
+    public boolean isMergeAllChannels(List<String> allChannelsCodes)
     {
-        return channelCodesOrNull == null;
+        if (channelCodesOrNull == null)
+        {
+            return true;
+        }
+        List<String> wantedChannelCodes = channelCodesOrNull;
+        if (allChannelsCodes.size() == 1)
+        {
+            return false; // there is only one channel in total, single channel transformation
+                          // should be used
+        }
+        for (String existingChannelCode : allChannelsCodes)
+        {
+            if (wantedChannelCodes.indexOf(existingChannelCode) == -1)
+            {
+                return false;
+            }
+        }
+        return true;
     }
 
-    public List<String> getChannelCodes()
+    public List<String> getChannelCodes(List<String> allChannelsCodes)
     {
-        return channelCodesOrNull;
+        if (channelCodesOrNull == null)
+        {
+            return allChannelsCodes;
+        } else
+        {
+            return channelCodesOrNull;
+        }
     }
 
     @Override
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java
index a397574ed0609ecf3ca79797f090c25df132dde8..959b93a38a5a2b3288cd22d5651c9f6bbcc30792 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java
@@ -32,6 +32,8 @@ public class ImageGenerationDescription
 {
     private final DatasetAcquiredImagesReference imageChannelsOrNull;
 
+    private final String singleChannelTransformationCodeOrNull;
+
     private final List<DatasetAcquiredImagesReference> overlayChannels;
 
     private final String sessionId;
@@ -40,10 +42,12 @@ public class ImageGenerationDescription
     private final Size thumbnailSizeOrNull;
 
     public ImageGenerationDescription(DatasetAcquiredImagesReference imageChannelsOrNull,
+            String singleChannelTransformationCodeOrNull,
             List<DatasetAcquiredImagesReference> overlayChannels, String sessionId,
             Size thumbnailSizeOrNull)
     {
         this.imageChannelsOrNull = imageChannelsOrNull;
+        this.singleChannelTransformationCodeOrNull = singleChannelTransformationCodeOrNull;
         this.overlayChannels = overlayChannels;
         this.sessionId = sessionId;
         this.thumbnailSizeOrNull = thumbnailSizeOrNull;
@@ -54,6 +58,11 @@ public class ImageGenerationDescription
         return imageChannelsOrNull;
     }
 
+    public String tryGetSingleChannelTransformationCode()
+    {
+        return singleChannelTransformationCodeOrNull;
+    }
+
     public List<DatasetAcquiredImagesReference> getOverlayChannels()
     {
         return overlayChannels;
@@ -116,10 +125,17 @@ public class ImageGenerationDescription
 
     private static void appendChannels(StringBuffer sb, DatasetAcquiredImagesReference imagesRef)
     {
-        for (String channel : imagesRef.getChannelCodes())
+        List<String> channelCodes = imagesRef.getChannelCodes(null);
+        if (channelCodes == null)
         {
-            sb.append("_");
-            sb.append(channel);
+            sb.append("merged");
+        } else
+        {
+            for (String channel : channelCodes)
+            {
+                sb.append("_");
+                sb.append(channel);
+            }
         }
     }
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageTransformationParams.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageTransformationParams.java
new file mode 100644
index 0000000000000000000000000000000000000000..2cc933d370a8cc22799fd3b348ab59022ac53555
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageTransformationParams.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * 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.dto;
+
+/**
+ * Describes which image transformations should be applied. Note that image-level transformation is
+ * always applied for single channels.
+ * 
+ * @author Tomasz Pylak
+ */
+public class ImageTransformationParams
+{
+    private final boolean applyNonImageLevelTransformation;
+
+    /** ignored if {@link #applyNonImageLevelTransformation} is false */
+    private final boolean useMergedChannelsTransformation;
+
+    /**
+     * ignored if {@link #applyNonImageLevelTransformation} is false or if
+     * {@link #useMergedChannelsTransformation} is false
+     */
+    private final String singleChannelTransformationCodeOrNull;
+
+    public ImageTransformationParams(boolean applyNonImageLevelTransformation,
+            boolean useMergedChannelsTransformation, String singleChannelTransformationCodeOrNull)
+    {
+        this.applyNonImageLevelTransformation = applyNonImageLevelTransformation;
+        this.useMergedChannelsTransformation = useMergedChannelsTransformation;
+        this.singleChannelTransformationCodeOrNull = singleChannelTransformationCodeOrNull;
+    }
+
+    public boolean isApplyNonImageLevelTransformation()
+    {
+        return applyNonImageLevelTransformation;
+    }
+
+    public boolean isUseMergedChannelsTransformation()
+    {
+        return useMergedChannelsTransformation;
+    }
+
+    public String tryGetSingleChannelTransformationCode()
+    {
+        return singleChannelTransformationCodeOrNull;
+    }
+
+    public ImageTransformationParams cloneAndSetUseMergedChannelsTransformation()
+    {
+        return new ImageTransformationParams(applyNonImageLevelTransformation, true,
+                singleChannelTransformationCodeOrNull);
+    }
+
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java
index 61c80b2b2e8a285ac907fc7f8507883356b7ad4d..84be648d935c31d0e63f855d24e43943c3d3a436 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java
@@ -153,7 +153,6 @@ public class ColorRangeCalculator
         }
 
         ReadParams params = new ReadParams();
-        params.setAllow16BitGrayscaleModel(true);
         return reader.readImage(file, ImageID.NULL, params);
     }
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java
index 944ecccc43086bd82de0a257eb6ba16ac04d119a..4842599c0c6126be2232d309522cd66cf16be775 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java
@@ -91,7 +91,7 @@ public class DynamixWellBrightnessEqualizerProcessingPlugin extends
     {
         IContent content = contentRepository.getContent(image.getFilePath());
         return ImageUtil.loadJavaAdvancedImagingTiff(content.getReadOnlyRandomAccessFile(),
-                ImageUtil.parseImageID(image.getImageID()), true);
+                ImageUtil.parseImageID(image.getImageID()));
     }
 
     private static IImageTransformerFactoryProvider createImageTransformerFactoryProvider(
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java
index 2ccee7a932e492e658d5f06fe922005026593e02..97716ac006063cc0ebcc5ca94158389096575970 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java
@@ -28,8 +28,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import net.lemnik.eodsql.QueryTool;
-
 import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
 
 import ch.systemsx.cisd.base.image.IImageTransformerFactory;
@@ -84,10 +82,11 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.FeatureVectorLoa
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.FeatureVectorLoader.WellFeatureCollection;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingTransformerDAO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureDefDTO;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.TransformerFactoryMapper;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO;
 
 /**
  * Implementation of the screening API interface using RPC. The instance will be created in spring
@@ -104,12 +103,6 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
      */
     public static final int MINOR_VERSION = 8;
 
-
-    static
-    {
-        QueryTool.getTypeMap().put(IImageTransformerFactory.class, new TransformerFactoryMapper());
-    }
-
     // this dao will hold one connection to the database
     private IImagingReadonlyQueryDAO dao;
 
@@ -282,7 +275,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                     imageAccessor.tryGetRepresentativeImage(channelCode, null, originalOrThumbnail);
             if (image != null)
             {
-                return image.getImage();
+                return image.getUnchangedImage();
             }
         }
         throw new IllegalStateException("Cannot find any image in a dataset: " + dataset);
@@ -298,7 +291,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                     imageAccessor.tryGetRepresentativeThumbnail(channelCode, null);
             if (image != null)
             {
-                return image.getImage();
+                return image.getUnchangedImage();
             }
         }
         return null;
@@ -319,7 +312,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                                     col, row));
                     if (image != null)
                     {
-                        return image.getImage();
+                        return image.getUnchangedImage();
                     }
                 }
             }
@@ -343,7 +336,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                                     row), originalOrThumbnail);
                     if (image != null)
                     {
-                        return image.getImage();
+                        return image.getUnchangedImage();
                     }
                 }
             }
@@ -461,13 +454,13 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
     public InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences,
             boolean convertToPng)
     {
-        return loadImages(sessionToken, imageReferences, null, convertToPng);
+        return loadImages(sessionToken, imageReferences, null, null, convertToPng);
     }
 
     public InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences,
             ImageSize thumbnailSizeOrNull)
     {
-        return loadImages(sessionToken, imageReferences, tryAsSize(thumbnailSizeOrNull), true);
+        return loadImages(sessionToken, imageReferences, tryAsSize(thumbnailSizeOrNull), null, true);
     }
 
     public InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences,
@@ -476,20 +469,24 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
         final Map<String, IImagingDatasetLoader> imageLoadersMap =
                 getImageDatasetsMap(sessionToken, imageReferences);
         return loadImages(imageReferences, tryAsSize(configuration.getDesiredImageSize()),
+                configuration.getSingleChannelImageTransformationCode(),
                 configuration.isDesiredImageFormatPng(),
                 configuration.isOpenBisImageTransformationApplied(), imageLoadersMap);
     }
 
     private InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences,
-            final Size sizeOrNull, final boolean convertToPng)
+            final Size sizeOrNull, String singleChannelImageTransformationCodeOrNull,
+            final boolean convertToPng)
     {
         final Map<String, IImagingDatasetLoader> imageLoadersMap =
                 getImageDatasetsMap(sessionToken, imageReferences);
-        return loadImages(imageReferences, sizeOrNull, convertToPng, false, imageLoadersMap);
+        return loadImages(imageReferences, sizeOrNull, singleChannelImageTransformationCodeOrNull,
+                convertToPng, false, imageLoadersMap);
     }
 
     private InputStream loadImages(List<PlateImageReference> imageReferences,
-            final Size sizeOrNull, final boolean convertToPng, final boolean transform,
+            final Size sizeOrNull, final String singleChannelImageTransformationCodeOrNull,
+            final boolean convertToPng, final boolean transform,
             final Map<String, IImagingDatasetLoader> imageLoadersMap)
     {
         final List<IContent> imageContents = new ArrayList<IContent>();
@@ -508,7 +505,8 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                     public IContent getContent()
                     {
                         return tryGetImageContent(imageAccessor, channelStackRef, channelCode,
-                                sizeOrNull, convertToPng, transform);
+                                sizeOrNull, singleChannelImageTransformationCodeOrNull,
+                                convertToPng, transform);
                     }
                 }));
         }
@@ -558,7 +556,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                 new HashMap<String, IImagingDatasetLoader>();
         imageLoadersMap.put(dataSetIdentifier.getDatasetCode(), imageAccessor);
 
-        return loadImages(imageReferences, size, true, false, imageLoadersMap);
+        return loadImages(imageReferences, size, null, true, false, imageLoadersMap);
     }
 
     public InputStream loadThumbnailImages(String sessionToken,
@@ -590,7 +588,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                     public IContent getContent()
                     {
                         return tryGetImageContent(imageAccessor, channelStackRef, channelCode,
-                                sizeOrNull, true, false);
+                                sizeOrNull, null, true, false);
                     }
                 }));
         }
@@ -720,6 +718,13 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
         return ScreeningConstants.MERGED_CHANNELS.equals(channel);
     }
 
+    static final String IMAGE_VIEWER_TRANSFORMATION_CODE = "_CUSTOM";
+
+    private static final String IMAGE_VIEWER_TRANSFORMATION_LABEL = "Custom";
+
+    private static final String IMAGE_VIEWER_TRANSFORMATION_DESCRIPTION =
+            "Custom image transformation defined with the Color Adjustment tool.";
+
     private void saveImageTransformerFactoryForExperiment(long experimentId, String channel,
             IImageTransformerFactory transformerFactory)
     {
@@ -738,11 +743,36 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                 operationLog.info("save image transformer factory " + transformerFactory
                         + " for experiment " + experimentId + " and channel '" + channel + "'.");
             }
-            transformerDAO.saveTransformerFactoryForExperimentChannel(experimentId, channel,
-                    transformerFactory);
+            Long channelId = transformerDAO.getExperimentChannelId(experimentId, channel);
+            createOrUpdateImageViewerTransformation(channelId, transformerFactory);
+        }
+    }
+
+    private void createOrUpdateImageViewerTransformation(long channelId,
+            IImageTransformerFactory transformerFactory)
+    {
+        Long transformationIdOrNull =
+                transformerDAO.tryGetImageTransformationId(channelId,
+                        IMAGE_VIEWER_TRANSFORMATION_CODE);
+        if (transformationIdOrNull == null)
+        {
+            transformerDAO.addImageTransformation(createImageViewerTransformation(channelId,
+                    transformerFactory));
+        } else
+        {
+            transformerDAO
+                    .updateImageTransformerFactory(transformationIdOrNull, transformerFactory);
         }
     }
 
+    private ImgImageTransformationDTO createImageViewerTransformation(long channelId,
+            IImageTransformerFactory transformerFactory)
+    {
+        return new ImgImageTransformationDTO(IMAGE_VIEWER_TRANSFORMATION_CODE,
+                IMAGE_VIEWER_TRANSFORMATION_LABEL, IMAGE_VIEWER_TRANSFORMATION_DESCRIPTION, true,
+                channelId, transformerFactory);
+    }
+
     private void saveImageTransformerFactoryForDataset(long datasetId, String channel,
             IImageTransformerFactory transformerFactory)
     {
@@ -761,8 +791,8 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                 operationLog.info("save image transformer factory " + transformerFactory
                         + " for dataset " + datasetId + " and channel '" + channel + "'.");
             }
-            transformerDAO.saveTransformerFactoryForDatasetChannel(datasetId, channel,
-                    transformerFactory);
+            long channelId = transformerDAO.getDatasetChannelId(datasetId, channel);
+            createOrUpdateImageViewerTransformation(channelId, transformerFactory);
         }
     }
 
@@ -818,11 +848,22 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
             return dataset.tryGetImageTransformerFactory();
         } else
         {
-            return getDAO().tryGetChannelForDataset(dataset.getId(), channel)
-                    .tryGetImageTransformerFactory();
+            long channelId = getDAO().getDatasetChannelId(dataset.getId(), channel);
+            return tryGetImageViewerTransformation(channelId);
         }
     }
 
+    private IImageTransformerFactory tryGetImageViewerTransformation(long channelId)
+    {
+        ImgImageTransformationDTO transformation =
+                getDAO().tryGetImageTransformation(channelId, IMAGE_VIEWER_TRANSFORMATION_CODE);
+        if (transformation == null)
+        {
+            return null;
+        }
+        return transformation.getImageTransformerFactory();
+    }
+
     private IImageTransformerFactory tryGetImageTransformerFactoryForExperiment(
             String experimentPermID, String channel)
     {
@@ -832,8 +873,11 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
                     .tryGetImageTransformerFactory();
         } else
         {
-            return getDAO().tryGetChannelForExperimentPermId(experimentPermID, channel)
-                    .tryGetImageTransformerFactory();
+            ImgChannelDTO channelDTO =
+                    getDAO().tryGetChannelForExperimentPermId(experimentPermID, channel);
+            assert channelDTO != null : String.format("No channel '%s' for experiment '%s'.",
+                    channel, experimentPermID);
+            return tryGetImageViewerTransformation(channelDTO.getId());
         }
     }
 
@@ -982,12 +1026,14 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc
 
     private IContent tryGetImageContent(IImagingDatasetLoader imageAccessor,
             final ImageChannelStackReference channelStackReference, String channelCode,
-            Size thumbnailSizeOrNull, boolean convertToPng, boolean transform)
+            Size thumbnailSizeOrNull, String singleChannelImageTransformationCodeOrNull,
+            boolean convertToPng, boolean transform)
     {
         try
         {
             return ImageChannelsUtils.getImage(imageAccessor, channelStackReference, channelCode,
-                    thumbnailSizeOrNull, convertToPng, transform);
+                    thumbnailSizeOrNull, singleChannelImageTransformationCodeOrNull, convertToPng,
+                    transform);
         } catch (EnvironmentFailureException e)
         {
             operationLog.error("Error reading image.", e);
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java
index 769f472d0354472d75cfc5dab0c2b06740e31d8d..e27b7078e43058421c1c11cd23d58cd230a42558 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java
@@ -43,6 +43,8 @@ public class LoadImageConfiguration implements Serializable
 
     private boolean openBisImageTransformationApplied = false;
 
+    private String singleChannelImageTransformationCodeOrNull = null;
+
     /**
      * The desired size of the image. Null if the original size is requested.
      * <p>
@@ -93,7 +95,8 @@ public class LoadImageConfiguration implements Serializable
     }
 
     /**
-     * Should the image transformation stored in openBIS be applied to the image?
+     * Should the image transformation stored in openBIS for a channel or merged channel be applied
+     * to the image?
      * 
      * @return True if the image transformation should be applied; false if the original image
      *         should be returned.
@@ -104,7 +107,8 @@ public class LoadImageConfiguration implements Serializable
     }
 
     /**
-     * Set whether the image transformation stored in openBIS should be applied.
+     * Set whether the image transformation stored in openBIS for a channel or merged channel should
+     * be applied.
      * 
      * @param openBisImageTransformationApplied Pass in true if the transformation should be
      *            applied; false otherwise.
@@ -112,6 +116,34 @@ public class LoadImageConfiguration implements Serializable
     public void setOpenBisImageTransformationApplied(boolean openBisImageTransformationApplied)
     {
         this.openBisImageTransformationApplied = openBisImageTransformationApplied;
+        if (openBisImageTransformationApplied && getSingleChannelImageTransformationCode() == null)
+        {
+            // for backward compatibility - apply Color Adjustment tool transformation
+            setSingleChannelImageTransformationCode("_CUSTOM");
+        }
+    }
+
+    /** Code of the transformation, which should be applied to the image. Can return null. */
+    public String getSingleChannelImageTransformationCode()
+    {
+        return singleChannelImageTransformationCodeOrNull;
+    }
+
+    /**
+     * Sets the code of the transformation, which should be applied to the image. This parameter
+     * will be ignored if {@link #isOpenBisImageTransformationApplied()} is false. To keep backward
+     * compatibility if {@link #isOpenBisImageTransformationApplied()} is true and no image
+     * transformation code is specified, then the transformation defined by the Color Adjustment
+     * tool will be applied.
+     * <p>
+     * Transformation has to be assigned (usually during dataset registration) to the channel which
+     * will be fetched. Note that such transformations can be channel-specific. If merged channels
+     * image will be requested or the transformation does not exist then untransformed image will be
+     * returned.
+     */
+    public void setSingleChannelImageTransformationCode(String transformationCodeOrNull)
+    {
+        this.singleChannelImageTransformationCodeOrNull = transformationCodeOrNull;
     }
 
     @Override
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java
index 20e40fac3e176e2c354f02a7a813abc1db403637..97f2e0987e4b4b78d22099c434a757f555f0f3ff 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java
@@ -20,10 +20,12 @@ import javax.sql.DataSource;
 
 import net.lemnik.eodsql.QueryTool;
 
+import ch.systemsx.cisd.base.image.IImageTransformerFactory;
 import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingTransformerDAO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.TransformerFactoryMapper;
 
 /**
  * Utility methods for DSS.
@@ -34,6 +36,11 @@ public class DssScreeningUtils
 {
     private static final IImagingReadonlyQueryDAO query = createQuery();
 
+    static
+    {
+        QueryTool.getTypeMap().put(IImageTransformerFactory.class, new TransformerFactoryMapper());
+    }
+
     /**
      * Returned query is reused and should NOT be closed.
      */
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java
index 57253aea57ee33f622af4b9503a1ab7c4e72fc4a..4812ae65a19a6fd1c99d59c8c8d3df0141e4a42c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java
@@ -116,4 +116,35 @@ public class ExampleImageTransformerFactory implements IImageTransformerFactory
         return getClass().getSimpleName() + "[" + colorPattern + "]";
     }
 
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + brightnessDelta;
+        result = prime * result + ((colorPattern == null) ? 0 : colorPattern.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ExampleImageTransformerFactory other = (ExampleImageTransformerFactory) obj;
+        if (brightnessDelta != other.brightnessDelta)
+            return false;
+        if (colorPattern == null)
+        {
+            if (other.colorPattern != null)
+                return false;
+        } else if (!colorPattern.equals(other.colorPattern))
+            return false;
+        return true;
+    }
+
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java
index 51115c7aab861bce9c99e6fda20a07252c3777c0..2bed12ee1400b19e6a3e22aeffe29b5d34b16975 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java
@@ -124,9 +124,11 @@ public class LogicalImageReference
         return datastoreCode;
     }
 
-    public String getTransformerFactorySignatureOrNull(String channelCode)
+    public String tryGetTransformerFactorySignature(String channelCodeOrNull,
+            String transformationCode)
     {
-        return imageParameters.getTransformerFactorySignatureOrNull(channelCode);
+        return imageParameters.tryGetTransformerFactorySignature(channelCodeOrNull,
+                transformationCode);
     }
 
     public List<DatasetOverlayImagesReference> getOverlayDatasets()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java
index 529dc03ff109a24faddcbf1117c68cf1c704a174..91534478d3f5e17bcd6f547227517f17f5df229c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java
@@ -146,6 +146,12 @@ public class ImageUrlUtils
         }
         methodWithParameters.addParameter(ImageServletUrlParameters.TILE_ROW_PARAM, tileRow);
         methodWithParameters.addParameter(ImageServletUrlParameters.TILE_COL_PARAM, tileCol);
+        if (channelReferences.tryGetImageTransformationCode() != null)
+        {
+            methodWithParameters.addParameter(
+                    ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM,
+                    channelReferences.tryGetImageTransformationCode());
+        }
         addImageTransformerSignature(methodWithParameters, channelReferences);
         String linkURLOrNull = createImageLinks ? methodWithParameters.toString() : null;
         addThumbnailSize(methodWithParameters, width, height);
@@ -170,7 +176,14 @@ public class ImageUrlUtils
         {
             for (String channel : channels)
             {
-                String signature = images.getTransformerFactorySignatureOrNull(channel);
+                String channelOrNull = channel;
+                if (channelOrNull.equalsIgnoreCase(ScreeningConstants.MERGED_CHANNELS))
+                {
+                    channelOrNull = null;
+                }
+                String signature =
+                        images.tryGetTransformerFactorySignature(channelOrNull,
+                                channelReferences.tryGetImageTransformationCode());
                 if (signature != null)
                 {
                     url.addParameter("transformerFactorySignature", signature);
@@ -203,7 +216,6 @@ public class ImageUrlUtils
                 methodWithParameters.addParameter(ImageServletUrlParameters.CHANNEL_PARAM, channel);
             }
         }
-                
 
         addOverlayParameters(channelReferences.getOverlayChannels(), methodWithParameters);
         return methodWithParameters;
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java
index e526f08518bae9d72dfd27492829742df86ab783..c3fd5d26526a66cc195801196bc392a859a145d3 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java
@@ -16,7 +16,6 @@
 
 package ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto;
 
-import java.util.ArrayList;
 import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
@@ -51,9 +50,9 @@ public class ImageChannel implements ISerializable
     {
     }
 
-    // FIXME add List<ImageTransformationInfo> availableImageTransformations param
     public ImageChannel(String code, String label, String description, Integer wavelength,
-            ImageChannelColor channelColor)
+            ImageChannelColor channelColor,
+            List<ImageTransformationInfo> availableImageTransformations)
     {
         assert code != null;
 //        assert availableImageTransformations != null;
@@ -63,7 +62,7 @@ public class ImageChannel implements ISerializable
         this.description = description;
         this.wavelength = wavelength;
         this.channelColor = channelColor;
-        this.availableImageTransformations = new ArrayList<ImageTransformationInfo>();
+        this.availableImageTransformations = availableImageTransformations;
     }
 
     public String getCode()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java
index c98e9a0bbaad8a61dc5ae1ad57484fe832ffa0ad..c00644bd8a0c0d420f465e8e2a50f2b0efd50ef9 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java
@@ -17,9 +17,7 @@
 package ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
@@ -47,12 +45,11 @@ public class ImageDatasetParameters implements ISerializable
 
     private List<ImageChannel> channels;
 
-    private Map<String/* channel code */, String/* signature */> channelsTransformerFactorySignatures =
-            new HashMap<String, String>();
-
     // true if any well in the dataset has a time series (or depth stack) of images
     private boolean isMultidimensional;
 
+    private String mergedChannelTransformerFactorySignatureOrNull;
+
     public Integer tryGetRowsNum()
     {
         return rowsNumOrNull;
@@ -161,13 +158,39 @@ public class ImageDatasetParameters implements ISerializable
         return channels;
     }
 
-    public void addTransformerFactorySignatureFor(String channelCode, String signatureOrNull)
+    /**
+     * @param channelCodeOrNull null for merged channels (transformationCode is ignored in that
+     *            case)
+     */
+    public String tryGetTransformerFactorySignature(String channelCodeOrNull,
+            String transformationCode)
+    {
+        if (channelCodeOrNull == null)
+        {
+            return mergedChannelTransformerFactorySignatureOrNull;
+        }
+        List<ImageTransformationInfo> transformations =
+                getAvailableImageTransformationsFor(channelCodeOrNull);
+        for (ImageTransformationInfo transformation : transformations)
+        {
+            if (transformation.getCode().equalsIgnoreCase(transformationCode))
+            {
+                return transformation.getTransformationSignature();
+            }
+        }
+        return null;
+    }
+
+    public String tryGetMergedChannelTransformerFactorySignature()
     {
-        channelsTransformerFactorySignatures.put(channelCode, signatureOrNull);
+        return mergedChannelTransformerFactorySignatureOrNull;
     }
 
-    public String getTransformerFactorySignatureOrNull(String channelCode)
+    public void setMergedChannelTransformerFactorySignature(
+            String mergedChannelTransformerFactorySignatureOrNull)
     {
-        return channelsTransformerFactorySignatures.get(channelCode);
+        this.mergedChannelTransformerFactorySignatureOrNull =
+                mergedChannelTransformerFactorySignatureOrNull;
     }
+
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java
index 625c47e1006d3fb53a407ed3dfa07ee850a4daa5..f1a4e3a326ea68d7f378d3fee2d2d19e7ed85dfe 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java
@@ -35,6 +35,8 @@ public class ImageTransformationInfo implements ISerializable
     // can be null
     private String description;
 
+    private String transformationSignature;
+
     private boolean isDefault;
 
     // GWT only
@@ -43,7 +45,8 @@ public class ImageTransformationInfo implements ISerializable
     {
     }
 
-    public ImageTransformationInfo(String code, String label, String description, boolean isDefault)
+    public ImageTransformationInfo(String code, String label, String description,
+            String transformationSignature, boolean isDefault)
     {
         assert code != null : "code is null";
         assert label != null : " label is null";
@@ -51,6 +54,7 @@ public class ImageTransformationInfo implements ISerializable
         this.code = code;
         this.label = label;
         this.description = description;
+        this.transformationSignature = transformationSignature;
         this.isDefault = isDefault;
     }
 
@@ -69,8 +73,18 @@ public class ImageTransformationInfo implements ISerializable
         return description;
     }
 
+    public String getTransformationSignature()
+    {
+        return transformationSignature;
+    }
+
     public boolean isDefault()
     {
         return isDefault;
     }
+
+    public void setDefault(boolean isDefault)
+    {
+        this.isDefault = isDefault;
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java
index b404115ca6df2a288f1bb0e1abbadde5cd42d26c..4cbe2e1c84db7e45d23cccff0e42059d603d3c7d 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java
@@ -178,6 +178,8 @@ public class ScreeningConstants
 
         public final static String WELL_COLUMN_PARAM = "wellCol";
 
+        public final static String SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM = "transformation";
+
         // -- mandatory servlet parameters
 
         public final static String DATASET_CODE_PARAM = "dataset";
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java
index a127fd776492d681289777686c9e27e2da04c440..a3ec275036596569e74bb46521a87ce339c36e44 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java
@@ -18,13 +18,17 @@ package ch.systemsx.cisd.openbis.plugin.screening.shared.imaging;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
+import ch.systemsx.cisd.common.collections.CollectionUtils;
 import ch.systemsx.cisd.common.utilities.MD5ChecksumCalculator;
+import ch.systemsx.cisd.openbis.generic.shared.basic.utils.GroupByMap;
+import ch.systemsx.cisd.openbis.generic.shared.basic.utils.IGroupKeyExtractor;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannel;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelStack;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageTransformationInfo;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO;
@@ -32,6 +36,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgCh
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgExperimentDTO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO;
 
 /**
  * Helper class for easy handling of HCS image dataset standard structure with no code for handling
@@ -46,8 +51,6 @@ public class HCSDatasetLoader implements IImageDatasetLoader
 
     protected final ImgDatasetDTO dataset;
 
-    private final String mergedChannelTransformerFactorySignatureOrNull;
-
     protected ImgContainerDTO containerOrNull;
 
     protected ImgExperimentDTO experimentOrNull;
@@ -56,6 +59,10 @@ public class HCSDatasetLoader implements IImageDatasetLoader
 
     protected List<ImgChannelDTO> channels;
 
+    protected Map<Long/* channel id */, List<ImgImageTransformationDTO>> availableImageTransformationsMap;
+
+    private final String mergedChannelTransformerFactorySignatureOrNull;
+
     public HCSDatasetLoader(IImagingReadonlyQueryDAO query, String datasetPermId)
     {
         this.query = query;
@@ -80,6 +87,26 @@ public class HCSDatasetLoader implements IImageDatasetLoader
         this.mergedChannelTransformerFactorySignatureOrNull =
                 tryGetImageTransformerFactorySignatureForMergedChannels();
         this.channels = loadChannels();
+        this.availableImageTransformationsMap = loadAvailableImageTransformations();
+    }
+
+    private Map<Long, List<ImgImageTransformationDTO>> loadAvailableImageTransformations()
+    {
+        List<ImgImageTransformationDTO> imageTransformations =
+                query.listImageTransformationsByDatasetId(dataset.getId());
+        if (imageTransformations.size() == 0 && containerOrNull != null)
+        {
+            imageTransformations =
+                    query.listImageTransformationsByExperimentId(containerOrNull.getExperimentId());
+        }
+        return GroupByMap.create(imageTransformations,
+                new IGroupKeyExtractor<Long, ImgImageTransformationDTO>()
+                    {
+                        public Long getKey(ImgImageTransformationDTO transformation)
+                        {
+                            return transformation.getChannelId();
+                        }
+                    }).getMap();
     }
 
     private List<ImgChannelDTO> loadChannels()
@@ -160,32 +187,69 @@ public class HCSDatasetLoader implements IImageDatasetLoader
         params.setTileRowsNum(getDataset().getFieldNumberOfRows());
         params.setTileColsNum(getDataset().getFieldNumberOfColumns());
         params.setIsMultidimensional(dataset.getIsMultidimensional());
-        params.addTransformerFactorySignatureFor(ScreeningConstants.MERGED_CHANNELS,
-                mergedChannelTransformerFactorySignatureOrNull);
+        params.setMergedChannelTransformerFactorySignature(mergedChannelTransformerFactorySignatureOrNull);
+        params.setChannels(convertChannels());
+        return params;
+    }
 
+    private List<ImageChannel> convertChannels()
+    {
         List<ImageChannel> convertedChannels = new ArrayList<ImageChannel>();
         for (ImgChannelDTO channelDTO : channels)
         {
             ImageChannel channel = convert(channelDTO);
             convertedChannels.add(channel);
-
-            String transformationSignature =
-                    tryGetSignature(channelDTO.getSerializedImageTransformerFactory());
-            params.addTransformerFactorySignatureFor(channelDTO.getCode(), transformationSignature);
         }
-        params.setChannels(convertedChannels);
-        return params;
+        return convertedChannels;
     }
 
-    private static ImageChannel convert(ImgChannelDTO channelDTO)
+    private ImageChannel convert(ImgChannelDTO channelDTO)
     {
         ImageChannelColor imageChannelColor =
                 ImageChannelColor.valueOf(channelDTO.getDbChannelColor());
+        List<ImageTransformationInfo> availableImageTransformations =
+                convertTransformations(availableImageTransformationsMap.get(channelDTO.getId()));
         return new ImageChannel(channelDTO.getCode(), channelDTO.getLabel(),
-                channelDTO.getDescription(), channelDTO.getWavelength(), imageChannelColor);
+                channelDTO.getDescription(), channelDTO.getWavelength(), imageChannelColor,
+                availableImageTransformations);
+    }
+
+    private static List<ImageTransformationInfo> convertTransformations(
+            List<ImgImageTransformationDTO> transformationsOrNull)
+    {
+        if (transformationsOrNull == null)
+        {
+            return new ArrayList<ImageTransformationInfo>();
+        } else
+        {
+            List<ImageTransformationInfo> transformations =
+                    CollectionUtils
+                            .map(transformationsOrNull,
+                                    new CollectionUtils.ICollectionMappingFunction<ImageTransformationInfo, ImgImageTransformationDTO>()
+                                        {
+                                            public ImageTransformationInfo map(
+                                                    ImgImageTransformationDTO transformation)
+                                            {
+                                                return convert(transformation);
+                                            }
+                                        });
+            if (transformations.size() > 0)
+            {
+                transformations.get(0).setDefault(true);
+            }
+            return transformations;
+        }
+    }
+
+    private static ImageTransformationInfo convert(ImgImageTransformationDTO transformation)
+    {
+        String transformationSignature =
+                tryGetSignature(transformation.getSerializedImageTransformerFactory());
+        return new ImageTransformationInfo(transformation.getCode(), transformation.getLabel(),
+                transformation.getDescription(), transformationSignature, false);
     }
 
-    private String tryGetSignature(byte[] bytesOrNull)
+    private static String tryGetSignature(byte[] bytesOrNull)
     {
         if (bytesOrNull == null)
         {
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java
index 0f5fba7ee47f7877b185139eaffbfd9e122a8ac7..4501533d29ece9e23011bb60975f1bec2d10e99f 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java
@@ -176,16 +176,6 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery
             + "where cs.ds_id = ?{1} and cs.spot_id is NULL")
     public List<ImgChannelStackDTO> listSpotlessChannelStacks(long datasetId);
 
-    @Select("select * from CHANNELS where DS_ID = ?{1} order by ID")
-    public List<ImgChannelDTO> getChannelsByDatasetId(long datasetId);
-
-    @Select("select * from CHANNELS where (DS_ID = ?{1}) and CODE = upper(?{2})")
-    public ImgChannelDTO tryGetChannelForDataset(long datasetId, String chosenChannelCode);
-
-    @Select("select count(*) > 0 from CHANNELS ch "
-            + "join DATA_SETS d on ch.ds_id = d.id where d.PERM_ID = ?{1}")
-    public boolean hasDatasetChannels(String datasetPermId);
-
     // ---------------- Generic ---------------------------------
 
     /** @return an image for the specified channel and channel stack or null */
@@ -221,7 +211,58 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery
         { StringArrayMapper.class }, fetchSize = FETCH_SIZE)
     public List<ImgDatasetDTO> listDatasetsByPermId(String... datasetPermIds);
 
-    // ---------------- HCS - experiments, containers, channels ---------------------------------
+    // ------------ dataset and experiment channels
+
+    @Select("select * from CHANNELS where DS_ID = ?{1} order by ID")
+    public List<ImgChannelDTO> getChannelsByDatasetId(long datasetId);
+
+    @Select(sql = "select * from CHANNELS where EXP_ID = ?{1} order by ID", fetchSize = FETCH_SIZE)
+    public List<ImgChannelDTO> getChannelsByExperimentId(long experimentId);
+
+    @Select("select * from CHANNELS where (DS_ID = ?{1}) and CODE = upper(?{2})")
+    public ImgChannelDTO tryGetChannelForDataset(long datasetId, String channelCode);
+
+    @Select("select * from CHANNELS where (EXP_ID = ?{1}) and CODE = upper(?{2})")
+    public ImgChannelDTO tryGetChannelForExperiment(long experimentId, String channelCode);
+
+    @Select("select id from channels where ds_id = ?{1} and code = upper(?{2})")
+    public long getDatasetChannelId(long datasetId, String channelCode);
+
+    @Select("select id from channels where exp_id = ?{1} and code = upper(?{2})")
+    public long getExperimentChannelId(long experimentId, String channelCode);
+
+    @Select("select count(*) > 0 from CHANNELS ch "
+            + "join DATA_SETS d on ch.ds_id = d.id where d.PERM_ID = ?{1}")
+    public boolean hasDatasetChannels(String datasetPermId);
+
+    @Select("select * from channels where code = ?{2} and "
+            + "exp_id in (select id from experiments where perm_id = ?{1})")
+    public ImgChannelDTO tryGetChannelForExperimentPermId(String experimentPermId,
+            String channelCode);
+
+    // ----------- image transformations
+
+    @Select("select * from IMAGE_TRANSFORMATIONS tr where tr.channel_id = ?{1} order by tr.ID")
+    public List<ImgImageTransformationDTO> listImageTransformations(long channelId);
+
+    @Select("select id from image_transformations where channel_id = ?{1} and code = ?{2}")
+    public Long tryGetImageTransformationId(long channelId, String transformationCode);
+
+    @Select("select * from image_transformations where channel_id = ?{1} and code = ?{2}")
+    public ImgImageTransformationDTO tryGetImageTransformation(long channelId,
+            String transformationCode);
+
+    @Select("select tr.* from IMAGE_TRANSFORMATIONS tr                       "
+            + " join channels ch on tr.channel_id = ch.id                    "
+            + " where ch.ds_id = ?{1} order by tr.ID                        ")
+    public List<ImgImageTransformationDTO> listImageTransformationsByDatasetId(long datasetId);
+
+    @Select("select tr.* from IMAGE_TRANSFORMATIONS tr                       "
+            + " join channels ch on tr.channel_id = ch.id                    "
+            + " where ch.exp_id = ?{1} order by tr.ID                        ")
+    public List<ImgImageTransformationDTO> listImageTransformationsByExperimentId(long experimentId);
+
+    // ---------------- HCS - experiments, containers ---------------------------------
 
     @Select("select * from EXPERIMENTS where PERM_ID = ?{1}")
     public ImgExperimentDTO tryGetExperimentByPermId(String experimentPermId);
@@ -246,20 +287,9 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery
             + "where cs.ds_id = ?{1} and s.x = ?{2} and s.y = ?{3}")
     public List<ImgChannelStackDTO> listChannelStacks(long datasetId, int spotX, int spotY);
 
-    @Select(sql = "select * from CHANNELS where EXP_ID = ?{1} order by ID", fetchSize = FETCH_SIZE)
-    public List<ImgChannelDTO> getChannelsByExperimentId(long experimentId);
-
     @Select("select * from SPOTS where cont_id = ?{1}")
     public List<ImgSpotDTO> listSpots(long contId);
 
-    @Select("select * from CHANNELS where (EXP_ID = ?{1}) and CODE = upper(?{2})")
-    public ImgChannelDTO tryGetChannelForExperiment(long experimentId, String chosenChannelCode);
-
-    @Select("select * from channels where code = ?{2} and "
-            + "exp_id in (select id from experiments where perm_id = ?{1})")
-    public ImgChannelDTO tryGetChannelForExperimentPermId(String experimentPermId,
-            String chosenChannelCode);
-
     // ---------------- HCS - feature vectors ---------------------------------
 
     @Select(sql = "select * from FEATURE_DEFS where DS_ID = any(?{1})", parameterBindings =
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java
index c9c45d9061666f7b934565fa8b501bb3c489789f..b30820274d76712a969eeeb530481175d194a3ca 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java
@@ -23,9 +23,11 @@ import net.lemnik.eodsql.Update;
 import ch.systemsx.cisd.base.image.IImageTransformerFactory;
 
 /**
+ * Write operations on {@link IImageTransformerFactory} + all inherited read operations.
+ * 
  * @author Franz-Josef Elmer
  */
-public interface IImagingTransformerDAO extends TransactionQuery
+public interface IImagingTransformerDAO extends TransactionQuery, IImagingReadonlyQueryDAO
 {
     @Update(sql = "update experiments set image_transformer_factory = ?{2} where id = ?{1}", parameterBindings =
         { TransformerFactoryMapper.class, TypeMapper.class /* default */})
@@ -41,16 +43,12 @@ public interface IImagingTransformerDAO extends TransactionQuery
     public void saveTransformerFactoryForImage(long acquiredImageId,
             IImageTransformerFactory factory);
 
-    @Update(sql = "update channels set image_transformer_factory = ?{3} "
-            + "where code = ?{2} and exp_id = ?{1}", parameterBindings =
-        { TransformerFactoryMapper.class, TypeMapper.class/* default */, TypeMapper.class /* default */})
-    public void saveTransformerFactoryForExperimentChannel(long experimentId, String channel,
-            IImageTransformerFactory factory);
+    @Update(sql = "insert into IMAGE_TRANSFORMATIONS(CODE, LABEL, DESCRIPTION, IMAGE_TRANSFORMER_FACTORY, IS_EDITABLE, CHANNEL_ID) values "
+            + "(?{1.code}, ?{1.label}, ?{1.description}, ?{1.serializedImageTransformerFactory}, ?{1.isEditable}, ?{1.channelId})")
+    public void addImageTransformation(ImgImageTransformationDTO imageTransformation);
 
-    @Update(sql = "update channels set image_transformer_factory = ?{3} "
-            + "where code = ?{2} and ds_id = ?{1}", parameterBindings =
-        { TransformerFactoryMapper.class, TypeMapper.class/* default */, TypeMapper.class /* default */})
-    public void saveTransformerFactoryForDatasetChannel(long datasetId, String channel,
+    @Update(sql = "update IMAGE_TRANSFORMATIONS set IMAGE_TRANSFORMER_FACTORY = ?{2} where id = ?{1}", parameterBindings =
+        { TransformerFactoryMapper.class, TypeMapper.class /* default */})
+    public void updateImageTransformerFactory(long imageTransformationId,
             IImageTransformerFactory factory);
-
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java
index ff2d9e6a57acf257e03c773daca8f9166cbd31ee..1c30c8fba51c8bbfc857e28eb89fe50db7c98069 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java
@@ -24,7 +24,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelCo
 /**
  * @author Tomasz Pylak
  */
-public class ImgChannelDTO extends AbstractImageTransformerFactoryHolder
+public class ImgChannelDTO extends AbstractImgIdentifiable
 {
     @ResultColumn("CODE")
     private String code;
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgImageTransformationDTO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgImageTransformationDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..c609382d9c6b50fe005fe37c8852fd3083c7db00
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgImageTransformationDTO.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * 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.plugin.screening.shared.imaging.dataaccess;
+
+import net.lemnik.eodsql.ResultColumn;
+
+import ch.systemsx.cisd.base.image.IImageTransformerFactory;
+import ch.systemsx.cisd.openbis.generic.shared.basic.CodeNormalizer;
+
+/**
+ * @author Tomasz Pylak
+ */
+public class ImgImageTransformationDTO extends AbstractImageTransformerFactoryHolder
+{
+    @ResultColumn("CODE")
+    private String code;
+
+    @ResultColumn("LABEL")
+    private String label;
+
+    @ResultColumn("DESCRIPTION")
+    private String descriptionOrNull;
+
+    @ResultColumn("IS_EDITABLE")
+    private boolean isEditable;
+
+    @ResultColumn("CHANNEL_ID")
+    private long channelId;
+
+    // EODSQL only
+    @SuppressWarnings("unused")
+    private ImgImageTransformationDTO()
+    {
+        // All Data-Object classes must have a default constructor.
+    }
+
+    public ImgImageTransformationDTO(String code, String label, String descriptionOrNull,
+            boolean isEditable, long channelId, IImageTransformerFactory imageTransformationFactory)
+    {
+        assert code != null : "code is null";
+        assert label != null : "label is null";
+        assert imageTransformationFactory != null : "transformationFactory is null";
+
+        this.code = CodeNormalizer.normalize(code);
+        this.label = label;
+        this.descriptionOrNull = descriptionOrNull;
+        this.isEditable = isEditable;
+        this.channelId = channelId;
+        setImageTransformerFactory(imageTransformationFactory);
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public void setCode(String code)
+    {
+        this.code = code;
+    }
+
+    public String getLabel()
+    {
+        return label;
+    }
+
+    public void setLabel(String label)
+    {
+        this.label = label;
+    }
+
+    /** Can be null. */
+    public String getDescription()
+    {
+        return descriptionOrNull;
+    }
+
+    public void setDescription(String descriptionOrNull)
+    {
+        this.descriptionOrNull = descriptionOrNull;
+    }
+
+    public boolean getIsEditable()
+    {
+        return isEditable;
+    }
+
+    public void setEditable(boolean isEditable)
+    {
+        this.isEditable = isEditable;
+    }
+
+    public long getChannelId()
+    {
+        return channelId;
+    }
+
+    public void setChannelId(long channelId)
+    {
+        this.channelId = channelId;
+    }
+
+    public final IImageTransformerFactory getImageTransformerFactory()
+    {
+        IImageTransformerFactory factory = super.tryGetImageTransformerFactory();
+        assert factory != null : "image factory is null";
+        return factory;
+    }
+
+}
diff --git a/screening/source/sql/imaging/postgresql/014/schema-014.sql b/screening/source/sql/imaging/postgresql/014/schema-014.sql
index 7b2517dd8b987a20d7c7658c90d7625199099e66..cb225d7d6cfefde5f37664d2172e7ba5ecedbc10 100644
--- a/screening/source/sql/imaging/postgresql/014/schema-014.sql
+++ b/screening/source/sql/imaging/postgresql/014/schema-014.sql
@@ -121,7 +121,7 @@ CREATE TABLE IMAGE_TRANSFORMATIONS (
     
     CODE NAME NOT NULL,
     LABEL NAME NOT NULL,
-    DESCRIPTION DESCRIPTION,
+    DESCRIPTION character varying(1000),
     IS_EDITABLE BOOLEAN_CHAR NOT NULL,
     IMAGE_TRANSFORMER_FACTORY BYTEA NOT NULL,
 
diff --git a/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql b/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql
index 10d6b0ec12d22d3ef5c1aff5130a8db3d716b809..c15735ca9590e265c5e88ee60ca59f546a797d62 100644
--- a/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql
+++ b/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql
@@ -5,7 +5,7 @@ CREATE TABLE IMAGE_TRANSFORMATIONS (
     
     CODE NAME NOT NULL,
     LABEL NAME NOT NULL,
-    DESCRIPTION DESCRIPTION,
+    DESCRIPTION character varying(1000),
 	IS_EDITABLE BOOLEAN_CHAR NOT NULL,
     IMAGE_TRANSFORMER_FACTORY BYTEA NOT NULL,
 		
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java
index 37894657d9a7e940b00496ac6bcc93e327f00507..96bf69b4bbbea838e2b2cefb5c3e111e72ec24e7 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java
@@ -30,7 +30,10 @@ import net.lemnik.eodsql.QueryTool;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import ch.systemsx.cisd.base.image.IImageTransformerFactory;
 import ch.systemsx.cisd.bds.hcs.Location;
+import ch.systemsx.cisd.openbis.generic.shared.basic.CodeNormalizer;
+import ch.systemsx.cisd.openbis.plugin.screening.client.api.v1.ExampleImageTransformerFactory;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.AbstractDBTest;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ColorComponent;
@@ -41,6 +44,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgCo
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageEnrichedDTO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgSpotDTO;
 
 /**
@@ -53,6 +57,14 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgSp
 public class ImagingQueryDAOTest extends AbstractDBTest
 {
 
+    private static final boolean IMAGE_TRANSFORMATION_IS_EDITABLE = false;
+
+    private static final String IMAGE_TRANSFORMATION_DESC = "tr_desc";
+
+    private static final String IMAGE_TRANSFORMATION_LABEL = "tr_label";
+
+    private static final String IMAGE_TRANSFORMATION_CODE = "tr_code";
+
     private static final String CHANNEL_LABEL = "Channel Label";
 
     private static final ImageChannelColor CHANNEL_COLOR = ImageChannelColor.RED;
@@ -216,8 +228,14 @@ public class ImagingQueryDAOTest extends AbstractDBTest
         final long datasetId = addDataset(DS_PERM_ID, containerId);
         final long spotId = addSpot(containerId);
         final long datasetChannelId1 = addDatasetChannel(datasetId);
+        addImageTransformationsAndTestListing(datasetChannelId1);
+        testAddingAndRemovingImageTransformation(datasetChannelId1, DS_CHANNEL, datasetId, null);
+
         assertTrue(dao.hasDatasetChannels(DS_PERM_ID));
         final long experimentChannelId2 = addExperimentChannel(experimentId);
+        addImageTransformationsAndTestListing(experimentChannelId2);
+        testAddingAndRemovingImageTransformation(experimentChannelId2, EXP_CHANNEL, null,
+                experimentId);
 
         testChannelMethods(experimentId, datasetId, datasetChannelId1, experimentChannelId2);
 
@@ -409,6 +427,125 @@ public class ImagingQueryDAOTest extends AbstractDBTest
         return dao.addChannel(channel);
     }
 
+    private void testAddingAndRemovingImageTransformation(long channelId, String channelCode,
+            Long datasetId, Long experimentId)
+    {
+        ImgImageTransformationDTO transformation = addImageTransformation(channelId, 312);
+
+        String transformationCode = transformation.getCode();
+        Long testedTransformationId =
+                tryGetTransformationIdForDatasetOrExperimentChannel(datasetId, experimentId,
+                        transformationCode);
+        assertNotNull("Cannot get the transformation id back", testedTransformationId);
+
+        ImgImageTransformationDTO testedTransformation =
+                dao.tryGetImageTransformation(channelId, transformationCode);
+        assertNotNull(testedTransformation);
+        assertEquals(testedTransformationId.longValue(), testedTransformation.getId());
+
+        List<ImgImageTransformationDTO> allTransformations =
+                listImageTransformations(datasetId, experimentId);
+        assertEquals(1, allTransformations.size());
+        ImgImageTransformationDTO testedTransformationListed = allTransformations.get(0);
+        assertEquals("list transformations failed", testedTransformation,
+                testedTransformationListed);
+
+        // test removal of transformation
+        dao.removeImageTransformation(transformationCode, channelId);
+        testedTransformationId =
+                tryGetTransformationIdForDatasetOrExperimentChannel(datasetId, experimentId,
+                        transformationCode);
+        assertNull(testedTransformationId);
+
+    }
+
+    private List<ImgImageTransformationDTO> listImageTransformations(Long datasetIdOrNull,
+            Long experimentIdOrNull)
+    {
+        if (datasetIdOrNull != null)
+        {
+            return dao.listImageTransformationsByDatasetId(datasetIdOrNull);
+        } else
+        {
+            assert experimentIdOrNull != null : "both dataset and exp id are null";
+            return dao.listImageTransformationsByExperimentId(experimentIdOrNull);
+        }
+    }
+
+    private Long tryGetTransformationIdForDatasetOrExperimentChannel(Long datasetId,
+            Long experimentId, String transformationCode)
+    {
+        long channelId = getChannelId(datasetId, experimentId);
+        return dao.tryGetImageTransformationId(channelId, transformationCode);
+    }
+
+    private long getChannelId(Long datasetIdOrNull, Long experimentIdOrNull)
+    {
+        if (datasetIdOrNull != null)
+        {
+            return dao.getDatasetChannelId(datasetIdOrNull, DS_CHANNEL);
+        } else
+        {
+            assert experimentIdOrNull != null : "both dataset and exp id are null";
+            return dao.getExperimentChannelId(experimentIdOrNull, EXP_CHANNEL);
+        }
+    }
+
+    private ImgImageTransformationDTO addImageTransformation(long channelId, long codeSuffix)
+    {
+        IImageTransformerFactory transformerFactory1 = new ExampleImageTransformerFactory(123);
+        ImgImageTransformationDTO transformation1 =
+                createImageTransformation(channelId, codeSuffix, transformerFactory1);
+        dao.addImageTransformations(Arrays.asList(transformation1));
+        return transformation1;
+    }
+
+    // adds two transformtion for the channel and tests that they are written correctly
+    private void addImageTransformationsAndTestListing(long channelId)
+    {
+        IImageTransformerFactory transformerFactory1 = new ExampleImageTransformerFactory(123);
+        ImgImageTransformationDTO transformation1 =
+                createImageTransformation(channelId, 0, transformerFactory1);
+        IImageTransformerFactory transformerFactory2 = new ExampleImageTransformerFactory(321);
+        ImgImageTransformationDTO transformation2 =
+                createImageTransformation(channelId, 1, transformerFactory2);
+
+        dao.addImageTransformations(Arrays.asList(transformation1, transformation2));
+
+        // read back and test
+
+        // listImageTransformations() test
+        List<ImgImageTransformationDTO> readTransformations =
+                dao.listImageTransformations(channelId);
+
+        assertEquals(2, readTransformations.size());
+        assertEquals(transformerFactory1, readTransformations.get(0)
+                .tryGetImageTransformerFactory());
+        assertEquals(transformerFactory2, readTransformations.get(1)
+                .tryGetImageTransformerFactory());
+
+        for (int i = 0; i < 2; i++)
+        {
+            ImgImageTransformationDTO readTransformation = readTransformations.get(i);
+            assertEquals(CodeNormalizer.normalize(IMAGE_TRANSFORMATION_CODE + i),
+                    readTransformation.getCode());
+            assertEquals(IMAGE_TRANSFORMATION_LABEL, readTransformation.getLabel());
+            assertEquals(IMAGE_TRANSFORMATION_DESC, readTransformation.getDescription());
+            assertEquals(IMAGE_TRANSFORMATION_IS_EDITABLE, readTransformation.getIsEditable());
+            // clean the db
+            dao.removeImageTransformation(readTransformation.getCode(), channelId);
+
+        }
+    }
+
+    private ImgImageTransformationDTO createImageTransformation(long channelId, long codeSuffix,
+            IImageTransformerFactory transformerFactory)
+    {
+        return new ImgImageTransformationDTO(IMAGE_TRANSFORMATION_CODE + codeSuffix,
+                IMAGE_TRANSFORMATION_LABEL, IMAGE_TRANSFORMATION_DESC,
+                IMAGE_TRANSFORMATION_IS_EDITABLE, channelId, transformerFactory);
+    }
+
     private long addExperimentChannel(long experimentId)
     {
         final ImgChannelDTO channel =
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java
index fdf8afbc2708edea82d11d56be8e5eb821412127..1b38791510b341219d875ae45d9ed7a9ad84d469 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java
@@ -29,6 +29,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageChannelStackR
 import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageGenerationDescription;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size;
 import ch.systemsx.cisd.openbis.generic.shared.ServletParamsParsingTestUtils;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants.ImageServletUrlParameters;
 
 /**
  * Tests of {@link ImageGenerationDescriptionFactory}.
@@ -62,7 +63,7 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT
 
         DatasetAcquiredImagesReference channelsToMerge = desc.tryGetImageChannels();
         assertEquals(channelStackRef, channelsToMerge.getChannelStackReference());
-        assertEquals(Arrays.asList("DAPI", "GFP"), channelsToMerge.getChannelCodes());
+        assertEquals(Arrays.asList("DAPI", "GFP"), channelsToMerge.getChannelCodes(null));
         assertEquals(BASIC_DATASET_CODE, channelsToMerge.getDatasetCode());
 
         List<DatasetAcquiredImagesReference> overlayChannels = desc.getOverlayChannels();
@@ -73,7 +74,7 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT
         DatasetAcquiredImagesReference firstChannel = overlayChannels.get(firstChannelIx);
 
         assertEquals(channelStackRef, firstChannel.getChannelStackReference());
-        assertEquals(Arrays.asList("X", "Y"), firstChannel.getChannelCodes());
+        assertEquals(Arrays.asList("X", "Y"), firstChannel.getChannelCodes(null));
         assertEquals(firstOverlayDatasetCode, firstChannel.getDatasetCode());
 
         DatasetAcquiredImagesReference secondChannel = overlayChannels.get(1 - firstChannelIx);
@@ -86,16 +87,21 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT
         Map<String, String[]> paramsMap = createBasicParamsMap();
         addListParams(paramsMap, "dataset", BASIC_DATASET_CODE);
         addListParams(paramsMap, "mergeChannels", "true");
+        String myImageTransformationCode = "make-darker";
+        addListParams(paramsMap,
+                ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM,
+                myImageTransformationCode);
+
         addRequestParamsExpectations(paramsMap);
 
         ImageGenerationDescription desc = ImageGenerationDescriptionFactory.create(request);
 
         DatasetAcquiredImagesReference channelsToMerge = desc.tryGetImageChannels();
         assertNotNull(channelsToMerge);
-        assertNull(channelsToMerge.getChannelCodes());
-        assertTrue(channelsToMerge.isMergeAllChannels());
+        assertNull(channelsToMerge.getChannelCodes(null));
+        assertTrue(channelsToMerge.isMergeAllChannels(null));
         assertEquals(BASIC_DATASET_CODE, channelsToMerge.getDatasetCode());
-
+        assertEquals(myImageTransformationCode, desc.tryGetSingleChannelTransformationCode());
         assertEquals(0, desc.getOverlayChannels().size());
     }
 
@@ -120,7 +126,8 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT
         final Map<String, String[]> paramsMap = new HashMap<String, String[]>();
         addSingleParams(paramsMap, "sessionID", "sessionXXX", "wellRow", "1", "wellCol", "4",
                 "tileRow", "7", "tileCol", "2", "mode", "thumbnail200x120", "channelStackId", null,
-                "mergeChannels", null, "dataset", null);
+                "mergeChannels", null, "dataset", null,
+                ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM, null);
 
         return paramsMap;
     }
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 69614af24fa68e4fba014e08b42fad28dc61d397..52983bd109def2c436ca3cd53887d9587615b563 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
@@ -20,7 +20,10 @@ import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.commons.io.IOUtils;
 import org.jmock.Expectations;
@@ -44,12 +47,14 @@ import ch.systemsx.cisd.openbis.dss.etl.IImagingDatasetLoader;
 import ch.systemsx.cisd.openbis.dss.etl.dto.ImageTransfomationFactories;
 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.dto.Size;
 import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtilTest;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannel;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageTransformationInfo;
 
 /**
  * @author Franz-Josef Elmer
@@ -115,7 +120,8 @@ public class ImageChannelsUtilsTest extends AssertJUnit
 
         try
         {
-            createImageChannelsUtils(null).calculateBufferedImage(imageRef, true);
+            createImageChannelsUtils(null).calculateBufferedImage(imageRef,
+                    createSingleChannelTransformationParams());
             fail("EnvironmentFailureException expected");
         } catch (EnvironmentFailureException ex)
         {
@@ -126,6 +132,11 @@ public class ImageChannelsUtilsTest extends AssertJUnit
         context.assertIsSatisfied();
     }
 
+    private ImageTransformationParams createSingleChannelTransformationParams()
+    {
+        return new ImageTransformationParams(true, false, null);
+    }
+
     @Test
     public void testGetTiffImage()
     {
@@ -152,8 +163,8 @@ public class ImageChannelsUtilsTest extends AssertJUnit
         prepareExpectations(absoluteImageReference, imageRef);
 
         BufferedImage image =
-                createImageChannelsUtils(thumbnailSizeOrNull)
-                        .calculateBufferedImage(imageRef, true);
+                createImageChannelsUtils(thumbnailSizeOrNull).calculateBufferedImage(imageRef,
+                        createSingleChannelTransformationParams());
         assertEquals(expectedImageContentDescription, getImageContentDescription(image));
 
         context.assertIsSatisfied();
@@ -165,17 +176,17 @@ public class ImageChannelsUtilsTest extends AssertJUnit
         context.checking(new Expectations()
             {
                 {
-                    one(loader).getImageParameters();
+                    allowing(loader).getImageParameters();
                     ImageDatasetParameters imgParams = new ImageDatasetParameters();
                     imgParams.setChannels(Arrays.asList(new ImageChannel(CHANNEL, CHANNEL, null,
-                            null, null)));
+                            null, null, new ArrayList<ImageTransformationInfo>())));
                     will(returnValue(imgParams));
 
                     RequestedImageSize requestedSize =
                             absoluteImageReferenceOrNull == null ? RequestedImageSize
                                     .createOriginal() : absoluteImageReferenceOrNull
                                     .getRequestedSize();
-                    one(loader).tryGetImage(imageRef.getChannelCodes().get(0),
+                    one(loader).tryGetImage(imageRef.getChannelCodes(null).get(0),
                             imageRef.getChannelStackReference(), requestedSize);
                     will(returnValue(absoluteImageReferenceOrNull));
                 }
@@ -187,8 +198,8 @@ public class ImageChannelsUtilsTest extends AssertJUnit
         ImageChannelStackReference channelStackReference =
                 ImageChannelStackReference.createFromId(4711);
         final DatasetAcquiredImagesReference imageRef =
-                new DatasetAcquiredImagesReference(DATASET_CODE, channelStackReference,
-                        Arrays.asList(CHANNEL));
+                DatasetAcquiredImagesReference.createForSingleChannel(DATASET_CODE,
+                        channelStackReference, CHANNEL);
         return imageRef;
     }
 
@@ -198,7 +209,10 @@ public class ImageChannelsUtilsTest extends AssertJUnit
         final DatasetAcquiredImagesReference imageRef = createDatasetAcquiredImagesReference();
         AbsoluteImageReference imgRef =
                 createAbsoluteImageReference("img1.gif", RequestedImageSize.createOriginal());
-        imgRef.getImageTransfomationFactories().setForChannel(transformerFactory);
+        String transformationCode = "MY_TRANSFORMATION";
+        Map<String, IImageTransformerFactory> transformations =
+                createImageTransformationsMap(transformationCode, transformerFactory);
+        imgRef.getImageTransfomationFactories().setForChannel(transformations);
 
         prepareExpectations(imgRef, imageRef);
         context.checking(new Expectations()
@@ -209,13 +223,24 @@ public class ImageChannelsUtilsTest extends AssertJUnit
                 }
             });
 
-        BufferedImage image = createImageChannelsUtils(null).calculateBufferedImage(imageRef, true);
+        BufferedImage image =
+                createImageChannelsUtils(null).calculateBufferedImage(imageRef,
+                        new ImageTransformationParams(true, false, transformationCode));
         assertEquals("e00 f00 f00 e00\n" + "f00 c00 c00 f00\n" + "f00 c00 c00 f00\n"
                 + "e00 f00 f00 e00\n", getImageContentDescription(image));
 
         context.assertIsSatisfied();
     }
 
+    private static Map<String, IImageTransformerFactory> createImageTransformationsMap(
+            String transformationCode, IImageTransformerFactory transformerFactory)
+    {
+        Map<String, IImageTransformerFactory> transformations =
+                new HashMap<String, IImageTransformerFactory>();
+        transformations.put(transformationCode, transformerFactory);
+        return transformations;
+    }
+
     private static AbsoluteImageReference createAbsoluteImageReference(String fileName,
             RequestedImageSize imageSize)
     {
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java
index 03df1842ff2cdd8bca9f8d79a57c316425fca010..1f6f50e8f46c06d92cba98124072b965c5e5e753 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java
@@ -83,6 +83,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellPosition;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannel;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageTransformationInfo;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.dto.PlateFeatureValues;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO;
@@ -94,6 +95,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgEx
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureDefDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureValuesDTO;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureVocabularyTermDTO;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO;
 
 /**
  * Test cases for the {@link DssServiceRpcScreening}.
@@ -175,8 +177,10 @@ public class DssServiceRpcScreeningTest extends AssertJUnit
         imageParameters.setTileRowsNum(1);
         imageParameters.setTileColsNum(2);
         imageParameters.setDatasetCode(DATASET_CODE);
-        imageParameters.setChannels(Arrays.asList(new ImageChannel(CHANNEL_CODE, CHANNEL_CODE,
-                null, null, null)));
+        ImageChannel imageChannel =
+                new ImageChannel(CHANNEL_CODE, CHANNEL_CODE, null, null, null,
+                        new ArrayList<ImageTransformationInfo>());
+        imageParameters.setChannels(Arrays.asList(imageChannel));
         context.checking(new Expectations()
             {
                 {
@@ -395,9 +399,16 @@ public class DssServiceRpcScreeningTest extends AssertJUnit
                     ImgChannelDTO channelDTO =
                             new ImgChannelDTO("dapi", null, null, new Long(42), null, "dapi",
                                     ImageChannelColor.BLUE);
-                    channelDTO.setSerializedImageTransformerFactory(SerializationUtils
-                            .serialize(transformerFactory));
+                    long channelId = 444;
+                    channelDTO.setId(channelId);
                     will(returnValue(channelDTO));
+
+                    one(dao).tryGetImageTransformation(channelId,
+                            DssServiceRpcScreening.IMAGE_VIEWER_TRANSFORMATION_CODE);
+                    ImgImageTransformationDTO transformationDTO =
+                            new ImgImageTransformationDTO("tr_code", "tr_labal", null, true,
+                                    channelId, transformerFactory);
+                    will(returnValue(transformationDTO));
                 }
             });
 
@@ -494,8 +505,18 @@ public class DssServiceRpcScreeningTest extends AssertJUnit
                     allowing(dao).hasDatasetChannels(DATASET_CODE);
                     will(returnValue(true));
 
-                    exactly(2).of(transformerDAO).saveTransformerFactoryForDatasetChannel(
-                            datasetId, channel, transformerFactory);
+                    long channelId = 5;
+                    long transactionId = 123;
+                    exactly(2).of(transformerDAO).getDatasetChannelId(datasetId, channel);
+                    will(returnValue(channelId));
+
+                    exactly(2).of(transformerDAO).tryGetImageTransformationId(channelId,
+                            DssServiceRpcScreening.IMAGE_VIEWER_TRANSFORMATION_CODE);
+
+                    will(returnValue(transactionId));
+
+                    exactly(2).of(transformerDAO).updateImageTransformerFactory(transactionId,
+                            transformerFactory);
 
                     one(transformerDAO).commit();
                 }