diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/AbstractSpotImagesTransformerProcessingPlugin.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/AbstractSpotImagesTransformerProcessingPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..26c26ccb57b6c7c88a70b13072e0a2f7bbe6d2df
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/AbstractSpotImagesTransformerProcessingPlugin.java
@@ -0,0 +1,138 @@
+/*
+ * 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.plugins;
+
+import java.io.File;
+import java.util.List;
+import java.util.Properties;
+
+import ch.systemsx.cisd.base.image.IImageTransformerFactory;
+import ch.systemsx.cisd.common.collections.GroupByMap;
+import ch.systemsx.cisd.common.collections.IKeyExtractor;
+import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.common.utilities.PropertyUtils;
+import ch.systemsx.cisd.openbis.dss.etl.HCSImageDatasetLoaderFactory;
+import ch.systemsx.cisd.openbis.dss.etl.IContentRepository;
+import ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.AbstractDatastorePlugin;
+import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.IProcessingPluginTask;
+import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.ProcessingStatus;
+import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetProcessingContext;
+import ch.systemsx.cisd.openbis.dss.shared.DssScreeningUtils;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
+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.ImgImageEnrichedDTO;
+
+/**
+ * @author Tomasz Pylak
+ */
+abstract public class AbstractSpotImagesTransformerProcessingPlugin extends AbstractDatastorePlugin
+        implements IProcessingPluginTask
+{
+    protected abstract IImageTransformerFactory tryCalculateTransformation(
+            List<ImgImageEnrichedDTO> spotImages, IContentRepository contentRepository);
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String CHANNEL_CODE_PROPERTY = "channel";
+
+    private final static IImagingReadonlyQueryDAO query = DssScreeningUtils.getQuery();
+
+    private final String channelCode;
+
+    public AbstractSpotImagesTransformerProcessingPlugin(Properties properties, File storeRoot)
+    {
+        super(properties, storeRoot);
+        this.channelCode = PropertyUtils.getMandatoryProperty(properties, CHANNEL_CODE_PROPERTY);
+    }
+
+    public ProcessingStatus process(List<DatasetDescription> datasets,
+            DataSetProcessingContext context)
+    {
+        IImagingTransformerDAO transformerDAO = DssScreeningUtils.createImagingTransformerDAO();
+        try
+        {
+            ProcessingStatus processingStatus = new ProcessingStatus();
+            for (DatasetDescription dataset : datasets)
+            {
+                IContentRepository contentRepository = createContentRepository(context, dataset);
+                GroupByMap<Long, ImgImageEnrichedDTO> imagesBySpot = fetchImages(dataset);
+                for (Long spotId : imagesBySpot.getKeys())
+                {
+                    List<ImgImageEnrichedDTO> spotImages = imagesBySpot.tryGet(spotId);
+                    calculateAndSetImageTransformation(spotImages, contentRepository,
+                            transformerDAO);
+                }
+                processingStatus.addDatasetStatus(dataset, Status.OK);
+            }
+            return processingStatus;
+        } finally
+        {
+            transformerDAO.close(true);
+        }
+    }
+
+    private void calculateAndSetImageTransformation(List<ImgImageEnrichedDTO> spotImages,
+            IContentRepository contentRepository, IImagingTransformerDAO transformerDAO)
+    {
+        IImageTransformerFactory imageTransformation =
+                tryCalculateTransformation(spotImages, contentRepository);
+        for (ImgImageEnrichedDTO image : spotImages)
+        {
+            transformerDAO.saveTransformerFactoryForImage(image.getAcquiredImageId(),
+                    imageTransformation);
+        }
+    }
+
+    private IContentRepository createContentRepository(DataSetProcessingContext context,
+            DatasetDescription dataset)
+    {
+        File dataSetDirectory = context.getDirectoryProvider().getDataSetDirectory(dataset);
+        IContentRepository contentRepository =
+                HCSImageDatasetLoaderFactory.createContentRepository(dataSetDirectory);
+        return contentRepository;
+    }
+
+    private GroupByMap<Long, ImgImageEnrichedDTO> fetchImages(DatasetDescription dataset)
+    {
+        List<ImgImageEnrichedDTO> allImages =
+                query.listHCSImages(dataset.getDatasetCode(), channelCode);
+        GroupByMap<Long, ImgImageEnrichedDTO> imagesBySpot =
+                GroupByMap.create(allImages, new IKeyExtractor<Long, ImgImageEnrichedDTO>()
+                    {
+                        public Long getKey(ImgImageEnrichedDTO image)
+                        {
+                            return image.getSpotId();
+                        }
+                    });
+        if (allImages.size() == 0)
+        {
+            operationLog
+                    .warn(String
+                            .format("Dataset %s has no images for channel '%s' to process! Have you specified the correct channel code?",
+                                    dataset.getDatasetCode(), channelCode));
+        } else
+        {
+            operationLog
+                    .info(String
+                            .format("Dataset %s has %d images (devided between %d spots) for channel '%s' to process.",
+                                    dataset.getDatasetCode(), allImages.size(), imagesBySpot
+                                            .getKeys().size(), channelCode));
+        }
+        return imagesBySpot;
+    }
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/RevertImageLevelTransformationProcessingPlugin.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/RevertImageLevelTransformationProcessingPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..67efcd5f5c1d665056fb2b133e5dc1f77423b577
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/RevertImageLevelTransformationProcessingPlugin.java
@@ -0,0 +1,46 @@
+/*
+ * 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.plugins;
+
+import java.io.File;
+import java.util.List;
+import java.util.Properties;
+
+import ch.systemsx.cisd.base.image.IImageTransformerFactory;
+import ch.systemsx.cisd.openbis.dss.etl.IContentRepository;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageEnrichedDTO;
+
+/**
+ * @author Tomasz Pylak
+ */
+public class RevertImageLevelTransformationProcessingPlugin extends
+        AbstractSpotImagesTransformerProcessingPlugin
+{
+    private static final long serialVersionUID = 1L;
+
+    public RevertImageLevelTransformationProcessingPlugin(Properties properties, File storeRoot)
+    {
+        super(properties, storeRoot);
+    }
+
+    @Override
+    protected IImageTransformerFactory tryCalculateTransformation(
+            List<ImgImageEnrichedDTO> spotImages, IContentRepository contentRepository)
+    {
+        return null;
+    }
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/WellImageIntensityTransformerProcessingPlugin.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/WellImageIntensityTransformerProcessingPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..d025e5bf32fb72f1d57941fe061f359cdd3e53df
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/WellImageIntensityTransformerProcessingPlugin.java
@@ -0,0 +1,190 @@
+/*
+ * 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.plugins;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+
+import ch.systemsx.cisd.base.image.IImageTransformerFactory;
+import ch.systemsx.cisd.common.io.IContent;
+import ch.systemsx.cisd.common.utilities.PropertyUtils;
+import ch.systemsx.cisd.openbis.dss.etl.IContentRepository;
+import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageEnrichedDTO;
+import ch.systemsx.sybit.imageviewer.data.ImageViewerParameters;
+import ch.systemsx.sybit.imageviewer.data.MinMaxInChannel;
+import ch.systemsx.sybit.imageviewer.transformers.IJImageTransformerFactory;
+
+/**
+ * @author Tomasz Pylak
+ */
+public class WellImageIntensityTransformerProcessingPlugin extends
+        AbstractSpotImagesTransformerProcessingPlugin
+{
+    private static final long serialVersionUID = 1L;
+
+    // Colors which are smaller or larger will be ignored when the rescaling parameters are
+    // calculated.
+    // If the encountered minimum and maximum is equal to the spcified, no rescaling happens.
+    private final int minRescaledColor, maxRescaledColor;
+
+    public WellImageIntensityTransformerProcessingPlugin(Properties properties, File storeRoot)
+    {
+        super(properties, storeRoot);
+        this.minRescaledColor = PropertyUtils.getInt(properties, "min-color", 2);
+        this.maxRescaledColor = PropertyUtils.getInt(properties, "max-color", 16000);
+    }
+
+    private static class ImagePixelsRange
+    {
+        private final int min, max;
+
+        public ImagePixelsRange(int min, int max)
+        {
+            this.min = min;
+            this.max = max;
+        }
+
+        public int getMin()
+        {
+            return min;
+        }
+
+        public int getMax()
+        {
+            return max;
+        }
+
+        public ImagePixelsRange createOverlap(ImagePixelsRange rangeOrNull)
+        {
+            if (rangeOrNull == null)
+            {
+                return this;
+            }
+            int newMin = Math.min(min, rangeOrNull.getMin());
+            int newMax = Math.max(max, rangeOrNull.getMax());
+            return new ImagePixelsRange(newMin, newMax);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "range [" + min + ", " + max + "]";
+        }
+    }
+
+    @Override
+    protected IImageTransformerFactory tryCalculateTransformation(
+            List<ImgImageEnrichedDTO> spotImages, IContentRepository contentRepository)
+    {
+        if (spotImages.size() < 2)
+        {
+            return null;
+        }
+        ImagePixelsRange range = null;
+        int i = 1;
+        for (ImgImageEnrichedDTO image : spotImages)
+        {
+            IContent content = contentRepository.getContent(image.getFilePath());
+            BufferedImage bufferedImage = ImageUtil.loadImage(content, image.getPage());
+            ImagePixelsRange imageRange = calculatePixelsRange(bufferedImage);
+
+            // debug
+            System.out.println(imageRange + " - " + i++ + " - " + image.getFilePath());
+
+            range = imageRange.createOverlap(range);
+        }
+        assert range != null;
+        // debug
+        System.out.println("Final range: " + range);
+        return tryCreateRescaleTransformationFactory(range);
+    }
+
+    private IImageTransformerFactory tryCreateRescaleTransformationFactory(ImagePixelsRange range)
+    {
+        ImageViewerParameters params = new ImageViewerParameters();
+        params.setMin(range.getMin());
+        params.setMax(range.getMax());
+        params.setLutOperation("");
+        params.setMinMaxInChannels(new HashMap<Integer, MinMaxInChannel>());
+        params.setSlice(1);
+        return new IJImageTransformerFactory(params);
+    }
+
+    private ImagePixelsRange calculatePixelsRange(BufferedImage bufferedImage)
+    {
+        int bucketSize = 2000;
+        int buckets[] = new int[17000 / bucketSize + 1];
+        long sum = 0;
+        System.out.println("component bits: " + bufferedImage.getColorModel().getComponentSize(0));
+        System.out.println("number of components: "
+                + bufferedImage.getColorModel().getNumColorComponents());
+
+        int minColor = maxRescaledColor;
+        int maxColor = minRescaledColor;
+        // first pass - calsulate min and max color
+        for (int x = 0; x < bufferedImage.getWidth(); x++)
+        {
+            for (int y = 0; y < bufferedImage.getHeight(); y++)
+            {
+                int dominantColorComponent = getDominantColorComponent(bufferedImage, x, y);
+                buckets[dominantColorComponent / bucketSize]++;
+                sum += dominantColorComponent;
+
+                if (dominantColorComponent >= minRescaledColor
+                        && dominantColorComponent <= maxRescaledColor)
+                {
+                    if (dominantColorComponent > maxColor)
+                    {
+                        maxColor = dominantColorComponent;
+                    } else if (dominantColorComponent < minColor)
+                    {
+                        minColor = dominantColorComponent;
+                    }
+                }
+            }
+        }
+        int max = 0;
+        for (int i = 0; i < buckets.length; i++)
+        {
+            if (buckets[i] > buckets[max])
+            {
+                max = i;
+            }
+            System.out.println("bucket " + i + ": " + buckets[i]);
+        }
+        System.out.println("Max bucket " + max + ": " + buckets[max] + ".\t Sum: " + sum);
+        return new ImagePixelsRange(minColor, maxColor);
+    }
+
+    // private static int getDominantColorComponent(BufferedImage bufferedImage, int x, int y)
+    // {
+    // int rgb = bufferedImage.getRGB(x, y);
+    // Color color = new Color(rgb);
+    // int max = Math.max(color.getRed(), color.getBlue());
+    // return Math.max(max, color.getGreen());
+    // }
+
+    private static int getDominantColorComponent(BufferedImage bufferedImage, int x, int y)
+    {
+        return bufferedImage.getRaster().getSample(x, y, 0);
+    }
+
+}