From d791a88e6743d1125c057c69bd182d8beed7eb8a Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Fri, 6 May 2011 12:50:23 +0000
Subject: [PATCH] LMS-2190 show images in a grid, fix heights

SVN: 21159
---
 .../web/client/IScreeningClientService.java   |   3 +-
 .../client/IScreeningClientServiceAsync.java  |   4 +-
 .../detailviewers/LogicalImageViewer.java     |  38 +--
 .../MaterialReplicaFeatureSummaryViewer.java  | 206 +++++++++++--
 .../detailviewers/WellContentDialog.java      |   6 +-
 .../detailviewers/WellSearchGrid.java         |   7 +-
 ...MaterialReplicaFeatureSummaryProvider.java |  16 +-
 .../web/server/ScreeningClientService.java    |   4 +-
 .../screening/server/ScreeningServer.java     |   9 +-
 .../server/ScreeningServerLogger.java         |   5 +-
 .../MaterialFeatureVectorSummaryLoader.java   | 287 +++++++-----------
 .../logic/ReplicateSequenceProvider.java      | 189 ++++++++++++
 .../server/logic/WellContentLoader.java       |  51 +++-
 .../screening/server/logic/dto/IWellData.java |   3 +-
 .../MaterialAllReplicasFeatureVectors.java    |  12 +-
 ...rialBiologicalReplicateFeatureVector.java} |  16 +-
 ...erialTechnicalReplicateFeatureVector.java} |  23 +-
 .../screening/server/logic/dto/WellData.java  |  12 +
 .../screening/shared/IScreeningServer.java    |   5 +-
 ...ialBiologicalReplicateFeatureSummary.java} |   7 +-
 .../dto/MaterialReplicaFeatureSummary.java    |  22 +-
 .../MaterialReplicaFeatureSummaryResult.java  |   5 +-
 .../basic/dto/MaterialSummarySettings.java    |  12 +-
 .../shared/basic/dto/WellContent.java         |  15 +-
 .../shared/basic/dto/WellReplicaImage.java    |  67 ++++
 ...aterialFeatureVectorSummaryLoaderTest.java |  10 +-
 26 files changed, 719 insertions(+), 315 deletions(-)
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ReplicateSequenceProvider.java
 rename screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/{MaterialReplicaSubgroupFeatureVector.java => MaterialBiologicalReplicateFeatureVector.java} (75%)
 rename screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/{MaterialSingleReplicaFeatureVector.java => MaterialTechnicalReplicateFeatureVector.java} (62%)
 rename screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/{MaterialReplicaSubgroupFeatureSummary.java => MaterialBiologicalReplicateFeatureSummary.java} (87%)
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellReplicaImage.java

diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java
index 6dc79a00e7d..62976b8ed3d 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientService.java
@@ -47,6 +47,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateImages;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellMetadata;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 
 /**
@@ -119,7 +120,7 @@ public interface IScreeningClientService extends IClientService
      * for the well, all but the first one are ignored. If there is no image dataset for the well,
      * the whole well is ignored.
      */
-    public List<WellContent> listWellImages(TechId materialId, TechId experimentId)
+    public List<WellReplicaImage> listWellImages(TechId materialId, TechId experimentId)
             throws UserFailureException;
 
     /**
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java
index 29721c23bd8..ce723e028f6 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/IScreeningClientServiceAsync.java
@@ -48,6 +48,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateImages;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellMetadata;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 
 /**
@@ -96,7 +97,7 @@ public interface IScreeningClientServiceAsync extends IClientServiceAsync
      * @see IScreeningClientService#listWellImages(TechId, TechId)
      */
     public void listWellImages(TechId materialId, TechId experimentId,
-            AsyncCallback<List<WellContent>> callback);
+            AsyncCallback<List<WellReplicaImage>> callback);
 
     /**
      * @see IScreeningClientService#prepareExportPlateWells(TableExportCriteria)
@@ -192,6 +193,7 @@ public interface IScreeningClientServiceAsync extends IClientServiceAsync
             IResultSetConfig<String, TableModelRowWithObject<MaterialReplicaFeatureSummary>> resultSetConfig,
             TechId experimentId, TechId materialId,
             AsyncCallback<TypedTableResultSet<MaterialReplicaFeatureSummary>> callback);
+
     /**
      * @see IScreeningClientService#prepareExportMaterialReplicaFeatureSummary(TableExportCriteria)
      */
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageViewer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageViewer.java
index 0a811946bb9..7ec13c984f9 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageViewer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageViewer.java
@@ -59,9 +59,7 @@ public class LogicalImageViewer
 {
     private static final String NO_IMAGES_AVAILABLE_MSG = "No images available";
 
-    private static final int ONE_IMAGE_WIDTH_PX = 200;
-
-    private static final int ONE_IMAGE_HEIGHT_PX = 120;
+    private static final int ONE_IMAGE_SIZE_PX = 120;
 
     private static final int CHANNEL_SPLITER_AND_LABEL_HEIGHT_PX = 120;
 
@@ -161,7 +159,8 @@ public class LogicalImageViewer
 
     private int getSeriesImageWidgetHeight()
     {
-        return CHANNEL_SPLITER_AND_LABEL_HEIGHT_PX + getImageHeight(logicalImageReference)
+        return CHANNEL_SPLITER_AND_LABEL_HEIGHT_PX
+                + getImageHeight(ONE_IMAGE_SIZE_PX, logicalImageReference)
                 * logicalImageReference.getTileRowsNum();
     }
 
@@ -176,8 +175,8 @@ public class LogicalImageViewer
                     String sessionId = getSessionId(viewContext);
                     setAdjustColorsButtonState(adjustColorsButton,
                             channelReferences.getChannelCodes());
-                    int imageWidth = getImageWidth(logicalImageReference);
-                    int imageHeight = getImageHeight(logicalImageReference);
+                    int imageWidth = getImageWidth(ONE_IMAGE_SIZE_PX, logicalImageReference);
+                    int imageHeight = getImageHeight(ONE_IMAGE_SIZE_PX, logicalImageReference);
                     return LogicalImageSeriesGrid.create(sessionId, channelStackImages,
                             channelReferences, imageWidth, imageHeight);
                 }
@@ -288,7 +287,7 @@ public class LogicalImageViewer
                     setAdjustColorsButtonState(adjustColorsButton,
                             channelReferences.getChannelCodes());
                     String sessionId = getSessionId(viewContext);
-                    return createTilesGrid(channelReferences, sessionId);
+                    return createTilesGrid(channelReferences, sessionId, ONE_IMAGE_SIZE_PX, true);
                 }
 
             };
@@ -340,17 +339,18 @@ public class LogicalImageViewer
         return "true".equals(viewContext.getPropertyOrNull("image-viewer-enabled"));
     }
 
-    private static LayoutContainer createTilesGrid(LogicalImageChannelsReference channelReferences,
-            String sessionId)
+    /** Creates a widget with a representative image of the specified logical image. */
+    public static LayoutContainer createTilesGrid(LogicalImageChannelsReference channelReferences,
+            String sessionId, int imageSizePx, boolean createImageLinks)
     {
         LogicalImageReference images = channelReferences.getBasicImage();
-        return createTilesGrid(channelReferences, sessionId, getImageWidth(images),
-                getImageHeight(images), true);
+        return createTilesGrid(channelReferences, sessionId, getImageWidth(imageSizePx, images),
+                getImageHeight(imageSizePx, images), createImageLinks);
     }
 
-    /** Creates a widget with a representative image of the specified logical image. */
-    public static LayoutContainer createTilesGrid(LogicalImageChannelsReference channelReferences,
-            String sessionId, int imageWidth, int imageHeight, boolean createImageLinks)
+    private static LayoutContainer createTilesGrid(LogicalImageChannelsReference channelReferences,
+            String sessionId, int logicalImageWidth, int logicalImageHeight,
+            boolean createImageLinks)
     {
         LogicalImageReference images = channelReferences.getBasicImage();
         LayoutContainer container = new LayoutContainer(new TableLayout(images.getTileColsNum()));
@@ -359,7 +359,7 @@ public class LogicalImageViewer
             for (int col = 1; col <= images.getTileColsNum(); col++)
             {
                 ImageUrlUtils.addImageUrlWidget(container, sessionId, channelReferences, row, col,
-                        imageWidth, imageHeight, createImageLinks);
+                        logicalImageWidth, logicalImageHeight, createImageLinks);
             }
         }
         return container;
@@ -389,16 +389,16 @@ public class LogicalImageViewer
         return viewContext.getModel().getSessionContext().getSessionID();
     }
 
-    private static int getImageHeight(LogicalImageReference images)
+    private static int getImageHeight(int imageSizePx, LogicalImageReference images)
     {
         float imageSizeMultiplyFactor = getImageSizeMultiplyFactor(images);
-        return (int) (ONE_IMAGE_HEIGHT_PX * imageSizeMultiplyFactor);
+        return (int) (imageSizePx * imageSizeMultiplyFactor);
     }
 
-    private static int getImageWidth(LogicalImageReference images)
+    private static int getImageWidth(int imageSizePx, LogicalImageReference images)
     {
         float imageSizeMultiplyFactor = getImageSizeMultiplyFactor(images);
-        return (int) (ONE_IMAGE_WIDTH_PX * imageSizeMultiplyFactor);
+        return (int) (imageSizePx * imageSizeMultiplyFactor);
     }
 
     private static float getImageSizeMultiplyFactor(LogicalImageReference images)
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaFeatureSummaryViewer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaFeatureSummaryViewer.java
index bc5dd275e04..5de170c288c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaFeatureSummaryViewer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/MaterialReplicaFeatureSummaryViewer.java
@@ -16,7 +16,12 @@
 
 package ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import com.extjs.gxt.ui.client.Style.Orientation;
@@ -30,6 +35,7 @@ import com.extjs.gxt.ui.client.widget.Text;
 import com.extjs.gxt.ui.client.widget.Viewport;
 import com.extjs.gxt.ui.client.widget.layout.RowData;
 import com.extjs.gxt.ui.client.widget.layout.RowLayout;
