From a34e9e38e40310185ba52c8fc91c6099bd0c6cc5 Mon Sep 17 00:00:00 2001
From: buczekp <buczekp>
Date: Wed, 9 Feb 2011 20:43:50 +0000
Subject: [PATCH] [LMS-2035] refactoring, use: - feature labels instead of
 indexes, - map <feature label -> feature value> instead of feature value
 array;

SVN: 19850
---
 .../plugin/screening/OpenBISIE.gwt.xml        | 13 +++
 .../detailviewers/dto/WellData.java           | 32 ++++--
 .../heatmaps/HeatmapPresenter.java            | 99 ++++++++++---------
 .../detailviewers/heatmaps/PlateLayouter.java | 37 ++++---
 .../heatmaps/PlateLayouterModel.java          | 55 ++++++-----
 .../shared/basic/dto/FeatureVectorValues.java | 20 ++--
 .../screening/shared/dto/FeatureTableRow.java |  2 +-
 .../shared/imaging/FeatureVectorLoader.java   | 19 +++-
 .../server/DssServiceRpcScreeningTest.java    | 11 ++-
 .../heatmaps/WellTooltipGeneratorTest.java    | 54 +++++++---
 10 files changed, 216 insertions(+), 126 deletions(-)
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/OpenBISIE.gwt.xml

diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/OpenBISIE.gwt.xml b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/OpenBISIE.gwt.xml
new file mode 100644
index 00000000000..3f5f98dea52
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/OpenBISIE.gwt.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.7.1/distro-source/core/src/gwt-module.dtd">
+<module rename-to="ch.systemsx.cisd.openbis.plugin.screening.OpenBIS">
+  <inherits name='ch.systemsx.cisd.openbis.OpenBIS-without-entry-point' />
+  <set-property name="user.agent" value="ie8" />
+
+  <entry-point class='ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.ScreeningClient' />
+
+  <script src="screening-dictionary.js" />
+  <public path="client/web/public" />
+  <source path="client/web/client" />
+  <source path="shared/basic" />
+</module>
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/WellData.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/WellData.java
index 556b9c2c1df..384cdba664c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/WellData.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/WellData.java
@@ -16,6 +16,10 @@
 
 package ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.dto;
 
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.FeatureValue;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation;
@@ -34,7 +38,10 @@ public class WellData
 
     private WellMetadata wellMetadataOrNull;
 
-    private FeatureValue[] featureValuesOrNull;
+    // ordered map from feature labels to feature values
+    // NOTE: it contains a subset of all feature values of a well (only the ones that were loaded)
+    private Map<String /* feature label */, FeatureValue> featureValuesMap =
+            new LinkedHashMap<String, FeatureValue>();
 
     public WellData(WellLocation wellLocation, Experiment experiment)
     {
@@ -42,18 +49,25 @@ public class WellData
         this.experiment = experiment;
     }
 
-    public void setFeatureValues(FeatureValue[] featureValues)
+    public void addFeatureValue(String featureName, FeatureValue value)
+    {
+        featureValuesMap.put(featureName, value);
+    }
+
+    public void resetFeatureValues()
+    {
+        featureValuesMap.clear();
+    }
+
+    public FeatureValue tryGetFeatureValue(String featureLabel)
     {
-        this.featureValuesOrNull = featureValues;
+        return featureValuesMap.get(featureLabel);
     }
 
-    public FeatureValue tryGetFeatureValue(int featureVectorIndex)
+    // ordered set of feature labels for which we have loaded feature values
+    public Set<String> getFeatureLabels()
     {
-        if (featureValuesOrNull == null)
-        {
-            return null;
-        }
-        return featureValuesOrNull[featureVectorIndex];
+        return featureValuesMap.keySet();
     }
 
     public void setMetadata(WellMetadata well)
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/HeatmapPresenter.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/HeatmapPresenter.java
index e90ecca5705..f6597b8bf2d 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/HeatmapPresenter.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/HeatmapPresenter.java
@@ -71,49 +71,48 @@ class HeatmapPresenter
     }
 
     /**
-     * Changes the presented heatmap to the one which shows the feature which appears in all
-     * {@link WellData} on a specified index.
+     * Changes the presented heatmap to the one which shows the feature with given label.
      */
