From 30089a0ccb0a82aa8226098fb323deaba1cfe7ab Mon Sep 17 00:00:00 2001
From: gpawel <gpawel>
Date: Tue, 4 Dec 2012 12:58:13 +0000
Subject: [PATCH] SP-366 BIS-254: SOB-6: Allow setting rescaling parameters per
 user per plate (implementation)

SVN: 27850
---
 .../etl/dto/ImageTransfomationFactories.java  |  31 ++++
 .../client/web/client/application/Dict.java   |  27 ++--
 .../ScreeningDisplaySettingsManager.java      |  20 ++-
 .../detailviewers/ChannelChooser.java         |  28 +++-
 .../detailviewers/ChannelChooserPanel.java    | 115 +++++++++++---
 .../ChannelWidgetWithListener.java            |  12 +-
 .../detailviewers/DefaultChannelState.java    |  17 ++
 .../detailviewers/IDefaultChannelState.java   |   4 +
 .../MaterialReplicaSummaryComponent.java      |  10 +-
 .../UserDefinedRescalingSettingsDialog.java   | 147 ++++++++++++++++++
 .../detailviewers/WellContentDialog.java      |   5 +-
 .../detailviewers/WellSearchGrid.java         |   9 +-
 .../dto/LogicalImageChannelsReference.java    |  17 +-
 .../detailviewers/image/Image.java            |  15 +-
 .../client/web/public/screening-dictionary.js |   4 +
 .../basic/dto/ImageDatasetParameters.java     |  12 +-
 .../shared/basic/dto/IntensityRange.java      |  80 ++++++++++
 .../shared/basic/dto/ScreeningConstants.java  |   1 +
 .../basic/dto/ScreeningDisplaySettings.java   |  23 ++-
 19 files changed, 516 insertions(+), 61 deletions(-)
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/UserDefinedRescalingSettingsDialog.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/IntensityRange.java

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 b47fa281993..bfd6e221852 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
@@ -19,6 +19,8 @@ package ch.systemsx.cisd.openbis.dss.etl.dto;
 import java.util.Map;
 
 import ch.systemsx.cisd.base.image.IImageTransformerFactory;