+import com.extjs.gxt.ui.client.widget.layout.TableLayout;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.ui.Widget;
@@ -58,8 +64,8 @@ import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.d
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.ui.columns.specific.ScreeningLinkExtractor;
 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.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellImage;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 
 /**
  * A viewer that comprises several UI elements to produce a holistic UI for material replica feature
@@ -69,7 +75,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellImage;
  */
 public class MaterialReplicaFeatureSummaryViewer
 {
-    private static final int WELL_IMAGES_SIZE_PX = 400;
+    private static final int ONE_IMAGE_SIZE_PX = 80;
 
     public static void openTab(IViewContext<IScreeningClientServiceAsync> screeningViewContext,
             String experimentPermId, MaterialIdentifier materialIdentifier)
@@ -196,7 +202,7 @@ public class MaterialReplicaFeatureSummaryViewer
             };
     }
 
-    private class ImagesFoundCallback extends AbstractAsyncCallback<List<WellContent>>
+    private class ImagesFoundCallback extends AbstractAsyncCallback<List<WellReplicaImage>>
     {
         private final LayoutContainer panel;
 
@@ -207,14 +213,14 @@ public class MaterialReplicaFeatureSummaryViewer
         }
 
         @Override
-        protected void process(List<WellContent> images)
+        protected void process(List<WellReplicaImage> images)
         {
-            panel.add(createImagesViewer(images));
+            panel.add(createImagePanel(images));
             panel.layout();
         }
     }
 
-    private Widget createImagesViewer(List<? extends WellImage> images)
+    private Widget createImagePanel(List<WellReplicaImage> images)
     {
         if (images.isEmpty())
         {
@@ -226,27 +232,161 @@ public class MaterialReplicaFeatureSummaryViewer
         ChannelChooserPanel channelChooser = new ChannelChooserPanel(defaultChannelState);
         LayoutContainer panel = new LayoutContainer();
         panel.setLayout(new RowLayout());
-        Html headingWidget = createHeader("<br><br><hr>" + "Images" + "<br><br>");
-        panel.add(headingWidget, new RowData(1, -1));
+        panel.add(new Html("<br>"), new RowData(1, -1));
         panel.add(channelChooser);
 
-        LayoutContainer imagePanel = new LayoutContainer();
-        imagePanel.setScrollMode(Scroll.AUTOY);
-        imagePanel.setLayout(new RowLayout());
-        int imageSize = Math.min(100, WELL_IMAGES_SIZE_PX / images.size());
-        for (WellImage wellImage : images)
+        Map<String, List<WellReplicaImage>> labelToReplicasMap = createSortedImageMap(images);
+        String orphanGroupKey = null;
+        List<WellReplicaImage> orphanTechnicalReplicates = labelToReplicasMap.get(orphanGroupKey);
+        if (orphanTechnicalReplicates != null)
+        {
+            panel.add(createOrphanTechnicalReplicatesPanel(orphanTechnicalReplicates,
+                    channelChooser));
+            labelToReplicasMap.remove(orphanGroupKey);
+        }
+        panel.add(createBiologicalReplicatesImagesPanel(labelToReplicasMap, channelChooser));
+        if (screeningViewContext.isSimpleOrEmbeddedMode() == false)
         {
-            Widget imageViewer = createImageViewer(wellImage, channelChooser, imageSize);
-            imagePanel.add(imageViewer);
+            // WORKAROUND: in normal mode the height of menu and tab is not taken into account,
+            // so we add empty space to make
+            Text box = new Text();
+            box.setHeight(100);
+            panel.add(box);
         }
-        // TODO 2011-04-12, Tomasz Pylak: correct the height
-        double imagePanelHeight = Math.min(500, images.size() * imageSize + 150);
-        panel.add(imagePanel, new RowData(1, imagePanelHeight));
         return panel;
     }
 
-    private Widget createImageViewer(final WellImage image, ChannelChooserPanel channelChooser,
-            final int imageSize)
+    private Widget createOrphanTechnicalReplicatesPanel(
+            List<WellReplicaImage> orphanTechnicalReplicates, ChannelChooserPanel channelChooser)
+    {
+        LayoutContainer imagePanel = new LayoutContainer();
+        for (WellReplicaImage image : orphanTechnicalReplicates)
+        {
+            LayoutContainer imageWithLabel = new LayoutContainer();
+            imageWithLabel.setLayout(new RowLayout());
+
+            Widget label =
+                    createTechnicalReplicateLabel(image.getTechnicalReplicateSequenceNumber());
+            imageWithLabel.add(label);
+
+            Widget imageViewer = createImageViewer(image.getWellImage(), channelChooser);
+            imageWithLabel.add(imageViewer);
+
+            imagePanel.add(imageWithLabel);
+        }
+        return imagePanel;
+    }
+
+    private LayoutContainer createBiologicalReplicatesImagesPanel(
+            Map<String, List<WellReplicaImage>> labelToReplicasMap,
+            ChannelChooserPanel channelChooser)
+    {
+        int maxReplicaNumber = calcMaxReplicaNumber(labelToReplicasMap);
+        LayoutContainer imagePanel = new LayoutContainer();
+        TableLayout layout = new TableLayout(maxReplicaNumber + 1);
+        layout.setBorder(1);
+        imagePanel.setLayout(layout);
+
+        addImageTableHeader(maxReplicaNumber, imagePanel);
+        List<String> sortedLabels = sortCopy(labelToReplicasMap.keySet());
+        for (String label : sortedLabels)
+        {
+            List<WellReplicaImage> sortedTechnicalReplicates = labelToReplicasMap.get(label);
+
+            imagePanel.add(new Text(label));
+            for (int i = 0; i < maxReplicaNumber; i++)
+            {
+                if (i < sortedTechnicalReplicates.size())
+                {
+                    WellImage wellImage = sortedTechnicalReplicates.get(i).getWellImage();
+                    Widget imageViewer = createImageViewer(wellImage, channelChooser);
+                    imagePanel.add(imageViewer);
+                } else
+                {
+                    imagePanel.add(createEmptyBox());
+                }
+            }
+        }
+        return imagePanel;
+    }
+
+    private static List<String> sortCopy(Set<String> values)
+    {
+        List<String> sorted = new ArrayList<String>(values);
+        Collections.sort(sorted);
+        return sorted;
+    }
+
+    private void addImageTableHeader(int maxReplicaNumber, LayoutContainer imagePanel)
+    {
+        for (int i = 0; i <= maxReplicaNumber; i++)
+        {
+            if (i == 0)
+            {
+                imagePanel.add(createEmptyBox());
+            } else
+            {
+                imagePanel.add(createTechnicalReplicateLabel(i));
+            }
+        }
+    }
+
+    private Widget createTechnicalReplicateLabel(int technicalReplicateSequence)
+    {
+        return new Text("repl. " + technicalReplicateSequence);
+    }
+
+    private Widget createEmptyBox()
+    {
+        return new Text();
+    }
+
+    private static int calcMaxReplicaNumber(Map<String, List<WellReplicaImage>> labelToReplicasMap)
+    {
+        int max = 0;
+        for (List<WellReplicaImage> technicalReplicates : labelToReplicasMap.values())
+        {
+            max = Math.max(max, technicalReplicates.size());
+        }
+        return max;
+    }
+
+    private static Map<String/* label */, List<WellReplicaImage>> createSortedImageMap(
+            List<WellReplicaImage> images)
+    {
+        Map<String, List<WellReplicaImage>> map = new HashMap<String, List<WellReplicaImage>>();
+        for (WellReplicaImage image : images)
+        {
+            String label = image.tryGetBiologicalReplicateLabel();
+            List<WellReplicaImage> technicalReplicas = map.get(label);
+            if (technicalReplicas == null)
+            {
+                technicalReplicas = new ArrayList<WellReplicaImage>();
+            }
+            technicalReplicas.add(image);
+            map.put(label, technicalReplicas);
+        }
+        sortTechnicalReplicas(map);
+        return map;
+    }
+
+    private static void sortTechnicalReplicas(Map<String, List<WellReplicaImage>> map)
+    {
+        for (List<WellReplicaImage> technicalReplicas : map.values())
+        {
+            Collections.sort(technicalReplicas, new Comparator<WellReplicaImage>()
+                {
+                    public int compare(WellReplicaImage arg1, WellReplicaImage arg2)
+                    {
+                        Integer s1 = arg1.getTechnicalReplicateSequenceNumber();
+                        Integer s2 = arg2.getTechnicalReplicateSequenceNumber();
+                        return s1.compareTo(s2);
+                    }
+                });
+        }
+    }
+
+    private Widget createImageViewer(final WellImage image, ChannelChooserPanel channelChooser)
     {
         assert image.tryGetImageDataset() != null;
         final ISimpleChanneledViewerFactory viewerFactory = new ISimpleChanneledViewerFactory()
@@ -254,7 +394,7 @@ public class MaterialReplicaFeatureSummaryViewer
                 public Widget create(List<String> channels)
                 {
                     return WellContentDialog.createImageViewerForChannel(screeningViewContext,
-                            image, imageSize, imageSize, channels);
+                            image, ONE_IMAGE_SIZE_PX, channels);
                 }
             };
         ChannelWidgetWithListener widgetWithListener = new ChannelWidgetWithListener(viewerFactory);
@@ -271,6 +411,7 @@ public class MaterialReplicaFeatureSummaryViewer
     {
         final LayoutContainer panel = new Viewport();
         panel.setLayout(new RowLayout(Orientation.VERTICAL));
+        panel.setScrollMode(Scroll.AUTOY);
 
         final Widget northPanel = createNorth(screeningViewContext, experiment, material);
         panel.add(northPanel);
@@ -278,8 +419,8 @@ public class MaterialReplicaFeatureSummaryViewer
         final IDisposableComponent gridComponent =
                 MaterialReplicaFeatureSummaryGrid.create(screeningViewContext, new TechId(
                         experiment), new TechId(material));
-        // TODO 2011-04-13, Tomasz Pylak: fix height
-        panel.add(gridComponent.getComponent(), new RowData(1, 400));
+        // NOTE: if the width is 100% then the vertical scrollbar of the grid is not visible
+        panel.add(gridComponent.getComponent(), new RowData(0.97, 400));
 
         screeningViewContext.getService().listWellImages(new TechId(material.getId()),
                 new TechId(experiment.getId()), new ImagesFoundCallback(panel));
@@ -339,6 +480,14 @@ public class MaterialReplicaFeatureSummaryViewer
         return panel;
     }
 