-    public void setFeatureValueMode(int featureIndex)
+    public void setFeatureValueMode(String featureName)
     {
-        IHeatmapRenderer<WellData> renderer = createFeatureHeatmapRenderer(featureIndex);
-        refreshHeatmap(renderer, featureIndex);
+        IHeatmapRenderer<WellData> renderer = createFeatureHeatmapRenderer(featureName);
+        refreshHeatmap(renderer, featureName);
     }
 
-    private IHeatmapRenderer<WellData> createFeatureHeatmapRenderer(int featureIndex)
+    private IHeatmapRenderer<WellData> createFeatureHeatmapRenderer(String featureLabel)
     {
         List<WellData> wellList = model.getWellList();
-        if (model.isVocabularyFeature(featureIndex))
+        if (model.isVocabularyFeature(featureLabel))
         {
-            return createVocabularyFeatureHeatmapRenderer(wellList, featureIndex);
+            return createVocabularyFeatureHeatmapRenderer(wellList, featureLabel);
         } else
         {
-            return createFloatFeatureHeatmapRenderer(wellList, featureIndex, realNumberRenderer);
+            return createFloatFeatureHeatmapRenderer(wellList, featureLabel, realNumberRenderer);
         }
     }
 
-    // here we are sure that all wells have a vocabulary feature at featureIndex index
+    // here we are sure that all wells have a vocabulary feature with given featureLabel
     private static IHeatmapRenderer<WellData> createVocabularyFeatureHeatmapRenderer(
-            List<WellData> wellList, final int featureIndex)
+            List<WellData> wellList, final String featureLabel)
     {
-        List<String> uniqueValues = extractUniqueVocabularyTerms(wellList, featureIndex);
+        List<String> uniqueValues = extractUniqueVocabularyTerms(wellList, featureLabel);
         return new DetegatingStringHeatmapRenderer<WellData>(uniqueValues, null)
             {
                 @Override
                 protected String extractLabel(WellData well)
                 {
-                    return tryAsVocabularyFeature(well, featureIndex);
+                    return tryAsVocabularyFeature(well, featureLabel);
                 }
             };
     }
 
     private static List<String> extractUniqueVocabularyTerms(List<WellData> wellList,
-            int featureIndex)
+            String featureLabel)
     {
         Set<String> uniqueValues = new HashSet<String>();
         for (WellData well : wellList)
         {
-            String term = tryAsVocabularyFeature(well, featureIndex);
+            String term = tryAsVocabularyFeature(well, featureLabel);
             if (term != null)
             {
                 uniqueValues.add(term);
@@ -125,7 +124,7 @@ class HeatmapPresenter
     }
 
     // updates color of all well components
-    private void refreshHeatmap(IHeatmapRenderer<WellData> renderer, Integer featureIndexOrNull)
+    private void refreshHeatmap(IHeatmapRenderer<WellData> renderer, String featureLabelOrNull)
     {
         WellData[][] wellMatrix = model.getWellMatrix();
         for (int rowIx = 0; rowIx < wellMatrix.length; rowIx++)
@@ -136,7 +135,7 @@ class HeatmapPresenter
                 Color color = renderer.getColor(wellData);
                 String tooltipOrNull =
                         WellTooltipGenerator.tryGenerateTooltip(model, rowIx, colIx,
-                                featureIndexOrNull, realNumberRenderer);
+                                featureLabelOrNull, realNumberRenderer);
                 viewManipulations.refreshWellStyle(rowIx, colIx, color, tooltipOrNull);
             }
         }
@@ -151,15 +150,15 @@ class HeatmapPresenter
         viewManipulations.updateLegend(legend);
     }
 
