From 6db4f61ffe96ab209afd3d8e100451bf0317de09 Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Tue, 29 Apr 2014 13:23:50 +0000
Subject: [PATCH] SSDM-75 / Javascript-based Microscopy Data Viewer - display
 images from multiple data sets

SVN: 31430
---
 .../webapps/image-viewer/html/image-viewer.js | 304 ++++++++++++++----
 .../1/as/webapps/image-viewer/html/index.html |   2 +-
 .../image-viewer/html/openbis-screening.js    |  12 +-
 .../screening/server/ScreeningServer.java     |  77 +++--
 .../screening/server/ScreeningServerJson.java |  15 +-
 .../server/ScreeningServerLogger.java         |  13 +-
 .../shared/api/v1/IScreeningApiServer.java    |  10 +-
 7 files changed, 304 insertions(+), 129 deletions(-)

diff --git a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js
index d42c90f8522..dcb284bb9ab 100644
--- a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js
+++ b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js
@@ -42,6 +42,10 @@ $.extend(AbstractWidget.prototype, {
 	},
 
 	render : function() {
+		if (this.rendered) {
+			return this.panel;
+		}
+
 		var thisWidget = this;
 
 		this.load(function() {
@@ -84,6 +88,173 @@ $.extend(AbstractWidget.prototype, {
 
 // TODO do not pollute the global namespace and expose only ImageViewer
 
+//
+// IMAGE VIEWER CHOOSER VIEW
+//
+
+function ImageViewerChooserView(controller) {
+	this.init(controller);
+}
+
+$.extend(ImageViewerChooserView.prototype, AbstractView.prototype, {
+
+	init : function(controller) {
+		AbstractView.prototype.init.call(this, controller);
+		this.panel = $("<div>");
+	},
+
+	render : function() {
+		this.panel.append(this.createDataSetChooserWidget());
+		this.panel.append(this.createImageViewerContainerWidget());
+
+		this.refresh();
+
+		return this.panel;
+	},
+
+	refresh : function() {
+		var select = this.panel.find(".dataSetChooser").find("select");
+		var container = this.panel.find(".imageViewerContainer");
+
+		if (this.controller.getSelectedDataSetCode() != null) {
+			select.val(this.controller.getSelectedDataSetCode());
+			container.children().detach();
+			container.append(this.controller.getImageViewer(this.controller.getSelectedDataSetCode()).render());
+		}
+	},
+
+	createDataSetChooserWidget : function() {
+		var thisView = this;
+		var widget = $("<div>").addClass("dataSetChooser");
+
+		$("<div>").text("Data set:").appendTo(widget);
+
+		var select = $("<select>").appendTo(widget);
+
+		this.controller.getAllDataSetCodes().forEach(function(dataSetCode) {
+			$("<option>").attr("value", dataSetCode).text(dataSetCode).appendTo(select);
+		});
+
+		select.change(function() {
+			thisView.controller.setSelectedDataSetCode(select.val());
+		});
+
+		return widget;
+	},
+
+	createImageViewerContainerWidget : function() {
+		return $("<div>").addClass("imageViewerContainer");
+	}
+
+});
+
+//
+// IMAGE VIEWER CHOOSER
+//
+
+function ImageViewerChooserWidget(openbis, dataSetCodes) {
+	this.init(openbis, dataSetCodes);
+}
+
+$.extend(ImageViewerChooserWidget.prototype, AbstractWidget.prototype, {
+	init : function(openbis, dataSetCodes) {
+		AbstractWidget.prototype.init.call(this, new ImageViewerChooserView(this));
+		this.facade = new OpenbisFacade(openbis);
+		this.setAllDataSetCodes(dataSetCodes)
+	},
+
+	load : function(callback) {
+		if (this.loaded) {
+			callback();
+		} else {
+			var thisViewer = this;
+
+			var manager = new CallbackManager(function() {
+				thisViewer.loaded = true;
+				callback();
+			});
+
+			this.facade.getDataStoreBaseURLs(thisViewer.dataSetCodes, manager.registerCallback(function(response) {
+				thisViewer.dataSetCodeToDataStoreUrlMap = response.result;
+			}));
+
+			this.facade.getImageInfo(thisViewer.dataSetCodes, manager.registerCallback(function(response) {
+				thisViewer.dataSetCodeToImageInfoMap = response.result;
+			}));
+
+			this.facade.getImageResolutions(thisViewer.dataSetCodes, manager.registerCallback(function(response) {
+				thisViewer.dataSetCodeToImageResolutionsMap = response.result;
+			}));
+		}
+	},
+
+	getSelectedDataSetCode : function() {
+		if (this.selectedDataSetCode != null) {
+			return this.selectedDataSetCode;
+		} else {
+			return null;
+		}
+	},
+
+	setSelectedDataSetCode : function(dataSetCode) {
+		if (this.getSelectedDataSetCode() != dataSetCode) {
+			this.selectedDataSetCode = dataSetCode;
+			this.refresh();
+		}
+	},
+
+	getAllDataSetCodes : function() {
+		if (this.dataSetCodes) {
+			return this.dataSetCodes;
+		} else {
+			return [];
+		}
+	},
+
+	setAllDataSetCodes : function(dataSetCodes) {
+		if (!dataSetCodes) {
+			dataSetCodes = [];
+		}
+		if (this.getAllDataSetCodes().toString() != dataSetCodes.toString()) {
+			this.dataSetCodes = dataSetCodes;
+			if (dataSetCodes.length > 0) {
+				this.setSelectedDataSetCode(dataSetCodes[0]);
+			}
+			this.refresh();
+		}
+	},
+
+	getImageViewer : function(dataSetCode) {
+		if (!this.imageViewerMap) {
+			this.imageViewerMap = {};
+		}
+
+		if (!this.imageViewerMap[dataSetCode]) {
+			this.imageViewerMap[dataSetCode] = new ImageViewerWidget(this._getSessionToken(), dataSetCode, this._getDataStoreUrl(dataSetCode), this
+					._getImageInfo(dataSetCode), this._getImageResolutions(dataSetCode));
+		}
+
+		return this.imageViewerMap[dataSetCode];
+	},
+
+	_getSessionToken : function() {
+		return this.facade.getSession();
+	},
+
+	_getDataStoreUrl : function(dataSetCode) {
+		return this.dataSetCodeToDataStoreUrlMap[dataSetCode];
+	},
+
+	_getImageInfo : function(dataSetCode) {
+		return this.dataSetCodeToImageInfoMap[dataSetCode];
+	},
+
+	_getImageResolutions : function(dataSetCode) {
+		return this.dataSetCodeToImageResolutionsMap[dataSetCode];
+	}
+
+});
+
 //
 // IMAGE VIEWER VIEW
 //
@@ -173,54 +344,30 @@ $.extend(ImageViewerView.prototype, AbstractView.prototype, {
 // IMAGE VIEWER
 //
 
-function ImageViewerWidget(openbis, dataSetCode) {
-	this.init(openbis, dataSetCode);
+function ImageViewerWidget(sessionToken, dataSetCode, dataStoreUrl, imageInfo, imageResolutions) {
+	this.init(sessionToken, dataSetCode, dataStoreUrl, imageInfo, imageResolutions);
 }
 
 $.extend(ImageViewerWidget.prototype, AbstractWidget.prototype, {
-	init : function(openbis, dataSetCode) {
+	init : function(sessionToken, dataSetCode, dataStoreUrl, imageInfo, imageResolutions) {
 		AbstractWidget.prototype.init.call(this, new ImageViewerView(this));
 		this.facade = new OpenbisFacade(openbis);
+		this.sessionToken = sessionToken;
 		this.dataSetCode = dataSetCode;
-	},
-
-	load : function(callback) {
-		if (this.loaded) {
-			callback();
-		} else {
-			var thisViewer = this;
-
-			var manager = new CallbackManager(function() {
-				thisViewer.loaded = true;
-
-				var channels = thisViewer.getAllChannels();
-
-				if (channels && channels.length > 0) {
-					thisViewer.setSelectedMergedChannels(channels.map(function(channel) {
-						return channel.code
-					}));
-				}
-
-				var channelStacks = thisViewer.getAllChannelStacks();
-
-				if (channelStacks && channelStacks.length > 0) {
-					thisViewer.setSelectedChannelStackId(channelStacks[0].id);
-				}
-
-				callback();
-			});
-
-			this.facade.tryGetDataStoreBaseURL(thisViewer.dataSetCode, manager.registerCallback(function(response) {
-				thisViewer.dataStoreUrl = response.result;
-			}));
+		this.dataStoreUrl = dataStoreUrl;
+		this.imageInfo = imageInfo;
+		this.imageResolutions = imageResolutions;
 
-			this.facade.getImageInfo(thisViewer.dataSetCode, manager.registerCallback(function(response) {
-				thisViewer.imageInfo = response.result;
+		var channels = this.getAllChannels();
+		if (channels && channels.length > 0) {
+			this.setSelectedMergedChannels(channels.map(function(channel) {
+				return channel.code
 			}));
+		}
 
-			this.facade.getImageResolutions(thisViewer.dataSetCode, manager.registerCallback(function(response) {
-				thisViewer.imageResolutions = response.result;
-			}));
+		var channelStacks = this.getAllChannelStacks();
+		if (channelStacks && channelStacks.length > 0) {
+			this.setSelectedChannelStackId(channelStacks[0].id);
 		}
 	},
 
@@ -291,7 +438,7 @@ $.extend(ImageViewerWidget.prototype, AbstractWidget.prototype, {
 	getSelectedImageData : function() {
 		var imageData = new ImageData();
 		imageData.setDataStoreUrl(this.dataStoreUrl);
-		imageData.setSessionToken(this.facade.getSession());
+		imageData.setSessionToken(this.sessionToken);
 		imageData.setDataSetCode(this.dataSetCode);
 		imageData.setChannelStackId(this.getSelectedChannelStackId());
 
@@ -337,36 +484,6 @@ $.extend(ImageViewerWidget.prototype, AbstractWidget.prototype, {
 
 });
 
-//
-// FACADE
-//
-
-function OpenbisFacade(openbis) {
-	this.init(openbis);
-}
-
-$.extend(OpenbisFacade.prototype, {
-	init : function(openbis) {
-		this.openbis = openbis;
-	},
-
-	getSession : function() {
-		return this.openbis.getSession();
-	},
-
-	tryGetDataStoreBaseURL : function(dataSetCode, action) {
-		this.openbis.tryGetDataStoreBaseURL(dataSetCode, action);
-	},
-
-	getImageInfo : function(dataSetCode, callback) {
-		this.openbis.getImageInfo(dataSetCode, null, callback);
-	},
-
-	getImageResolutions : function(dataSetCode, callback) {
-		this.openbis.getImageResolutions(dataSetCode, callback);
-	}
-});
-
 //
 // CHANNEL CHOOSER VIEW
 //
@@ -517,6 +634,8 @@ $.extend(ChannelChooserWidget.prototype, AbstractWidget.prototype, {
 	isMergedChannelEnabled : function(channel) {
 		if (this.getSelectedMergedChannels().length == 1) {
 			return !this.isMergedChannelSelected(channel);
+		}else{
+			return true;
 		}
 	},
 
@@ -782,7 +901,7 @@ $.extend(ChannelStackMatrixChooserView.prototype, AbstractView.prototype, {
 	createButtonsWidget : function() {
 		var thisView = this;
 
-		buttons = new MovieButtonsWidget(this.controller.getTimePoints().length);
+		var buttons = new MovieButtonsWidget(this.controller.getTimePoints().length);
 
 		buttons.setFrameContentLoader(function(frameIndex, callback) {
 			var timePoint = thisView.controller.getTimePoints()[frameIndex];
@@ -1225,6 +1344,49 @@ $.extend(ImageWidget.prototype, AbstractWidget.prototype, {
 
 });
 
+//
+// FACADE
+//
+
+function OpenbisFacade(openbis) {
+	this.init(openbis);
+}
+
+$.extend(OpenbisFacade.prototype, {
+	init : function(openbis) {
+		this.openbis = openbis;
+	},
+
+	getSession : function() {
+		return this.openbis.getSession();
+	},
+
+	getDataStoreBaseURLs : function(dataSetCodes, action) {
+		this.openbis.getDataStoreBaseURLs(dataSetCodes, function(response) {
+			var dataSetCodeToUrlMap = {};
+
+			if (response.result) {
+				response.result.forEach(function(urlForDataSets) {
+					urlForDataSets.dataSetCodes.forEach(function(dataSetCode) {
+						dataSetCodeToUrlMap[dataSetCode] = urlForDataSets.dataStoreURL;
+					});
+				});
+				response.result = dataSetCodeToUrlMap;
+			}
+
+			action(response);
+		});
+	},
+
+	getImageInfo : function(dataSetCodes, callback) {
+		this.openbis.getImageInfo(dataSetCodes, callback);
+	},
+
+	getImageResolutions : function(dataSetCodes, callback) {
+		this.openbis.getImageResolutions(dataSetCodes, callback);
+	}
+});
+
 //
 // CHANNEL STACK MANAGER
 //
diff --git a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/index.html b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/index.html
index c92f89f6844..db8ee1a5842 100644
--- a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/index.html
+++ b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/index.html
@@ -25,7 +25,7 @@
     	var facade = new openbis();
     	
     	facade.login("admin", "password", function(response){
-			var widget = new ImageViewerWidget(facade, "20140415140347875-53");
+			var widget = new ImageViewerChooserWidget(facade, ["20140415140347875-53", "20140429125231346-56", "20140429125614418-59"]);
 			$("body").append(widget.render());				
     	});
     });
diff --git a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/openbis-screening.js b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/openbis-screening.js
index f8e4a232297..a445cba1d6a 100644
--- a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/openbis-screening.js
+++ b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/openbis-screening.js
@@ -144,27 +144,27 @@ openbis.prototype.listImageDatasets = function(plateIdentifiers, action) {
 }
 
 /**
- * @see IScreeningApiServer.getImageInfo(String, String, WellLocation)
+ * @see IScreeningApiServer.getImageInfo(String, List<String>)
  * @method
  */
-openbis.prototype.getImageInfo = function(dataSetCode, wellLocation, action) {
+openbis.prototype.getImageInfo = function(dataSetCodes, action) {
     this._internal.ajaxRequest({
             url: this._internal.screeningUrl,
             data: { "method" : "getImageInfo",
-                    "params" : [ this.getSession(), dataSetCode, wellLocation ] },
+                    "params" : [ this.getSession(), dataSetCodes ] },
             success: action
     });
 }
 
 /**
- * @see IScreeningApiServer.getImageResolutions(String, String)
+ * @see IScreeningApiServer.getImageResolutions(String, List<String>)
  * @method
  */
-openbis.prototype.getImageResolutions = function(dataSetCode, action) {
+openbis.prototype.getImageResolutions = function(dataSetCodes, action) {
     this._internal.ajaxRequest({
             url: this._internal.screeningUrl,
             data: { "method" : "getImageResolutions",
-                    "params" : [ this.getSession(), dataSetCode ] },
+                    "params" : [ this.getSession(), dataSetCodes ] },
             success: action
     });
 }
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 8ca11efceda..a0ca24d91dc 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
@@ -18,15 +18,16 @@ package ch.systemsx.cisd.openbis.plugin.screening.server;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Resource;
 
 import net.lemnik.eodsql.DataIterator;
 import net.lemnik.eodsql.QueryTool;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.time.DateUtils;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.stereotype.Component;
@@ -113,7 +114,6 @@ import ch.systemsx.cisd.openbis.plugin.screening.server.logic.ScreeningUtils;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.WellContentLoader;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.ImageResolutionTranslator;
 import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.LogicalImageInfoTranslator;
-import ch.systemsx.cisd.openbis.plugin.screening.server.logic.dto.WellLocationTranslator;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.IScreeningServer;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.ResourceNames;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.IScreeningApiServer;
@@ -551,67 +551,74 @@ public final class ScreeningServer extends AbstractServer<IScreeningServer> impl
 
     @Override
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    public ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.LogicalImageInfo getImageInfo(String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodePredicate.class)
-            String datasetCode, ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellLocation wellLocationOrNull)
+    public Map<String, ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.LogicalImageInfo> getImageInfo(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> datasetCodes)
     {
         checkSession(sessionToken);
 
-        if (StringUtils.isBlank(datasetCode))
+        if (datasetCodes == null)
         {
-            throw new IllegalArgumentException("Data set code was null or empty");
+            throw new IllegalArgumentException("Data set codes were null");
         }
 
-        DataPE dataSet = daoFactory.getDataDAO().tryToFindDataSetByCode(datasetCode);
+        Map<String, ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.LogicalImageInfo> map =
+                new HashMap<String, ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.LogicalImageInfo>();
 
-        if (dataSet == null)
+        for (String datasetCode : datasetCodes)
         {
-            return null;
-        }
+            DataPE dataSet = daoFactory.getDataDAO().tryToFindDataSetByCode(datasetCode);
 
-        WellLocation internalWellLocation = new WellLocationTranslator().translate(wellLocationOrNull);
-
-        LogicalImageInfo internalInfo = getImageDatasetInfo(sessionToken, datasetCode, dataSet.getDataStore().getCode(), internalWellLocation);
+            if (dataSet != null)
+            {
+                LogicalImageInfo internalInfo = getImageDatasetInfo(sessionToken, datasetCode, dataSet.getDataStore().getCode(), null);
+                map.put(datasetCode, new LogicalImageInfoTranslator().translate(internalInfo));
+            }
+        }
 
-        return new LogicalImageInfoTranslator().translate(internalInfo);
+        return map;
     }
 
     @Override
     @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
-    public List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution> getImageResolutions(String sessionToken,
-            @AuthorizationGuard(guardClass = DataSetCodePredicate.class)
-            String datasetCode)
+    public Map<String, List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution>> getImageResolutions(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodeCollectionPredicate.class)
+            List<String> datasetCodes)
     {
         checkSession(sessionToken);
 
-        if (StringUtils.isBlank(datasetCode))
+        if (datasetCodes == null)
         {
-            throw new IllegalArgumentException("Data set code was null or empty");
+            throw new IllegalArgumentException("Data set codes were null");
         }
 
-        DataPE dataSet = daoFactory.getDataDAO().tryToFindDataSetByCode(datasetCode);
+        Map<String, List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution>> map =
+                new HashMap<String, List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution>>();
 
-        if (dataSet == null)
+        for (String datasetCode : datasetCodes)
         {
-            return null;
-        }
+            DataPE dataSet = daoFactory.getDataDAO().tryToFindDataSetByCode(datasetCode);
 
-        List<ImageResolution> internalResolutions = getImageDatasetResolutions(sessionToken, datasetCode, dataSet.getDataStore().getCode());
+            if (dataSet != null)
+            {
+                List<ImageResolution> internalResolutions = getImageDatasetResolutions(sessionToken, datasetCode, dataSet.getDataStore().getCode());
 
-        if (internalResolutions == null)
-        {
-            return null;
-        }
+                if (internalResolutions != null)
+                {
+                    List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution> apiResolutions =
+                            new LinkedList<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution>();
 
-        List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution> apiResolutions =
-                new LinkedList<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution>();
+                    for (ImageResolution internalResolution : internalResolutions)
+                    {
+                        apiResolutions.add(new ImageResolutionTranslator().translate(internalResolution));
+                    }
 
-        for (ImageResolution internalResolution : internalResolutions)
-        {
-            apiResolutions.add(new ImageResolutionTranslator().translate(internalResolution));
+                    map.put(datasetCode, apiResolutions);
+                }
+            }
         }
 
-        return apiResolutions;
+        return map;
     }
 
     @Override
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerJson.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerJson.java
index 3978a3b46e6..a37d9ce48d4 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerJson.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServerJson.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.plugin.screening.server;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 import ch.systemsx.cisd.common.collection.IModifiable;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Sample;
@@ -51,7 +52,6 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateMetadata
 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.WellIdentifier;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellLocation;
 
 /**
  * @author pkupczyk
@@ -320,15 +320,20 @@ public class ScreeningServerJson implements IScreeningApiServer
     }
 
     @Override
-    public LogicalImageInfo getImageInfo(String sessionToken, String datasetCode, WellLocation wellLocationOrNull)
+    public Map<String, LogicalImageInfo> getImageInfo(String sessionToken, List<String> datasetCodes)
     {
-        return server.getImageInfo(sessionToken, datasetCode, wellLocationOrNull);
+        return server.getImageInfo(sessionToken, datasetCodes);
     }
 
     @Override
-    public List<ImageResolution> getImageResolutions(String sessionToken, String datasetCode)
+    public Map<String, List<ImageResolution>> getImageResolutions(String sessionToken, List<String> datasetCodes)
     {
-        return new ImageResolutionList(server.getImageResolutions(sessionToken, datasetCode));
+        Map<String, List<ImageResolution>> map = server.getImageResolutions(sessionToken, datasetCodes);
+        for (Map.Entry<String, List<ImageResolution>> entry : map.entrySet())
+        {
+            map.put(entry.getKey(), new ImageResolutionList(entry.getValue()));
+        }
+        return map;
     }
 
     /*
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 52de1ee8332..8af771896aa 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
@@ -18,6 +18,7 @@ package ch.systemsx.cisd.openbis.plugin.screening.server;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 import ch.systemsx.cisd.authentication.ISessionManager;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
@@ -180,18 +181,18 @@ final class ScreeningServerLogger extends AbstractServerLogger implements IScree
     }
 
     @Override
-    public ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.LogicalImageInfo getImageInfo(String sessionToken, String datasetCode,
-            ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellLocation wellLocationOrNull)
+    public Map<String, ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.LogicalImageInfo> getImageInfo(String sessionToken,
+            List<String> datasetCodes)
     {
-        logAccess(sessionToken, "getImageInfo", "dataset(%s) well(%s)", datasetCode, wellLocationOrNull);
+        logAccess(sessionToken, "getImageInfo", "datasets(%s)", abbreviate(datasetCodes));
         return null;
     }
 
     @Override
-    public List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution> getImageResolutions(String sessionToken,
-            String datasetCode)
+    public Map<String, List<ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageResolution>> getImageResolutions(String sessionToken,
+            List<String> datasetCodes)
     {
-        logAccess(sessionToken, "getImageResolutions", "dataset(%s)", datasetCode);
+        logAccess(sessionToken, "getImageResolutions", "datasets(%s)", abbreviate(datasetCodes));
         return null;
     }
 
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/IScreeningApiServer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/IScreeningApiServer.java
index bcd691be547..2a50d42cdde 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/IScreeningApiServer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/IScreeningApiServer.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1;
 
 import java.util.List;
+import java.util.Map;
 
 import org.springframework.transaction.annotation.Transactional;
 
@@ -54,7 +55,6 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateMetadata
 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.WellIdentifier;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellLocation;
 
 /**
  * This interface is a part of the official public screening API. It is forbidden to change it in a non-backward-compatible manner without discussing
@@ -156,18 +156,18 @@ public interface IScreeningApiServer extends IRpcService
             List<? extends PlateIdentifier> plates) throws IllegalArgumentException;
 
     /**
-     * Returns information about logical image in the given dataset. In HCS case the well location should be specified.
+     * Returns information about logical image in the given image datasets.
      */
     @Transactional(readOnly = true)
     @MinimalMinorVersion(11)
-    public LogicalImageInfo getImageInfo(String sessionToken, String datasetCode, WellLocation wellLocationOrNull);
+    public Map<String, LogicalImageInfo> getImageInfo(String sessionToken, List<String> datasetCode);
 
     /**
-     * Returns information about available image resolutions for a given image dataset.
+     * Returns information about available image resolutions for the given image datasets.
      */
     @Transactional(readOnly = true)
     @MinimalMinorVersion(11)
-    public List<ImageResolution> getImageResolutions(String sessionToken, String datasetCode);
+    public Map<String, List<ImageResolution>> getImageResolutions(String sessionToken, List<String> datasetCode);
 
     /**
      * For a given set of plates provide the list of all data sets containing raw images for each of these plates.
-- 
GitLab