+import ch.systemsx.cisd.openbis.dss.etl.dto.api.transformations.IntensityRangeImageTransformerFactory;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 
 /**
  * Stores transformations defined for the image on different levels.
@@ -48,6 +50,12 @@ public class ImageTransfomationFactories
         {
             return defaultTransformationOrNull;
         }
+        if (transformationCodeOrNull.toLowerCase().startsWith(
+                ScreeningConstants.USER_DEFINED_RESCALING_CODE.toLowerCase()))
+        {
+            return getUserDefinedTransformationOrDefault(transformationCodeOrNull);
+        }
+
         return singleChannelMap.get(transformationCodeOrNull);
     }
 
@@ -88,4 +96,27 @@ public class ImageTransfomationFactories
     {
         return defaultTransformationCodeOrNull;
     }
+
+    private IImageTransformerFactory getUserDefinedTransformationOrDefault(
+            String userDefinedTransformation)
+    {
+        String[] parameters =
+                userDefinedTransformation.replaceAll(
+                        "^.*\\(\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*\\)\\s*$", "$1,$2").split(",");
+
+        if (parameters.length == 2)
+        {
+            try
+            {
+                int blackPoint = Integer.parseInt(parameters[0]);
+                int whitePoint = Integer.parseInt(parameters[1]);
+
+                return new IntensityRangeImageTransformerFactory(blackPoint, whitePoint);
+            } catch (NumberFormatException ex)
+            {
+            }
+        }
+
+        return defaultTransformationOrNull;
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/Dict.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/Dict.java
index 196843adc4b..3d493f813c9 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/Dict.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/Dict.java
@@ -132,31 +132,36 @@ public final class Dict extends ch.systemsx.cisd.openbis.generic.client.web.clie
     public static final String RESOLUTION_CHOOSER_DEFAULT = "RESOLUTION_CHOOSER_DEFAULT";
 
     public static final String RESOLUTION_CHOOSER_RESOLUTION = "RESOLUTION_CHOOSER_RESOLUTION";
-    
-    public static final String HEAT_MAP_RANGE_CHOOSER_BUTTON =
-            "heat_map_range_chooser_button";
 
-    public static final String HEAT_MAP_RANGE_CHOOSER_TITLE =
-            "heat_map_range_chooser_title";
-    
+    public static final String HEAT_MAP_RANGE_CHOOSER_BUTTON = "heat_map_range_chooser_button";
+
+    public static final String TITLE_USER_DEFINED_RESCALING_DIALOG =
+            "TITLE_USER_DEFINED_RESCALING_DIALOG";
+
+    public static final String RESCALING_DIALOG_MIN = "RESCALING_DIALOG_MIN";
+
+    public static final String RESCALING_DIALOG_MAX = "RESCALING_DIALOG_MAX";
+
+    public static final String HEAT_MAP_RANGE_CHOOSER_TITLE = "heat_map_range_chooser_title";
+
     public static final String HEAT_MAP_RANGE_CHOOSER_TYPE_LABEL_PREFIX =
             "heat_map_range_chooser_type_label_";
-    
+
     public static final String HEAT_MAP_RANGE_CHOOSER_TYPE_TOOLTIP_PREFIX =
             "heat_map_range_chooser_type_tooltip_";
 
     public static final String HEAT_MAP_RANGE_CHOOSER_FIXED_TYPE_LABEL =
             "heat_map_range_chooser_type_fixed_type_label";
-    
+
     public static final String HEAT_MAP_RANGE_CHOOSER_FIXED_TYPE_LOWEST_SCALE_LABEL =
             "heat_map_range_chooser_type_fixed_type_lowest_scale_label";
-    
+
     public static final String HEAT_MAP_RANGE_CHOOSER_FIXED_TYPE_HIGHEST_SCALE_LABEL =
             "heat_map_range_chooser_type_fixed_type_highest_scale_label";
-    
+
     public static final String HEAT_MAP_RANGE_CHOOSER_FIXED_TYPE_SAME_VALUE_VALIDATION_MSG =
             "heat_map_range_chooser_type_fixed_type_same_value_validation_message";
-    
+
     private Dict()
     {
         // Can not be instantiated.
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/ScreeningDisplaySettingsManager.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/ScreeningDisplaySettingsManager.java
index 15bc186be88..83d772e583a 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/ScreeningDisplaySettingsManager.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/ScreeningDisplaySettingsManager.java
@@ -26,6 +26,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewConte
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DisplaySettingsManager;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.IRangeType;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageResolution;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.RangeType;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningDisplaySettings;
 
@@ -148,7 +149,7 @@ public class ScreeningDisplaySettingsManager
         }
         delays.put(displayTypeId, delay);
     }
-    
+
     public IRangeType getHeatMapRangeType(String featureCode)
     {
         Map<String, IRangeType> featureRangeTypes = screeningSettings.getDefaultFeatureRangeTypes();
@@ -162,7 +163,7 @@ public class ScreeningDisplaySettingsManager
         }
         return RangeType.MIN_MAX;
     }
-    
+
     public void setHeatMapRangeType(String featureCode, IRangeType rangeType)
     {
         Map<String, IRangeType> featureRangeTypes = screeningSettings.getDefaultFeatureRangeTypes();
@@ -174,4 +175,19 @@ public class ScreeningDisplaySettingsManager
         featureRangeTypes.put(featureCode, rangeType);
     }
 
+    @SuppressWarnings("deprecation")
+    public Map<String, IntensityRange> getIntensityRangesForChannels(String displayTypeId)
+    {
+        Map<String, IntensityRange> intensityRangesForChannels =
+                screeningSettings.getIntensityRangesForChannels().get(displayTypeId);
+
+        if (intensityRangesForChannels == null)
+        {
+            intensityRangesForChannels = new HashMap<String, IntensityRange>();
+            screeningSettings.getIntensityRangesForChannels().put(displayTypeId,
+                    intensityRangesForChannels);
+        }
+
+        return intensityRangesForChannels;
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooser.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooser.java
index af0014311fd..849d86e9916 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooser.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooser.java
@@ -50,6 +50,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.DatasetImagesR
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.DatasetOverlayImagesReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageResolution;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.InternalImageChannel;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.InternalImageTransformationInfo;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria.AnalysisProcedureCriteria;
@@ -94,6 +95,8 @@ class ChannelChooser
 
     private String imageTransformationCodeOrNull;
 
+    private IntensityRange rangeOrNull;
+
     public ChannelChooser(LogicalImageReference basicImage, IChanneledViewerFactory viewerFactory,
             IDefaultChannelState defaultChannelState)
     {
@@ -106,6 +109,7 @@ class ChannelChooser
         this.imageTransformationCodeOrNull =
                 tryGetInitialImageTransformationCode(defaultChannelState, basicChannelCodes,
                         basicImage.getImagetParameters());
+        this.rangeOrNull = tryGetInitialIntensityRange(defaultChannelState, basicChannelCodes);
         this.defaultChannelState = defaultChannelState;
         this.selectedOverlayChannels = new HashSet<ImageDatasetChannel>();
     }
@@ -122,7 +126,7 @@ class ChannelChooser
     {
         LogicalImageChannelsReference state =
                 new LogicalImageChannelsReference(basicImage, basicChannelCodes,
-                        imageTransformationCodeOrNull, selectedOverlayChannels);
+                        imageTransformationCodeOrNull, rangeOrNull, selectedOverlayChannels);
 
         LayoutContainer view =
                 viewerFactory.create(state,
@@ -137,7 +141,9 @@ class ChannelChooser
             final IViewContext<IScreeningClientServiceAsync> context,
             final AsyncCallback<Void> callback)
     {
-        final Widget loading = new Text(context.getMessage(ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict.LOAD_IN_PROGRESS));
+        final Widget loading =
+                new Text(
+                        context.getMessage(ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict.LOAD_IN_PROGRESS));
         container.add(loading);
 
         context.getService().getImageDatasetResolutions(basicImage.getDatasetCode(),
@@ -330,11 +336,13 @@ class ChannelChooser
                         @Override
                         public void selectionChanged(List<String> newlySelectedChannels,
                                 @SuppressWarnings("hiding")
-                                String imageTransformationCodeOrNull)
+                                String imageTransformationCodeOrNull, @SuppressWarnings("hiding")
+                                IntensityRange rangeOrNull)
                         {
                             basicChannelCodes = newlySelectedChannels;
                             ChannelChooser.this.imageTransformationCodeOrNull =
                                     imageTransformationCodeOrNull;
+                            ChannelChooser.this.rangeOrNull = rangeOrNull;
                             refresh();
                         }
                     });
@@ -408,4 +416,18 @@ class ChannelChooser
 
         return null;
     }
+
+    private static IntensityRange tryGetInitialIntensityRange(
+            IDefaultChannelState defaultChannelState, List<String> channels)
+    {
+        if (channels.size() == 1)
+        {
+            String channel = channels.get(0);
+            IntensityRange rangeOrNull = defaultChannelState.tryGetIntensityRange(channel);
+
+            return rangeOrNull;
+        }
+
+        return null;
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooserPanel.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooserPanel.java
index d95b597b95e..d04fad25187 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooserPanel.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelChooserPanel.java
@@ -27,9 +27,13 @@ import java.util.Map;
 import java.util.Set;
 
 import com.extjs.gxt.ui.client.event.BaseEvent;
+import com.extjs.gxt.ui.client.event.ButtonEvent;
 import com.extjs.gxt.ui.client.event.Events;
 import com.extjs.gxt.ui.client.event.Listener;
+import com.extjs.gxt.ui.client.event.SelectionListener;
 import com.extjs.gxt.ui.client.widget.LayoutContainer;
+import com.extjs.gxt.ui.client.widget.button.Button;
+import com.extjs.gxt.ui.client.widget.form.AdapterField;
 import com.extjs.gxt.ui.client.widget.form.CheckBox;
 import com.extjs.gxt.ui.client.widget.form.CheckBoxGroup;
 import com.extjs.gxt.ui.client.widget.form.Field;
@@ -43,6 +47,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.SimpleModelComboBox;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IMessageProvider;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.InternalImageTransformationInfo;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 
@@ -68,7 +73,8 @@ public class ChannelChooserPanel extends LayoutContainer
      */
     public static interface ChannelSelectionListener
     {
-        void selectionChanged(List<String> channels, String imageTransformationCodeOrNull);
+        void selectionChanged(List<String> channels, String imageTransformationCodeOrNull,
+                IntensityRange rangeOrNull);
     }
 
     public static final String DEFAULT_TRANSFORMATION_CODE = "$DEFAULT$";