-    // here we are sure that all wells have a float feature at featureIndex index
+    // here we are sure that all wells have a float feature with given featureLabel
     private static IHeatmapRenderer<WellData> createFloatFeatureHeatmapRenderer(
-            List<WellData> wells, final int featureIndex, IRealNumberRenderer realNumberRenderer)
+            List<WellData> wells, final String featureLabel, IRealNumberRenderer realNumberRenderer)
     {
         float min = Float.MAX_VALUE;
         float max = Float.MIN_VALUE;
         for (WellData well : wells)
         {
-            Float value = tryAsFloatFeature(well, featureIndex);
+            Float value = tryAsFloatFeature(well, featureLabel);
             if (value != null && Float.isNaN(value) == false && Float.isInfinite(value) == false)
             {
                 min = Math.min(min, value);
@@ -171,20 +170,20 @@ class HeatmapPresenter
                 @Override
                 protected Float convert(WellData well)
                 {
-                    return tryAsFloatFeature(well, featureIndex);
+                    return tryAsFloatFeature(well, featureLabel);
                 }
             };
     }
 
-    private static float tryAsFloatFeature(WellData well, final int featureIndex)
+    private static float tryAsFloatFeature(WellData well, final String featureLabel)
     {
-        FeatureValue value = well.tryGetFeatureValue(featureIndex);
+        FeatureValue value = well.tryGetFeatureValue(featureLabel);
         return value != null ? value.asFloat() : null;
     }
 
-    private static String tryAsVocabularyFeature(WellData well, final int featureIndex)
+    private static String tryAsVocabularyFeature(WellData well, final String featureLabel)
     {
-        FeatureValue value = well.tryGetFeatureValue(featureIndex);
+        FeatureValue value = well.tryGetFeatureValue(featureLabel);
         return value != null ? value.tryAsVocabularyTerm() : null;
     }
 
@@ -311,14 +310,14 @@ class HeatmapPresenter
         /**
          * Generates a short description of the well, which can be used as e.g. a tooltip
          * 
-         * @param featureIndexOrNull if not null contains an index of the feature which should be
+         * @param featureLabelOrNull if not null contains label of the feature which should be
          *            distinguished.
          */
         public static String tryGenerateTooltip(PlateLayouterModel model, int rowIx, int colIx,
-                Integer featureIndexOrNull, IRealNumberRenderer realNumberRenderer)
+                String featureLabelOrNull, IRealNumberRenderer realNumberRenderer)
         {
             return new WellTooltipGenerator(model, realNumberRenderer).tryGenerateShortDescription(
-                    rowIx, colIx, featureIndexOrNull);
+                    rowIx, colIx, featureLabelOrNull);
         }
 
         private final PlateLayouterModel model;
@@ -332,19 +331,21 @@ class HeatmapPresenter
             this.realNumberRenderer = realNumberRenderer;
         }
 