+    private static Html createHeader(String headingText)
+    {
+        Html headingWidget = new Html(headingText);
+        // NOTE: this should be refactored to an external CSS style
+        headingWidget.setTagName("h1");
+        return headingWidget;
+    }
+
     private static String getMaterialType(Material material)
     {
         String materialTypeCode = material.getMaterialType().getCode();
@@ -352,14 +501,6 @@ public class MaterialReplicaFeatureSummaryViewer
         return ("" + text.charAt(0)).toUpperCase() + text.substring(1).toLowerCase();
     }
 
-    private static Html createHeader(String headingText)
-    {
-        Html headingWidget = new Html(headingText);
-        // NOTE: this should be refactored to an external CSS style
-        headingWidget.setTagName("h1");
-        return headingWidget;
-    }
-
     private static String getMaterialName(Material material)
     {
         if (material.getEntityType().getCode()
@@ -393,9 +534,8 @@ public class MaterialReplicaFeatureSummaryViewer
             {
                 public void onClick(ClickEvent event)
                 {
-                    // TODO KE: ugly, ugly, ugly !!! We bind ourselves with
+                    // TODO KE: We bind ourselves with
                     // the implementation of the other view instead of relying on the browser
-                    // report a refactoring JIRA
                     FeatureVectorSummaryViewer.openTab(viewContext, experiment.getPermId());
                 }
             }, linkUrl);
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 92285b59109..e4cfd9442ee 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
@@ -136,7 +136,7 @@ public class WellContentDialog extends Dialog
      */
     public static Widget createImageViewerForChannel(
             final IViewContext<IScreeningClientServiceAsync> viewContext,
-            final WellImage wellImage, int imageWidthPx, int imageHeightPx, List<String> channels)
+            final WellImage wellImage, int imageSizePx, List<String> channels)
     {
         final ImageDatasetEnrichedReference imageDataset = tryGetImageDataset(wellImage);
         if (imageDataset == null)
@@ -162,8 +162,8 @@ public class WellContentDialog extends Dialog
         LogicalImageChannelsReference channelReferences =
                 LogicalImageChannelsReference.createWithoutOverlays(wellImages, channels);
         LayoutContainer staticTilesGrid =
-                LogicalImageViewer.createTilesGrid(channelReferences, sessionId, imageWidthPx,
-                        imageHeightPx, createImageLinks);
+                LogicalImageViewer.createTilesGrid(channelReferences, sessionId, imageSizePx,
+                        createImageLinks);
 
         if (imageParameters.isMultidimensional())
         {
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 7e3081465e0..dd9108cb18d 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
@@ -104,9 +104,7 @@ public class WellSearchGrid extends TypedTableGrid<WellContent>
 
     private static final String CHOOSE_ONE_EXPERIMENT_TEXT = "Choose one experiment...";
 
-    private static final int IMAGE_WIDTH_PX = 200;
-
-    private static final int IMAGE_HEIGHT_PX = 120;
+    private static final int IMAGE_SIZE_PX = 80;
 
     // by experiment perm id
     public static void openTab(
@@ -679,8 +677,7 @@ public class WellSearchGrid extends TypedTableGrid<WellContent>
                                     public Widget create(List<String> channels)
                                     {
                                         return WellContentDialog.createImageViewerForChannel(
-                                                viewContext, entity, IMAGE_WIDTH_PX,
-                                                IMAGE_HEIGHT_PX, channels);
+                                                viewContext, entity, IMAGE_SIZE_PX, channels);
                                     }
                                 };
                     ChannelWidgetWithListener widgetWithListener =
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/MaterialReplicaFeatureSummaryProvider.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/MaterialReplicaFeatureSummaryProvider.java
index 9e4fa939bbd..e8a3324832c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/MaterialReplicaFeatureSummaryProvider.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/MaterialReplicaFeatureSummaryProvider.java
@@ -32,7 +32,7 @@ import ch.systemsx.cisd.openbis.generic.shared.util.TypedTableModelBuilder;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.IScreeningServer;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaFeatureSummary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaFeatureSummaryResult;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaSubgroupFeatureSummary;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialBiologicalReplicateFeatureSummary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaSummaryAggregationType;
 
 /**
@@ -68,14 +68,14 @@ class MaterialReplicaFeatureSummaryProvider extends
         TypedTableModelBuilder<MaterialReplicaFeatureSummary> builder =
                 new TypedTableModelBuilder<MaterialReplicaFeatureSummary>();
         MaterialReplicaFeatureSummaryResult replicaResult =
-                server.getFeatureVectorReplicaSummary(sessionToken, experimentId, materialId);
+                server.getMaterialFeatureVectorSummary(sessionToken, experimentId, materialId);
 
         builder.addColumn(FEATURE);
         builder.addColumn(MEDIAN).withDataType(DataTypeCode.REAL);
         builder.addColumn(DEVIATION).withDataType(DataTypeCode.REAL);
         builder.addColumn(RANK).withDataType(DataTypeCode.INTEGER);
 
-        List<MaterialReplicaFeatureSummary> rows = replicaResult.getReplicaSummaries();
+        List<MaterialReplicaFeatureSummary> rows = replicaResult.getFeatureSummaries();
         List<String> subgroupLabels = replicaResult.getSubgroupLabels();
         if (rows.isEmpty())
         {
@@ -88,7 +88,7 @@ class MaterialReplicaFeatureSummaryProvider extends
             builder.columnGroup(subgroup);
         }
 
-        if (rows.get(0).getDefaultSubgroup() != null)
+        if (rows.get(0).getTechnicalReplicates() != null)
         {
             builder.columnGroup(DEFAULT_SUBGROUP);
         }
@@ -111,24 +111,24 @@ class MaterialReplicaFeatureSummaryProvider extends
         builder.column(DEVIATION).addDouble(row.getFeatureVectorDeviation());
         builder.column(RANK).addInteger((long) row.getFeatureVectorRank());
 
-        MaterialReplicaSubgroupFeatureSummary defaultSubgroup = row.getDefaultSubgroup();
+        MaterialBiologicalReplicateFeatureSummary defaultSubgroup = row.getTechnicalReplicates();
         if (defaultSubgroup != null)
         {
             addSubgroup(builder, DEFAULT_SUBGROUP, "", defaultSubgroup);
         }
 
         int numSubgroups = subgroupLabels.size();
-        List<MaterialReplicaSubgroupFeatureSummary> subgroups = row.getReplicaSubgroups();
+        List<MaterialBiologicalReplicateFeatureSummary> subgroups = row.getBiologicalRelicates();
         for (int i = 0; i < numSubgroups; i++)
         {
             String subgroupLabel = subgroupLabels.get(i);
-            MaterialReplicaSubgroupFeatureSummary subgroup = subgroups.get(i);
+            MaterialBiologicalReplicateFeatureSummary subgroup = subgroups.get(i);
             addSubgroup(builder, subgroupLabel, subgroupLabel, subgroup);
         }
     }
 
     private void addSubgroup(TypedTableModelBuilder<MaterialReplicaFeatureSummary> builder,
-            String groupId, String groupLabel, MaterialReplicaSubgroupFeatureSummary subgroup)
+            String groupId, String groupLabel, MaterialBiologicalReplicateFeatureSummary subgroup)
     {
         IColumnGroup columnGroup = builder.columnGroup(groupId);
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
index 2a49cae3efb..15d27e1b107 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/server/ScreeningClientService.java
@@ -67,6 +67,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConst
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellMetadata;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 
 /**
@@ -212,7 +213,7 @@ public final class ScreeningClientService extends AbstractClientService implemen
         return prepareExportEntities(criteria);
     }
 
-    public List<WellContent> listWellImages(TechId materialId, TechId experimentId)
+    public List<WellReplicaImage> listWellImages(TechId materialId, TechId experimentId)
             throws UserFailureException
     {
         return server.listWellImages(getSessionToken(), materialId, experimentId);
@@ -355,6 +356,7 @@ public final class ScreeningClientService extends AbstractClientService implemen
                         materialId);
         return listEntities(provider, resultSetConfig);
     }
+
     public String prepareExportFeatureVectorSummary(
             TableExportCriteria<TableModelRowWithObject<MaterialFeatureVectorSummary>> criteria)
             throws UserFailureException
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
index b5a059215e9..7213a26b21c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
@@ -102,6 +102,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateImages;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 
 /**
@@ -204,12 +205,12 @@ public final class ScreeningServer extends AbstractServer<IScreeningServer> impl
                 materialCriteria);
     }
 
-    public List<WellContent> listWellImages(String sessionToken, TechId materialId,
+    public List<WellReplicaImage> listWellImages(String sessionToken, TechId materialId,
             TechId experimentId)
     {
         Session session = getSession(sessionToken);
         return WellContentLoader.loadWithImages(session, businessObjectFactory, getDAOFactory(),
-                materialId, experimentId);
+                materialId, experimentId, createDefaultSettings());
     }
 
     public List<Material> listMaterials(String sessionToken, WellSearchCriteria materialCriteria)
@@ -311,11 +312,11 @@ public final class ScreeningServer extends AbstractServer<IScreeningServer> impl
         settings.setAggregationType(MaterialReplicaSummaryAggregationType.MEDIAN);
         settings.setFeatureCodes(new ArrayList<String>());
         settings.setReplicaMatrialTypePatterns(ScreeningConstants.REPLICA_METERIAL_TYPE_PATTERN);
-        settings.setSubgroupPropertyTypeCodes("CONCENTRATION", "SIRNA");
+        settings.setBiologicalReplicatePropertyTypeCodes("CONCENTRATION", "SIRNA");
         return settings;
     }
 
-    public MaterialReplicaFeatureSummaryResult getFeatureVectorReplicaSummary(String sessionToken,
+    public MaterialReplicaFeatureSummaryResult getMaterialFeatureVectorSummary(String sessionToken,
             TechId experimentId, TechId materialId)
     {
         Session session = getSession(sessionToken);
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java
index 3d6b7c02516..02cb5fb1232 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerLogger.java
@@ -62,6 +62,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateImages;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 
 /**
@@ -118,7 +119,7 @@ final class ScreeningServerLogger extends AbstractServerLogger implements IScree
         return null;
     }
 
-    public List<WellContent> listWellImages(String sessionToken, TechId materialId,
+    public List<WellReplicaImage> listWellImages(String sessionToken, TechId materialId,
             TechId experimentId)
     {
         logAccess(sessionToken, "listWellImages", "material(%s) experiment(%s)", materialId,
@@ -352,7 +353,7 @@ final class ScreeningServerLogger extends AbstractServerLogger implements IScree
         return null;
     }
 
-    public MaterialReplicaFeatureSummaryResult getFeatureVectorReplicaSummary(String sessionToken,
+    public MaterialReplicaFeatureSummaryResult getMaterialFeatureVectorSummary(String sessionToken,
             TechId experimentId, TechId materialId)
     {
         logAccess(sessionToken, "getFeatureVectorReplicaSummary",
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoader.java
index 97aa99f268a..ccc887bbf1f 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoader.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoader.java
@@ -17,10 +17,8 @@
 package ch.systemsx.cisd.openbis.plugin.screening.server.logic;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.common.collections.CollectionUtils;
@@ -30,26 +28,25 @@ import ch.systemsx.cisd.common.collections.IKeyExtractor;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.CodeAndLabel;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.plugin.screening.server.IScreeningBusinessObjectFactory;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.IWellData;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialAllReplicasFeatureVectors;
-import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialReplicaSubgroupFeatureVector;
-import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialSingleReplicaFeatureVector;
+import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialBiologicalReplicateFeatureVector;
+import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialTechnicalReplicateFeatureVector;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.WellDataCollection;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialBiologicalReplicateFeatureSummary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialFeatureVectorSummary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaFeatureSummary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaFeatureSummaryResult;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaSubgroupFeatureSummary;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplicaSummaryAggregationType;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialSummarySettings;
 
 /**
- * Loads feature vectors (details and statistics) for the specified material in the specified
- * experiment.
+ * For the specified material in the specified experiment loads feature vectors (details and
+ * statistics).<br>
+ * Considers only one dataset of a particular type per plate.
  * 
  * @author Tomasz Pylak
  */
@@ -62,35 +59,27 @@ public class MaterialFeatureVectorSummaryLoader extends ExperimentFeatureVectorS
             IScreeningBusinessObjectFactory businessObjectFactory, IDAOFactory daoFactory,
             TechId materialId, TechId experimentId, MaterialSummarySettings settings)
     {
-        MaterialAllReplicasFeatureVectors result =
+        MaterialAllReplicasFeatureVectors resultOrNull =
                 new MaterialFeatureVectorSummaryLoader(session, businessObjectFactory, daoFactory,
                         settings).tryLoadMaterialFeatureVectors(materialId, experimentId);
-        if (result == null)
-        {
-            return createEmptyMaterialReplicaFeatureSummaryResult();
-        }
-        return convert(result);
-    }
 
