diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java
index 3aefd8b58d26e8f6442408cfa3d407bf31a0b378..9d6194321e71be37f3cc8beda6f8745ab5e7f226 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java
@@ -333,14 +333,14 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc implements
     }
 
     public InputStream loadImages(String sessionToken, IDatasetIdentifier dataSetIdentifier,
-            List<String> wellsOrNull, String channel, ImageSize thumbnailSizeOrNull)
+            List<WellPosition> wellPositions, String channel, ImageSize thumbnailSizeOrNull)
     {
         String datasetCode = dataSetIdentifier.getDatasetCode();
         File rootDir = getRootDirectoryForDataSet(datasetCode);
         IHCSImageDatasetLoader imageAccessor =
                 HCSImageDatasetLoaderFactory.create(rootDir, datasetCode);
         List<PlateImageReference> imageReferences =
-                createPlateImageReferences(imageAccessor, dataSetIdentifier, wellsOrNull, channel);
+                createPlateImageReferences(imageAccessor, dataSetIdentifier, wellPositions, channel);
         Size size = null;
         if (thumbnailSizeOrNull != null)
         {
@@ -357,14 +357,14 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc implements
 
     private List<PlateImageReference> createPlateImageReferences(
             IHCSImageDatasetLoader imageAccessor, IDatasetIdentifier dataSetIdentifier,
-            List<String> wellsOrNull, String channel)
+            List<WellPosition> wellPositions, String channel)
     {
         PlateImageParameters imageParameters = imageAccessor.getImageParameters();
         int rowsNum = imageParameters.getRowsNum();
         int colsNum = imageParameters.getColsNum();
         List<PlateImageReference> imageReferences = new ArrayList<PlateImageReference>();
         int numberOfTiles = imageParameters.getTileRowsNum() * imageParameters.getTileColsNum();
-        if (wellsOrNull == null || wellsOrNull.isEmpty())
+        if (wellPositions == null || wellPositions.isEmpty())
         {
             // all wells
             for (int i = 1; i <= rowsNum; i++)
@@ -377,69 +377,15 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc implements
             }
         } else
         {
-            for (String well : wellsOrNull)
+            for (WellPosition wellPosition : wellPositions)
             {
-                int indexOfDot = well.indexOf('.');
-                if (indexOfDot < 1)
-                {
-                    throw createException("Expecting a '.'", well);
-                }
-                int row = getAndCheckRowNumber(indexOfDot, well, rowsNum);
-                int col = getAndCheckColumnNumber(indexOfDot, well, colsNum);
-                addImageReferencesForAllTiles(imageReferences, row, col, channel,
-                        dataSetIdentifier, numberOfTiles);
+                addImageReferencesForAllTiles(imageReferences, wellPosition.getWellRow(),
+                        wellPosition.getWellColumn(), channel, dataSetIdentifier, numberOfTiles);
             }
         }
         return imageReferences;
     }
 
