diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoader.java index c0903f38b386e36983027bded10777cb01799a41..7cc3867846c14345a436ee3feff94ea492ef6bd8 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoader.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoader.java @@ -18,19 +18,28 @@ package ch.systemsx.cisd.openbis.plugin.screening.server.logic; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageChannel; +import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageSize; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageTransformationInfo; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.WidthAndHeightAndPermIdDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.WidthAndHeightDTO; /** + * Implementation of {@link IExperimentMetadataLoader} based on imaging database. + * * @author Kaloyan Enimanev + * @author Franz-Josef Elmer */ public class ExperimentMetadaLoader implements IExperimentMetadataLoader { @@ -95,6 +104,63 @@ public class ExperimentMetadaLoader implements IExperimentMetadataLoader return asImageChannels(uniqueChannels); } + public ImageSize tryGetOriginalImageSize() + { + List<WidthAndHeightAndPermIdDTO> imageSizes = getImageSizes(true); + Set<ImageSize> distinctSizes = new HashSet<ImageSize>(); + for (WidthAndHeightAndPermIdDTO size : imageSizes) + { + distinctSizes.add(asImageSize(size)); + } + return distinctSizes.size() == 1 ? distinctSizes.iterator().next() : null; + } + + public List<ImageSize> getThumbnailImageSizes() + { + List<WidthAndHeightAndPermIdDTO> imageSizes = getImageSizes(false); + Map<String, Set<ImageSize>> dataSet2SizesMap = new HashMap<String, Set<ImageSize>>(); + for (WidthAndHeightAndPermIdDTO size : imageSizes) + { + String dataSetCode = size.getPermID(); + Set<ImageSize> set = dataSet2SizesMap.get(dataSetCode); + if (set == null) + { + set = new HashSet<ImageSize>(); + dataSet2SizesMap.put(dataSetCode, set); + } + set.add(asImageSize(size)); + } + if (dataSet2SizesMap.isEmpty()) + { + return Collections.emptyList(); + } + List<Set<ImageSize>> values = new ArrayList<Set<ImageSize>>(dataSet2SizesMap.values()); + List<ImageSize> sizes = new ArrayList<ImageSize>(values.get(0)); + for (int i = 1, n = values.size(); i < n; i++) + { + sizes.retainAll(values.get(i)); + } + Collections.sort(sizes, new Comparator<ImageSize>() + { + public int compare(ImageSize s1, ImageSize s2) + { + return s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getWidth(); + } + }); + return sizes; + } + + private List<WidthAndHeightAndPermIdDTO> getImageSizes(final boolean original) + { + return getMergedResult(new IExperimentMetadataQuery<WidthAndHeightAndPermIdDTO>() + { + public List<WidthAndHeightAndPermIdDTO> select(IImagingReadonlyQueryDAO query) + { + return query.listImageSizesForExperiment(experimentId, original); + } + }); + } + private List<ImgChannelDTO> removeDuplicates(List<ImgChannelDTO> channels) { Map<String, ImgChannelDTO> channelsByCode = new TreeMap<String, ImgChannelDTO>(); @@ -135,6 +201,11 @@ public class ExperimentMetadaLoader implements IExperimentMetadataLoader } } + private ImageSize asImageSize(WidthAndHeightAndPermIdDTO size) + { + return new ImageSize(size.getWidth(), size.getHeight()); + } + private <T> T getUniqueOrNull(IExperimentMetadataQuery<T> experimentMetadataQuery) { List<T> mergedResults = getMergedResult(experimentMetadataQuery); diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/IExperimentMetadataLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/IExperimentMetadataLoader.java index 36f13c2acd6880c06bd73953d9c415d4baf7cda9..ca1db9ca6bcfc27d6ad08d833adfcf59a3cad232 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/IExperimentMetadataLoader.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/IExperimentMetadataLoader.java @@ -20,11 +20,13 @@ import java.util.List; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageChannel; +import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageSize; /** * Loader for experiment image metadata. * * @author Kaloyan Enimanev + * @author Franz-Josef Elmer */ public interface IExperimentMetadataLoader { @@ -44,4 +46,17 @@ public interface IExperimentMetadataLoader * Return all image channels within an experiment. */ List<ImageChannel> getImageChannels(); + + /** + * Returns the original image size. + * + * @return <code>null</code> if not all data sets have the same original image size. + */ + ImageSize tryGetOriginalImageSize(); + + /** + * Returns a sorted list of image sizes where for all data sets thumbnail images of these sizes + * exist. + */ + List<ImageSize> getThumbnailImageSizes(); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java index a59f07e2a189c7eed68099a7aefd30b3646afb0c..91a83db2a22cd674bce39e70e12b81295b1f92a1 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java @@ -78,6 +78,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.IDatasetIdentifier; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageChannel; 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.Material; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.MaterialIdentifier; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.MaterialTypeIdentifier; @@ -980,9 +981,11 @@ public class ScreeningApiImpl Geometry plateGeometry = loader.tryGetPlateGeometry(); Geometry tileGeometry = loader.tryGetTileGeometry(); List<ImageChannel> channels = loader.getImageChannels(); + ImageSize originalImageSize = loader.tryGetOriginalImageSize(); + List<ImageSize> thumbnailImageSizes = loader.getThumbnailImageSizes(); return new ExperimentImageMetadata(experimentIdentifer, plateGeometry, tileGeometry, - channels); + channels, originalImageSize, thumbnailImageSizes); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ExperimentImageMetadata.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ExperimentImageMetadata.java index c970c1d15406e3388b0541c2e4207bf897d60858..348e03a394d720c46c9595cff8d817fc5e83ac52 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ExperimentImageMetadata.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/api/v1/dto/ExperimentImageMetadata.java @@ -25,8 +25,8 @@ import java.util.List; * Contains summary meta data for images within an experiment. * <p> * Most of the class member fields are optional and will have data populated only if it is valid for - * all entities within the experiment. For example, the field <code>imageSize</code> will not be - * NULL only if all the images in the experiment have the same size. + * all entities within the experiment. For example, the field <code>originalImageSize</code> will + * not be NULL only if all the images in the experiment have the same size. * * @author Kaloyan Enimanev */ @@ -43,19 +43,19 @@ public class ExperimentImageMetadata implements Serializable private final Geometry tileGeometry; - // private final ImageSize imageSize; - // - // private final ImageSize thumbnailSize; + private final ImageSize originalImageSize; + + private final List<ImageSize> thumbnailImageSizes; public ExperimentImageMetadata(ExperimentIdentifier identifier, Geometry plateGeometry, - Geometry tileGeometry, List<ImageChannel> channels) + Geometry tileGeometry, List<ImageChannel> channels, ImageSize originalImageSize, List<ImageSize> thumbnailImageSizes) { this.identifier = identifier; + this.originalImageSize = originalImageSize; + this.thumbnailImageSizes = thumbnailImageSizes; this.channels = new ArrayList<ImageChannel>(channels); this.plateGeometry = plateGeometry; this.tileGeometry = tileGeometry; - // this.imageSize = imageSize; - // this.thumbnailSize = thumbnailSize; } /** @@ -92,24 +92,23 @@ public class ExperimentImageMetadata implements Serializable return tileGeometry; } - // /** - // * Returns the image size if all images in the experiment have the same size. Returns - // * <code>NULL</code> if two or more of the experiment's images have different sizes. - // */ - // public ImageSize getImageSize() - // { - // return imageSize; - // } - // - // /** - // * Returns the thumbnail size if all images in the experiment have the same thumbnail size. - // * Returns <code>NULL</code> if two or more of the experiment's images have different - // thumbnail - // * sizes. - // */ - // public ImageSize getThumbnailSize() - // { - // return thumbnailSize; - // } + /** + * Returns the image size if all images in the experiment have the same size. Returns + * <code>NULL</code> if two or more of the experiment's images have different sizes. + */ + public ImageSize getOriginalImageSize() + { + return originalImageSize; + } + /** + * Returns a sorted list of image sizes where for all experiment's images thumbnail images of + * these sizes exist. + * + * @return an empty list if no common thumbnail image size exists. + */ + public List<ImageSize> getThumbnailImageSizes() + { + return thumbnailImageSizes; + } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java index 52efb64ba2862c435339f1deefbd75430dfcd1af..7469efb70e5e5dbb9f3113de04c0b4b0488186db 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingReadonlyQueryDAO.java @@ -358,4 +358,17 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery @Select(sql = SELECT_TILE_GEOMETRIES_FOR_EXPERIMENT) public List<WidthAndHeightDTO> listTileGeometriesForExperiment(long experimentId); + + final static String SELECT_IMAGE_SIZES_FOR_EXPERIMENT = + " select dataset.perm_id, level.width, level.height " + + " from experiments exp " + + " join containers container on container.expe_id = exp.id " + + " join image_data_sets dataset on dataset.cont_id = container.id " + + " join image_zoom_levels level on level.container_dataset_id = dataset.id " + + " where exp.id = ?{1} and level.is_original = ?{2}"; + + @Select(sql = SELECT_IMAGE_SIZES_FOR_EXPERIMENT) + public List<WidthAndHeightAndPermIdDTO> listImageSizesForExperiment(long experimentId, boolean original); + + } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/WidthAndHeightAndPermIdDTO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/WidthAndHeightAndPermIdDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..425d4780924bde7da37d37f97e12b0af9e08d9e1 --- /dev/null +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/WidthAndHeightAndPermIdDTO.java @@ -0,0 +1,40 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess; + +import net.lemnik.eodsql.ResultColumn; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class WidthAndHeightAndPermIdDTO extends WidthAndHeightDTO +{ + @ResultColumn("PERM_ID") + private String permID; + + public String getPermID() + { + return permID; + } + + public void setPermID(String permID) + { + this.permID = permID; + } +} diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoaderTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoaderTest.java index b9fe04ba510338084102d629fdebc45d6b9d8e97..890791d1fd2df7fba4977451941cc1f6dd7cdb6a 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoaderTest.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ExperimentMetadaLoaderTest.java @@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.plugin.screening.server.logic; import static org.testng.AssertJUnit.assertEquals; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -33,11 +34,13 @@ import org.testng.annotations.Test; import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageChannel; +import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageSize; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.AbstractDBTest; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDatasetDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageZoomLevelDTO; /** * @author Kaloyan Enimanev @@ -137,7 +140,103 @@ public class ExperimentMetadaLoaderTest extends AbstractDBTest context.assertIsSatisfied(); } + + @Test + public void testAllSameOriginalImageSize() + { + Geometry plateGeometry = Geometry.createFromRowColDimensions(15, 10); + Geometry tileGeometry = Geometry.createFromRowColDimensions(2, 3); + long experimentId = createExperiment(); + ImgContainerDTO plate1 = container(plateGeometry, experimentId); + ImgImageDatasetDTO ds1 = dataset(tileGeometry); + ImgImageDatasetDTO ds2 = dataset(tileGeometry); + List<Long> dataSetIds = createContainerWithDataSets(plate1, ds1, ds2); + createZoomLevels(dataSetIds.get(0), zoomLevel(400, 300, true), zoomLevel(40, 30, false)); + createZoomLevels(dataSetIds.get(1), zoomLevel(400, 300, true), zoomLevel(80, 60, false)); + + ExperimentMetadaLoader loader = createLoader(experimentId); + ImageSize imageSize = loader.tryGetOriginalImageSize(); + + assertEquals("400x300", imageSize.toString()); + } + + @Test + public void testDifferentOriginalImageSize() + { + Geometry plateGeometry = Geometry.createFromRowColDimensions(15, 10); + Geometry tileGeometry = Geometry.createFromRowColDimensions(2, 3); + long experimentId = createExperiment(); + ImgContainerDTO plate1 = container(plateGeometry, experimentId); + ImgImageDatasetDTO ds1 = dataset(tileGeometry); + ImgImageDatasetDTO ds2 = dataset(tileGeometry); + List<Long> dataSetIds = createContainerWithDataSets(plate1, ds1, ds2); + createZoomLevels(dataSetIds.get(0), zoomLevel(400, 300, true), zoomLevel(40, 30, false)); + createZoomLevels(dataSetIds.get(1), zoomLevel(400, 200, true), zoomLevel(80, 60, false)); + + ExperimentMetadaLoader loader = createLoader(experimentId); + ImageSize imageSize = loader.tryGetOriginalImageSize(); + + assertEquals(null, imageSize); + } + + @Test + public void testMissingOriginalImageSize() + { + Geometry plateGeometry = Geometry.createFromRowColDimensions(15, 10); + Geometry tileGeometry = Geometry.createFromRowColDimensions(2, 3); + long experimentId = createExperiment(); + ImgContainerDTO plate1 = container(plateGeometry, experimentId); + ImgImageDatasetDTO ds1 = dataset(tileGeometry); + createContainerWithDataSets(plate1, ds1); + + ExperimentMetadaLoader loader = createLoader(experimentId); + ImageSize imageSize = loader.tryGetOriginalImageSize(); + + assertEquals(null, imageSize); + } + + @Test + public void testCommonThumbnailImageSizes() + { + Geometry plateGeometry = Geometry.createFromRowColDimensions(15, 10); + Geometry tileGeometry = Geometry.createFromRowColDimensions(2, 3); + long experimentId = createExperiment(); + ImgContainerDTO plate1 = container(plateGeometry, experimentId); + ImgImageDatasetDTO ds1 = dataset(tileGeometry); + ImgImageDatasetDTO ds2 = dataset(tileGeometry); + ImgImageDatasetDTO ds3 = dataset(tileGeometry); + List<Long> dataSetIds = createContainerWithDataSets(plate1, ds1, ds2, ds3); + createZoomLevels(dataSetIds.get(0), zoomLevel(4, 3, false), zoomLevel(40, 30, false), zoomLevel(80, 60, false), zoomLevel(400, 300, true)); + createZoomLevels(dataSetIds.get(1), zoomLevel(40, 30, false), zoomLevel(80, 60, false), zoomLevel(400, 300, true)); + createZoomLevels(dataSetIds.get(2), zoomLevel(40, 30, false), zoomLevel(80, 60, false), zoomLevel(200, 150, false), zoomLevel(400, 300, true)); + ExperimentMetadaLoader loader = createLoader(experimentId); + List<ImageSize> sizes = loader.getThumbnailImageSizes(); + + assertEquals("[40x30, 80x60]", sizes.toString()); + } + + @Test + public void testNoCommonThumbnailImageSizes() + { + Geometry plateGeometry = Geometry.createFromRowColDimensions(15, 10); + Geometry tileGeometry = Geometry.createFromRowColDimensions(2, 3); + long experimentId = createExperiment(); + ImgContainerDTO plate1 = container(plateGeometry, experimentId); + ImgImageDatasetDTO ds1 = dataset(tileGeometry); + ImgImageDatasetDTO ds2 = dataset(tileGeometry); + ImgImageDatasetDTO ds3 = dataset(tileGeometry); + List<Long> dataSetIds = createContainerWithDataSets(plate1, ds1, ds2, ds3); + createZoomLevels(dataSetIds.get(0), zoomLevel(40, 30, false), zoomLevel(400, 300, true)); + createZoomLevels(dataSetIds.get(1), zoomLevel(40, 30, false), zoomLevel(400, 300, true)); + createZoomLevels(dataSetIds.get(2), zoomLevel(80, 60, false), zoomLevel(400, 300, true)); + + ExperimentMetadaLoader loader = createLoader(experimentId); + List<ImageSize> sizes = loader.getThumbnailImageSizes(); + + assertEquals("[]", sizes.toString()); + } + private ImgChannelDTO channel(String code) { return new ImgChannelDTO(code, null, null, null, 0L, null, 0, 1, 2); @@ -147,18 +246,30 @@ public class ExperimentMetadaLoaderTest extends AbstractDBTest { return dao.addExperiment(generatePermId()); } + - private void createContainerWithDataSets(ImgContainerDTO container, + private List<Long> createContainerWithDataSets(ImgContainerDTO container, ImgImageDatasetDTO... datasets) { long containerId = dao.addContainer(container); + List<Long> dataSetIds = new ArrayList<Long>(); for (ImgImageDatasetDTO dataset : datasets) { dataset.setContainerId(containerId); - dao.addImageDataset(dataset); + dataSetIds.add(dao.addImageDataset(dataset)); } + return dataSetIds; } - + + private void createZoomLevels(long dataSetId, ImgImageZoomLevelDTO... zoomLevels) + { + for (ImgImageZoomLevelDTO zoomLevel : zoomLevels) + { + zoomLevel.setContainerDatasetId(dataSetId); + dao.addImageZoomLevel(zoomLevel); + } + } + private ImgContainerDTO container(Geometry plateGeometry, long experimentId) { return new ImgContainerDTO(generatePermId(), plateGeometry.getNumberOfRows(), @@ -171,6 +282,11 @@ public class ExperimentMetadaLoaderTest extends AbstractDBTest tileGeometry.getNumberOfColumns(), 0L, true, null, null); } + private ImgImageZoomLevelDTO zoomLevel(int width, int height, boolean original) + { + return new ImgImageZoomLevelDTO(generatePermId(), original, "", width, height, 0); + } + private ExperimentMetadaLoader createLoader(long experimentId) { return createLoader(experimentId, dao);