-    private static MaterialReplicaFeatureSummaryResult createEmptyMaterialReplicaFeatureSummaryResult()
-    {
-        return new MaterialReplicaFeatureSummaryResult(new ArrayList<String>(),
-                new ArrayList<MaterialReplicaFeatureSummary>());
+        List<MaterialReplicaFeatureSummary> replicaRows = convertToFeatureRows(resultOrNull);
+        List<String> subgroupLabels = tryGetSubgroupLabels(resultOrNull);
+        return new MaterialReplicaFeatureSummaryResult(subgroupLabels, replicaRows);
     }
 
-    private static MaterialReplicaFeatureSummaryResult convert(
+    private static List<MaterialReplicaFeatureSummary> convertToFeatureRows(
             MaterialAllReplicasFeatureVectors backendResult)
     {
-        List<String> subgroupLabels = new ArrayList<String>();
-        final List<MaterialReplicaSubgroupFeatureVector> backendSubgroups =
-                backendResult.getSubgroups();
-        for (MaterialReplicaSubgroupFeatureVector backendSubgroup : backendSubgroups)
+        List<MaterialReplicaFeatureSummary> replicaRows =
+                new ArrayList<MaterialReplicaFeatureSummary>();
+        if (backendResult == null)
         {
-            subgroupLabels.add(backendSubgroup.getSubgroupLabel());
+            return replicaRows;
         }
+        final List<MaterialBiologicalReplicateFeatureVector> backendSubgroups =
+                backendResult.getSubgroups();
 
-        List<MaterialReplicaFeatureSummary> replicaRows =
-                new ArrayList<MaterialReplicaFeatureSummary>();
         float[] featureVectorDeviatons =
                 backendResult.getGeneralSummary().getFeatureVectorDeviations();
         float[] featureVectorSummaries =
@@ -113,41 +102,54 @@ public class MaterialFeatureVectorSummaryLoader extends ExperimentFeatureVectorS
             float[] defaultFeatureValues = extractFeatureValues(i, backendResult.getReplicas());
             if (defaultFeatureValues != null)
             {
-                MaterialReplicaSubgroupFeatureSummary defaultReplica =
-                        new MaterialReplicaSubgroupFeatureSummary(defaultFeatureValues, 0,
+                MaterialBiologicalReplicateFeatureSummary defaultReplica =
+                        new MaterialBiologicalReplicateFeatureSummary(defaultFeatureValues, 0,
                                 MaterialReplicaSummaryAggregationType.MEDIAN);
-                replicaRow.setDefaultSubgroup(defaultReplica);
+                replicaRow.setTechnicalReplicates(defaultReplica);
             }
 
-            List<MaterialReplicaSubgroupFeatureSummary> subgroups =
-                    new ArrayList<MaterialReplicaSubgroupFeatureSummary>();
-            replicaRow.setReplicaSubgroups(subgroups);
+            List<MaterialBiologicalReplicateFeatureSummary> subgroups =
+                    new ArrayList<MaterialBiologicalReplicateFeatureSummary>();
+            replicaRow.setBiologicalRelicates(subgroups);
             for (int tmp = 0; tmp < backendSubgroups.size(); tmp++)
             {
-                MaterialReplicaSubgroupFeatureVector backendGroup = backendSubgroups.get(tmp);
+                MaterialBiologicalReplicateFeatureVector backendGroup = backendSubgroups.get(tmp);
                 final float[] aggregatedSummaries = backendGroup.getAggregatedSummary();
                 float[] featureValues =
-                        extractFeatureValues(i, backendGroup.getSingleReplicaValues());
-                MaterialReplicaSubgroupFeatureSummary subgroup =
-                        new MaterialReplicaSubgroupFeatureSummary(featureValues,
+                        extractFeatureValues(i, backendGroup.getTechnicalReplicatesValues());
+                MaterialBiologicalReplicateFeatureSummary subgroup =
+                        new MaterialBiologicalReplicateFeatureSummary(featureValues,
                                 aggregatedSummaries[i], backendGroup.getSummaryAggregationType());
                 subgroups.add(subgroup);
             }
         }
+        return replicaRows;
+    }
 
-        return new MaterialReplicaFeatureSummaryResult(subgroupLabels, replicaRows);
-
+    private static List<String> tryGetSubgroupLabels(
+            MaterialAllReplicasFeatureVectors backendResultOrNull)
+    {
+        List<String> subgroupLabels = new ArrayList<String>();
+        if (backendResultOrNull == null)
+        {
+            return subgroupLabels;
+        }
+        for (MaterialBiologicalReplicateFeatureVector backendSubgroup : backendResultOrNull
+                .getSubgroups())
+        {
+            subgroupLabels.add(backendSubgroup.getSubgroupLabel());
+        }
+        return subgroupLabels;
     }
 
     private static float[] extractFeatureValues(int i,
-            List<MaterialSingleReplicaFeatureVector> replicas)
+            List<MaterialTechnicalReplicateFeatureVector> replicas)
     {
         float[] result = new float[replicas.size()];
         for (int pos = 0; pos < result.length; pos++)
         {
-            float[] aggregatedValues = replicas.get(pos).getFeatueVectorSummary();
+            float[] aggregatedValues = replicas.get(pos).getFeatueVector();
             result[pos] = aggregatedValues[i];
-
         }
         return result;
     }
@@ -165,165 +167,112 @@ public class MaterialFeatureVectorSummaryLoader extends ExperimentFeatureVectorS
     private MaterialAllReplicasFeatureVectors tryLoadMaterialFeatureVectors(TechId materialId,
             TechId experimentId)
     {
-        WellDataCollection wellDataCollection = tryLoadWellData(experimentId);
-        if (wellDataCollection == null)
+        WellDataCollection experimentWells = tryLoadWellData(experimentId);
+        if (experimentWells == null)
         {
             return null;
         }
-        return tryLoadMaterialFeatureVectors(materialId, wellDataCollection);
+        return tryLoadMaterialFeatureVectors(materialId, experimentWells);
     }
 
     @Private
     MaterialAllReplicasFeatureVectors tryLoadMaterialFeatureVectors(TechId materialId,
-            WellDataCollection wellDataCollection)
+            WellDataCollection experimentWells)
     {
         MaterialFeatureVectorSummary materialGeneralSummary =
-                tryCalculateMaterialSummary(materialId, wellDataCollection);
+                tryCalculateMaterialSummary(materialId, experimentWells);
         if (materialGeneralSummary == null)
         {
             return null;
         }
-        List<IWellData> materialWellDataList =
-                filterWellsByMaterial(wellDataCollection.getWellDataList(), materialId);
-        GroupByMap<Double, IWellData> subgroupMap = groupBySubgroup(materialWellDataList);
-
-        List<MaterialReplicaSubgroupFeatureVector> subgroups = Collections.emptyList();
-        List<MaterialSingleReplicaFeatureVector> replicas = Collections.emptyList();
-        if (hasNoSubgroups(subgroupMap))
+        List<IWellData> materialWells =
+                filterWellsByMaterial(experimentWells.getWellDataList(), materialId);
+        ReplicateSequenceProvider replicaSequences =
+                new ReplicateSequenceProvider(materialWells,
+                        settings.getBiologicalReplicatePropertyTypeCodes());
+
+        List<MaterialBiologicalReplicateFeatureVector> subgroups = Collections.emptyList();
+        List<MaterialTechnicalReplicateFeatureVector> replicas = Collections.emptyList();
+        if (replicaSequences.hasNoBiologicalReplicates())
         {
-            replicas = createReplicas(materialWellDataList);
+            replicas = createTechnicalReplicas(materialWells, replicaSequences);
         } else
         {
-            subgroups = createSubgroups(subgroupMap);
+            subgroups = createBiologicalReplicates(materialWells, replicaSequences);
         }
-        return new MaterialAllReplicasFeatureVectors(wellDataCollection.getFeatureDescriptions(),
+        return new MaterialAllReplicasFeatureVectors(experimentWells.getFeatureDescriptions(),
                 materialGeneralSummary, subgroups, replicas);
     }
 
-    private List<MaterialReplicaSubgroupFeatureVector> createSubgroups(
-            GroupByMap<Double, IWellData> subgroupMap)
+    private List<MaterialBiologicalReplicateFeatureVector> createBiologicalReplicates(
+            List<IWellData> materialWells, ReplicateSequenceProvider replicaSequences)
     {
-        List<MaterialReplicaSubgroupFeatureVector> subgroups =
-                new ArrayList<MaterialReplicaSubgroupFeatureVector>();
+        GroupByMap<Integer, IWellData> biologicalReplicateMap =
+                groupByBiologicalReplicate(materialWells, replicaSequences);
+
+        List<MaterialBiologicalReplicateFeatureVector> subgroups =
+                new ArrayList<MaterialBiologicalReplicateFeatureVector>();
         MaterialReplicaSummaryAggregationType aggregationType = settings.getAggregationType();
-        int subgroupSequenceNumber = 1;
-        Collection<Double> sortedKeys = sortSubgroupKeys(subgroupMap.getKeys());
-        for (Double subgroupKey : sortedKeys)
+        for (Integer biologicalReplicateSeq : replicaSequences.getBiologicalReplicateKeys())
         {
-            if (subgroupKey != null)
-            {
-                List<IWellData> subgroupWellDataList = subgroupMap.getOrDie(subgroupKey);
-                MaterialReplicaSubgroupFeatureVector subgroup =
-                        createSubgroup(subgroupWellDataList, subgroupSequenceNumber,
-                                aggregationType);
-                subgroups.add(subgroup);
-                subgroupSequenceNumber++;
-            }
+            List<IWellData> technicalReplicateWells =
+                    biologicalReplicateMap.getOrDie(biologicalReplicateSeq);
+            MaterialBiologicalReplicateFeatureVector subgroup =
+                    createBiologicalReplicate(technicalReplicateWells, replicaSequences,
+                            aggregationType);
+            subgroups.add(subgroup);
         }
         return subgroups;
     }
 
-    private static Collection<Double> sortSubgroupKeys(Set<Double> keys)
-    {
-        ArrayList<Double> sortedKeys = new ArrayList<Double>(keys);
-        Collections.sort(sortedKeys);
-        return sortedKeys;
-    }
-
-    private MaterialReplicaSubgroupFeatureVector createSubgroup(
-            List<IWellData> subgroupWellDataList, int subgroupSequenceNumber,
+    private MaterialBiologicalReplicateFeatureVector createBiologicalReplicate(
+            List<IWellData> technicalReplicateWells, ReplicateSequenceProvider replicaSequences,
             MaterialReplicaSummaryAggregationType aggregationType)
     {
         float[] aggregatedSummary =
-                WellReplicaSummaryCalculator.calculateSummaryFeatureVector(subgroupWellDataList,
+                WellReplicaSummaryCalculator.calculateSummaryFeatureVector(technicalReplicateWells,
                         aggregationType);
-        List<MaterialSingleReplicaFeatureVector> replicas = createReplicas(subgroupWellDataList);
-        String subgroupLabel = getSubgroupLabel(subgroupWellDataList, subgroupSequenceNumber);
-        return new MaterialReplicaSubgroupFeatureVector(replicas, aggregatedSummary,
+        List<MaterialTechnicalReplicateFeatureVector> replicas =
+                createTechnicalReplicas(technicalReplicateWells, replicaSequences);
+        String subgroupLabel = getSubgroupLabel(technicalReplicateWells, replicaSequences);
+        return new MaterialBiologicalReplicateFeatureVector(replicas, aggregatedSummary,
                 aggregationType, subgroupLabel);
     }
 
-    private String getSubgroupLabel(List<IWellData> subgroupWellDataList, int subgroupSequenceNumber)
+    private String getSubgroupLabel(List<IWellData> subgroupWellDataList,
+            ReplicateSequenceProvider replicaSequences)
     {
         assert subgroupWellDataList.size() > 0 : "empty subgroup";
+        // all wells belong to the same subgroup, so it does not matter which one we take
         Sample well = subgroupWellDataList.get(0).getWell();
-        IEntityProperty subgroupProperty = tryFindSubgroupProperty(well);
-        assert subgroupProperty != null : "cannot fnd the subgroup property";
-
-        String propertyLabel = subgroupProperty.getPropertyType().getLabel();
-        Material subgroupMaterial = subgroupProperty.getMaterial();
-        if (subgroupMaterial != null)
-        {
-            return propertyLabel + " " + subgroupSequenceNumber;
-        } else
-        {
-            return propertyLabel + " " + subgroupProperty.tryGetAsString();
-        }
-    }
-
-    /**
-     * A subgroup can be e.g. oligo or compound concentration.
-     */
-    private GroupByMap<Double, IWellData> groupBySubgroup(List<IWellData> materialWellDataList)
-    {
-        return GroupByMap.create(materialWellDataList, new IKeyExtractor<Double, IWellData>()
-            {
-                public Double getKey(IWellData wellData)
-                {
-                    return tryFindSubgroup(wellData.getWell());
-                }
-            });
-    }
 
-    private Double tryFindSubgroup(Sample well)
-    {
-        IEntityProperty subgroupProperty = tryFindSubgroupProperty(well);
-        if (subgroupProperty == null)
-        {
-            return null;
-        }
-        return tryExtractSubgroupValue(subgroupProperty);
-    }
-
-    private IEntityProperty tryFindSubgroupProperty(Sample well)
-    {
-        List<String> subgroupPropertyTypeCodes = settings.getSubgroupPropertyTypeCodes();
-        if (subgroupPropertyTypeCodes == null)
-        {
-            return null;
-        }
-        return tryFindProperty(well.getProperties(), subgroupPropertyTypeCodes);
+        return replicaSequences.getBiologicalReplicateLabel(well);
     }
 
     private MaterialFeatureVectorSummary tryCalculateMaterialSummary(TechId materialId,
-            WellDataCollection wellDataCollection)
+            WellDataCollection experimentWellDataList)
     {
         List<MaterialFeatureVectorSummary> featureSummaries =
-                calculateReplicasFeatureVectorSummaries(wellDataCollection);
+                calculateReplicasFeatureVectorSummaries(experimentWellDataList);
         return tryFindMaterialSummary(materialId, featureSummaries);
     }
 
-    private static List<MaterialSingleReplicaFeatureVector> createReplicas(
-            List<IWellData> materialWellDataList)
+    private static List<MaterialTechnicalReplicateFeatureVector> createTechnicalReplicas(
+            List<IWellData> materialWellDataList, ReplicateSequenceProvider replicaSequences)
     {
-        List<MaterialSingleReplicaFeatureVector> replicas =
-                new ArrayList<MaterialSingleReplicaFeatureVector>();
-        int replicaSequenceNumber = 1;
+        List<MaterialTechnicalReplicateFeatureVector> replicas =
+                new ArrayList<MaterialTechnicalReplicateFeatureVector>();
         for (IWellData wellData : materialWellDataList)
         {
-            MaterialSingleReplicaFeatureVector featureVector =
-                    new MaterialSingleReplicaFeatureVector(replicaSequenceNumber++,
+            int replicaSequenceNumber = replicaSequences.getTechnicalReplicateSequenceNum(wellData);
+            MaterialTechnicalReplicateFeatureVector featureVector =
+                    new MaterialTechnicalReplicateFeatureVector(replicaSequenceNumber,
                             wellData.getFeatureVector());
             replicas.add(featureVector);
         }
         return replicas;
     }
 
-    private static boolean hasNoSubgroups(GroupByMap<Double, IWellData> subgroupMap)
-    {
-        return subgroupMap.getKeys().size() == 1 && subgroupMap.getKeys().contains(null);
-    }
-
     private static List<IWellData> filterWellsByMaterial(List<IWellData> wellDataList,
             final TechId materialId)
     {
@@ -336,6 +285,21 @@ public class MaterialFeatureVectorSummaryLoader extends ExperimentFeatureVectorS
             });
     }
 
+    /**
+     * A subgroup can be e.g. oligo or compound concentration.
+     */
+    private GroupByMap<Integer, IWellData> groupByBiologicalReplicate(
+            List<IWellData> materialWellDataList, final ReplicateSequenceProvider replicaSequences)
+    {
+        return GroupByMap.create(materialWellDataList, new IKeyExtractor<Integer, IWellData>()
+            {
+                public Integer getKey(IWellData wellData)
+                {
+                    return replicaSequences.tryGetBiologicalReplicateSequenceNum(wellData.getWell());
+                }
+            });
+    }
+
     private static MaterialFeatureVectorSummary tryFindMaterialSummary(TechId materialId,
             List<MaterialFeatureVectorSummary> featureSummaries)
     {
@@ -348,33 +312,4 @@ public class MaterialFeatureVectorSummaryLoader extends ExperimentFeatureVectorS
         }
         return null;
     }
-
-    private static Double tryExtractSubgroupValue(IEntityProperty subgroupProperty)
-    {
-        Material subgroupMaterial = subgroupProperty.getMaterial();
-        if (subgroupMaterial != null)
-        {
-            return new Double(subgroupMaterial.getId());
-        }
-        try
-        {
-            return new Double(subgroupProperty.tryGetAsString());
-        } catch (NumberFormatException ex)
-        {
-            return null;
-        }
-    }
-
-    private static IEntityProperty tryFindProperty(List<IEntityProperty> properties,
-            List<String> subgroupPropertyTypeCodes)
-    {
-        for (IEntityProperty property : properties)
-        {
-            if (subgroupPropertyTypeCodes.contains(property.getPropertyType().getCode()))
-            {
-                return property;
-            }
-        }
-        return null;
-    }
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ReplicateSequenceProvider.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ReplicateSequenceProvider.java
new file mode 100644
index 00000000000..ba11ffca355
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ReplicateSequenceProvider.java
@@ -0,0 +1,189 @@
+/*
+ * 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.server.logic;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import ch.systemsx.cisd.common.collections.GroupByMap;
+import ch.systemsx.cisd.common.collections.IKeyExtractor;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityPropertiesHolder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
+
+/**
+ * Provides sequence numbers for technical and biological well replicates for a fixed material.
+ * 
+ * @author Tomasz Pylak
+ */
+class ReplicateSequenceProvider
+{
+    private final List<String> biologicalReplicatePropertyTypeCodesOrNull;
+
+    private final Map<Double/* subgroup key */, Integer/* biological replicate sequence number */> biologicalReplicateSeqMap;
+
+    private final Map<Long/* well id */, Integer/* technical replicate sequence number */> technicalReplicateSeqMap;
+
+    public ReplicateSequenceProvider(List<? extends IEntityPropertiesHolder> replicaWells,
+            List<String> biologicalReplicatePropertyTypeCodesOrNull)
+    {
+        this.biologicalReplicatePropertyTypeCodesOrNull = biologicalReplicatePropertyTypeCodesOrNull;
+        this.biologicalReplicateSeqMap = new LinkedHashMap<Double, Integer>();
+        this.technicalReplicateSeqMap = new LinkedHashMap<Long, Integer>();
+
+        GroupByMap<Double, IEntityPropertiesHolder> biologicalReplicates =
+                groupByBiologicalReplicate(replicaWells);
+        int biologicalReplicateSeq = 1;
+        for (Double biologicalReplicateKey : createSortedCopy(biologicalReplicates.getKeys()))
+        {
+            if (biologicalReplicateKey != null)
+            {
+                biologicalReplicateSeqMap.put(biologicalReplicateKey, biologicalReplicateSeq++);
+            }
+
+            int technicalReplicateSeq = 1;
+            List<IEntityPropertiesHolder> technicalReplicates =
+                    biologicalReplicates.getOrDie(biologicalReplicateKey);
+            for (IEntityPropertiesHolder technicalReplicate : technicalReplicates)
+            {
+                technicalReplicateSeqMap.put(technicalReplicate.getId(), technicalReplicateSeq++);
+            }
+        }
+    }
+
+    public Collection<Integer> getBiologicalReplicateKeys()
+    {
+        return createSortedCopy(biologicalReplicateSeqMap.values());
+    }
+
+    private GroupByMap<Double, IEntityPropertiesHolder> groupByBiologicalReplicate(
+            List<? extends IEntityPropertiesHolder> wells)
+    {
+        return GroupByMap.create(wells, new IKeyExtractor<Double, IEntityPropertiesHolder>()
+            {
+                public Double getKey(IEntityPropertiesHolder well)
+                {
+                    return tryFindSubgroup(well);
+                }
+            });
+    }
+
+    /** Subgroup sequence, the same for all technical replicates of one biological replicate. */
+    public Integer tryGetBiologicalReplicateSequenceNum(IEntityPropertiesHolder well)
+    {
+        Double subgroupKey = tryFindSubgroup(well);
+        if (subgroupKey == null)
+        {
+            return null;
+        }
+        Integer seqNum = biologicalReplicateSeqMap.get(subgroupKey);
+        assert seqNum != null : "no biological replicate found for " + subgroupKey;
+        return seqNum;
+    }
+
+    /**
+     * Technical Replicate Sequence (unique in one biological replicate)
+     */
+    public int getTechnicalReplicateSequenceNum(IEntityPropertiesHolder well)
+    {
+        return technicalReplicateSeqMap.get(well.getId());
+    }
+
+    private static <T extends Comparable<T>> Collection<T> createSortedCopy(Collection<T> keys)
+    {
+        ArrayList<T> sortedKeys = new ArrayList<T>(keys);
+        Collections.sort(sortedKeys);
+        return sortedKeys;
+    }
+
+    public boolean hasNoBiologicalReplicates()
+    {
+        Set<Double> keys = biologicalReplicateSeqMap.keySet();
+        return keys.size() == 1 && keys.contains(null);
+    }
+
+    public String getBiologicalReplicateLabel(IEntityPropertiesHolder well)
+    {
+        int biologicalReplicateSeq = tryGetBiologicalReplicateSequenceNum(well);
+        IEntityProperty subgroupProperty = tryFindSubgroupProperty(well);
+        assert subgroupProperty != null : "cannot fnd the subgroup property";
+        boolean isMaterialProperty = (subgroupProperty.getMaterial() != null);
+
+        String propertyLabel = subgroupProperty.getPropertyType().getLabel();
+        if (isMaterialProperty)
+        {
+            return propertyLabel + " " + biologicalReplicateSeq;
+        } else
+        {
+            return propertyLabel + " " + subgroupProperty.tryGetAsString();
+        }
+    }
+
+    private Double tryFindSubgroup(IEntityPropertiesHolder well)
+    {
+        IEntityProperty subgroupProperty = tryFindSubgroupProperty(well);
+        if (subgroupProperty == null)
+        {
+            return null;
+        }
+        return tryExtractSubgroupValue(subgroupProperty);
+    }
+
+    private IEntityProperty tryFindSubgroupProperty(IEntityPropertiesHolder well)
+    {
+        if (biologicalReplicatePropertyTypeCodesOrNull == null)
+        {
+            return null;
+        }
+        return tryFindProperty(well.getProperties(), biologicalReplicatePropertyTypeCodesOrNull);
+    }
+
+    private static Double tryExtractSubgroupValue(IEntityProperty subgroupProperty)
+    {
+        Material subgroupMaterial = subgroupProperty.getMaterial();
+        if (subgroupMaterial != null)
+        {
+            return new Double(subgroupMaterial.getId());
+        }
+        try
+        {
+            return new Double(subgroupProperty.tryGetAsString());
+        } catch (NumberFormatException ex)
+        {
+            return null;
+        }
+    }
+
+    private static IEntityProperty tryFindProperty(List<IEntityProperty> properties,
+            List<String> subgroupPropertyTypeCodes)
+    {
+        for (IEntityProperty property : properties)
+        {
+            if (subgroupPropertyTypeCodes.contains(property.getPropertyType().getCode()))
+            {
+                return property;
+            }
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/WellContentLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/WellContentLoader.java
index 63a1d5464d3..99757e5a419 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/WellContentLoader.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/WellContentLoader.java
@@ -65,10 +65,12 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.DatasetReferen
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ExperimentReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.FeatureVectorValues;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialSummarySettings;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.NamedFeatureVector;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellFeatureVectorReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria.ExperimentSearchCriteria;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria.MaterialSearchCodesCriteria;
@@ -138,28 +140,59 @@ public class WellContentLoader extends AbstractContentLoader
      * for the well, all but the first one are ignored. If there is no image dataset for the well,
      * the whole well is ignored.
      */
-    public static List<WellContent> loadWithImages(Session session,
+    public static List<WellReplicaImage> loadWithImages(Session session,
             IScreeningBusinessObjectFactory businessObjectFactory, IDAOFactory daoFactory,
-            TechId materialId, TechId experimentId)
+            TechId materialId, TechId experimentId, MaterialSummarySettings settings)
     {
         WellContentLoader loader =
                 new WellContentLoader(session, businessObjectFactory, daoFactory);
         List<WellContent> locations = loader.loadLocations(materialId, experimentId.getId());
-        return loader.enrichWithSingleImageDatasets(locations);
+        locations = loader.enrichWithSingleImageDatasets(locations);
+        return annotateWithReplicaLabels(locations, settings);
+    }
+
+    private static List<WellReplicaImage> annotateWithReplicaLabels(
+            List<WellContent> wellsWithImages, MaterialSummarySettings settings)
+    {
+        ReplicateSequenceProvider replicaSequences =
+                new ReplicateSequenceProvider(wellsWithImages,
+                        settings.getBiologicalReplicatePropertyTypeCodes());
+        List<WellReplicaImage> wellReplicaImages = new ArrayList<WellReplicaImage>();
+        for (WellContent wellContent : wellsWithImages)
+        {
+            wellReplicaImages.add(annotateWithReplicaLabels(wellContent, replicaSequences));
+        }
+        return wellReplicaImages;
+    }
+
+    private static WellReplicaImage annotateWithReplicaLabels(WellContent wellContent,
+            ReplicateSequenceProvider replicaSequences)
+    {
+        int technicalReplicaSequenceNumber =
+                replicaSequences.getTechnicalReplicateSequenceNum(wellContent);
+        String biologicalReplicateLabel = replicaSequences.getBiologicalReplicateLabel(wellContent);
+        return new WellReplicaImage(wellContent, technicalReplicaSequenceNumber,
+                biologicalReplicateLabel);
     }
 
     private List<WellContent> enrichWithSingleImageDatasets(List<WellContent> locations)
     {
-        HCSImageDatasetLoader datasetsRetriever =
-                createImageDatasetsRetriever(extractPlates(locations));
+        Set<PlateIdentifier> plateIdentifiers = extractPlates(locations);
+        Map<Long, DatasetImagesReference> plateToDatasetReferenceMap =
+                loadPlateToSingleImageDatasetMap(plateIdentifiers);
+        return enrichWithSingleImageDatasets(locations, plateToDatasetReferenceMap);
+    }
+
+    private Map<Long, DatasetImagesReference> loadPlateToSingleImageDatasetMap(
+            Set<PlateIdentifier> plateIdentifiers)
+    {
+        HCSImageDatasetLoader datasetsRetriever = createImageDatasetsRetriever(plateIdentifiers);
         Collection<ExternalData> imageDatasets = datasetsRetriever.getImageDatasets();
         if (imageDatasets.isEmpty())
         {
-            return Collections.emptyList();
+            return new HashMap<Long, DatasetImagesReference>();
         }
-        Map<Long, DatasetImagesReference> plateToDatasetReferenceMap =
-                createPlateToSingleDatasetReferenceMap(imageDatasets);
-        return enrichWithSingleImageDatasets(locations, plateToDatasetReferenceMap);
+        return createPlateToSingleDatasetReferenceMap(imageDatasets);
     }
 
     /** Note: locations without a corresponding image dataset are removed */
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/IWellData.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/IWellData.java
index 48878136684..2d91dad7530 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/IWellData.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/IWellData.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityPropertiesHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.WellReplicaSummaryCalculator;
@@ -26,7 +27,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.server.logic.WellReplicaSummary
  * 
  * @author Tomasz Pylak
  */
-public interface IWellData
+public interface IWellData extends IEntityPropertiesHolder
 {
     /** @return feature vector for the well */
     float[] getFeatureVector();
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialAllReplicasFeatureVectors.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialAllReplicasFeatureVectors.java
index c6f6a6cf908..658782483fb 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialAllReplicasFeatureVectors.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialAllReplicasFeatureVectors.java
@@ -22,10 +22,10 @@ public class MaterialAllReplicasFeatureVectors implements ISerializable
     private MaterialFeatureVectorSummary generalSummary;
 
     // NOTE: Can be empty.
-    private List<MaterialReplicaSubgroupFeatureVector> subgroups;
+    private List<MaterialBiologicalReplicateFeatureVector> subgroups;
 
     // NOTE: Can be empty. Used for replicas which have no subgroups
-    private List<MaterialSingleReplicaFeatureVector> replicas;
+    private List<MaterialTechnicalReplicateFeatureVector> replicas;
 
     // GWT only
     @SuppressWarnings("unused")
@@ -35,8 +35,8 @@ public class MaterialAllReplicasFeatureVectors implements ISerializable
 
     public MaterialAllReplicasFeatureVectors(List<CodeAndLabel> featureDescriptions,
             MaterialFeatureVectorSummary generalSummary,
-            List<MaterialReplicaSubgroupFeatureVector> subgroups,
-            List<MaterialSingleReplicaFeatureVector> replicas)
+            List<MaterialBiologicalReplicateFeatureVector> subgroups,
+            List<MaterialTechnicalReplicateFeatureVector> replicas)
     {
         this.featureDescriptions = featureDescriptions;
         this.generalSummary = generalSummary;
@@ -54,12 +54,12 @@ public class MaterialAllReplicasFeatureVectors implements ISerializable
         return generalSummary;
     }
 
-    public List<MaterialReplicaSubgroupFeatureVector> getSubgroups()
+    public List<MaterialBiologicalReplicateFeatureVector> getSubgroups()
     {
         return subgroups;
     }
 
-    public List<MaterialSingleReplicaFeatureVector> getReplicas()
+    public List<MaterialTechnicalReplicateFeatureVector> getReplicas()
     {
         return replicas;
     }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialReplicaSubgroupFeatureVector.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialBiologicalReplicateFeatureVector.java
similarity index 75%
rename from screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialReplicaSubgroupFeatureVector.java
rename to screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialBiologicalReplicateFeatureVector.java
index b064c3e85e9..c1167af2ae0 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialReplicaSubgroupFeatureVector.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialBiologicalReplicateFeatureVector.java
@@ -12,11 +12,11 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialReplic
  * 
  * @author Tomasz Pylak
  */
-public class MaterialReplicaSubgroupFeatureVector implements ISerializable
+public class MaterialBiologicalReplicateFeatureVector implements ISerializable
 {
     private static final long serialVersionUID = ServiceVersionHolder.VERSION;
 
-    private List<MaterialSingleReplicaFeatureVector> singleReplicaValues;
+    private List<MaterialTechnicalReplicateFeatureVector> technicalReplicatesValues;
 
     // e.g. average or median of all replica values in this supgroup
     // This is the aggregation of a subgroup of replicas for e.g. the same SIRNA
@@ -29,24 +29,24 @@ public class MaterialReplicaSubgroupFeatureVector implements ISerializable
 
     // GWT only
     @SuppressWarnings("unused")
-    private MaterialReplicaSubgroupFeatureVector()
+    private MaterialBiologicalReplicateFeatureVector()
     {
     }
 
-    public MaterialReplicaSubgroupFeatureVector(
-            List<MaterialSingleReplicaFeatureVector> singleReplicaValues,
+    public MaterialBiologicalReplicateFeatureVector(
+            List<MaterialTechnicalReplicateFeatureVector> singleReplicaValues,
             float[] aggregatedSummary,
             MaterialReplicaSummaryAggregationType summaryAggregationType, String subgroupLabel)
     {
-        this.singleReplicaValues = singleReplicaValues;
+        this.technicalReplicatesValues = singleReplicaValues;
         this.aggregatedSummary = aggregatedSummary;
         this.summaryAggregationType = summaryAggregationType;
         this.subgroupLabel = subgroupLabel;
     }
 
-    public List<MaterialSingleReplicaFeatureVector> getSingleReplicaValues()
+    public List<MaterialTechnicalReplicateFeatureVector> getTechnicalReplicatesValues()
     {
-        return singleReplicaValues;
+        return technicalReplicatesValues;
     }
 
     public float[] getAggregatedSummary()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialSingleReplicaFeatureVector.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialTechnicalReplicateFeatureVector.java
similarity index 62%
rename from screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialSingleReplicaFeatureVector.java
rename to screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialTechnicalReplicateFeatureVector.java
index cc71a22746b..97bf703ecba 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialSingleReplicaFeatureVector.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/MaterialTechnicalReplicateFeatureVector.java
@@ -24,34 +24,35 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
  * 
  * @author Tomasz Pylak
  */
-public class MaterialSingleReplicaFeatureVector implements ISerializable
+public class MaterialTechnicalReplicateFeatureVector implements ISerializable
 {
     private static final long serialVersionUID = ServiceVersionHolder.VERSION;
 
-    private int replicaSequenceNumber;
+    private int technicalReplicateSequenceNumber;
 
-    private float[] featueVectorSummary;
+    private float[] featueVector;
 
     // GWT only
     @SuppressWarnings("unused")
-    private MaterialSingleReplicaFeatureVector()
+    private MaterialTechnicalReplicateFeatureVector()
     {
     }
 
-    public MaterialSingleReplicaFeatureVector(int replicaSequenceNumber, float[] featueVectorSummary)
+    public MaterialTechnicalReplicateFeatureVector(int technicalReplicateSequenceNumber,
+            float[] featueVector)
     {
-        this.replicaSequenceNumber = replicaSequenceNumber;
-        this.featueVectorSummary = featueVectorSummary;
+        this.technicalReplicateSequenceNumber = technicalReplicateSequenceNumber;
+        this.featueVector = featueVector;
     }
 
-    public int getReplicaSequenceNumber()
+    public int getTechnicalReplicateSequenceNumber()
     {
-        return replicaSequenceNumber;
+        return technicalReplicateSequenceNumber;
     }
 
-    public float[] getFeatueVectorSummary()
+    public float[] getFeatueVector()
     {
-        return featueVectorSummary;
+        return featueVector;
     }
 
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/WellData.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/WellData.java
index d6e7bce5036..9e0734c3111 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/WellData.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/dto/WellData.java
@@ -17,7 +17,9 @@
 package ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto;
 
 import java.util.Arrays;
+import java.util.List;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 
@@ -70,4 +72,14 @@ public class WellData implements IWellData
         return "repl " + replicaId + ": " + Arrays.toString(featureVector);
     }
 
+    public List<IEntityProperty> getProperties()
+    {
+        return well.getProperties();
+    }
+
+    public Long getId()
+    {
+        return well.getId();
+    }
+
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java
index 0983c1a5768..3b317136f1d 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/IScreeningServer.java
@@ -55,6 +55,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.PlateImages;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellContent;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellReplicaImage;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellSearchCriteria;
 
 /**
@@ -121,7 +122,7 @@ public interface IScreeningServer extends IServer
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    public List<WellContent> listWellImages(String sessionToken, TechId materialId,
+    public List<WellReplicaImage> listWellImages(String sessionToken, TechId materialId,
             @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class) TechId experimentId);
 
     /**
@@ -223,7 +224,7 @@ public interface IScreeningServer extends IServer
      */
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    public MaterialReplicaFeatureSummaryResult getFeatureVectorReplicaSummary(String sessionToken,
+    public MaterialReplicaFeatureSummaryResult getMaterialFeatureVectorSummary(String sessionToken,
             @AuthorizationGuard(guardClass = ExperimentTechIdPredicate.class) TechId experimentId,
             TechId materialId);
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaSubgroupFeatureSummary.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialBiologicalReplicateFeatureSummary.java
similarity index 87%
rename from screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaSubgroupFeatureSummary.java
rename to screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialBiologicalReplicateFeatureSummary.java
index cb9ee0e0eae..bb8cc2126fe 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaSubgroupFeatureSummary.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialBiologicalReplicateFeatureSummary.java
@@ -22,10 +22,11 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
 /**
  * @author Kaloyan Enimanev
  */
-public class MaterialReplicaSubgroupFeatureSummary implements ISerializable
+public class MaterialBiologicalReplicateFeatureSummary implements ISerializable
 {
     private static final long serialVersionUID = ServiceVersionHolder.VERSION;
 
+    // i-th element is the value for (i+1) technical replicate
     private float[] featureValues;
 
     // e.g. average or median of all replica values in this subgroup
@@ -39,11 +40,11 @@ public class MaterialReplicaSubgroupFeatureSummary implements ISerializable
 
     // GWT only
     @SuppressWarnings("unused")
-    private MaterialReplicaSubgroupFeatureSummary()
+    private MaterialBiologicalReplicateFeatureSummary()
     {
     }
 
-    public MaterialReplicaSubgroupFeatureSummary(float[] featureValues, float aggregatedSummary,
+    public MaterialBiologicalReplicateFeatureSummary(float[] featureValues, float aggregatedSummary,
             MaterialReplicaSummaryAggregationType summaryAggregationType)
     {
         this.featureValues = featureValues;
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummary.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummary.java
index b541a087508..eb94e95da5d 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummary.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummary.java
@@ -40,9 +40,10 @@ public class MaterialReplicaFeatureSummary implements ISerializable
     // DISPLAY NOTE: The header of each column should be:
     // <subgroupName> repl. <replicaSequenceNumber>
     // e.g. SIRNA XYZ repl. 2
-    private List<MaterialReplicaSubgroupFeatureSummary> replicaSubgroups;
+    private List<MaterialBiologicalReplicateFeatureSummary> biologicalRelicates;
 
-    private MaterialReplicaSubgroupFeatureSummary defaultSubgroup;
+    // technical replicates which do not belong to any biological replicate
+    private MaterialBiologicalReplicateFeatureSummary technicalReplicates;
 
     public CodeAndLabel getFeatureDescription()
     {
@@ -84,24 +85,25 @@ public class MaterialReplicaFeatureSummary implements ISerializable
         this.featureVectorRank = featureVectorRank;
     }
 
-    public List<MaterialReplicaSubgroupFeatureSummary> getReplicaSubgroups()
+    public List<MaterialBiologicalReplicateFeatureSummary> getBiologicalRelicates()
     {
-        return replicaSubgroups;
+        return biologicalRelicates;
     }
 
-    public void setReplicaSubgroups(List<MaterialReplicaSubgroupFeatureSummary> replicaSubgroups)
+    public void setBiologicalRelicates(
+            List<MaterialBiologicalReplicateFeatureSummary> replicaSubgroups)
     {
-        this.replicaSubgroups = replicaSubgroups;
+        this.biologicalRelicates = replicaSubgroups;
     }
 
-    public MaterialReplicaSubgroupFeatureSummary getDefaultSubgroup()
+    public MaterialBiologicalReplicateFeatureSummary getTechnicalReplicates()
     {
-        return defaultSubgroup;
+        return technicalReplicates;
     }
 
-    public void setDefaultSubgroup(MaterialReplicaSubgroupFeatureSummary defaultReplica)
+    public void setTechnicalReplicates(MaterialBiologicalReplicateFeatureSummary defaultReplica)
     {
-        this.defaultSubgroup = defaultReplica;
+        this.technicalReplicates = defaultReplica;
     }
 
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummaryResult.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummaryResult.java
index 0a7f3c66d0f..5ed046fb71b 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummaryResult.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialReplicaFeatureSummaryResult.java
@@ -23,6 +23,8 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
 
 /**
+ * Info about feature vectors of all wells for one material in one experiment.
+ * 
  * @author Kaloyan Enimanev
  */
 public class MaterialReplicaFeatureSummaryResult implements ISerializable
@@ -46,7 +48,8 @@ public class MaterialReplicaFeatureSummaryResult implements ISerializable
         return subgroupLabels;
     }
 
-    public List<MaterialReplicaFeatureSummary> getReplicaSummaries()
+    /** Can be empty if there are no analysis results. */
+    public List<MaterialReplicaFeatureSummary> getFeatureSummaries()
     {
         return replicaSummaries;
     }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialSummarySettings.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialSummarySettings.java
index df09b8bbc71..df15eca9ba5 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialSummarySettings.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/MaterialSummarySettings.java
@@ -33,7 +33,7 @@ public class MaterialSummarySettings implements ISerializable
 
     private String replicaMatrialTypePattern;
 
-    private List<String> subgroupPropertyTypeCodes;
+    private List<String> biologicalReplicatePropertyTypeCodes;
 
     private MaterialReplicaSummaryAggregationType aggregationType;
 
@@ -57,14 +57,16 @@ public class MaterialSummarySettings implements ISerializable
         this.replicaMatrialTypePattern = replicaMatrialTypePattern;
     }
 
-    public List<String> getSubgroupPropertyTypeCodes()
+    public List<String> getBiologicalReplicatePropertyTypeCodes()
     {
-        return subgroupPropertyTypeCodes;
+        return biologicalReplicatePropertyTypeCodes;
     }
 
-    public void setSubgroupPropertyTypeCodes(String... subgroupPropertyTypeCodes)
+    public void setBiologicalReplicatePropertyTypeCodes(
+            String... biologicalReplicatePropertyTypeCodes)
     {
-        this.subgroupPropertyTypeCodes = Arrays.asList(subgroupPropertyTypeCodes);
+        this.biologicalReplicatePropertyTypeCodes =
+                Arrays.asList(biologicalReplicatePropertyTypeCodes);
     }
 
     public MaterialReplicaSummaryAggregationType getAggregationType()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellContent.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellContent.java
index de9a523d829..78fec8caa49 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellContent.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellContent.java
@@ -23,6 +23,7 @@ import java.util.List;
 import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityReference;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityPropertiesHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
@@ -33,7 +34,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
  * 
  * @author Tomasz Pylak
  */
-public class WellContent extends WellImage implements ISerializable
+public class WellContent extends WellImage implements ISerializable, IEntityPropertiesHolder
 {
     private static final long serialVersionUID = ServiceVersionHolder.VERSION;
 
@@ -177,4 +178,16 @@ public class WellContent extends WellImage implements ISerializable
         this.wellProperties = properties;
     }
 
+    /** id of the well */
+    public Long getId()
+    {
+        return well.getId();
+    }
+
+    /** properties of the well */
+    public List<IEntityProperty> getProperties()
+    {
+        return getWellProperties();
+    }
+
 }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellReplicaImage.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellReplicaImage.java
new file mode 100644
index 00000000000..eba7366ace1
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/WellReplicaImage.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.plugin.screening.shared.basic.dto;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
+
+/**
+ * Contains well description, its image dataset (single) and biological and technical replicate
+ * labels.
+ * 
+ * @author Tomasz Pylak
+ */
+public class WellReplicaImage implements ISerializable
+{
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    private WellContent wellImage;
+
+    private int technicalReplicateSequenceNumber;
+
+    // null if there are no biological replicates
+    private String biologicalReplicateLabelOrNull;
+
+    // GWT only
+    @SuppressWarnings("unused")
+    private WellReplicaImage()
+    {
+    }
+
+    public WellReplicaImage(WellContent wellImage, int technicalReplicaSequenceNumber,
+            String subgroupLabelOrNull)
+    {
+        this.wellImage = wellImage;
+        this.technicalReplicateSequenceNumber = technicalReplicaSequenceNumber;
+        this.biologicalReplicateLabelOrNull = subgroupLabelOrNull;
+    }
+
+    public WellContent getWellImage()
+    {
+        return wellImage;
+    }
+
+    public int getTechnicalReplicateSequenceNumber()
+    {
+        return technicalReplicateSequenceNumber;
+    }
+
+    public String tryGetBiologicalReplicateLabel()
+    {
+        return biologicalReplicateLabelOrNull;
+    }
+}
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoaderTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoaderTest.java
index c3b77dea110..f966bad4db2 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoaderTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/MaterialFeatureVectorSummaryLoaderTest.java
@@ -37,7 +37,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.IWellData;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialAllReplicasFeatureVectors;
-import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialReplicaSubgroupFeatureVector;
+import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.MaterialBiologicalReplicateFeatureVector;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.WellData;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.WellDataCollection;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.MaterialFeatureVectorSummary;
@@ -59,7 +59,7 @@ public class MaterialFeatureVectorSummaryLoaderTest extends AssertJUnit
     {
         MaterialSummarySettings settings = new MaterialSummarySettings();
         settings.setAggregationType(MaterialReplicaSummaryAggregationType.MEDIAN);
-        settings.setSubgroupPropertyTypeCodes(SIRNA_PROPERTY_TYPE_CODE);
+        settings.setBiologicalReplicatePropertyTypeCodes(SIRNA_PROPERTY_TYPE_CODE);
         int replId = 0;
         List<IWellData> wellDataList = Arrays.asList(
         // repl. 1 group 1
@@ -95,7 +95,7 @@ public class MaterialFeatureVectorSummaryLoaderTest extends AssertJUnit
 
         assertEquals(featuresDesc, featureVectors.getFeatureDescriptions());
         int groupId = 1;
-        for (MaterialReplicaSubgroupFeatureVector subgroup : featureVectors.getSubgroups())
+        for (MaterialBiologicalReplicateFeatureVector subgroup : featureVectors.getSubgroups())
         {
             switch (groupId)
             {
@@ -103,13 +103,13 @@ public class MaterialFeatureVectorSummaryLoaderTest extends AssertJUnit
                     assertArraysEqual(new float[]
                         { 20, 200 }, subgroup.getAggregatedSummary());
                     assertEquals("siRNA 1", subgroup.getSubgroupLabel());
-                    assertEquals(3, subgroup.getSingleReplicaValues().size());
+                    assertEquals(3, subgroup.getTechnicalReplicatesValues().size());
                     break;
                 case 2:
                     assertArraysEqual(new float[]
                         { 200, 2000 }, subgroup.getAggregatedSummary());
                     assertEquals("siRNA 2", subgroup.getSubgroupLabel());
-                    assertEquals(3, subgroup.getSingleReplicaValues().size());
+                    assertEquals(3, subgroup.getTechnicalReplicatesValues().size());
                     break;
                 default:
                     fail("unexpected subgroup " + groupId);
-- 
GitLab