@@ -80,6 +86,11 @@ public class ChannelChooserPanel extends LayoutContainer
                     "Grayscale images with color depth higher then 8 bits are transformed in the optimal way for a single image. Otherwise no filter is applied.",
                     "", false));
 
+    public static final LabeledItem<InternalImageTransformationInfo> USER_DEFINED_RESCALING_TRANSFORMATION =
+            convertToLabeledItem(new InternalImageTransformationInfo(
+                    ScreeningConstants.USER_DEFINED_RESCALING_CODE, "User defined",
+                    "User defined intensity rescaling", "", false));
+
     private final IMessageProvider messageProvider;
 
     private IDefaultChannelState defaultChannelState;
@@ -92,6 +103,11 @@ public class ChannelChooserPanel extends LayoutContainer
 
     private LabelField adjustLabel = new LabelField("Filter:");
 
+    private Button userDefinedTransformationSettingsButton = new Button("Settings");
+
+    private AdapterField userDefinedTransformationSettingsButtonField = new AdapterField(
+            userDefinedTransformationSettingsButton);
+
     private Map<String, Set<InternalImageTransformationInfo>> transformationsForChannels =
             new HashMap<String, Set<InternalImageTransformationInfo>>();
 
@@ -105,7 +121,6 @@ public class ChannelChooserPanel extends LayoutContainer
             {
                 selectionChanged();
             }
-
         };
 
     private final Listener<BaseEvent> transformationSelection = new Listener<BaseEvent>()
@@ -113,11 +128,15 @@ public class ChannelChooserPanel extends LayoutContainer
             @Override
             public void handleEvent(BaseEvent be)
             {
+                String transformationCode =
+                        transformationsComboBox.getSimpleValue().getItem().getCode();
                 defaultChannelState.setDefaultTransformation(getSelectedValues().get(0),
-                        transformationsComboBox.getSimpleValue().getItem().getCode());
-                notifySelectionListeners(getSelectedValues(), tryGetSelectedTransformationCode());
+                        transformationCode);
+                changeTransformationSettingsButtonVisibility(true, false);
+                notifySelectionListeners(getSelectedValues(),
+                        tryGetSelectedTransformationCode(false),
+                        defaultChannelState.tryGetIntensityRange(getSelectedValues().get(0)));
             }
-
         };
 
     public ChannelChooserPanel(IMessageProvider messageProvider,
@@ -126,7 +145,7 @@ public class ChannelChooserPanel extends LayoutContainer
         this(messageProvider, defChannelState, Collections.<String> emptyList(), null);
     }
 