-        private String tryGenerateShortDescription(int rowIx, int colIx, Integer featureIndexOrNull)
+        private String tryGenerateShortDescription(int rowIx, int colIx,
+                String distinguishedLabelOrNull)
         {
             WellData wellData = model.getWellMatrix()[rowIx][colIx];
             String tooltip = "";
-            if (featureIndexOrNull != null)
+            if (distinguishedLabelOrNull != null)
             {
-                tooltip += generateOneFeatureDescription(wellData, featureIndexOrNull, true);
+                tooltip += generateOneFeatureDescription(wellData, distinguishedLabelOrNull, true);
             }
 
             tooltip += generateMetadataDescription(wellData);
 
-            int featuresNum = getNumberOfFeatures();
-            if (featuresNum - (featureIndexOrNull != null ? 1 : 0) > 0)
+            int allFeaturesNum = getNumberOfAllFeatures();
+            int loadedFeaturesNum = getNumberOfLoadedFeatures(wellData);
+            if (loadedFeaturesNum - (distinguishedLabelOrNull != null ? 1 : 0) > 0)
             {
                 if (tooltip.length() == 0)
                 {
@@ -353,15 +354,21 @@ class HeatmapPresenter
                 {
                     tooltip += NEWLINE; // separate metadata from the text below
                 }
-                int describedFeaturesNum = Math.min(MAX_DESCRIBED_FEATURES, featuresNum);
-                for (int ix = 0; ix < describedFeaturesNum; ix++)
+                int describedFeaturesNum = Math.min(MAX_DESCRIBED_FEATURES, loadedFeaturesNum);
+                int fCounter = 0;
+                for (String featureLabel : wellData.getFeatureLabels())
                 {
-                    if (featureIndexOrNull == null || ix != featureIndexOrNull.intValue())
+                    if (featureLabel.equals(distinguishedLabelOrNull) == false)
                     {
-                        tooltip += generateOneFeatureDescription(wellData, ix, false);
+                        tooltip += generateOneFeatureDescription(wellData, featureLabel, false);
+                    }
+                    fCounter++;
+                    if (fCounter == describedFeaturesNum)
+                    {
+                        break;
                     }
                 }
-                if (featuresNum > describedFeaturesNum)
+                if (allFeaturesNum > describedFeaturesNum)
                 {
                     tooltip += "...";
                 }
@@ -370,19 +377,21 @@ class HeatmapPresenter
 
         }
 
-        private int getNumberOfFeatures()
+        private int getNumberOfLoadedFeatures(WellData wellData)
+        {
+            return wellData.getFeatureLabels().size();
+        }
+
+        private int getNumberOfAllFeatures()
         {
             List<String> featureLabels = model.tryGetFeatureLabels();
             return featureLabels == null ? 0 : featureLabels.size();
         }
 
-        private String generateOneFeatureDescription(WellData wellData, int featureIndex,
+        private String generateOneFeatureDescription(WellData wellData, String featureLabel,
                 boolean distinguished)
         {
-            List<String> featureLabels = model.tryGetFeatureLabels();
-            assert featureLabels != null : "feature labels not set";
-
-            FeatureValue value = wellData.tryGetFeatureValue(featureIndex);
+            FeatureValue value = wellData.tryGetFeatureValue(featureLabel);
             // if the value should be distinguished we show it even if it's null
             if (value == null && distinguished == false)
             {
@@ -393,7 +402,7 @@ class HeatmapPresenter
             {
                 textValue = "<b>" + textValue + "</b>";
             }
-            return featureLabels.get(featureIndex) + ": " + textValue + NEWLINE;
+            return featureLabel + ": " + textValue + NEWLINE;
         }
 
         private String renderValue(FeatureValue value)
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouter.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouter.java
index 71252b3f483..adf5209da84 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouter.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouter.java
@@ -99,7 +99,7 @@ public class PlateLayouter
 
     private final Widget view;
 
-    private final SimpleModelComboBox<Integer> heatmapKindChooser;
+    private final SimpleModelComboBox<String> heatmapKindChooser;
 
     public PlateLayouter(ScreeningViewContext viewContext, PlateMetadata plateMetadata)
     {
@@ -173,7 +173,7 @@ public class PlateLayouter
      * re-rendering.
      */
     private static Widget renderView(Component[][] renderedWells,
-            SimpleModelComboBox<Integer> heatmapKindChooser, LayoutContainer legendContainer)
+            SimpleModelComboBox<String> heatmapKindChooser, LayoutContainer legendContainer)
     {
         LayoutContainer container = new LayoutContainer();
         container.setScrollMode(Scroll.AUTO);
@@ -370,54 +370,51 @@ public class PlateLayouter
 
     // ---------
 
-    private static SimpleModelComboBox<Integer> createHeatmapKindComboBox(
+    private static SimpleModelComboBox<String> createHeatmapKindComboBox(
             final HeatmapPresenter presenter, IMessageProvider messageProvider)
     {
-        List<LabeledItem<Integer>> items = createHeatmapKindModel(null);
-        final SimpleModelComboBox<Integer> chooser =
-                new SimpleModelComboBox<Integer>(messageProvider, items,
+        List<LabeledItem<String>> items = createHeatmapKindModel(null);
+        final SimpleModelComboBox<String> chooser =
+                new SimpleModelComboBox<String>(messageProvider, items,
                         HEATMAP_KIND_COMBOBOX_CHOOSER_WIDTH_PX);
-        chooser.addSelectionChangedListener(new SelectionChangedListener<SimpleComboValue<LabeledItem<Integer>>>()
+        chooser.addSelectionChangedListener(new SelectionChangedListener<SimpleComboValue<LabeledItem<String>>>()
             {
                 @Override
                 public void selectionChanged(
-                        SelectionChangedEvent<SimpleComboValue<LabeledItem<Integer>>> se)
+                        SelectionChangedEvent<SimpleComboValue<LabeledItem<String>>> se)
                 {
-                    Integer value = SimpleModelComboBox.getChosenItem(se);
-                    if (value == null)
+                    String featureName = SimpleModelComboBox.getChosenItem(se);
+                    if (featureName == null)
                     {
                         presenter.setWellMetadataMode();
                     } else
                     {
-                        presenter.setFeatureValueMode(value);
+                        presenter.setFeatureValueMode(featureName);
                     }
                 }
             });
         return chooser;
     }
 
-    private static void updateHeatmapKindComboBox(SimpleModelComboBox<Integer> chooser,
+    private static void updateHeatmapKindComboBox(SimpleModelComboBox<String> chooser,
             List<String> featureLabelsOrNull)
     {
-        List<LabeledItem<Integer>> items = createHeatmapKindModel(featureLabelsOrNull);
+        List<LabeledItem<String>> items = createHeatmapKindModel(featureLabelsOrNull);
         chooser.removeAll();
         chooser.add(items);
         GWTUtils.autoselect(chooser, false);
     }
 
-    private static List<LabeledItem<Integer>> createHeatmapKindModel(
-            List<String> featureLabelsOrNull)
+    private static List<LabeledItem<String>> createHeatmapKindModel(List<String> featureLabelsOrNull)
     {
-        List<LabeledItem<Integer>> items = new ArrayList<LabeledItem<Integer>>();
-        items.add(new LabeledItem<Integer>(null, METADATA_HEATMAP_KIND_MSG));
+        List<LabeledItem<String>> items = new ArrayList<LabeledItem<String>>();
+        items.add(new LabeledItem<String>(null, METADATA_HEATMAP_KIND_MSG));
         if (featureLabelsOrNull != null)
         {
-            int i = 0;
             for (String featureLabel : featureLabelsOrNull)
             {
                 String label = FEATURE_HEATMAP_KIND_PREFIX_MSG + featureLabel;
-                items.add(new LabeledItem<Integer>(i, label));
-                i++;
+                items.add(new LabeledItem<String>(featureLabel, label));
             }
         }
         return items;
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouterModel.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouterModel.java
index 9575b487417..564851df0dd 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouterModel.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/PlateLayouterModel.java
@@ -17,7 +17,10 @@
 package ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.heatmaps;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.dto.WellData;
@@ -44,9 +47,9 @@ class PlateLayouterModel
 
     private ImageDatasetEnrichedReference imageDatasetOrNull;
 
-    private List<String> featureLabelsOrNull;
+    private List<String> featureLabelsOrNull; // names of all features
 
-    private boolean[] isVocabularyFeatureMapOrNull;
+    private Set<String> vocabularyFeatureLabels = new HashSet<String>();
 
     // ---
 
@@ -81,15 +84,9 @@ class PlateLayouterModel
         return featureLabelsOrNull;
     }
 
-    public boolean isVocabularyFeature(int featureVectorIndex)
+    public boolean isVocabularyFeature(String featureLabel)
     {
-        if (isVocabularyFeatureMapOrNull == null)
-        {
-            return false;
-        } else
-        {
-            return isVocabularyFeatureMapOrNull[featureVectorIndex];
-        }
+        return vocabularyFeatureLabels.contains(featureLabel);
     }
 
     // --- some logic
@@ -97,7 +94,7 @@ class PlateLayouterModel
     public void setFeatureVectorDataset(FeatureVectorDataset featureVectorDatasetOrNull)
     {
         unsetFeatureVectors();
-        this.isVocabularyFeatureMapOrNull = null;
+        this.vocabularyFeatureLabels.clear(); // TODO PTR: don't clear on update
         if (featureVectorDatasetOrNull == null)
         {
             this.featureLabelsOrNull = null;
@@ -106,32 +103,42 @@ class PlateLayouterModel
             this.featureLabelsOrNull = featureVectorDatasetOrNull.getFeatureLabels();
             List<? extends FeatureVectorValues> features =
                     featureVectorDatasetOrNull.getDatasetFeatures();
+            if (features.isEmpty() == false)
+            {
+                // NOTE: for each feature vector in the dataset this set is the same
+                this.vocabularyFeatureLabels = extractVocabularyFeatureLabels(features.get(0));
+            }
             for (FeatureVectorValues featureVector : features)
             {
-                if (this.isVocabularyFeatureMapOrNull == null)
-                {
-                    // NOTE: for each feature vector in the dataset this map would be the same
-                    this.isVocabularyFeatureMapOrNull = createIsVocabularyMap(featureVector);
-                }
                 WellLocation loc = featureVector.getWellLocation();
                 WellData wellData = tryGetWellData(loc);
                 if (wellData != null)
                 {
-                    wellData.setFeatureValues(featureVector.getFeatureValues());
+                    for (Entry<String, FeatureValue> entry : featureVector.getFeatureMap()
+                            .entrySet())
+                    {
+                        String featureLabel = entry.getKey();
+                        FeatureValue value = entry.getValue();
+                        wellData.addFeatureValue(featureLabel, value);
+                    }
                 }
             }
         }
     }
 
-    private static boolean[] createIsVocabularyMap(FeatureVectorValues featureVector)
+    private static Set<String> extractVocabularyFeatureLabels(FeatureVectorValues featureVector)
     {
-        FeatureValue[] values = featureVector.getFeatureValues();
-        boolean[] map = new boolean[values.length];
-        for (int i = 0; i < map.length; i++)
+        final Set<String> result = new HashSet<String>();
+        for (Entry<String, FeatureValue> entry : featureVector.getFeatureMap().entrySet())
         {
-            map[i] = values[i].isVocabularyTerm();
+            String featureLabel = entry.getKey();
+            FeatureValue featureValue = entry.getValue();
+            if (featureValue.isVocabularyTerm())
+            {
+                result.add(featureLabel);
+            }
         }
-        return map;
+        return result;
     }
 
     private WellData tryGetWellData(WellLocation loc)
@@ -152,7 +159,7 @@ class PlateLayouterModel
     {
         for (WellData well : wellList)
         {
-            well.setFeatureValues(null);
+            well.resetFeatureValues(); // TODO PTR: needed? keep values of all datasets
         }
     }
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/FeatureVectorValues.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/FeatureVectorValues.java
index e0c8a21abba..0c76c12be1c 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/FeatureVectorValues.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/FeatureVectorValues.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto;
 
+import java.util.Map;
+
 import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
 import ch.systemsx.cisd.openbis.generic.shared.basic.annotation.DoNotEscape;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder;
@@ -33,7 +35,7 @@ public class FeatureVectorValues implements ISerializable
 
     private WellFeatureVectorReference featureVectorReference;
 
-    private FeatureValue[] featureValues;
+    private Map<String /* feature label */, FeatureValue /* value */> featureMap;
 
     // GWT only
     @SuppressWarnings("unused")
@@ -42,21 +44,27 @@ public class FeatureVectorValues implements ISerializable
     }
 
     public FeatureVectorValues(String dataSetCode, WellLocation wellLocation,
-            FeatureValue[] featureValues)
+            Map<String, FeatureValue> featureMap)
     {
-        this(new WellFeatureVectorReference(dataSetCode, wellLocation), featureValues);
+        this(new WellFeatureVectorReference(dataSetCode, wellLocation), featureMap);
     }
 
     public FeatureVectorValues(WellFeatureVectorReference featureVectorReference,
-            FeatureValue[] featureValues)
+            Map<String, FeatureValue> featureMap)
     {
         this.featureVectorReference = featureVectorReference;
-        this.featureValues = featureValues;
+        this.featureMap = featureMap;
     }
 
+    // NOTE: don't use this method multiple times for performance reasons
     public FeatureValue[] getFeatureValues()
     {
-        return featureValues;
+        return featureMap.values().toArray(new FeatureValue[0]);
+    }
+
+    public Map<String, FeatureValue> getFeatureMap()
+    {
+        return featureMap;
     }
 
     public String getDataSetCode()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/FeatureTableRow.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/FeatureTableRow.java
index f4003b95aca..28d791d3dd6 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/FeatureTableRow.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/dto/FeatureTableRow.java
@@ -38,7 +38,7 @@ public class FeatureTableRow extends FeatureVectorValues
     public FeatureTableRow(FeatureVectorValues featureVector)
     {
         super(featureVector.getDataSetCode(), featureVector.getWellLocation(), featureVector
-                .getFeatureValues());
+                .getFeatureMap());
     }
 
     public FeatureVectorDatasetWellReference getReference()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/FeatureVectorLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/FeatureVectorLoader.java
index f6e908fbd5b..921c7a9592d 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/FeatureVectorLoader.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/FeatureVectorLoader.java
@@ -134,12 +134,13 @@ public class FeatureVectorLoader
         List<FeatureVectorValues> fvs = new ArrayList<FeatureVectorValues>();
         for (FeatureTableRow row : featureRows)
         {
-            fvs.add(new FeatureVectorValues(row.getFeatureVectorReference(), row.getFeatureValues()));
+            fvs.add(new FeatureVectorValues(row.getFeatureVectorReference(), row.getFeatureMap()));
         }
         return new WellFeatureCollection<FeatureVectorValues>(fvs,
                 featureRowsCollection.getFeatureCodesAndLabels());
     }
 
+    // TODO PTR use filtering by codes
     /**
      * fetches specified features of all wells
      * 
@@ -499,10 +500,24 @@ public class FeatureVectorLoader
             WellLocation wellLocation)
     {
         String permId = bundle.dataSet.getPermId();
+
         FeatureValue[] valueArray =
                 createFeatureValueArray(bundle.featureDefToValuesMap,
                         bundle.featureDefToVocabularyTerms, wellLocation);
-        return new FeatureVectorValues(permId, wellLocation, valueArray);
+        return new FeatureVectorValues(permId, wellLocation, asValueMap(valueArray));
+    }
+
+    private Map<String, FeatureValue> asValueMap(FeatureValue[] valueArray)
+    {
+        List<CodeAndLabel> features = getCodesAndLabels();
+        assert features.size() == valueArray.length;
+
+        Map<String, FeatureValue> result = new LinkedHashMap<String, FeatureValue>();
+        for (int i = 0; i < valueArray.length; i++)
+        {
+            result.put(features.get(i).getLabel(), valueArray[i]);
+        }
+        return result;
     }
 
     private FeatureValue[] createFeatureValueArray(
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java
index 5e1f8958769..bb399189139 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java
@@ -45,7 +45,6 @@ import ch.systemsx.cisd.common.io.ByteArrayBasedContent;
 import ch.systemsx.cisd.common.io.ConcatenatedFileOutputStreamWriter;
 import ch.systemsx.cisd.common.io.FileBasedContent;
 import ch.systemsx.cisd.common.io.IContent;
-import ch.systemsx.cisd.etlserver.Constants;
 import ch.systemsx.cisd.openbis.dss.etl.AbsoluteImageReference;
 import ch.systemsx.cisd.openbis.dss.etl.IImagingDatasetLoader;
 import ch.systemsx.cisd.openbis.dss.generic.server.DssServiceRpcAuthorizationAdvisor;
@@ -203,7 +202,7 @@ public class DssServiceRpcScreeningTest extends AssertJUnit
         final String channel = "dapi";
         prepareGetHomeDatabaseInstance();
         prepareListDataSetsByCode();
-        
+
         List<PlateImageReference> plateImageReferences =
                 screeningService
                         .listPlateImageReferences(SESSION_TOKEN, ds, wellPositions, channel);
@@ -610,8 +609,12 @@ public class DssServiceRpcScreeningTest extends AssertJUnit
             {
                 {
                     one(service).listDataSetsByCode(Arrays.asList(DATASET_CODE));
-                    will(returnValue(Arrays.asList(new DataSetBuilder().code(DATASET_CODE)
-                            .shareId(ch.systemsx.cisd.openbis.dss.generic.shared.Constants.DEFAULT_SHARE_ID).getDataSet())));
+                    will(returnValue(Arrays
+                            .asList(new DataSetBuilder()
+                                    .code(DATASET_CODE)
+                                    .shareId(
+                                            ch.systemsx.cisd.openbis.dss.generic.shared.Constants.DEFAULT_SHARE_ID)
+                                    .getDataSet())));
                 }
             });
     }
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/WellTooltipGeneratorTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/WellTooltipGeneratorTest.java
index c6deebb9add..2a88079fcb2 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/WellTooltipGeneratorTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/heatmaps/WellTooltipGeneratorTest.java
@@ -20,7 +20,9 @@ import static ch.systemsx.cisd.openbis.plugin.screening.client.web.client.applic
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.testng.AssertJUnit;
 import org.testng.annotations.Test;
@@ -73,9 +75,9 @@ public class WellTooltipGeneratorTest extends AssertJUnit
     }
 
     private static String tryGenerateShortDescription(PlateLayouterModel model, int rowIx,
-            int colIx, Integer featureIndexOrNull)
+            int colIx, String featureLabelOrNull)
     {
-        return WellTooltipGenerator.tryGenerateTooltip(model, rowIx, colIx, featureIndexOrNull,
+        return WellTooltipGenerator.tryGenerateTooltip(model, rowIx, colIx, featureLabelOrNull,
                 createDummyRealNumberRenderer());
     }
 
@@ -102,7 +104,7 @@ public class WellTooltipGeneratorTest extends AssertJUnit
         assertEquals(METADATA_EXPECTED_DESC_A2 + "\n" + ALL_FEATURES_DESC, desc);
 
         // one feature distingushed
-        desc = tryGenerateShortDescription(model, 0, 1, 1);
+        desc = tryGenerateShortDescription(model, 0, 1, "FeatureY");
         assertEquals("FeatureY: <b>2.0</b>\n" + METADATA_EXPECTED_DESC_A2 + "\n" + FEATURE_X_DESC,
                 desc);
 
@@ -118,7 +120,7 @@ public class WellTooltipGeneratorTest extends AssertJUnit
         String desc = tryGenerateShortDescription(model, 0, 1, null);
         assertEquals(ALL_FEATURES_DESC, desc);
 
-        desc = tryGenerateShortDescription(model, 0, 1, 0);
+        desc = tryGenerateShortDescription(model, 0, 1, "FeatureX");
         assertEquals("FeatureX: <b>1.0</b>\n" + "\n" + FEATURE_Y_DESC, desc);
 
     }
@@ -134,7 +136,7 @@ public class WellTooltipGeneratorTest extends AssertJUnit
         AssertionUtil.assertStarts("Feature0: 0.0", desc);
         AssertionUtil.assertEnds("Feature29: 29.0\n" + "...", desc);
 
-        desc = tryGenerateShortDescription(model, 0, 1, 4);
+        desc = tryGenerateShortDescription(model, 0, 1, "Feature4");
         AssertionUtil.assertStarts("Feature4: <b>4.0</b>", desc);
         AssertionUtil.assertEnds("Feature29: 29.0\n" + "...", desc);
     }
@@ -153,27 +155,49 @@ public class WellTooltipGeneratorTest extends AssertJUnit
     {
         int size = 40;
         List<String> featureLabels = new ArrayList<String>(size);
-        FeatureValue[] featureValues = new FeatureValue[size];
-        for (int i = 0; i < featureValues.length; i++)
+        Map<String, FeatureValue> featureValuesMap = new LinkedHashMap<String, FeatureValue>();
+        for (int i = 0; i < size; i++)
         {
-            featureValues[i] = FeatureValue.createFloat(i);
+            final String label = "Feature" + i;
+            final FeatureValue featureValue = FeatureValue.createFloat(i);
             featureLabels.add("Feature" + i);
+            featureValuesMap.put(label, featureValue);
         }
         List<FeatureVectorValues> features = new ArrayList<FeatureVectorValues>();
-        features.add(new FeatureVectorValues(null, getLocation(WELL_A2), featureValues));
+        features.add(new FeatureVectorValues(null, getLocation(WELL_A2), featureValuesMap));
         return new FeatureVectorDataset(createDatasetReference(), features, featureLabels);
     }
 
     private static FeatureVectorDataset createFeatureVectorDataset()
     {
-        List<String> featureLabels = Arrays.asList("FeatureX", "FeatureY");
+
+        String[] featureLabels =
+            { "FeatureX", "FeatureY" };
 
         List<FeatureVectorValues> features = new ArrayList<FeatureVectorValues>();
-        features.add(new FeatureVectorValues(null, getLocation(WELL_A2), new FeatureValue[]
-            { FeatureValue.createFloat(1), FeatureValue.createFloat(2) }));
-        features.add(new FeatureVectorValues(null, getLocation(WELL_B3), new FeatureValue[]
-            { FeatureValue.createFloat(-1), FeatureValue.createFloat(-2) }));
-        return new FeatureVectorDataset(createDatasetReference(), features, featureLabels);
+        features.add(new FeatureVectorValues(null, getLocation(WELL_A2), createFeatureVectorMap(
+                featureLabels, new FeatureValue[]
+                    { FeatureValue.createFloat(1), FeatureValue.createFloat(2) })));
+        features.add(new FeatureVectorValues(null, getLocation(WELL_B3), createFeatureVectorMap(
+                featureLabels, new FeatureValue[]
+                    { FeatureValue.createFloat(-1), FeatureValue.createFloat(-2) })));
+        return new FeatureVectorDataset(createDatasetReference(), features,
+                Arrays.asList(featureLabels));
+    }
+
+    private static Map<String, FeatureValue> createFeatureVectorMap(String[] labels,
+            FeatureValue[] values)
+    {
+        assert labels.length == values.length;
+
+        Map<String, FeatureValue> result = new LinkedHashMap<String, FeatureValue>();
+        for (int i = 0; i < labels.length; i++)
+        {
+            final String label = labels[i];
+            final FeatureValue featureValue = values[i];
+            result.put(label, featureValue);
+        }
+        return result;
     }
 
     private static DatasetReference createDatasetReference()
-- 
GitLab