-    private int getAndCheckColumnNumber(int indexOfDot, String well, int colsNum)
-    {
-        int col;
-        try
-        {
-            col = Integer.parseInt(well.substring(indexOfDot + 1));
-        } catch (NumberFormatException ex)
-        {
-            throw createException("String after '.' isn't a number", well);
-        }
-        if (col < 1)
-        {
-            throw createException("Column number starts with 1", well);
-        }
-        if (col > colsNum)
-        {
-            throw createException("There are only " + colsNum + " columns", well);
-        }
-        return col;
-    }
-
-    private int getAndCheckRowNumber(int indexOfDot, String well, int rowsNum)
-    {
-        int row;
-        try
-        {
-            row = Integer.parseInt(well.substring(0, indexOfDot));
-        } catch (NumberFormatException ex)
-        {
-            throw createException("String before '.' isn't a number", well);
-        }
-        if (row < 1)
-        {
-            throw createException("Row number starts with 1", well);
-        }
-        if (row > rowsNum)
-        {
-            throw createException("There are only " + rowsNum + " rows", well);
-        }
-        return row;
-    }
-
-    private UserFailureException createException(String description, String well)
-    {
-        return new UserFailureException("Invalid well description: " + description + ": " + well);
-    }
-    
     private void addImageReferencesForAllTiles(List<PlateImageReference> imageReferences,
             int wellRow, int wellColumn, String channel, IDatasetIdentifier dataset,
             int numberOfTiles)
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/IDssServiceRpcScreening.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/IDssServiceRpcScreening.java
index 7f9076fda7e87d7392bff31d696a54a5ac509c66..bb1395d28636340ee0eb52f66896e8f74ed489ad 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/IDssServiceRpcScreening.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/IDssServiceRpcScreening.java
@@ -33,6 +33,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.IImageDataset
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageDatasetMetadata;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageSize;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateImageReference;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellPosition;
 
 /**
  * Public DSS API for screening. Since version 1.2 features are no longer identified by a name but
@@ -143,11 +144,28 @@ public interface IDssServiceRpcScreening extends IRpcService
     public InputStream loadImages(
             String sessionToken,
             @AuthorizationGuard(guardClass = DatasetIdentifierPredicate.class) List<PlateImageReference> imageReferences);
-    
+
+    /**
+     * Provide images for a specified data set, a list of well positions (empty list means all
+     * wells), a channel, and an optional thumb nail size. Images of all tiles are delivered. If
+     * thumb nail size isn't specified the original image is delivered otherwise a thumb nail image
+     * with same aspect ratio as the original image but which fits into specified size will be
+     * delivered.
+     * <p>
+     * The result is encoded into one stream, which consist of multiple blocks in a format:
+     * (<block-size><block-of-bytes>)*, where block-size is the block size in bytes encoded as one
+     * long number. The number of blocks is equal to the number of specified references and the
+     * order of blocks corresponds to the order of image references. The images will be converted to
+     * PNG format before being shipped.
+     * 
+     * @since 1.4
+     */
+    @MinimalMinorVersion(4)
+    @DataSetAccessGuard
     public InputStream loadImages(
             String sessionToken,
-            @AuthorizationGuard(guardClass = DatasetIdentifierPredicate.class) IDatasetIdentifier dataSetIdentifier,
-            List<String> wellsOrNull, String channel, ImageSize thumbnailSizeOrNull);
+            @AuthorizationGuard(guardClass = SingleDataSetIdentifierPredicate.class) IDatasetIdentifier dataSetIdentifier,
+            List<WellPosition> wellPositions, String channel, ImageSize thumbnailSizeOrNull);
 
     /**
      * For a given set of image data sets, provide all image channels that have been acquired and
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/SingleDataSetIdentifierPredicate.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/SingleDataSetIdentifierPredicate.java
new file mode 100644
index 0000000000000000000000000000000000000000..ecdad6d7f1bcfa71b8d468c3e11726bd2fef66d0
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/SingleDataSetIdentifierPredicate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.dss.screening.shared.api.v1;
+
+import java.util.Collections;
+import java.util.List;
+
+import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.authorization.IAuthorizationGuardPredicate;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.IDatasetIdentifier;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class SingleDataSetIdentifierPredicate implements
+        IAuthorizationGuardPredicate<IDssServiceRpcScreeningInternal, IDatasetIdentifier>
+
+{
+    private static final DatasetIdentifierPredicate PREDICATE = new DatasetIdentifierPredicate();
+
+    public Status evaluate(IDssServiceRpcScreeningInternal receiver, String sessionToken,
+            IDatasetIdentifier datasetIdentifier) throws UserFailureException
+    {
+        return PREDICATE.evaluate(receiver, sessionToken,
+                Collections.singletonList(datasetIdentifier));
+    }
+
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/IScreeningOpenbisServiceFacade.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/IScreeningOpenbisServiceFacade.java
index dfdb77b9220ecb19174985bae896b48759de2dbe..2cb44013e12003fa2d9e1b44d547ad3a7710b0ff 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/IScreeningOpenbisServiceFacade.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/IScreeningOpenbisServiceFacade.java
@@ -38,6 +38,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateIdentifi
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateImageReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateWellMaterialMapping;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateWellReferenceWithDatasets;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellPosition;
 
 /**
  * A client side facade of openBIS and Datastore Server API. Since version 1.2 of the API features
@@ -250,8 +251,18 @@ public interface IScreeningOpenbisServiceFacade
             IImageOutputStreamProvider outputStreamProvider, boolean convertToPNG)
             throws IOException;
     
+    /**
+     * Loads original images or thumbnails for a specified data set, a list of well positions (empty
+     * list means all wells), a channel, and an optional thumb nail size. Images of all tiles are
+     * delivered. If thumb nail size isn't specified the original image is delivered otherwise a
+     * thumb nail image with same aspect ratio as the original image but which fits into specified
+     * size will be delivered.
+     * 
+     * @return a list of byte arrays where each array contains a PNG encoded image.
+     */
     public List<byte[]> loadImages(IDatasetIdentifier dataSetIdentifier,
-            List<String> wellsOrNull, String channel, ImageSize thumbnailSizeOrNull) throws IOException;
+            List<WellPosition> wellPositions, String channel, ImageSize thumbnailSizeOrNull)
+            throws IOException;
 
     /**
      * For a given set of image data sets, provide all image channels that have been acquired and
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ImageViewer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ImageViewer.java
index c6f3378bf3ee20db94697ae6ffa25164c0078fff..a9ecceeef0ab053c18575f17c432c9be156d4c75 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ImageViewer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ImageViewer.java
@@ -31,8 +31,10 @@ import javax.swing.JPanel;
 
 import org.apache.commons.lang.StringUtils;
 
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.DataSetCodeAndWellPositions;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.IDatasetIdentifier;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageSize;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellPosition;
 
 
 /**
@@ -42,67 +44,46 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageSize;
  */
 public class ImageViewer
 {
-    
-    private static final class DataSetAndWells
-    {
-        private final String dataSetCode;
-        private final List<String> wells = new ArrayList<String>();
-        
-        DataSetAndWells(String description)
-        {
-            int indexOfColon = description.indexOf(':');
-            if (indexOfColon < 0)
-            {
-                dataSetCode = description;
-            } else
-            {
-                dataSetCode = description.substring(0, indexOfColon);
-                wells.addAll(Arrays.asList(StringUtils.split(description.substring(indexOfColon + 1))));
-            }
-        }
-
-        public final String getDataSetCode()
-        {
-            return dataSetCode;
-        }
-
-        public final List<String> getWells()
-        {
-            return wells;
-        }
-    }
-
     public static void main(String[] args)
     {
         String serviceURL = args[0];
         String sessionToken = args[1];
         long experimentID = Long.parseLong(args[2]);
         String channel = args[3];
-        Map<String, DataSetAndWells> dataSets = new HashMap<String, DataSetAndWells>();
+        Map<String, DataSetCodeAndWellPositions> dataSets =
+                new HashMap<String, DataSetCodeAndWellPositions>();
         for (int i = 4; i < args.length; i++)
         {
-            DataSetAndWells dataSetAndWells = new DataSetAndWells(args[i]);
-            dataSets.put(dataSetAndWells.getDataSetCode(), dataSetAndWells);
+            DataSetCodeAndWellPositions dw = new DataSetCodeAndWellPositions(args[i]);
+            dataSets.put(dw.getDataSetCode(), dw);
         }
+
         JFrame frame = new JFrame("Image Viewer");
         frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
         Container contentPane = frame.getContentPane();
         JPanel content = new JPanel();
         content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
         contentPane.add(content);
-        ImageSize thumbnailSize = new ImageSize(200, 160);
+        ImageSize thumbnailSize = new ImageSize(100, 60);
         try
         {
-            IScreeningOpenbisServiceFacade facade = ScreeningOpenbisServiceFacadeFactory.tryCreate(sessionToken, serviceURL);
-            List<IDatasetIdentifier> dsIdentifier = facade.getDatasetIdentifiers(new ArrayList<String>(dataSets.keySet()));
+            IScreeningOpenbisServiceFacade facade =
+                    ScreeningOpenbisServiceFacadeFactory.tryCreate(sessionToken, serviceURL);
+            List<IDatasetIdentifier> dsIdentifier =
+                    facade.getDatasetIdentifiers(new ArrayList<String>(dataSets.keySet()));
             for (IDatasetIdentifier identifier : dsIdentifier)
             {
+                content.add(new JLabel("Images for data set " + identifier.getDatasetCode()));
+                JPanel imagePanel = new JPanel();
+                imagePanel.setLayout(new BoxLayout(imagePanel, BoxLayout.X_AXIS));
+                content.add(imagePanel);
+                List<WellPosition> wellPositions =
+                        dataSets.get(identifier.getDatasetCode()).getWellPositions();
                 List<byte[]> imageBytes =
-                        facade.loadImages(identifier, dataSets.get(identifier.getDatasetCode())
-                                .getWells(), channel, thumbnailSize);
+                        facade.loadImages(identifier, wellPositions, channel, thumbnailSize);
                 for (byte[] bytes : imageBytes)
                 {
-                    content.add(new JLabel(new ImageIcon(bytes)));
+                    imagePanel.add(new JLabel(new ImageIcon(bytes)));
                 }
             }
         } catch (Exception ex)
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningClientApiTest.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningClientApiTest.java
index 0fa30a1598d3f1cab04313d1481fee79aa2d8889..fab871c1ff6f16077f10dec0a6e4e5a9e0d7bcf8 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningClientApiTest.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningClientApiTest.java
@@ -16,11 +16,7 @@
 
 package ch.systemsx.cisd.openbis.plugin.screening.client.api.v1;
 
-import java.awt.Component;
-import java.awt.Graphics;
-import java.awt.image.BufferedImage;
 import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -37,12 +33,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
-import javax.imageio.ImageIO;
-import javax.swing.Icon;
-import javax.swing.ImageIcon;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-
 import org.apache.log4j.PropertyConfigurator;
 
 import ch.systemsx.cisd.openbis.plugin.screening.client.api.v1.ScreeningOpenbisServiceFacade.IImageOutputStreamProvider;
@@ -55,7 +45,6 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.IDatasetIdent
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.IImageDatasetIdentifier;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageDatasetMetadata;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageDatasetReference;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageSize;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.MaterialIdentifier;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.MaterialTypeIdentifier;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Plate;
@@ -70,7 +59,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellPosition;
  */
 public class ScreeningClientApiTest
 {
-    public static void main(String[] args) throws Exception
+    public static void main(String[] args) throws IOException
     {
         if (args.length != 3)
         {
@@ -94,24 +83,6 @@ public class ScreeningClientApiTest
             System.exit(1);
             return;
         }
-        IDatasetIdentifier dataSetID = facade.getDatasetIdentifiers(Arrays.asList("20101006020318852-10")).get(0);
-        List<byte[]> images = facade.loadImages(dataSetID, Arrays.asList("1.3"), "DAPI", new ImageSize(100, 60));
-        for (int i = 0; i < images.size(); i++)
-        {
-            byte[] imageBytes = images.get(i);
-            BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
-            JFrame frame = new JFrame("image " + i);
-            frame.getContentPane().add(new JLabel(new ImageIcon(image)));
-            frame.pack();
-            frame.setLocation(10 * i, 10 * i);
-            frame.setVisible(true);
-            
-            System.out.println(image.getWidth()+"x"+image.getHeight());
-        }
-        Thread.sleep(10000);
-        System.exit(0);
-        
-        
         List<ExperimentIdentifier> experiments = facade.listExperiments();
         print("Experiments: " + experiments);
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningOpenbisServiceFacade.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningOpenbisServiceFacade.java
index 6d45d79f1ba50dde7dfc6b1fdcffc4ec73a27b49..47ccad7a181a161f110f71ed7b9ee2d1a6000adc 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningOpenbisServiceFacade.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ScreeningOpenbisServiceFacade.java
@@ -540,14 +540,15 @@ public class ScreeningOpenbisServiceFacade implements IScreeningOpenbisServiceFa
         }
     }
     
-    public List<byte[]> loadImages(IDatasetIdentifier dataSetIdentifier, List<String> wellsOrNull,
-            String channel, ImageSize thumbnailSizeOrNull) throws IOException
+    public List<byte[]> loadImages(IDatasetIdentifier dataSetIdentifier,
+            List<WellPosition> wellPositions, String channel, ImageSize thumbnailSizeOrNull)
+            throws IOException
     {
         DssServiceRpcScreeningHolder dssServiceHolder =
                 dssServiceCache.createDssService(dataSetIdentifier.getDatastoreServerUrl());
         InputStream stream =
                 dssServiceHolder.getService().loadImages(sessionToken, dataSetIdentifier,
-                        wellsOrNull, channel, thumbnailSizeOrNull);
+                        wellPositions, channel, thumbnailSizeOrNull);
         ConcatenatedFileOutputStreamWriter imagesWriter =
                 new ConcatenatedFileOutputStreamWriter(stream);
         List<byte[]> result = new ArrayList<byte[]>();
@@ -560,7 +561,6 @@ public class ScreeningOpenbisServiceFacade implements IScreeningOpenbisServiceFa
             {
                 result.add(outputStream.toByteArray());
             }
-            System.out.println("size "+ size);
         } while (size >= 0);
         return result;
     }
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/DataSetCodeAndWellPositions.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/DataSetCodeAndWellPositions.java
new file mode 100644
index 0000000000000000000000000000000000000000..8fd7171db531c1416dc677f97cb0a41128ed9a36
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/DataSetCodeAndWellPositions.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2010 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.api.v1.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for parsing strings containing data set code and well positions. Two forms are
+ * accepted:
+ * <pre>
+ *   &lt;data set code&gt;
+ * </pre>
+ * and
+ * <pre>
+ *   &lt;data set code&gt;:&lt;row 1&gt;.&lt;col 1&gt; &lt;row 2&gt;.&lt;col 2&gt; ...
+ * </pre>
+ *
+ * @since 1.4
+ * @author Franz-Josef Elmer
+ */
+public class DataSetCodeAndWellPositions
+{
+    private final String dataSetCode;
+    private final List<WellPosition> wellPositions = new ArrayList<WellPosition>();
+    
+    /**
+     * Creates an instane from the specified description.
+     * 
+     * @throws IllegalArgumentException in case of parsing error.
+     */
+    public DataSetCodeAndWellPositions(String dataSetCodeAndWellPositionsDescription)
+    {
+        int indexOfColon = dataSetCodeAndWellPositionsDescription.indexOf(':');
+        if (indexOfColon < 0)
+        {
+            dataSetCode = dataSetCodeAndWellPositionsDescription;
+        } else
+        {
+            dataSetCode = dataSetCodeAndWellPositionsDescription.substring(0, indexOfColon);
+            wellPositions.addAll(WellPosition
+                    .parseWellPositions(dataSetCodeAndWellPositionsDescription
+                            .substring(indexOfColon + 1)));
+        }
+    }
+
+    /**
+     * Returns the data set code.
+     */
+    public final String getDataSetCode()
+    {
+        return dataSetCode;
+    }
+
+    /**
+     * Returns the well positions. An empty list is returned in case of missing well position
+     * descriptions.
+     */
+    public final List<WellPosition> getWellPositions()
+    {
+        return wellPositions;
+    }
+    
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ImageSize.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ImageSize.java
index 8afed751f09a222c35953efb28590558d82c0d98..90a2db667086a3d61ee1fd575da11ffa70d6695a 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ImageSize.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ImageSize.java
@@ -21,6 +21,7 @@ import java.io.Serializable;
 /**
  * Width and height of an image.
  *
+ * @since 1.4
  * @author Franz-Josef Elmer
  */
 public class ImageSize implements Serializable
@@ -29,6 +30,9 @@ public class ImageSize implements Serializable
     private final int width;
     private final int height;
 
+    /**
+     * Creates an instance for specified width and height.
+     */
     public ImageSize(int width, int height)
     {
         this.width = width;
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPosition.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPosition.java
index 54bd1d5f66395f403b0c06c5726dc17d7fdfac8f..d970275dcb424c05206d716fd407358e8cbfacb0 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPosition.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPosition.java
@@ -1,6 +1,9 @@
 package ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto;
 
 import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
 
 /**
  * Identifier of a well on a screening plate, contains row and column number.
@@ -11,6 +14,83 @@ public class WellPosition implements Serializable
 {
     private static final long serialVersionUID = 1L;
 
+    /**
+     * Parses a white-space separated list of well position descriptions. A well position
+     * description is of the form
+     * <code><it>&lt;row number&gt;</it>.<it>&lt;column number&gt;</it></code>.
+     * 
+     * @throws IllegalArgumentException in case of parsing error.
+     * @since 1.4
+     */
+    public static List<WellPosition> parseWellPositions(String wellPositionDescriptions)
+    {
+        StringTokenizer tokenizer = new StringTokenizer(wellPositionDescriptions);
+        List<WellPosition> positions = new ArrayList<WellPosition>();
+        while (tokenizer.hasMoreTokens())
+        {
+            positions.add(parseWellPosition(tokenizer.nextToken()));
+        }
+        return positions;
+    }
+
+    /**
+     * Parses a well position description of the form
+     * <code><it>&lt;row number&gt;</it>.<it>&lt;column number&gt;</it></code>.
+     * 
+     * @throws IllegalArgumentException in case of parsing error.
+     * @since 1.4
+     */
+    public static WellPosition parseWellPosition(String wellDescription)
+    {
+        int indexOfDot = wellDescription.indexOf('.');
+        if (indexOfDot < 1)
+        {
+            throw createException("Expecting a '.' in well description", wellDescription);
+        }
+        int row = getAndCheckRowNumber(indexOfDot, wellDescription);
+        int col = getAndCheckColumnNumber(indexOfDot, wellDescription);
+        return new WellPosition(row, col);
+    }
+
+    private static int getAndCheckColumnNumber(int indexOfDot, String well)
+    {
+        int col;
+        try
+        {
+            col = Integer.parseInt(well.substring(indexOfDot + 1));
+        } catch (NumberFormatException ex)
+        {
+            throw createException("String after '.' isn't a number", well);
+        }
+        if (col < 1)
+        {
+            throw createException("First column < 1", well);
+        }
+        return col;
+    }
+
+    private static int getAndCheckRowNumber(int indexOfDot, String well)
+    {
+        int row;
+        try
+        {
+            row = Integer.parseInt(well.substring(0, indexOfDot));
+        } catch (NumberFormatException ex)
+        {
+            throw createException("String before '.' isn't a number", well);
+        }
+        if (row < 1)
+        {
+            throw createException("First row < 1", well);
+        }
+        return row;
+    }
+
+    private static IllegalArgumentException createException(String description, String well)
+    {
+        return new IllegalArgumentException("Invalid well description: " + description + ": " + well);
+    }
+
     private final int wellRow, wellColumn;
 
     public WellPosition(int wellRow, int wellColumn)
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPositionTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPositionTest.java
index c1b3a74ea956869e603d0710cd35a7f24d370d6f..3318f08f1009905d2017c0f87b466327c35f4f67 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPositionTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/WellPositionTest.java
@@ -32,4 +32,78 @@ public class WellPositionTest extends AssertJUnit
         assertEquals(pos1, pos2);
         assertEquals(pos1.hashCode(), pos2.hashCode());
     }
+    
+    @Test
+    public void testParseWellPositions()
+    {
+        assertEquals(0, WellPosition.parseWellPositions("").size());
+        assertEquals("[[1, 2]]", WellPosition.parseWellPositions("1.2").toString());
+        assertEquals("[[1, 2], [2, 3], [3, 4], [4, 5]]",
+                WellPosition.parseWellPositions("1.2 2.3  3.4\t4.5").toString());
+    }
+    
+    @Test
+    public void testParseWellPoistionWithMissingDot()
+    {
+        try
+        {
+            WellPosition.parseWellPosition("A03");
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex)
+        {
+            assertEquals("Invalid well description: Expecting a '.' in well description: A03", ex.getMessage());
+        }
+    }
+    
+    @Test
+    public void testParseWellPoistionWithRowIsNotANumber()
+    {
+        try
+        {
+            WellPosition.parseWellPosition("a.1");
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex)
+        {
+            assertEquals("Invalid well description: String before '.' isn't a number: a.1", ex.getMessage());
+        }
+    }
+    
+    @Test
+    public void testParseWellPoistionWithRowIsLessThanOne()
+    {
+        try
+        {
+            WellPosition.parseWellPosition("0.1");
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex)
+        {
+            assertEquals("Invalid well description: First row < 1: 0.1", ex.getMessage());
+        }
+    }
+    
+    @Test
+    public void testParseWellPoistionWithColumnIsNotANumber()
+    {
+        try
+        {
+            WellPosition.parseWellPosition("1.a");
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex)
+        {
+            assertEquals("Invalid well description: String after '.' isn't a number: 1.a", ex.getMessage());
+        }
+    }
+    
+    @Test
+    public void testParseWellPoistionWithColumnIsLessThanOne()
+    {
+        try
+        {
+            WellPosition.parseWellPosition("1.0");
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex)
+        {
+            assertEquals("Invalid well description: First column < 1: 1.0", ex.getMessage());
+        }
+    }
 }