-    public ChannelChooserPanel(IMessageProvider messageProvider,
+    public ChannelChooserPanel(final IMessageProvider messageProvider,
             IDefaultChannelState defChannelState, List<String> selectedChannelsOrNull,
             ImageDatasetParameters imageDatasetParameters)
     {
@@ -147,6 +166,21 @@ public class ChannelChooserPanel extends LayoutContainer
         group.add(channelsComboBox);
         group.add(adjustLabel);
         group.add(transformationsComboBox);
+        group.add(userDefinedTransformationSettingsButtonField);
+
+        userDefinedTransformationSettingsButton
+                .addSelectionListener(new SelectionListener<ButtonEvent>()
+                    {
+                        @Override
+                        public void componentSelected(ButtonEvent ce)
+                        {
+                            UserDefinedRescalingSettingsDialog dialog =
+                                    new UserDefinedRescalingSettingsDialog(messageProvider,
+                                            defaultChannelState, getSelectedValues().get(0));
+                            dialog.addListener(Events.Hide, transformationSelection);
+                            dialog.show();
+                        }
+                    });
 
         add(group);
 
@@ -357,34 +391,49 @@ public class ChannelChooserPanel extends LayoutContainer
         ensureAtLeastOneCheckboxChecked();
         updateTransformationComboBox();
 
-        notifySelectionListeners(selection, tryGetSelectedTransformationCode());
+        notifySelectionListeners(selection, tryGetSelectedTransformationCode(false),
+                defaultChannelState.tryGetIntensityRange(selectedComboValue));
     }
 
-    public String tryGetSelectedTransformationCode()
+    public String tryGetSelectedTransformationCode(boolean force)
     {
-        if (transformationsComboBox.isVisible())
-        {
-            String code =
-                    transformationsComboBox.getSelection().get(0).getValue().getItem().getCode();
+        String code = null;
 
-            if (DEFAULT_TRANSFORMATION_CODE.equals(code))
-            {
-                return null;
-            } else
-            {
-                return code;
-            }
+        if (force || transformationsComboBox.isVisible(false))
+        {
+            code = transformationsComboBox.getSelection().get(0).getValue().getItem().getCode();
+        } else if (transformationsComboBox.getStore().getModels().size() == 1)
+        {
+            code =
+                    transformationsComboBox.getStore().getModels().get(0).getValue().getItem()
+                            .getCode();
         }
 
-        return null;
+        return transformCode(code);
+    }
+
+    public IntensityRange tryGetSelectedIntensityRange()
+    {
+        return defaultChannelState.tryGetIntensityRange(getSelectedValues().get(0));
+    }
+
+    private static String transformCode(String code)
+    {
+        if (code == null || DEFAULT_TRANSFORMATION_CODE.equals(code))
+        {
+            return null;
+        } else
+        {
+            return code;
+        }
     }
 
     private void notifySelectionListeners(List<String> selection,
-            String imageTransformationCodeOrNull)
+            String imageTransformationCodeOrNull, IntensityRange rangeOrNull)
     {
         for (ChannelSelectionListener listener : channelSelectionListeners)
         {
-            listener.selectionChanged(selection, imageTransformationCodeOrNull);
+            listener.selectionChanged(selection, imageTransformationCodeOrNull, rangeOrNull);
         }
     }
 
@@ -490,10 +539,10 @@ public class ChannelChooserPanel extends LayoutContainer
             {
                 transformationsComboBox.setVisible(false);
                 adjustLabel.setVisible(false);
+                userDefinedTransformationSettingsButtonField.setVisible(false);
                 return;
             }
             transformationsComboBox.add(model);
-            setTransformationsVisible(true);
 
             selectTransformation(selectedValues.get(0));
         } else
@@ -502,6 +551,19 @@ public class ChannelChooserPanel extends LayoutContainer
         }
     }
 
+    private void changeTransformationSettingsButtonVisibility(boolean visible, boolean force)
+    {
+        if (visible
+                && ScreeningConstants.USER_DEFINED_RESCALING_CODE
+                        .equals(tryGetSelectedTransformationCode(force)))
+        {
+            userDefinedTransformationSettingsButtonField.setVisible(true);
+        } else
+        {
+            userDefinedTransformationSettingsButtonField.setVisible(false);
+        }
+    }
+
     private static boolean isDefaultTransformationDefined(Set<InternalImageTransformationInfo> infos)
     {
         for (InternalImageTransformationInfo imageTransformationInfo : infos)
@@ -518,6 +580,7 @@ public class ChannelChooserPanel extends LayoutContainer
     {
         transformationsComboBox.setVisible(visible);
         adjustLabel.setVisible(visible);
+        changeTransformationSettingsButtonVisibility(visible, false);
     }
 
     private void selectTransformation(String channelCode)
@@ -546,6 +609,8 @@ public class ChannelChooserPanel extends LayoutContainer
             transformationsComboBox.setSelection(Collections.singletonList(transformationsComboBox
                     .getStore().getModels().get(0)));
         }
+
+        changeTransformationSettingsButtonVisibility(true, true);
     }
 
     private boolean setSelectedValue(String code,
@@ -553,8 +618,8 @@ public class ChannelChooserPanel extends LayoutContainer
     {
         if (code != null)
         {
-            for (SimpleComboValue<LabeledItem<InternalImageTransformationInfo>> info : combobox.getStore()
-                    .getModels())
+            for (SimpleComboValue<LabeledItem<InternalImageTransformationInfo>> info : combobox
+                    .getStore().getModels())
             {
                 if (info.getValue().getItem().getCode().equals(code))
                 {
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelWidgetWithListener.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelWidgetWithListener.java
index 2bc0f4a8558..0e83909df37 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelWidgetWithListener.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/ChannelWidgetWithListener.java
@@ -22,10 +22,12 @@ import com.extjs.gxt.ui.client.widget.LayoutContainer;
 import com.google.gwt.user.client.ui.Widget;
 
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.utils.GuiUtils;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 
 /**
  * Allows to create a {@link Widget} ({@link #asWidget()}) containing channel view and allows to
- * manually update visible channel via the ({@link #selectionChanged(List, String)}) method.
+ * manually update visible channel via the ({@link #selectionChanged(List, String, IntensityRange)})
+ * method.
  * 
  * @author Izabela Adamczyk
  */
@@ -37,7 +39,8 @@ public class ChannelWidgetWithListener implements ChannelChooserPanel.ChannelSel
 
     interface ISimpleChanneledViewerFactory
     {
-        Widget create(List<String> channelCodes, String imageTransformationCodeOrNull);
+        Widget create(List<String> channelCodes, String imageTransformationCodeOrNull,
+                IntensityRange rangeOrNull);
     }
 
     public ChannelWidgetWithListener(final ISimpleChanneledViewerFactory viewerFactory)
@@ -52,12 +55,13 @@ public class ChannelWidgetWithListener implements ChannelChooserPanel.ChannelSel
     }
 
     @Override
-    public void selectionChanged(List<String> channelNames, String imageTransformationCodeOrNull)
+    public void selectionChanged(List<String> channelNames, String imageTransformationCodeOrNull,
+            IntensityRange rangeOrNull)
     {
         if (channelNames != null)
         {
             GuiUtils.replaceLastItem(container,
-                    viewerFactory.create(channelNames, imageTransformationCodeOrNull));
+                    viewerFactory.create(channelNames, imageTransformationCodeOrNull, rangeOrNull));
         }
     }
 }
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/DefaultChannelState.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/DefaultChannelState.java
index c118f892a83..921561aebd4 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/DefaultChannelState.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/DefaultChannelState.java
@@ -23,6 +23,7 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewConte
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.ScreeningDisplaySettingsManager;
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.ScreeningViewContext;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageResolution;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 
 /**
  * @author Kaloyan Enimanev
@@ -85,4 +86,20 @@ public class DefaultChannelState implements IDefaultChannelState
         getDisplaySettingManager().setDefaultResolution(displayTypeId, resolution);
     }
 
+    @Override
+    public void setIntensityRange(String channel, IntensityRange intensityRange)
+    {
+        getIntensityRange().put(channel, intensityRange);
+    }
+
+    @Override
+    public IntensityRange tryGetIntensityRange(String channel)
+    {
+        return getIntensityRange().get(channel);
+    }
+
+    private Map<String, IntensityRange> getIntensityRange()
+    {
+        return getDisplaySettingManager().getIntensityRangesForChannels(displayTypeId);
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/IDefaultChannelState.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/IDefaultChannelState.java
index 90d3182c1bf..d91f1ed3401 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/IDefaultChannelState.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/IDefaultChannelState.java
@@ -3,6 +3,7 @@ package ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.
 import java.util.List;
 
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageResolution;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 
 /**
  * Allows to get and set the channels chosen by default when images are shown in a specific context.
@@ -23,4 +24,7 @@ public interface IDefaultChannelState
 
     public void setDefaultResolution(ImageResolution resolution);
 
+    public void setIntensityRange(String channel, IntensityRange intensityRange);
+
+    public IntensityRange tryGetIntensityRange(String channel);
 }
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaSummaryComponent.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaSummaryComponent.java
index 1da95be910a..fc2bcda9326 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaSummaryComponent.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaSummaryComponent.java
@@ -63,6 +63,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria.AnalysisProcedureCriteria;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria.ExperimentSearchCriteria;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 
 /**
  * Component which for a specified material and experiment presents 1. feature vectors (detailed and
@@ -345,15 +346,18 @@ public class MaterialReplicaSummaryComponent
         final ISimpleChanneledViewerFactory viewerFactory = new ISimpleChanneledViewerFactory()
             {
                 @Override
-                public Widget create(List<String> channels, String imageTransformationCodeOrNull)
+                public Widget create(List<String> channels, String imageTransformationCodeOrNull,
+                        IntensityRange rangeOrNull)
                 {
                     return WellContentDialog.createImageViewerForChannel(screeningViewContext,
-                            image, oneImageSizeFactorPx, channels, imageTransformationCodeOrNull);
+                            image, oneImageSizeFactorPx, channels, imageTransformationCodeOrNull,
+                            rangeOrNull);
                 }
             };
         ChannelWidgetWithListener widgetWithListener = new ChannelWidgetWithListener(viewerFactory);
         widgetWithListener.selectionChanged(channelChooser.getSelectedValues(),
-                channelChooser.tryGetSelectedTransformationCode());
+                channelChooser.tryGetSelectedTransformationCode(false),
+                channelChooser.tryGetSelectedIntensityRange());
 
         ImageDatasetParameters imageParameters = image.tryGetImageDataset().getImageParameters();
         channelChooser.addSelectionChangedListener(widgetWithListener);
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/UserDefinedRescalingSettingsDialog.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/UserDefinedRescalingSettingsDialog.java
new file mode 100644
index 00000000000..e05c6767bb5
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/UserDefinedRescalingSettingsDialog.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012 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.client.web.client.application.detailviewers;
+
+import com.extjs.gxt.ui.client.event.BaseEvent;
+import com.extjs.gxt.ui.client.event.Events;
+import com.extjs.gxt.ui.client.event.Listener;
+import com.extjs.gxt.ui.client.widget.Dialog;
+import com.extjs.gxt.ui.client.widget.Label;
+import com.extjs.gxt.ui.client.widget.button.Button;
+import com.google.gwt.user.client.ui.Grid;
+
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.field.IntegerField;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IMessageProvider;
+import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.Dict;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class UserDefinedRescalingSettingsDialog extends Dialog
+{
+    private final Label labelMin;
+
+    private final Label labelMax;
+
+    private final IntegerField minTextField;
+
+    private final IntegerField maxTextField;
+
+    private final IMessageProvider messageProvider;
+
+    private final IDefaultChannelState defaultChannelState;
+
+    private final String channelCode;
+
+    public UserDefinedRescalingSettingsDialog(IMessageProvider messageProvider,
+            IDefaultChannelState defaultChannelState, String channelCode)
+    {
+        super();
+
+        this.messageProvider = messageProvider;
+        this.defaultChannelState = defaultChannelState;
+        this.channelCode = channelCode;
+
+        setHeading(this.messageProvider.getMessage(Dict.TITLE_USER_DEFINED_RESCALING_DIALOG));
+        setButtons(Dialog.OKCANCEL);
+        labelMin = new Label(this.messageProvider.getMessage(Dict.RESCALING_DIALOG_MIN));
+        labelMax = new Label(this.messageProvider.getMessage(Dict.RESCALING_DIALOG_MAX));
+        minTextField =
+                new IntegerField(messageProvider.getMessage(Dict.RESCALING_DIALOG_MIN), true);
+        maxTextField =
+                new IntegerField(messageProvider.getMessage(Dict.RESCALING_DIALOG_MAX), true);
+
+        setInitialValues();
+
+        Grid grid = new Grid(2, 2);
+        grid.setWidget(0, 0, labelMin);
+        grid.setWidget(0, 1, minTextField);
+        grid.setWidget(1, 0, labelMax);
+        grid.setWidget(1, 1, maxTextField);
+
+        add(grid);
+
+        setAutoWidth(true);
+        setAutoHeight(true);
+
+        addListener(Events.Show, new Listener<BaseEvent>()
+            {
+                @Override
+                public void handleEvent(BaseEvent be)
+                {
+                    center();
+                }
+            });
+    }
+
+    private void setInitialValues()
+    {
+        minTextField.setValue(0);
+        maxTextField.setValue(255);
+
+        IntensityRange range = defaultChannelState.tryGetIntensityRange(channelCode);
+        if (range != null)
+        {
+            minTextField.setValue(range.getBlackPoint());
+            maxTextField.setValue(range.getWhitePoint());
+        }
+    }
+
+    @Override
+    protected final void onButtonPressed(final Button button)
+    {
+        if (button.getItemId().equals(Dialog.OK))
+        {
+            button.disable();
+            if (minTextField.isValid() && maxTextField.isValid())
+            {
+                super.onButtonPressed(button);
+                updateIntensityRescaling();
+
+                hide();
+            } else
+            {
+                button.enable();
+            }
+        } else
+        {
+            super.onButtonPressed(button);
+            hide();
+        }
+    }
+
+    public void updateIntensityRescaling()
+    {
+        IntensityRange result = null;
+
+        try
+        {
+            int min = Integer.parseInt(minTextField.getValue().toString());
+            int max = Integer.parseInt(maxTextField.getValue().toString());
+
+            result = new IntensityRange(min, max);
+        } catch (NumberFormatException e)
+        {
+        }
+
+        if (result != null)
+        {
+            defaultChannelState.setIntensityRange(channelCode, result);
+        }
+    }
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellContentDialog.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellContentDialog.java
index 4976fa4114e..f86322e8d6c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellContentDialog.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellContentDialog.java
@@ -63,6 +63,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.PlateUtils;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ExperimentReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetEnrichedReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.TileLocation;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
@@ -170,7 +171,7 @@ public class WellContentDialog extends ImageDialog
     public static Widget createImageViewerForChannel(
             final IViewContext<IScreeningClientServiceAsync> viewContext,
             final WellContent wellImage, int imageSizePx, List<String> channels,
-            String imageTransformationCodeOrNull)
+            String imageTransformationCodeOrNull, IntensityRange rangeOrNull)
     {
         final ImageDatasetEnrichedReference imageDataset = tryGetImageDataset(wellImage);
         if (imageDataset == null)
@@ -208,7 +209,7 @@ public class WellContentDialog extends ImageDialog
                 new LogicalImageReference(imageDataset, locationOrNull);
         LogicalImageChannelsReference channelReferences =
                 LogicalImageChannelsReference.createWithoutOverlays(wellImages, channels,
-                        imageTransformationCodeOrNull);
+                        imageTransformationCodeOrNull, rangeOrNull);
         LayoutContainer staticTilesGrid =
                 LogicalImageViewer.createTilesGrid(channelReferences, sessionId, imageSizePx,
                         clickHandler, null);
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellSearchGrid.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellSearchGrid.java
index 8fd690102eb..76abc4000ed 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellSearchGrid.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/WellSearchGrid.java
@@ -72,6 +72,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.u
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.DatasetImagesReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.DatasetReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria.AnalysisProcedureCriteria;
@@ -637,17 +638,19 @@ public class WellSearchGrid extends TypedTableGrid<WellContent> implements
                                 {
                                     @Override
                                     public Widget create(List<String> channels,
-                                            String imageTransformationCodeOrNull)
+                                            String imageTransformationCodeOrNull,
+                                            IntensityRange rangeOrNull)
                                     {
                                         return WellContentDialog.createImageViewerForChannel(
                                                 getViewContext(), entity, IMAGE_SIZE_PX, channels,
-                                                imageTransformationCodeOrNull);
+                                                imageTransformationCodeOrNull, rangeOrNull);
                                     }
                                 };
                     ChannelWidgetWithListener widgetWithListener =
                             new ChannelWidgetWithListener(viewerFactory);
                     widgetWithListener.selectionChanged(channelChooser.getSelectedValues(),
-                            channelChooser.tryGetSelectedTransformationCode());
+                            channelChooser.tryGetSelectedTransformationCode(false),
+                            channelChooser.tryGetSelectedIntensityRange());
 
                     ImageDatasetParameters imageParameters = images.getImageParameters();
                     channelChooser.addSelectionChangedListener(widgetWithListener);
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageChannelsReference.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageChannelsReference.java
index 2929a72282e..87034aefc2d 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageChannelsReference.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageChannelsReference.java
@@ -20,6 +20,8 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
+
 /**
  * Channels of the basic images and overlay images.
  * 
@@ -29,10 +31,10 @@ public class LogicalImageChannelsReference
 {
     public static LogicalImageChannelsReference createWithoutOverlays(
             LogicalImageReference basicImage, List<String> channels,
-            String imageTransformationCodeOrNull)
+            String imageTransformationCodeOrNull, IntensityRange rangeOrNull)
     {
         return new LogicalImageChannelsReference(basicImage, channels,
-                imageTransformationCodeOrNull, new HashSet<ImageDatasetChannel>());
+                imageTransformationCodeOrNull, rangeOrNull, new HashSet<ImageDatasetChannel>());
     }
 
     // ----
@@ -43,14 +45,18 @@ public class LogicalImageChannelsReference
 
     private final String imageTransformationCodeOrNull;
 
+    private final IntensityRange rangeOrNull;
+
     private final Set<ImageDatasetChannel> overlayChannels;
 
     public LogicalImageChannelsReference(LogicalImageReference basicImage, List<String> channels,
-            String imageTransformationCodeOrNull, Set<ImageDatasetChannel> overlayChannels)
+            String imageTransformationCodeOrNull, IntensityRange rangeOrNull,
+            Set<ImageDatasetChannel> overlayChannels)
     {
         this.basicImage = basicImage;
         this.channels = channels;
         this.imageTransformationCodeOrNull = imageTransformationCodeOrNull;
+        this.rangeOrNull = rangeOrNull;
         this.overlayChannels = overlayChannels;
     }
 
@@ -73,4 +79,9 @@ public class LogicalImageChannelsReference
     {
         return imageTransformationCodeOrNull;
     }
+
+    public IntensityRange tryGetIntensityRange()
+    {
+        return rangeOrNull;
+    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/image/Image.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/image/Image.java
index 9645a47fc94..27b95c867b8 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/image/Image.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/image/Image.java
@@ -32,6 +32,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.d
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.dto.ImageDatasetChannel;
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.dto.LogicalImageChannelsReference;
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.dto.LogicalImageReference;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.IntensityRange;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants.ImageServletUrlParameters;
 
@@ -61,8 +62,20 @@ public class Image extends LayoutContainer
 
         if (getInitializer().getChannelReferences().tryGetImageTransformationCode() != null)
         {
+            String suffix = "";
+            if (ScreeningConstants.USER_DEFINED_RESCALING_CODE.equalsIgnoreCase(getInitializer()
+                    .getChannelReferences().tryGetImageTransformationCode()))
+            {
+                IntensityRange range =
+                        getInitializer().getChannelReferences().tryGetIntensityRange();
+                if (range != null)
+                {
+                    suffix = "(" + range.getBlackPoint() + "," + range.getWhitePoint() + ")";
+                }
+            }
             url.addParameter(ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM,
-                    getInitializer().getChannelReferences().tryGetImageTransformationCode());
+                    getInitializer().getChannelReferences().tryGetImageTransformationCode()
+                            + suffix);
         }
 
         addImageTransformerSignature(url, getInitializer().getChannelReferences());
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/public/screening-dictionary.js b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/public/screening-dictionary.js
index 8816bbc4497..b47d96e6c8f 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/public/screening-dictionary.js
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/public/screening-dictionary.js
@@ -141,6 +141,10 @@ RESOLUTION_CHOOSER_LABEL: "Resolution:",
 RESOLUTION_CHOOSER_DEFAULT: "Default",
 RESOLUTION_CHOOSER_RESOLUTION: "{0}x{1}",
 
+TITLE_USER_DEFINED_RESCALING_DIALOG: "Set rescaling parameters",
+RESCALING_DIALOG_MIN: "black point: ",
+RESCALING_DIALOG_MAX: "white point: ",
+
 // LAST LINE: KEEP IT AT THE END
 lastline: "" // we need a line without a comma
 };
\ No newline at end of file
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 e185875d656..a5bce90cfc0 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
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
+import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.ChannelChooserPanel;
 
 /**
  * Describes the images in the dataset: tiles geometry, channels, dataset code and plate geometry if
@@ -49,7 +50,7 @@ public class ImageDatasetParameters implements Serializable
     private boolean isMultidimensional;
 
     private String mergedChannelTransformerFactorySignatureOrNull;
-    
+
     public Integer tryGetRowsNum()
     {
         return rowsNumOrNull;
@@ -129,14 +130,19 @@ public class ImageDatasetParameters implements Serializable
     public List<InternalImageTransformationInfo> getAvailableImageTransformationsFor(
             String channelCode)
     {
+        List<InternalImageTransformationInfo> result =
+                new ArrayList<InternalImageTransformationInfo>();
+        result.add(ChannelChooserPanel.USER_DEFINED_RESCALING_TRANSFORMATION.getItem());
+
         for (InternalImageChannel channel : channels)
         {
             if (channel.getCode().equalsIgnoreCase(channelCode))
             {
-                return channel.getAvailableImageTransformations();
+                result.addAll(channel.getAvailableImageTransformations());
+                return result;
             }
         }
-        return new ArrayList<InternalImageTransformationInfo>();
+        return result;
     }
 
     public boolean isMultidimensional()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/IntensityRange.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/IntensityRange.java
new file mode 100644
index 00000000000..655e7da495a
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/IntensityRange.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 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.basic.dto;
+
+import java.io.Serializable;
+
+/**
+ * The intensity range in a distribution of pixel values for a given symmetric quantile value.
+ * 
+ * @author Pawel Glyzewski
+ */
+public class IntensityRange implements Serializable
+{
+
+    private static final long serialVersionUID = 1L;
+
+    private int blackPoint;
+
+    private int whitePoint;
+
+    @SuppressWarnings("unused")
+    private IntensityRange()
+    {
+    }
+
+    public IntensityRange(int blackPoint, int whitePoint)
+    {
+        this.blackPoint = blackPoint;
+        this.whitePoint = whitePoint;
+    }
+
+    /**
+     * The minimal level (black point).
+     */
+    public int getBlackPoint()
+    {
+        return blackPoint;
+    }
+
+    /**
+     * The maximal level (white point).
+     */
+    public int getWhitePoint()
+    {
+        return whitePoint;
+    }
+
+    @SuppressWarnings("unused")
+    private void setBlackPoint(int blackPoint)
+    {
+        this.blackPoint = blackPoint;
+    }
+
+    @SuppressWarnings("unused")
+    private void setWhitePoint(int whitePoint)
+    {
+        this.whitePoint = whitePoint;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "MinMax [minLevel=" + blackPoint + ", maxLevel=" + whitePoint + "]";
+    }
+
+}
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 933e4f98033..f1e1e87242e 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
@@ -257,4 +257,5 @@ public class ScreeningConstants
         public final static String TILE_COL_PARAM = "tileCol";
     }
 
+    public static final String USER_DEFINED_RESCALING_CODE = "$USER_DEFINED_RESCALING$";
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningDisplaySettings.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningDisplaySettings.java
index 4a54e287b10..08e17004823 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningDisplaySettings.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningDisplaySettings.java
@@ -40,11 +40,14 @@ public class ScreeningDisplaySettings implements Serializable
     private Map<String, ImageResolution> defaultResolutions;
 
     private Map<String, Integer> defaultMovieDelays;
-    
+
     private Map<String, IRangeType> defaultFeatureRangeTypes;
 
     private String defaultAnalysisProcedure;
 
+    private Map<String, Map<String, IntensityRange>> intensityRangesForChannels =
+            new HashMap<String, Map<String, IntensityRange>>();
+
     /** @deprecated Should be used only by ScreeningDisplaySettingsManager. */
     @Deprecated
     public Map<String, String> getDefaultChannels()
@@ -118,6 +121,24 @@ public class ScreeningDisplaySettings implements Serializable
         this.defaultMovieDelays = defaultMovieDelays;
     }
 
+    /** @deprecated Should be used only by ScreeningDisplaySettingsManager. */
+    @Deprecated
+    public Map<String, Map<String, IntensityRange>> getIntensityRangesForChannels()
+    {
+        if (intensityRangesForChannels == null)
+        {
+            intensityRangesForChannels = new HashMap<String, Map<String, IntensityRange>>();
+        }
+        return intensityRangesForChannels;
+    }
+
+    @SuppressWarnings("unused")
+    private void setIntensityRangesForChannels(
+            Map<String, Map<String, IntensityRange>> intensityRangesForChannels)
+    {
+        this.intensityRangesForChannels = intensityRangesForChannels;
+    }
+
     public Map<String, IRangeType> getDefaultFeatureRangeTypes()
     {
         return defaultFeatureRangeTypes;
-- 
GitLab