diff --git a/screening/dist/etc/service.properties b/screening/dist/etc/service.properties index 6901f3595d13c171207ad897f6baf6c5f358c055..0fa1001749a7d6ae2541b6a062bd0170b6c5a521 100644 --- a/screening/dist/etc/service.properties +++ b/screening/dist/etc/service.properties @@ -137,6 +137,23 @@ tabular-data-graph-servlet.properties-file = etc/tabular-data-graph.properties screening-dss-api-exporter-servlet.class = ch.systemsx.cisd.openbis.dss.generic.server.DssScreeningApiServlet screening-dss-api-exporter-servlet.path = /rmi-datastore-server-screening-api-v1/* +# --------------------------------------------------------------------------- +# image overview plugins configuration +# --------------------------------------------------------------------------- + +# Comma separated names of image overview plugins. +# Each plugin should have configuration properties prefixed with its name. +# Generic properties for each <plugin> include: +# <plugin>.class - Fully qualified plugin class name (mandatory). +# <plugin>.default - If true all data set types not handled by other plugins should be handled +# by the plugin (default = false). +# <plugin>.dataset-types - Comma separated list of data set types handled by the plugin +# (optional and ignored if default is true, otherwise mandatory). +overview-plugins = microscopy-image-overview + +microscopy-image-overview.class = ch.systemsx.cisd.openbis.dss.generic.server.MergingImagesDownloadServlet +microscopy-image-overview.dataset-types = MICROSCOPY_IMAGE + # --------------------------------------------------------------------------- maintenance-plugins=data-set-clean-up @@ -289,3 +306,51 @@ image-analysis-results.storage-processor.ignore-comments = true image-analysis-results.storage-processor.well-name-row = row image-analysis-results.storage-processor.well-name-col = col image-analysis-results.storage-processor.well-name-col-is-alphanum = true + +# --- Example configuration of a dropbox for images which are not connected to wells on the plate + +# The directory to watch for incoming data. +#microscopy-dropbox.incoming-dir = ${incoming-root-dir}/incoming-microscopy +#microscopy-dropbox.incoming-data-completeness-condition = auto-detection + +# The extractor class to use for code extraction +#microscopy-dropbox.data-set-info-extractor = ch.systemsx.cisd.etlserver.DefaultDataSetInfoExtractor +#microscopy-dropbox.data-set-info-extractor.entity-separator = . +#microscopy-dropbox.data-set-info-extractor.index-of-sample-code = 0 +#microscopy-dropbox.data-set-info-extractor.space-code = ${import-space-code} + +# The extractor class to use for type extraction +#microscopy-dropbox.type-extractor = ch.systemsx.cisd.etlserver.SimpleTypeExtractor +#microscopy-dropbox.type-extractor.file-format-type = TIFF +#microscopy-dropbox.type-extractor.locator-type = RELATIVE_LOCATION +#microscopy-dropbox.type-extractor.data-set-type = MICROSCOPY_IMAGE +#microscopy-dropbox.type-extractor.is-measured = true + +#microscopy-dropbox.storage-processor = ch.systemsx.cisd.openbis.dss.etl.MicroscopyStorageProcessor +#microscopy-dropbox.storage-processor.file-extractor = ch.systemsx.cisd.openbis.dss.etl.MicroscopyImageFileExtractor +#microscopy-dropbox.storage-processor.data-source = imaging-db +#microscopy-dropbox.storage-processor.channel-names = BLUE, GREEN, RED +#microscopy-dropbox.storage-processor.well_geometry = 2x3 +#microscopy-dropbox.storage-processor.tile_mapping = 1,2,3;4,5,6 + +# --- Microscopy dropbox with a series of images with any names --------------------------- + +# The directory to watch for incoming data. +#microscopy-series-dropbox.incoming-dir = ${incoming-root-dir}/incoming-microscopy-series +#microscopy-series-dropbox.incoming-data-completeness-condition = auto-detection + +# The extractor class to use for code extraction +#microscopy-series-dropbox.data-set-info-extractor = ch.systemsx.cisd.etlserver.DefaultDataSetInfoExtractor +#microscopy-series-dropbox.data-set-info-extractor.entity-separator = . +#microscopy-series-dropbox.data-set-info-extractor.index-of-sample-code = 0 +#microscopy-series-dropbox.data-set-info-extractor.space-code = ${import-space-code} + +# The extractor class to use for type extraction +#microscopy-series-dropbox.type-extractor = ch.systemsx.cisd.etlserver.SimpleTypeExtractor +#microscopy-series-dropbox.type-extractor.file-format-type = TIFF +#microscopy-series-dropbox.type-extractor.locator-type = RELATIVE_LOCATION +#microscopy-series-dropbox.type-extractor.data-set-type = MICROSCOPY_IMAGE +#microscopy-series-dropbox.type-extractor.is-measured = true + +#microscopy-series-dropbox.storage-processor = ch.systemsx.cisd.openbis.dss.etl.MicroscopyBlackboxSeriesStorageProcessor +#microscopy-series-dropbox.storage-processor.data-source = imaging-db diff --git a/screening/dist/server/web-client.properties b/screening/dist/server/web-client.properties index ae923f04e69c29cb0d43c19d4a9a148ce9e89f94..58d0637fdfd83c1fc2c81cea5e0cade6bb87c6f5 100644 --- a/screening/dist/server/web-client.properties +++ b/screening/dist/server/web-client.properties @@ -3,6 +3,12 @@ # #default-view-mode = SIMPLE +# (optional) List of data set types for which there should be an image overview shown in dataset tables. +# If not specified image overview will not be shown for any datasets +# even if some overview plugins have been configured. +# +data-set-types-with-image-overview = MICROSCOPY_IMAGE + # Configuration of entity (experiment, sample, data set, material) detail views. # Allows to hide chosen sections. # @@ -19,6 +25,7 @@ # data-set-parents-section # data-set-children-section # plate-layout-dataset-section +# logical-image-dataset-section (valid only for microscopy datasets) # query-section # generic_experiment_viewer # data-sets-section @@ -35,11 +42,12 @@ # data-sets-section # attachment-section # query-section +# logical-image-well-section (valid only for HCS well samples) # generic_material_viewer # plate-locations-material-section # query-section # -detail-views = plate-or-well-view, image-data-view, image-analysis-data-view +detail-views = plate-or-well-view, image-data-view, image-analysis-data-view, microscopy-dataset-view #experiment-view.view = generic_experiment_viewer #experiment-view.types = SIRNA_HCS @@ -61,5 +69,11 @@ image-analysis-data-view.hide-sections = data-set-parents-section, data-set-chil image-analysis-data-view.hide-smart-view = true image-analysis-data-view.hide-file-view = true +microscopy-dataset-view.view = generic_dataset_viewer +microscopy-dataset-view.types = MICROSCOPY_IMAGE +microscopy-dataset-view.hide-sections = data-set-children-section, data-set-parents-section +microscopy-dataset-view.hide-smart-view = false +microscopy-dataset-view.hide-file-view = true + technologies = screening -#screening.image-viewer-enabled = true +screening.image-viewer-enabled = true diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageDatasetUploader.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageDatasetUploader.java index 6350058ffff0664af20d1013f50ae5e598486f21..32d0e9b160ec38fffcf42953cefe8d3a89132fea 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageDatasetUploader.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageDatasetUploader.java @@ -17,12 +17,16 @@ package ch.systemsx.cisd.openbis.dss.etl; 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.Map.Entry; import java.util.Set; +import ch.rinn.restrictions.Private; import ch.systemsx.cisd.openbis.dss.etl.ImagingDatabaseHelper.ImagingChannelsMap; import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgAcquiredImageDTO; @@ -105,10 +109,23 @@ abstract class AbstractImageDatasetUploader stackImages.add(makeAcquiredImageInStack(image)); map.put(stackDTO, stackImages); } - setChannelStackIds(map.keySet()); + Set<ImgChannelStackDTO> channelStacks = map.keySet(); + setChannelStackIds(channelStacks); + setChannelStackRepresentatives(channelStacks); return map; } + private void setChannelStackRepresentatives(Set<ImgChannelStackDTO> channelStacks) + { + Set<ImgChannelStackDTO> representatives = + ChannelStackRepresentativesOracle.calculateRepresentatives(channelStacks); + for (ImgChannelStackDTO channelStack : channelStacks) + { + boolean isRepresentative = representatives.contains(channelStack); + channelStack.setRepresentative(isRepresentative); + } + } + private void setChannelStackIds(Set<ImgChannelStackDTO> channelStacks) { for (ImgChannelStackDTO channelStack : channelStacks) @@ -129,7 +146,8 @@ abstract class AbstractImageDatasetUploader Long spotId = spotProvider.tryGetSpotId(image); int dummyId = 0; return new ImgChannelStackDTO(dummyId, image.getTileRow(), image.getTileColumn(), - datasetId, spotId, image.tryGetTimePoint(), image.tryGetDepth()); + datasetId, spotId, image.tryGetTimePoint(), image.tryGetDepth(), + image.tryGetSeriesNumber(), false); } private void createImages(Map<ImgChannelStackDTO, List<AcquiredImageInStack>> stackImagesMap, @@ -229,4 +247,86 @@ abstract class AbstractImageDatasetUploader imageReferenceOrNull.tryGetColorComponent()); return dto; } + + @Private + static final class ChannelStackRepresentativesOracle + { + /** + * For all images of one spot chooses the 'smallest' element as an representative. If there + * are no spots than the smallest element of all specified images is chosen. + */ + public static Set<ImgChannelStackDTO> calculateRepresentatives( + Set<ImgChannelStackDTO> images) + { + Map<Long/* spot or null */, List<ImgChannelStackDTO>> mapBySpot = + createMapBySpot(images); + Comparator<? super ImgChannelStackDTO> spotChannelStacksComparator = + createChannelStacksComparator(); + for (List<ImgChannelStackDTO> spotChannelStacks : mapBySpot.values()) + { + Collections.sort(spotChannelStacks, spotChannelStacksComparator); + } + + Set<ImgChannelStackDTO> representatives = new HashSet<ImgChannelStackDTO>(); + for (List<ImgChannelStackDTO> spotChannelStacks : mapBySpot.values()) + { + representatives.add(spotChannelStacks.get(0)); + } + return representatives; + } + + private static Comparator<? super ImgChannelStackDTO> createChannelStacksComparator() + { + return new Comparator<ImgChannelStackDTO>() + { + public int compare(ImgChannelStackDTO o1, ImgChannelStackDTO o2) + { + int cmp = compareNullable(o1.getRow(), o2.getRow()); + if (cmp != 0) + return cmp; + cmp = compareNullable(o1.getColumn(), o2.getColumn()); + if (cmp != 0) + return cmp; + cmp = compareNullable(o1.getT(), o2.getT()); + if (cmp != 0) + return cmp; + cmp = compareNullable(o1.getZ(), o2.getZ()); + if (cmp != 0) + return cmp; + cmp = compareNullable(o1.getSeriesNumber(), o2.getSeriesNumber()); + return cmp; + } + + private <T extends Comparable<T>> int compareNullable(T v1OrNull, T v2OrNull) + { + if (v1OrNull == null) + { + return v2OrNull == null ? 0 : -1; + } else + { + return v2OrNull == null ? 1 : v1OrNull.compareTo(v2OrNull); + } + } + }; + } + + private static Map<Long/* spot or null */, List<ImgChannelStackDTO>> createMapBySpot( + Set<ImgChannelStackDTO> channelStacks) + { + Map<Long, List<ImgChannelStackDTO>> mapBySpot = + new HashMap<Long, List<ImgChannelStackDTO>>(); + for (ImgChannelStackDTO channelStack : channelStacks) + { + Long spotId = channelStack.getSpotId(); + List<ImgChannelStackDTO> spotChannelStacks = mapBySpot.get(spotId); + if (spotChannelStacks == null) + { + spotChannelStacks = new ArrayList<ImgChannelStackDTO>(); + } + spotChannelStacks.add(channelStack); + mapBySpot.put(spotId, spotChannelStacks); + } + return mapBySpot; + } + } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageFileExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageFileExtractor.java index 90ff5dcebaafc6d2ab5242a45c784604583eb039..572255e05ab8f1bd9f0c0d72fdc96c35d00cf795 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageFileExtractor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageFileExtractor.java @@ -96,12 +96,21 @@ abstract public class AbstractImageFileExtractor implements IImageFileExtractor protected final Geometry wellGeometry; - protected AbstractImageFileExtractor(final Properties properties) + protected AbstractImageFileExtractor(Properties properties) { - this.channelDescriptions = tryExtractChannelDescriptions(properties); + this(extractChannelDescriptions(properties), getWellGeometry(properties), properties); + } + + protected AbstractImageFileExtractor(List<ChannelDescription> channelDescriptions, + Geometry wellGeometry, Properties properties) + { + assert wellGeometry != null : "wel geometry is null"; + assert channelDescriptions != null : "channelDescriptions is null"; + + this.channelDescriptions = channelDescriptions; + this.wellGeometry = wellGeometry; this.channelColorComponentsOrNull = tryGetChannelComponents(properties); checkChannelsAndColorComponents(); - this.wellGeometry = getWellGeometry(properties); this.tileMapperOrNull = TileMapper.tryCreate(properties.getProperty(TILE_MAPPING_PROPERTY), wellGeometry); } @@ -252,7 +261,7 @@ abstract public class AbstractImageFileExtractor implements IImageFileExtractor return channels; } - protected final static List<ChannelDescription> tryExtractChannelDescriptions( + protected final static List<ChannelDescription> extractChannelDescriptions( final Properties properties) { return PlateStorageProcessor.extractChannelDescriptions(properties); @@ -289,7 +298,7 @@ abstract public class AbstractImageFileExtractor implements IImageFileExtractor colorComponentOrNull); return new AcquiredSingleImage(imageInfo.tryGetWellLocation(), imageInfo.getTileLocation(), imageInfo.getChannelCode(), imageInfo.tryGetTimepoint(), imageInfo.tryGetDepth(), - relativeImageRef); + imageInfo.tryGetSeriesNumber(), relativeImageRef); } protected static Integer tryAsInt(String valueOrNull) diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java index e2dc3e06eb01982c271aac3a7ef9eaa229435930..506221fdea355304766a11ceee86ce2227a07dbd 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbstractImageStorageProcessor.java @@ -184,28 +184,42 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor // --- public AbstractImageStorageProcessor(final Properties properties) + { + this(getMandatorySpotGeometry(properties), extractChannelDescriptions(properties), + tryCreateImageExtractor(properties), properties); + } + + protected AbstractImageStorageProcessor(Geometry spotGeometry, + List<ChannelDescription> channelDescriptions, IImageFileExtractor imageFileExtractor, + Properties properties) { super(properties); - String spotGeometryText = getMandatoryProperty(SPOT_GEOMETRY_PROPERTY); - this.spotGeometry = Geometry.createFromString(spotGeometryText); - channelDescriptions = extractChannelDescriptions(properties); - thumbnailMaxWidth = + this.spotGeometry = spotGeometry; + this.channelDescriptions = channelDescriptions; + this.imageFileExtractor = imageFileExtractor; + this.thumbnailMaxWidth = PropertyUtils.getInt(properties, THUMBNAIL_MAX_WIDTH_PROPERTY, DEFAULT_THUMBNAIL_MAX_WIDTH); - thumbnailMaxHeight = + this.thumbnailMaxHeight = PropertyUtils.getInt(properties, THUMBNAIL_MAX_HEIGHT_PROPERTY, DEFAULT_THUMBNAIL_MAX_HEIGHT); - generateThumbnails = + this.generateThumbnails = PropertyUtils.getBoolean(properties, GENERATE_THUMBNAILS_PROPERTY, false); - areThumbnailsCompressed = + this.areThumbnailsCompressed = PropertyUtils.getBoolean(properties, COMPRESS_THUMBNAILS_PROPERTY, false); - originalDataStorageFormat = getOriginalDataStorageFormat(properties); + this.originalDataStorageFormat = getOriginalDataStorageFormat(properties); - this.imageFileExtractor = tryCreateImageExtractor(properties); this.dataSource = ServiceProvider.getDataSourceProvider().getDataSource(properties); this.currentTransaction = null; } + private static Geometry getMandatorySpotGeometry(Properties properties) + { + String spotGeometryText = + PropertyUtils.getMandatoryProperty(properties, SPOT_GEOMETRY_PROPERTY); + return Geometry.createFromString(spotGeometryText); + } + private static IImageFileExtractor tryCreateImageExtractor(final Properties properties) { String fileExtractorClass = PropertyUtils.getProperty(properties, FILE_EXTRACTOR_PROPERTY); @@ -720,7 +734,8 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor { for (AcquiredSingleImage image : images) { - if (image.tryGetTimePoint() != null || image.tryGetDepth() != null) + if (image.tryGetTimePoint() != null || image.tryGetDepth() != null + || image.tryGetSeriesNumber() != null) { return true; } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AcquiredSingleImage.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AcquiredSingleImage.java index 2bc1f80f4298c632a98515a4ceac32b28f51def5..745daa8167c6a7257d2d8db5fa7f9385309031a4 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AcquiredSingleImage.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AcquiredSingleImage.java @@ -37,13 +37,16 @@ public class AcquiredSingleImage extends AbstractHashable // can be null private final Float timePointOrNull, depthOrNull; + // can be null + private Integer seriesNumberOrNull; + private final RelativeImageReference imageFilePath; // relative to the original dataset // directory private RelativeImageReference thumbnailFilePathOrNull; public AcquiredSingleImage(Location wellLocationOrNull, Location tileLocation, - String channelCode, Float timePointOrNull, Float depthOrNull, + String channelCode, Float timePointOrNull, Float depthOrNull, Integer seriesNumberOrNull, RelativeImageReference imageFilePath) { this.wellLocationOrNull = wellLocationOrNull; @@ -51,9 +54,15 @@ public class AcquiredSingleImage extends AbstractHashable this.channelCode = channelCode.toUpperCase(); this.timePointOrNull = timePointOrNull; this.depthOrNull = depthOrNull; + this.seriesNumberOrNull = seriesNumberOrNull; this.imageFilePath = imageFilePath; } + public Location tryGetWellLocation() + { + return wellLocationOrNull; + } + /** Valid only in HCS case, do not call this method for microscopy images. */ public int getWellRow() { @@ -103,9 +112,20 @@ public class AcquiredSingleImage extends AbstractHashable return thumbnailFilePathOrNull; } + public Integer tryGetSeriesNumber() + { + return seriesNumberOrNull; + } + + // ---- setters + public final void setThumbnailFilePathOrNull(RelativeImageReference thumbnailFilePathOrNull) { this.thumbnailFilePathOrNull = thumbnailFilePathOrNull; } + public void setSeriesNumber(int seriesNumber) + { + this.seriesNumberOrNull = seriesNumber; + } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageCheckList.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageCheckList.java index 43a590c245855954cba063d4e5ca8e9f1a58ae3c..05ebadacd74581458d29732fb4e6f2258c51ee70 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageCheckList.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageCheckList.java @@ -23,11 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.log4j.Logger; - import ch.systemsx.cisd.bds.hcs.Geometry; -import ch.systemsx.cisd.common.logging.LogCategory; -import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.utilities.AbstractHashable; import ch.systemsx.cisd.openbis.plugin.screening.shared.dto.PlateDimension; @@ -42,9 +38,6 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.dto.PlateDimension; */ public final class HCSImageCheckList { - private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, - HCSImageCheckList.class); - private final Map<FullLocation, Check> imageMap; public HCSImageCheckList(final List<String> channelCodes, final PlateDimension plateGeometry, @@ -95,16 +88,12 @@ public final class HCSImageCheckList } Float timepointOrNull = image.tryGetTimePoint(); Float depthOrNull = image.tryGetDepth(); - if (check.isCheckedOff(timepointOrNull, depthOrNull)) + Integer seriesNumberOrNull = image.tryGetSeriesNumber(); + if (check.isCheckedOff(timepointOrNull, depthOrNull, seriesNumberOrNull)) { throw new IllegalArgumentException("Image already handled: " + image); } - if (operationLog.isDebugEnabled()) - { - operationLog.debug("Checking location " + location - + (timepointOrNull == null ? "" : " timepoint " + timepointOrNull)); - } - check.checkOff(timepointOrNull, depthOrNull); + check.checkOff(timepointOrNull, depthOrNull, seriesNumberOrNull); } private static FullLocation createLocation(AcquiredSingleImage image) @@ -118,7 +107,7 @@ public final class HCSImageCheckList final List<FullLocation> fullLocations = new ArrayList<FullLocation>(); for (final Map.Entry<FullLocation, Check> entry : imageMap.entrySet()) { - if (entry.getValue().isCheckedOff(null, null) == false) + if (entry.getValue().isCheckedOff(null, null, null) == false) { fullLocations.add(entry.getKey()); } @@ -136,10 +125,13 @@ public final class HCSImageCheckList private final Float depthOrNull; - public CheckDimension(Float timeOrNull, Float depthOrNull) + private final Integer seriesNumberOrNull; + + public CheckDimension(Float timeOrNull, Float depthOrNull, Integer seriesNumberOrNull) { this.timeOrNull = timeOrNull; this.depthOrNull = depthOrNull; + this.seriesNumberOrNull = seriesNumberOrNull; } @Override @@ -149,6 +141,9 @@ public final class HCSImageCheckList int result = 1; result = prime * result + ((depthOrNull == null) ? 0 : depthOrNull.hashCode()); result = prime * result + ((timeOrNull == null) ? 0 : timeOrNull.hashCode()); + result = + prime * result + + ((seriesNumberOrNull == null) ? 0 : seriesNumberOrNull.hashCode()); return result; } @@ -174,6 +169,12 @@ public final class HCSImageCheckList return false; } else if (!timeOrNull.equals(other.timeOrNull)) return false; + if (seriesNumberOrNull == null) + { + if (other.seriesNumberOrNull != null) + return false; + } else if (!seriesNumberOrNull.equals(other.seriesNumberOrNull)) + return false; return true; } } @@ -184,18 +185,19 @@ public final class HCSImageCheckList private final Set<CheckDimension> dimensions = new HashSet<CheckDimension>(); - final void checkOff(Float timepointOrNull, Float depthOrNull) + final void checkOff(Float timepointOrNull, Float depthOrNull, Integer seriesNumberOrNull) { - dimensions.add(new CheckDimension(timepointOrNull, depthOrNull)); + dimensions.add(new CheckDimension(timepointOrNull, depthOrNull, seriesNumberOrNull)); checkedOff = true; } - final boolean isCheckedOff(Float timepointOrNull, Float depthOrNull) + final boolean isCheckedOff(Float timepointOrNull, Float depthOrNull, + Integer seriesNumberOrNull) { CheckDimension dim = null; - if (timepointOrNull != null || depthOrNull != null) + if (timepointOrNull != null || depthOrNull != null || seriesNumberOrNull != null) { - dim = new CheckDimension(timepointOrNull, depthOrNull); + dim = new CheckDimension(timepointOrNull, depthOrNull, seriesNumberOrNull); } return checkedOff && (dim == null || dimensions.contains(dim)); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageFileExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageFileExtractor.java index b02502a15e9034811c2812843a17f7052c82b92c..adcaf45b70636e3f0f67a5751f21e613728caaa2 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageFileExtractor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageFileExtractor.java @@ -172,9 +172,10 @@ public class HCSImageFileExtractor extends AbstractImageFileExtractor Float timepointOrNull = tryAsFloat(unparsedInfo.getTimepointToken()); Float depthOrNull = tryAsFloat(unparsedInfo.getDepthToken()); + Integer seriesNumberOrNull = tryAsInt(unparsedInfo.getSeriesNumberToken()); String imageRelativePath = getRelativeImagePath(incomingDataSetDirectory, imageFile); return new ImageFileInfo(wellLocation, channelCode, tileLocation, imageRelativePath, - timepointOrNull, depthOrNull); + timepointOrNull, depthOrNull, seriesNumberOrNull); } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/IImagingDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/IImagingDatasetLoader.java index 0fcc5948895a954df41e0fdac9f07094da8f9cc4..79a3ed836795619744c63b18e13c7178b473e94b 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/IImagingDatasetLoader.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/IImagingDatasetLoader.java @@ -16,6 +16,7 @@ package ch.systemsx.cisd.openbis.dss.etl; +import ch.systemsx.cisd.bds.hcs.Location; import ch.systemsx.cisd.openbis.dss.generic.server.images.ImageChannelStackReference; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.IImageDatasetLoader; @@ -34,4 +35,16 @@ public interface IImagingDatasetLoader extends IImageDatasetLoader */ AbsoluteImageReference tryGetImage(String chosenChannelCode, ImageChannelStackReference channelStackReference, Size thumbnailSizeOrNull); + + /** + * Finds representative image of this dataset in a given channel. + * + * @param channelCode channel code for which representative image is requested + * @param wellLocationOrNull if not null the returned images are restricted to one well. + * Otherwise the dataset is assumed to have no container and spots. + * @param thumbnailSizeOrNull if not null the thumbnail in th especified size will be returned. + */ + AbsoluteImageReference tryGetRepresentativeImage(String channelCode, + Location wellLocationOrNull, Size thumbnailSizeOrNull); + } \ No newline at end of file diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyBlackboxSeriesStorageProcessor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyBlackboxSeriesStorageProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..c0ea63d0ab1963fdb3179767e0ebb0143f89060c --- /dev/null +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyBlackboxSeriesStorageProcessor.java @@ -0,0 +1,137 @@ +/* + * 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.etl; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Properties; + +import ch.systemsx.cisd.bds.hcs.Geometry; +import ch.systemsx.cisd.bds.hcs.Location; +import ch.systemsx.cisd.common.mail.IMailClient; +import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; +import ch.systemsx.cisd.openbis.dss.etl.dto.ImageFileInfo; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ChannelDescription; + +/** + * Storage processor which stores microscopy images in a special-purpose imaging database. Image + * files do not have to adhere to any naming convention, it's assumed that there is exactly one + * tile, one channel, no time or depth dimensions. Images are sorted lexicographically and + * subsequent series numbers are assigned to them. + * <p> + * In this storage processor one can set neither well geometry, channels nor image file extractor.<br> + * See {@link AbstractImageStorageProcessor} documentation to check how to configure thumbnails + * generation. + * + * @author Tomasz Pylak + */ +public class MicroscopyBlackboxSeriesStorageProcessor extends AbstractImageStorageProcessor +{ + private static final String DEFAULT_CHANNEL_CODE = "DEFAULT"; + + private static final String DEFAULT_CHANNEL_LABEL = "Default"; + + private static final Location DEFAULT_TILE = new Location(1, 1); + + private static final Geometry DEFAULT_WELL_GEOMETRY = new Geometry(1, 1); + + private static final List<ChannelDescription> DEFAULT_CHANNELS = Arrays + .asList(new ChannelDescription(DEFAULT_CHANNEL_CODE, DEFAULT_CHANNEL_LABEL)); + + public MicroscopyBlackboxSeriesStorageProcessor(Properties properties) + { + super(DEFAULT_WELL_GEOMETRY, DEFAULT_CHANNELS, new BlackboxSeriesImageFileExtractor( + properties), properties); + } + + private static class BlackboxSeriesImageFileExtractor extends AbstractImageFileExtractor + { + protected BlackboxSeriesImageFileExtractor(Properties properties) + { + super(DEFAULT_CHANNELS, DEFAULT_WELL_GEOMETRY, properties); + } + + @Override + protected ImageFileInfo tryExtractImageInfo(File imageFile, File incomingDataSetDirectory, + SampleIdentifier datasetSample) + { + String imageRelativePath = getRelativeImagePath(incomingDataSetDirectory, imageFile); + // we postpone assigning series numbers until all images are extracted + return new ImageFileInfo(null, DEFAULT_CHANNEL_CODE, DEFAULT_TILE, imageRelativePath, + null, null, null); + } + } + + @Override + protected void storeInDatabase(IImagingQueryDAO dao, DataSetInformation dataSetInformation, + ImageFileExtractionResult extractedImages) + { + List<AcquiredSingleImage> images = extractedImages.getImages(); + setSeriesNumber(images); + MicroscopyImageDatasetInfo dataset = + createMicroscopyImageDatasetInfo(dataSetInformation, images); + + MicroscopyImageDatasetUploader.upload(dao, dataset, images, extractedImages.getChannels()); + } + + private void setSeriesNumber(List<AcquiredSingleImage> images) + { + Collections.sort(images, createPathComparator()); + int seriesNumber = 1; + for (AcquiredSingleImage image : images) + { + image.setSeriesNumber(seriesNumber++); + } + } + + private Comparator<AcquiredSingleImage> createPathComparator() + { + return new Comparator<AcquiredSingleImage>() + { + public int compare(AcquiredSingleImage o1, AcquiredSingleImage o2) + { + return getPath(o1).compareTo(getPath(o2)); + } + + private String getPath(AcquiredSingleImage o1) + { + return o1.getImageReference().getRelativeImagePath(); + } + }; + } + + private MicroscopyImageDatasetInfo createMicroscopyImageDatasetInfo( + DataSetInformation dataSetInformation, List<AcquiredSingleImage> images) + { + boolean hasImageSeries = hasImageSeries(images); + return new MicroscopyImageDatasetInfo(dataSetInformation.getDataSetCode(), + spotGeometry.getRows(), spotGeometry.getColumns(), hasImageSeries); + } + + @Override + protected void validateImages(DataSetInformation dataSetInformation, IMailClient mailClient, + File incomingDataSetDirectory, ImageFileExtractionResult extractionResult) + { + // do nothing - for now we do not have good examples of real data + } + +} diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyImageFileExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyImageFileExtractor.java index c908f2c3fdef402d9021c3191a898a590ac5420c..d1bf1fbe627d09410a820772924bec9f990a2be6 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyImageFileExtractor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyImageFileExtractor.java @@ -74,10 +74,11 @@ public class MicroscopyImageFileExtractor extends AbstractImageFileExtractor Float timepointOrNull = tryAsFloat(unparsedInfo.getTimepointToken()); Float depthOrNull = tryAsFloat(unparsedInfo.getDepthToken()); + Integer seriesNumberOrNull = tryAsInt(unparsedInfo.getSeriesNumberToken()); String imageRelativePath = getRelativeImagePath(incomingDataSetDirectory, imageFile); return new ImageFileInfo(null, channelCode, tileLocation, imageRelativePath, - timepointOrNull, depthOrNull); + timepointOrNull, depthOrNull, seriesNumberOrNull); } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/PlateStorageProcessor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/PlateStorageProcessor.java index 85caba84f479cb6aa811068e125b80e3190bd3aa..d02e7bad0743067a656803dbc5e6b215f73be5e4 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/PlateStorageProcessor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/PlateStorageProcessor.java @@ -97,7 +97,7 @@ public final class PlateStorageProcessor extends AbstractImageStorageProcessor String channelCode = getChannelCodeOrLabel(channelCodes, channel); AcquiredSingleImage imageDesc = new AcquiredSingleImage(wellLocation, tileLocation, channelCode, null, null, - new RelativeImageReference(imageRelativePath, null, null)); + null, new RelativeImageReference(imageRelativePath, null, null)); images.add(imageDesc); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/UnparsedImageFileInfoLexer.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/UnparsedImageFileInfoLexer.java index f32bcbc8b68a80322c1b9fc00f867b1fafe0ee62..f7b3521f22c7c305310ada602ebea8d9de4c6c09 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/UnparsedImageFileInfoLexer.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/UnparsedImageFileInfoLexer.java @@ -59,6 +59,8 @@ public class UnparsedImageFileInfoLexer private static final char TIME_MARKER = 't'; + private static final char SERIES_NUMBER_MARKER = 'n'; + public static UnparsedImageFileInfo tryExtractHCSImageFileInfo(File imageFile, File incomingDataSetPath) { @@ -107,6 +109,7 @@ public class UnparsedImageFileInfoLexer final String channelToken = tokensMap.get(CHANNEL_MARKER); final String timepointToken = tokensMap.get(TIME_MARKER); final String depthToken = tokensMap.get(DEPTH_MARKER); + final String seriesNumberToken = tokensMap.get(SERIES_NUMBER_MARKER); UnparsedImageFileInfo info = new UnparsedImageFileInfo(); info.setWellLocationToken(wellLocationToken); @@ -114,6 +117,7 @@ public class UnparsedImageFileInfoLexer info.setChannelToken(channelToken); info.setTimepointToken(timepointToken); info.setDepthToken(depthToken); + info.setSeriesNumberToken(seriesNumberToken); return info; } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java index 2932b505145f24ace96cbb3c4b98e805f7516a04..417eef63693ab7f65a2bf74e5e81a8d8357eed5e 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/IImagingQueryDAO.java @@ -52,8 +52,8 @@ public interface IImagingQueryDAO extends TransactionQuery, IImagingReadonlyQuer // batch updates - @Update(sql = "insert into CHANNEL_STACKS (ID, X, Y, Z_in_M, T_in_SEC, DS_ID, SPOT_ID) values " - + "(?{1.id}, ?{1.column}, ?{1.row}, ?{1.z}, ?{1.t}, ?{1.datasetId}, ?{1.spotId})", batchUpdate = true) + @Update(sql = "insert into CHANNEL_STACKS (ID, X, Y, Z_in_M, T_in_SEC, SERIES_NUMBER, IS_REPRESENTATIVE, DS_ID, SPOT_ID) values " + + "(?{1.id}, ?{1.column}, ?{1.row}, ?{1.z}, ?{1.t}, ?{1.seriesNumber}, ?{1.isRepresentative}, ?{1.datasetId}, ?{1.spotId})", batchUpdate = true) public void addChannelStacks(List<ImgChannelStackDTO> channelStacks); @Update(sql = "insert into IMAGES (ID, PATH, PAGE, COLOR) values " diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java index 7dca0ee839fc05a3126beabc4e111fb18d5f8bd7..29740923cde967e380561200d7ab2be63caa4241 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingDatasetLoader.java @@ -80,16 +80,23 @@ public class ImagingDatasetLoader extends HCSDatasetLoader implements IImagingDa { return null; } + AbsoluteImageReference imgRef = + createAbsoluteImageReference(imageDTO, channel, thumbnailSizeOrNull); + + return imgRef; + } + + private AbsoluteImageReference createAbsoluteImageReference(ImgImageDTO imageDTO, + ImgChannelDTO channel, Size thumbnailSizeOrNull) + { String path = imageDTO.getFilePath(); IContent content = contentRepository.getContent(path); ColorComponent colorComponent = imageDTO.getColorComponent(); AbsoluteImageReference imgRef = new AbsoluteImageReference(content, path, imageDTO.getPage(), colorComponent, thumbnailSizeOrNull); - - imgRef.setTransformerFactory(channel.getImageTransformerFactory()); imgRef.setTransformerFactoryForMergedChannels(tryGetImageTransformerFactoryForMergedChannels()); - + imgRef.setTransformerFactory(channel.getImageTransformerFactory()); return imgRef; } @@ -205,4 +212,54 @@ public class ImagingDatasetLoader extends HCSDatasetLoader implements IImagingDa return query.tryGetThumbnail(channelId, channelStackId, datasetId); } } + + private ImgImageDTO tryGetRepresentativeImageDTO(long channelId, Location wellLocationOrNull, + boolean thumbnailWanted) + { + long datasetId = dataset.getId(); + ImgImageDTO image = null; + if (wellLocationOrNull == null) + { + if (thumbnailWanted) + { + image = query.tryGetMicroscopyRepresentativeThumbnail(datasetId, channelId); + } + if (image == null) + { + image = query.tryGetMicroscopyRepresentativeImage(datasetId, channelId); + } + } else + { + if (thumbnailWanted) + { + image = + query.tryGetHCSRepresentativeThumbnail(datasetId, wellLocationOrNull, + channelId); + } + if (image == null) + { + image = + query.tryGetHCSRepresentativeImage(datasetId, wellLocationOrNull, channelId); + } + } + return image; + } + + public AbsoluteImageReference tryGetRepresentativeImage(String channelCode, + Location wellLocationOrNull, Size thumbnailSizeOrNull) + { + ImgChannelDTO channel = tryLoadChannel(channelCode); + if (channel == null) + { + return null; + } + ImgImageDTO imageDTO = + tryGetRepresentativeImageDTO(channel.getId(), wellLocationOrNull, + thumbnailSizeOrNull != null); + if (imageDTO == null) + { + return null; + } + return createAbsoluteImageReference(imageDTO, channel, thumbnailSizeOrNull); + } } \ No newline at end of file diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageFileInfo.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageFileInfo.java index 12ff5b1cac3cf08d9527d3943417a023a2860449..d4dfb34702d9d7ccabdbf6318ccb32a5e7799bf5 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageFileInfo.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageFileInfo.java @@ -21,8 +21,10 @@ public final class ImageFileInfo private final Float depthOrNull; + private final Integer seriesNumber; + public ImageFileInfo(Location wellLocationOrNull, String channelCode, Location tileLocation, - String imageRelativePath, Float timepointOrNull, Float depthOrNull) + String imageRelativePath, Float timepointOrNull, Float depthOrNull, Integer seriesNumber) { assert channelCode != null; assert tileLocation != null; @@ -34,6 +36,7 @@ public final class ImageFileInfo this.imageRelativePath = imageRelativePath; this.timepointOrNull = timepointOrNull; this.depthOrNull = depthOrNull; + this.seriesNumber = seriesNumber; } public Location tryGetWellLocation() @@ -66,6 +69,11 @@ public final class ImageFileInfo return depthOrNull; } + public Integer tryGetSeriesNumber() + { + return seriesNumber; + } + public void setChannelCode(String channelCode) { this.channelCode = channelCode; @@ -76,7 +84,8 @@ public final class ImageFileInfo { return "ImageFileInfo [well=" + wellLocationOrNull + ", tile=" + tileLocation + ", channel=" + channelCode + ", path=" + imageRelativePath + ", timepoint=" - + timepointOrNull + ", depth=" + depthOrNull + "]"; + + timepointOrNull + ", depth=" + depthOrNull + ", seriesNumber=" + seriesNumber + + "]"; } } \ No newline at end of file diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/UnparsedImageFileInfo.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/UnparsedImageFileInfo.java index df02548da37b63d90c25dd03f385d7b9bd437bff..bb02be652e00e63c79d3fd606f79945b5a67ccfc 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/UnparsedImageFileInfo.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/UnparsedImageFileInfo.java @@ -23,6 +23,9 @@ public class UnparsedImageFileInfo extends AbstractHashable // can be null private String depthToken; + // can be null + private String seriesNumberToken; + /** can be null */ public String getWellLocationToken() { @@ -75,4 +78,14 @@ public class UnparsedImageFileInfo extends AbstractHashable { this.depthToken = depthToken; } + + public String getSeriesNumberToken() + { + return seriesNumberToken; + } + + public void setSeriesNumberToken(String seriesNumberToken) + { + this.seriesNumberToken = seriesNumberToken; + } } \ No newline at end of file diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java index 3802e195bcbce39dc9eaadb64636fdf7936684d3..53f6afcace0eb4bb69dd4b13ba2d8110919541ba 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dynamix/HCSImageFileExtractor.java @@ -84,7 +84,7 @@ public class HCSImageFileExtractor extends AbstractImageFileExtractor String imageRelativePath = getRelativeImagePath(incomingDataSetDirectory, imageFile); return new ImageFileInfo(asLocation(wellLocation), channelCode, tileLocation, - imageRelativePath, timepoint, null); + imageRelativePath, timepoint, null, null); } private long getSecondsFromFirstMeasurement(File imageFile, String[] tokens) diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MergingImagesDownloadServlet.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MergingImagesDownloadServlet.java index 96e91344f5a5718b6478b6e6807357e833c9263f..a97bc7d6a3844dfe4cf73af71c1fc5743005dbf6 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MergingImagesDownloadServlet.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MergingImagesDownloadServlet.java @@ -18,26 +18,36 @@ package ch.systemsx.cisd.openbis.dss.generic.server; import java.io.File; import java.io.IOException; +import java.util.Properties; import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; -import ch.systemsx.cisd.common.io.IContent; -import ch.systemsx.cisd.openbis.dss.etl.HCSImageDatasetLoaderFactory; -import ch.systemsx.cisd.openbis.dss.etl.IImagingDatasetLoader; import ch.systemsx.cisd.openbis.dss.generic.server.images.ImageChannelsUtils; import ch.systemsx.cisd.openbis.dss.generic.server.images.TileImageReference; +import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ImageResolutionKind; /** - * Allows to download screening images in a chosen size for a specified channels or with all + * Allows to:<br> + * - download screening and microscopy images in a chosen size for a specified channels or with all * channels merged.<br> - * Assumes that originally there is one image for each channel and no image with all the channels - * merged exist. + * - fetch representative microscopy dataset image * * @author Tomasz Pylak */ -public class MergingImagesDownloadServlet extends AbstractImagesDownloadServlet +public class MergingImagesDownloadServlet extends AbstractImagesDownloadServlet implements + IDatasetImageOverviewPlugin { private static final long serialVersionUID = 1L; + /** Used to construct {@link IDatasetImageOverviewPlugin}. */ + public MergingImagesDownloadServlet(Properties pluginProperties) + { + } + + public MergingImagesDownloadServlet() + { + } + /** * @throws EnvironmentFailureException if image does not exist **/ @@ -45,10 +55,28 @@ public class MergingImagesDownloadServlet extends AbstractImagesDownloadServlet protected final ResponseContentStream createImageResponse(TileImageReference params, File datasetRoot, String datasetCode) throws IOException, EnvironmentFailureException { - IImagingDatasetLoader imageAccessor = - HCSImageDatasetLoaderFactory.create(datasetRoot, datasetCode); - IContent image = ImageChannelsUtils.getImage(imageAccessor, params); - return ResponseContentStream.create(image.getInputStream(), image.getSize(), - ImageChannelsUtils.IMAGES_CONTENT_TYPE, image.tryGetName()); + return ImageChannelsUtils.getImageStream(datasetRoot, datasetCode, params); + } + + private static final Size DEFAULT_THUMBNAIL_SIZE = new Size(200, 120); + + /** Provides overview of microscopy datasets. */ + public ResponseContentStream createImageOverview(String datasetCode, String datasetTypeCode, + File datasetRoot, ImageResolutionKind resolution) + { + Size thumbnailSize = tryGetThumbnailSize(resolution); + return ImageChannelsUtils.getRepresentativeImageStream(datasetRoot, datasetCode, null, + thumbnailSize); + } + + private static Size tryGetThumbnailSize(ImageResolutionKind resolution) + { + if (resolution == ImageResolutionKind.NORMAL) + { + return null; + } else + { + return DEFAULT_THUMBNAIL_SIZE; + } } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java index cc6a31b01b566aa23f3a09561b70d01413c981c9..7d395c6cea76325f1695296bdbb479db8d9715db 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtils.java @@ -20,6 +20,7 @@ import java.awt.Color; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -29,14 +30,18 @@ import javax.imageio.ImageIO; import org.apache.log4j.Logger; +import ch.rinn.restrictions.Private; import ch.systemsx.cisd.base.image.IImageTransformerFactory; +import ch.systemsx.cisd.bds.hcs.Location; import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; import ch.systemsx.cisd.common.io.ByteArrayBasedContent; import ch.systemsx.cisd.common.io.IContent; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.openbis.dss.etl.AbsoluteImageReference; +import ch.systemsx.cisd.openbis.dss.etl.HCSImageDatasetLoaderFactory; import ch.systemsx.cisd.openbis.dss.etl.IImagingDatasetLoader; +import ch.systemsx.cisd.openbis.dss.generic.server.ResponseContentStream; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size; import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants; @@ -56,16 +61,46 @@ public class ImageChannelsUtils // MIME type of the images which are produced by this class public static final String IMAGES_CONTENT_TYPE = "image/png"; - /** * Returns content of image for the specified tile in the specified size and for the requested * channel or with all channels merged. */ - public static IContent getImage(IImagingDatasetLoader imageAccessor, TileImageReference params) + public static ResponseContentStream getImageStream(File datasetRoot, String datasetCode, + TileImageReference params) + { + IImagingDatasetLoader imageAccessor = + HCSImageDatasetLoaderFactory.create(datasetRoot, datasetCode); + IContent image = getImage(imageAccessor, params); + return asResponseContentStream(image); + } + + /** + * Returns content of the image which is representative for the given dataset. + */ + public static ResponseContentStream getRepresentativeImageStream(File datasetRoot, + String datasetCode, Location wellLocationOrNull, Size thumbnailSizeOrNull) + { + IImagingDatasetLoader imageAccessor = + HCSImageDatasetLoaderFactory.create(datasetRoot, datasetCode); + List<AbsoluteImageReference> imageReferences = + getRepresentativeImageReferences(imageAccessor, wellLocationOrNull, + thumbnailSizeOrNull); + IContent imageContent = mergeAllChannels(imageReferences, true, true); + return asResponseContentStream(imageContent); + } + + private static ResponseContentStream asResponseContentStream(IContent image) + { + return ResponseContentStream.create(image.getInputStream(), image.getSize(), + ImageChannelsUtils.IMAGES_CONTENT_TYPE, image.tryGetName()); + } + + @Private + static IContent getImage(IImagingDatasetLoader imageAccessor, TileImageReference params) { return getImage(imageAccessor, params, true, true); } - + private static IContent getImage(IImagingDatasetLoader imageAccessor, TileImageReference params, boolean transform, boolean convertToPng) { @@ -93,10 +128,48 @@ public class ImageChannelsUtils tileImageReference.setChannelStack(channelStackReference); tileImageReference.setChannel(chosenChannelCode); tileImageReference.setThumbnailSizeOrNull(thumbnailSizeOrNull); - tileImageReference.setMergeAllChannels(ScreeningConstants.MERGED_CHANNELS.equalsIgnoreCase(chosenChannelCode)); + tileImageReference.setMergeAllChannels(ScreeningConstants.MERGED_CHANNELS + .equalsIgnoreCase(chosenChannelCode)); return getImage(imageAccessor, tileImageReference, false, convertToPng); } + private static List<AbsoluteImageReference> getRepresentativeImageReferences( + IImagingDatasetLoader imageAccessor, Location wellLocationOrNull, + Size thumbnailSizeOrNull) + { + List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>(); + + for (String chosenChannel : imageAccessor.getImageParameters().getChannelsCodes()) + { + AbsoluteImageReference image = + getRepresentativeImageReference(imageAccessor, chosenChannel, + wellLocationOrNull, thumbnailSizeOrNull); + images.add(image); + } + return images; + } + + /** + * @throw {@link EnvironmentFailureException} when image does not exist + */ + private static AbsoluteImageReference getRepresentativeImageReference( + IImagingDatasetLoader imageAccessor, String channelCode, Location wellLocationOrNull, + Size thumbnailSizeOrNull) + { + AbsoluteImageReference image = + imageAccessor.tryGetRepresentativeImage(channelCode, wellLocationOrNull, + thumbnailSizeOrNull); + if (image != null) + { + return image; + } else + { + throw EnvironmentFailureException.fromTemplate("No representative " + + (thumbnailSizeOrNull != null ? "thumbnail" : "image") + + " found for well %s and channel %s", wellLocationOrNull, channelCode); + } + } + private static List<AbsoluteImageReference> getImageReferences( IImagingDatasetLoader imageAccessor, TileImageReference params) { @@ -128,7 +201,8 @@ public class ImageChannelsUtils final IContent content; if (convertToPng || imageReference.tryGetColorComponent() != null) { - final BufferedImage image = transform(calculateSingleImage(imageReference), transformerFactoryOrNull); + final BufferedImage image = + transform(calculateSingleImage(imageReference), transformerFactoryOrNull); long start = operationLog.isDebugEnabled() ? System.currentTimeMillis() : 0; content = createPngContent(image, imageReference.getContent().tryGetName()); @@ -181,7 +255,8 @@ public class ImageChannelsUtils image = transformToChannel(image, colorComponentOrNull); if (operationLog.isDebugEnabled()) { - operationLog.debug("Select single channel: " + (System.currentTimeMillis() - start)); + operationLog + .debug("Select single channel: " + (System.currentTimeMillis() - start)); } } return image; @@ -199,7 +274,8 @@ public class ImageChannelsUtils return image; } - private static IContent mergeAllChannels(List<AbsoluteImageReference> imageReferences, boolean transform, boolean convertToPng) + private static IContent mergeAllChannels(List<AbsoluteImageReference> imageReferences, + boolean transform, boolean convertToPng) { AbsoluteImageReference allChannelsImageReference = tryCreateAllChannelsImageReference(imageReferences); @@ -215,13 +291,15 @@ public class ImageChannelsUtils { List<BufferedImage> images = calculateSingleImages(imageReferences); BufferedImage mergedImage = mergeChannels(images); - IImageTransformerFactory transformerFactory = transform ? - imageReferences.get(0).getTransformerFactoryForMergedChannels() : null; + IImageTransformerFactory transformerFactory = + transform ? imageReferences.get(0).getTransformerFactoryForMergedChannels() + : null; return createPngContent(transform(mergedImage, transformerFactory), null); } } - - private static BufferedImage transform(BufferedImage input, IImageTransformerFactory factoryOrNull) + + private static BufferedImage transform(BufferedImage input, + IImageTransformerFactory factoryOrNull) { if (factoryOrNull == null) { diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageSeriesViewer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageSeriesViewer.java index 11561437dcd598bfa97a6e4aee6f1924363f747a..2ca9e031b6c8e050630985d4db2e71c8d5e93116 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageSeriesViewer.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageSeriesViewer.java @@ -17,9 +17,10 @@ package ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Map.Entry; +import java.util.Set; import java.util.TreeMap; import com.extjs.gxt.ui.client.event.Events; @@ -40,23 +41,203 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelSt class LogicalImageSeriesViewer { + static class ImageSeriesPoint implements Comparable<ImageSeriesPoint> + { + private final Float tOrNull, zOrNull; + + private final Integer seriesNumberOrNull; + + public ImageSeriesPoint(ImageChannelStack stack) + { + this.tOrNull = stack.tryGetTimepoint(); + this.zOrNull = stack.tryGetDepth(); + this.seriesNumberOrNull = stack.tryGetSeriesNumber(); + } + + public ImageSeriesPoint(Float tOrNull, Float zOrNull, Integer seriesNumberOrNull) + { + this.tOrNull = tOrNull; + this.zOrNull = zOrNull; + this.seriesNumberOrNull = seriesNumberOrNull; + } + + public String getLabel() + { + String desc = ""; + if (tOrNull != null) + { + if (desc.length() > 0) + { + desc += ". "; + } + desc += "Time: " + tOrNull + " sec"; + } + if (zOrNull != null) + { + if (desc.length() > 0) + { + desc += ". "; + } + desc += "Depth: " + zOrNull; + } + if (seriesNumberOrNull != null) + { + if (desc.length() > 0) + { + desc += ". "; + } + desc += "Series: " + seriesNumberOrNull; + } + return desc; + } + + public int compareTo(ImageSeriesPoint o) + { + int cmp; + cmp = compareNullable(seriesNumberOrNull, o.seriesNumberOrNull); + if (cmp != 0) + return cmp; + cmp = compareNullable(tOrNull, o.tOrNull); + if (cmp != 0) + return cmp; + return compareNullable(zOrNull, o.zOrNull); + } + + private static <T extends Comparable<T>> int compareNullable(T v1OrNull, T v2OrNull) + { + if (v1OrNull == null) + { + return v2OrNull == null ? 0 : -1; + } else + { + return v2OrNull == null ? 1 : v1OrNull.compareTo(v2OrNull); + } + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = + prime * result + + ((seriesNumberOrNull == null) ? 0 : seriesNumberOrNull.hashCode()); + result = prime * result + ((tOrNull == null) ? 0 : tOrNull.hashCode()); + result = prime * result + ((zOrNull == null) ? 0 : zOrNull.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ImageSeriesPoint other = (ImageSeriesPoint) obj; + if (seriesNumberOrNull == null) + { + if (other.seriesNumberOrNull != null) + return false; + } else if (!seriesNumberOrNull.equals(other.seriesNumberOrNull)) + return false; + if (tOrNull == null) + { + if (other.tOrNull != null) + return false; + } else if (!tOrNull.equals(other.tOrNull)) + return false; + if (zOrNull == null) + { + if (other.zOrNull != null) + return false; + } else if (!zOrNull.equals(other.zOrNull)) + return false; + return true; + } + } + + static class LogicalImageSeriesViewerModel + { + private final List<ImageSeriesPoint> sortedPoints; + + private final List<List<ImageChannelStack>> sortedChannelStackSeriesPoints; + + public LogicalImageSeriesViewerModel(List<ImageChannelStack> channelStackImages) + { + Map<ImageSeriesPoint, List<ImageChannelStack>> channelStackImagesBySeries = + groupImagesBySeries(channelStackImages); + this.sortedPoints = sortPoints(channelStackImagesBySeries.keySet()); + this.sortedChannelStackSeriesPoints = + getSortedSeries(channelStackImagesBySeries, sortedPoints); + } + + private static Map<ImageSeriesPoint, List<ImageChannelStack>> groupImagesBySeries( + List<ImageChannelStack> channelStackImages) + { + Map<ImageSeriesPoint, List<ImageChannelStack>> result = + new TreeMap<ImageSeriesPoint, List<ImageChannelStack>>(); + for (ImageChannelStack ref : channelStackImages) + { + ImageSeriesPoint point = new ImageSeriesPoint(ref); + List<ImageChannelStack> imageReferences = result.get(point); + if (imageReferences == null) + { + imageReferences = new ArrayList<ImageChannelStack>(); + result.put(point, imageReferences); + } + imageReferences.add(ref); + } + return result; + } + + public List<ImageSeriesPoint> getSortedPoints() + { + return sortedPoints; + } + + public List<List<ImageChannelStack>> getSortedChannelStackSeriesPoints() + { + return sortedChannelStackSeriesPoints; + } + } + public static LayoutContainer create(String sessionId, List<ImageChannelStack> channelStackImages, LogicalImageReference images, String channel, int imageWidth, int imageHeight) { - Map<Float, List<ImageChannelStack>> channelStackImagesByTimepoint = - groupImagesByTimepoint(channelStackImages); - + LogicalImageSeriesViewerModel model = new LogicalImageSeriesViewerModel(channelStackImages); List<LayoutContainer> frames = - createTimepointFrames(channelStackImagesByTimepoint, images, channel, sessionId, - imageWidth, imageHeight); + createTimepointFrames(model.getSortedChannelStackSeriesPoints(), images, channel, + sessionId, imageWidth, imageHeight); + + return createMoviePlayer(frames, model.getSortedPoints()); + } - Float[] timepoints = channelStackImagesByTimepoint.keySet().toArray(new Float[0]); - return createMoviePlayer(frames, timepoints); + private static List<List<ImageChannelStack>> getSortedSeries( + Map<ImageSeriesPoint, List<ImageChannelStack>> channelStackImagesBySeries, + List<ImageSeriesPoint> sortedPoints) + { + List<List<ImageChannelStack>> sortedSeries = new ArrayList<List<ImageChannelStack>>(); + for (ImageSeriesPoint point : sortedPoints) + { + List<ImageChannelStack> series = channelStackImagesBySeries.get(point); + sortedSeries.add(series); + } + return sortedSeries; + } + + private static List<ImageSeriesPoint> sortPoints(Set<ImageSeriesPoint> points) + { + ArrayList<ImageSeriesPoint> pointsList = new ArrayList<ImageSeriesPoint>(points); + Collections.sort(pointsList); + return pointsList; } private static LayoutContainer createMoviePlayer(final List<LayoutContainer> frames, - final Float[] timepoints) + final List<ImageSeriesPoint> sortedPoints) { final LayoutContainer mainContainer = new LayoutContainer(); addAll(mainContainer, frames); @@ -73,29 +254,33 @@ class LogicalImageSeriesViewer } frames.get(newValue - 1).show(); mainContainer.remove(mainContainer.getItem(0)); - mainContainer.insert(new Label(createTimepointLabel(timepoints, newValue)), 0); + mainContainer + .insert(new Label(createTimepointLabel(sortedPoints, newValue)), 0); mainContainer.layout(); } }); mainContainer.insert(slider, 0); - mainContainer.insert(new Label(createTimepointLabel(timepoints, 1)), 0); + mainContainer.insert(new Label(createTimepointLabel(sortedPoints, 1)), 0); slider.setValue(1); return mainContainer; } + /** + * @param sortedChannelStackSeriesPoints - one element on the list are all tiles for a fixed + * series point + */ private static List<LayoutContainer> createTimepointFrames( - Map<Float, List<ImageChannelStack>> channelStackImagesByTimepoint, + List<List<ImageChannelStack>> sortedChannelStackSeriesPoints, LogicalImageReference images, String channel, String sessionId, int imageWidth, int imageHeight) { final List<LayoutContainer> frames = new ArrayList<LayoutContainer>(); int counter = 0; - for (Entry<Float, List<ImageChannelStack>> entry : channelStackImagesByTimepoint.entrySet()) + for (List<ImageChannelStack> seriesPointStacks : sortedChannelStackSeriesPoints) { - List<ImageChannelStack> imageReferences = entry.getValue(); final LayoutContainer container = - createTilesGridForTimepoint(imageReferences, images, channel, sessionId, + createTilesGridForTimepoint(seriesPointStacks, images, channel, sessionId, imageWidth, imageHeight); frames.add(container); if (counter > 0) @@ -116,14 +301,14 @@ class LogicalImageSeriesViewer } private static LayoutContainer createTilesGridForTimepoint( - List<ImageChannelStack> channelStackReferences, LogicalImageReference images, + List<ImageChannelStack> seriesPointStacks, LogicalImageReference images, String channel, String sessionId, int imageWidth, int imageHeight) { final LayoutContainer container = new LayoutContainer(new TableLayout(images.getTileColsNum())); ImageChannelStack[/* tileRow */][/* tileCol */] tilesMap = - createTilesMap(channelStackReferences, images); + createTilesMap(seriesPointStacks, images); for (int row = 1; row <= images.getTileRowsNum(); row++) { for (int col = 1; col <= images.getTileColsNum(); col++) @@ -163,29 +348,12 @@ class LogicalImageSeriesViewer container.add(dummy); } - private static String createTimepointLabel(Float[] timepoints, int sequenceNumber) - { - Float timepoint = timepoints[sequenceNumber - 1]; - int numberOfSequences = timepoints.length; - return "Timepoint: " + timepoint + "sec (" + sequenceNumber + "/" + numberOfSequences + ")"; - } - - private static Map<Float, List<ImageChannelStack>> groupImagesByTimepoint( - List<ImageChannelStack> channelStackImages) + private static String createTimepointLabel(List<ImageSeriesPoint> sortedPoints, + int sequenceNumber) { - Map<Float, List<ImageChannelStack>> result = new TreeMap<Float, List<ImageChannelStack>>(); - for (ImageChannelStack ref : channelStackImages) - { - Float t = ref.tryGetTimepoint(); - List<ImageChannelStack> imageReferences = result.get(t); - if (imageReferences == null) - { - imageReferences = new ArrayList<ImageChannelStack>(); - result.put(t, imageReferences); - } - imageReferences.add(ref); - } - return result; + ImageSeriesPoint point = sortedPoints.get(sequenceNumber - 1); + int numberOfSequences = sortedPoints.size(); + return point.getLabel() + " (" + sequenceNumber + "/" + numberOfSequences + ")"; } private static final Slider createTimepointsSlider(int maxValue, Listener<SliderEvent> listener) diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannelStack.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannelStack.java index 0a403091cb834b667bbeb2791e9ba2a9cea22b88..6b97736df150047c66e99563221a567ad5aef39d 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannelStack.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannelStack.java @@ -37,6 +37,8 @@ public class ImageChannelStack implements ISerializable private Float tOrNull, zOrNull; + private Integer seriesNumberOrNull; + // GWT only @SuppressWarnings("unused") private ImageChannelStack() @@ -44,13 +46,14 @@ public class ImageChannelStack implements ISerializable } public ImageChannelStack(long channelStackTechId, int tileRow, int tileCol, Float tOrNull, - Float zOrNull) + Float zOrNull, Integer seriesNumberOrNull) { this.channelStackTechId = channelStackTechId; this.tileRow = tileRow; this.tileCol = tileCol; this.tOrNull = tOrNull; this.zOrNull = zOrNull; + this.seriesNumberOrNull = seriesNumberOrNull; } public long getChannelStackTechId() @@ -78,6 +81,11 @@ public class ImageChannelStack implements ISerializable return zOrNull; } + public Integer tryGetSeriesNumber() + { + return seriesNumberOrNull; + } + @Override public String toString() { @@ -90,7 +98,10 @@ public class ImageChannelStack implements ISerializable { desc += ", z=" + zOrNull; } - + if (seriesNumberOrNull != null) + { + desc += ", series=" + seriesNumberOrNull; + } return "channelStack=" + channelStackTechId + ", tile[" + tileRow + "," + tileCol + "]" + desc; } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java index 68e1df0c4d892c4defbb6b35a29e4fb0e96679d2..d0e56ba874790e79073f953a00c381c8c79d729d 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/HCSDatasetLoader.java @@ -144,7 +144,7 @@ public class HCSDatasetLoader implements IImageDatasetLoader private static ImageChannelStack convert(ImgChannelStackDTO stack) { return new ImageChannelStack(stack.getId(), stack.getRow(), stack.getColumn(), - stack.getT(), stack.getZ()); + stack.getT(), stack.getZ(), stack.getSeriesNumber()); } public ImageDatasetParameters getImageParameters() 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 79194a2867df09b5a5401f811161faacfc5e506b..c9084979a196da6fcc1eaf6d4c4b17d335c80942 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 @@ -54,13 +54,35 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery // joins + "ACQUIRED_IMAGES.CHANNEL_STACK_ID = CHANNEL_STACKS.ID "; - // FIXME 2010-12-10, Tomasz Pylak: uncomment when we are able to show a representative image + public static final String SQL_HCS_IMAGE_REPRESENTATIVE = + "select i.* from CHANNEL_STACKS, SPOTS, ACQUIRED_IMAGES, IMAGES as i " + + "where " + + "CHANNEL_STACKS.is_representative = 'T' and " + + "CHANNEL_STACKS.DS_ID = ?{1} and " + + "ACQUIRED_IMAGES.channel_id = ?{3} and " + + "SPOTS.x = ?{2.x} and SPOTS.y = ?{2.y} and " + // joins + + "ACQUIRED_IMAGES.CHANNEL_STACK_ID = CHANNEL_STACKS.ID and " + + "CHANNEL_STACKS.SPOT_ID = SPOTS.ID "; + + public static final String SQL_MICROSCOPY_IMAGE_REPRESENTATIVE = + "select i.* from CHANNEL_STACKS, ACQUIRED_IMAGES, IMAGES as i " + + "where " + + "CHANNEL_STACKS.is_representative = 'T' and " + + "CHANNEL_STACKS.DS_ID = ?{1} and " + + "ACQUIRED_IMAGES.channel_id = ?{2} and " + // joins + + "ACQUIRED_IMAGES.CHANNEL_STACK_ID = CHANNEL_STACKS.ID "; + + // TODO 2010-12-10, Tomasz Pylak: uncomment when we are able to show a representative image public static final String SQL_NO_MULTIDIMENTIONAL_DATA_COND = " order by CHANNEL_STACKS.T_in_SEC, CHANNEL_STACKS.Z_in_M limit 1"; // " and CHANNEL_STACKS.T_in_SEC IS NULL " // + " and CHANNEL_STACKS.Z_in_M IS NULL "; + // ---------------- HCS --------------------------------- + /** * @return an HCS image for the specified chanel, well and tile. If many images (e.g. for * different timepoints or depths) exist, null is returned. @@ -79,6 +101,24 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery public ImgImageDTO tryGetHCSThumbnail(long channelId, long datasetId, Location tileLocation, Location wellLocation); + /** + * @return an representative HCS image for the specified dataset and well. Returns null if there + * are no images at all for the specified well. + */ + @Select(SQL_HCS_IMAGE_REPRESENTATIVE + " and ACQUIRED_IMAGES.IMG_ID = i.ID ") + public ImgImageDTO tryGetHCSRepresentativeImage(long datasetId, Location wellLocation, + long channelId); + + /** + * @return an representative HCS thumbnail for the specified dataset and well. Returns null if + * there are no images at all for the specified well. + */ + @Select(SQL_HCS_IMAGE_REPRESENTATIVE + " and ACQUIRED_IMAGES.THUMBNAIL_ID = i.ID ") + public ImgImageDTO tryGetHCSRepresentativeThumbnail(long datasetId, Location wellLocation, + long channelId); + + // ---------------- Microscopy --------------------------------- + /** * @return an microscopy image for the specified channel and tile. If many images (e.g. for * different timepoints or depths) exist, null is returned. @@ -96,6 +136,33 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery public ImgImageDTO tryGetMicroscopyThumbnail(long channelId, long datasetId, Location tileLocation); + /** + * @return an representative microscopy image for the specified dataset. + */ + @Select(SQL_MICROSCOPY_IMAGE_REPRESENTATIVE + " and ACQUIRED_IMAGES.IMG_ID = i.ID ") + public ImgImageDTO tryGetMicroscopyRepresentativeImage(long datasetId, long channelId); + + /** + * @return an representative microscopy thumbnail for the specified dataset. Can be null if + * thumbnail has not been generated. + */ + @Select(SQL_MICROSCOPY_IMAGE_REPRESENTATIVE + " and ACQUIRED_IMAGES.THUMBNAIL_ID = i.ID ") + public ImgImageDTO tryGetMicroscopyRepresentativeThumbnail(long datasetId, long channelId); + + // ---------------- Microscopy - channels --------------------------------- + + @Select("select cs.* from CHANNEL_STACKS cs " + + "where cs.ds_id = ?{1} and cs.spot_id is NULL") + public List<ImgChannelStackDTO> listSpotlessChannelStacks(long datasetId); + + @Select("select * from CHANNELS where DS_ID = ?{1} order by ID") + public List<ImgChannelDTO> getChannelsByDatasetId(long datasetId); + + @Select("select * from CHANNELS where (DS_ID = ?{1}) and CODE = upper(?{2})") + public ImgChannelDTO tryGetChannelForDataset(long datasetId, String chosenChannelCode); + + // ---------------- Generic --------------------------------- + /** @return an image for the specified channel and channel stack or null */ @Select("select i.* from IMAGES as i " + "join ACQUIRED_IMAGES on ACQUIRED_IMAGES.IMG_ID = i.ID " @@ -120,6 +187,11 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery // simple getters + @Select("select * from DATA_SETS where PERM_ID = ?{1}") + public ImgDatasetDTO tryGetDatasetByPermId(String datasetPermId); + + // ---------------- HCS - experiments, containers, channels --------------------------------- + @Select("select * from EXPERIMENTS where PERM_ID = ?{1}") public ImgExperimentDTO tryGetExperimentByPermId(String experimentPermId); @@ -129,9 +201,6 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery @Select("select ID from CONTAINERS where PERM_ID = ?{1}") public Long tryGetContainerIdPermId(String containerPermId); - @Select("select * from DATA_SETS where PERM_ID = ?{1}") - public ImgDatasetDTO tryGetDatasetByPermId(String datasetPermId); - @Select("select * from CONTAINERS where ID = ?{1}") public ImgContainerDTO getContainerById(long containerId); @@ -142,19 +211,22 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery + "where cs.ds_id = ?{1} and s.x = ?{2} and s.y = ?{3}") public List<ImgChannelStackDTO> listChannelStacks(long datasetId, int spotX, int spotY); - @Select("select cs.* from CHANNEL_STACKS cs " - + "where cs.ds_id = ?{1} and cs.spot_id is NULL") - public List<ImgChannelStackDTO> listSpotlessChannelStacks(long datasetId); - - @Select("select * from CHANNELS where DS_ID = ?{1} order by ID") - public List<ImgChannelDTO> getChannelsByDatasetId(long datasetId); - @Select(sql = "select * from CHANNELS where EXP_ID = ?{1} order by ID", fetchSize = FETCH_SIZE) public List<ImgChannelDTO> getChannelsByExperimentId(long experimentId); @Select("select * from SPOTS where cont_id = ?{1}") public List<ImgSpotDTO> listSpots(long contId); + @Select("select * from CHANNELS where (EXP_ID = ?{1}) and CODE = upper(?{2})") + public ImgChannelDTO tryGetChannelForExperiment(long experimentId, String chosenChannelCode); + + @Select("select * from channels where code = ?{2} and " + + "exp_id in (select id from experiments where perm_id = ?{1})") + public ImgChannelDTO tryGetChannelForExperimentPermId(String experimentPermId, + String chosenChannelCode); + + // ---------------- HCS - feature vectors --------------------------------- + @Select("select * from FEATURE_DEFS where DS_ID = ?{1}") public List<ImgFeatureDefDTO> listFeatureDefsByDataSetId(long dataSetId); @@ -166,15 +238,4 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery @Select(sql = "select * from FEATURE_VALUES where FD_ID = ?{1.id} order by T_in_SEC, Z_in_M", resultSetBinding = FeatureVectorDataObjectBinding.class) public List<ImgFeatureValuesDTO> getFeatureValues(ImgFeatureDefDTO featureDef); - @Select("select * from CHANNELS where (EXP_ID = ?{1}) and CODE = upper(?{2})") - public ImgChannelDTO tryGetChannelForExperiment(long experimentId, String chosenChannelCode); - - @Select("select * from CHANNELS where (DS_ID = ?{1}) and CODE = upper(?{2})") - public ImgChannelDTO tryGetChannelForDataset(long datasetId, String chosenChannelCode); - - @Select("select * from channels where code = ?{2} and " - + "exp_id in (select id from experiments where perm_id = ?{1})") - public ImgChannelDTO tryGetChannelForExperimentPermId(String experimentPermId, - String chosenChannelCode); - } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTO.java index f262b732a3f21c4f1ae93225f8353e83a75a3898..eb973b183d0a88850fd86c4bc6c6bf1431452573 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTO.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTO.java @@ -47,12 +47,18 @@ public class ImgChannelStackDTO @ResultColumn("T_in_SEC") private Float t; + @ResultColumn("SERIES_NUMBER") + private Integer seriesNumber; + @ResultColumn("DS_ID") private long datasetId; @ResultColumn("SPOT_ID") private Long spotId; + @ResultColumn("IS_REPRESENTATIVE") + private boolean isRepresentative; + @SuppressWarnings("unused") private ImgChannelStackDTO() { @@ -60,7 +66,7 @@ public class ImgChannelStackDTO } public ImgChannelStackDTO(long id, int row, int column, long datasetId, Long spotIdOrNull, - Float tOrNull, Float zOrNull) + Float tOrNull, Float zOrNull, Integer seriesNumber, boolean isRepresentative) { this.id = id; this.row = row; @@ -69,6 +75,8 @@ public class ImgChannelStackDTO this.spotId = spotIdOrNull; this.t = tOrNull; this.z = zOrNull; + this.seriesNumber = seriesNumber; + this.isRepresentative = isRepresentative; } public long getId() @@ -111,6 +119,16 @@ public class ImgChannelStackDTO this.t = t; } + public Integer getSeriesNumber() + { + return seriesNumber; + } + + public void setSeriesNumber(Integer seriesNumber) + { + this.seriesNumber = seriesNumber; + } + public long getDatasetId() { return datasetId; @@ -132,10 +150,21 @@ public class ImgChannelStackDTO this.spotId = spotId; } + public boolean getIsRepresentative() + { + return isRepresentative; + } + + public void setRepresentative(boolean isRepresentative) + { + this.isRepresentative = isRepresentative; + } + @Override // use all fields besides id public int hashCode() { + // NOTE: isRepresentative field is derived and can be skipped final int prime = 31; int result = 1; result = prime * result + (int) (datasetId ^ (datasetId >>> 32)); @@ -144,6 +173,7 @@ public class ImgChannelStackDTO result = prime * result + ((spotId == null) ? 0 : spotId.hashCode()); result = prime * result + ((t == null) ? 0 : t.hashCode()); result = prime * result + ((z == null) ? 0 : z.hashCode()); + result = prime * result + ((seriesNumber == null) ? 0 : seriesNumber.hashCode()); return result; } @@ -151,6 +181,7 @@ public class ImgChannelStackDTO // use all fields besides id public boolean equals(Object obj) { + // NOTE: isRepresentative field is derived and can be skipped if (obj == null) return false; if (this == obj) @@ -192,6 +223,12 @@ public class ImgChannelStackDTO return false; } else if (!z.equals(other.z)) return false; + if (seriesNumber == null) + { + if (other.seriesNumber != null) + return false; + } else if (!seriesNumber.equals(other.seriesNumber)) + return false; return true; } diff --git a/screening/source/sql/postgresql/009/schema-009.sql b/screening/source/sql/postgresql/009/schema-009.sql index 834d82dbf0e26750adc8b42c6e639e5271d78010..a94fe7dceadada7ec33792d554f474255de81770 100644 --- a/screening/source/sql/postgresql/009/schema-009.sql +++ b/screening/source/sql/postgresql/009/schema-009.sql @@ -112,13 +112,9 @@ CREATE TABLE CHANNEL_STACKS ( X INTEGER, Y INTEGER, -- We use the fixed dimension meters here. - -- Not null only if SERIES_NUMBER is null. Z_in_M REAL, -- We use the fixed dimension seconds here. - -- Not null only if SERIES_NUMBER is null. T_in_SEC REAL, - -- not null if and only if t and z are null - -- TODO: write constraint which checks this SERIES_NUMBER INTEGER, -- For all channel stacks of a well (HCS) or image dataset (microscopy) there should be exactly diff --git a/screening/source/sql/postgresql/migration/migration-008-009.sql b/screening/source/sql/postgresql/migration/migration-008-009.sql index 65f164222760cf2fb165b6b12aada8366b1f59d2..54d146739b65a4115b32e4e70ad2b102c6ce879b 100644 --- a/screening/source/sql/postgresql/migration/migration-008-009.sql +++ b/screening/source/sql/postgresql/migration/migration-008-009.sql @@ -33,3 +33,11 @@ $$ LANGUAGE 'plpgsql'; CREATE TRIGGER CHANNEL_STACKS_CHECK BEFORE INSERT OR UPDATE ON CHANNEL_STACKS FOR EACH ROW EXECUTE PROCEDURE CHANNEL_STACKS_CHECK(); +--- for each spot set exactly one representative channel_stacks record (with minimal id) --------- + +update channel_stacks as cs + set cs.is_representative = 'T' + where cs.id in (select min(cs.id) + from channel_stacks cs + join spots on spots.id = cs.spot_id + group by spots.id) \ No newline at end of file diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/ImagesRepresentativesOracleTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/ImagesRepresentativesOracleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1b77f1efa8dd03f7b117b4314c2ab0e7cd3c1472 --- /dev/null +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/ImagesRepresentativesOracleTest.java @@ -0,0 +1,79 @@ +/* + * 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.etl; + +import java.util.HashSet; +import java.util.Set; + +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +import ch.rinn.restrictions.Friend; +import ch.systemsx.cisd.openbis.dss.etl.AbstractImageDatasetUploader.ChannelStackRepresentativesOracle; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelStackDTO; + +/** + * @author Tomasz Pylak + */ +@Friend(toClasses = ChannelStackRepresentativesOracle.class) +public class ImagesRepresentativesOracleTest extends AssertJUnit +{ + @Test + public void testWithSpots() + { + ImgChannelStackDTO img1 = createStack(2, 2, 2L, 1f, 1f); + ImgChannelStackDTO img2 = createStack(1, 1, 1L, 200f, 200f); + ImgChannelStackDTO img3 = createStack(1, 1, 1L, 1f, 1f); + + Set<ImgChannelStackDTO> representatives = calculateRepresentatives(img1, img2, img3); + assertTrue(representatives.contains(img1)); + assertFalse(representatives.contains(img2)); + assertTrue(representatives.contains(img3)); + } + + private Set<ImgChannelStackDTO> calculateRepresentatives(ImgChannelStackDTO... stacks) + { + Set<ImgChannelStackDTO> stacksSet = new HashSet<ImgChannelStackDTO>(); + for (ImgChannelStackDTO stack : stacks) + { + stacksSet.add(stack); + } + Set<ImgChannelStackDTO> representatives = + ChannelStackRepresentativesOracle.calculateRepresentatives(stacksSet); + return representatives; + } + + @Test + public void testWithoutSpots() + { + ImgChannelStackDTO img1 = createStack(2, 2, null, 1f, 1f); + ImgChannelStackDTO img2 = createStack(1, 1, null, 200f, 200f); + ImgChannelStackDTO img3 = createStack(1, 1, null, 1f, 1f); + + Set<ImgChannelStackDTO> representatives = calculateRepresentatives(img1, img2, img3); + assertFalse(representatives.contains(img1)); + assertFalse(representatives.contains(img2)); + assertTrue(representatives.contains(img3)); + } + + private ImgChannelStackDTO createStack(int row, int column, Long spotIdOrNull, Float tOrNull, + Float zOrNull) + { + return new ImgChannelStackDTO(0L, row, column, 0L, spotIdOrNull, tOrNull, zOrNull, null, + false); + } +} diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java index 1994fed759163733b9c37ac79facc1f14e66d66b..f12cfbaf16820089276f1f8d63fca3ef059478e2 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dataaccess/ImagingQueryDAOTest.java @@ -129,12 +129,20 @@ public class ImagingQueryDAOTest extends AbstractDBTest assertEquals(MICROSCOPY_IMAGE_PATH1, image1.getFilePath()); assertEquals(ColorComponent.BLUE, image1.getColorComponent()); + ImgImageDTO representativeImage = + dao.tryGetMicroscopyRepresentativeImage(datasetId, channelId); + assertEquals(image1, representativeImage); + ImgImageDTO image1bis = dao.tryGetImage(channelId, channelStackId, datasetId); assertEquals(image1, image1bis); ImgImageDTO thumbnail1 = dao.tryGetMicroscopyThumbnail(channelId, datasetId, tileLocation); assertEquals(image1, thumbnail1); + ImgImageDTO representativeThumbnail = + dao.tryGetMicroscopyRepresentativeThumbnail(datasetId, channelId); + assertEquals(image1, representativeThumbnail); + ImgImageDTO thumbnail1bis = dao.tryGetThumbnail(channelId, channelStackId, datasetId); assertEquals(thumbnail1, thumbnail1bis); } @@ -249,6 +257,10 @@ public class ImagingQueryDAOTest extends AbstractDBTest assertEquals(PATH1, image1.getFilePath()); assertEquals(ColorComponent.BLUE, image1.getColorComponent()); + ImgImageDTO representativeImage = + dao.tryGetHCSRepresentativeImage(datasetId, wellLocation, channelId1); + assertEquals(image1, representativeImage); + ImgImageDTO image1bis = dao.tryGetImage(channelId1, channelStackId, datasetId); assertEquals(image1, image1bis); @@ -256,6 +268,10 @@ public class ImagingQueryDAOTest extends AbstractDBTest dao.tryGetHCSThumbnail(channelId1, datasetId, tileLocation, wellLocation); assertEquals(image1, thumbnail1); + ImgImageDTO representativeThumbnail = + dao.tryGetHCSRepresentativeThumbnail(datasetId, wellLocation, channelId1); + assertEquals(thumbnail1, representativeThumbnail); + ImgImageDTO thumbnail1bis = dao.tryGetThumbnail(channelId1, channelStackId, datasetId); assertEquals(thumbnail1, thumbnail1bis); @@ -379,7 +395,7 @@ public class ImagingQueryDAOTest extends AbstractDBTest { final ImgChannelStackDTO channelStack = new ImgChannelStackDTO(dao.createChannelStackId(), Y_TILE_ROW, X_TILE_COLUMN, - datasetId, spotIdOrNull, timeOrNull, depthOrNull); + datasetId, spotIdOrNull, timeOrNull, depthOrNull, null, true); dao.addChannelStacks(Arrays.asList(channelStack)); return channelStack.getId(); } diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java index 8b4e89434c1c60d29bb8c51f05c425d6b305720f..d1cd52963926070df64b0d7f5d473942d0b1b74a 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/images/ImageChannelsUtilsTest.java @@ -29,6 +29,7 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import ch.rinn.restrictions.Friend; import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; import ch.systemsx.cisd.base.image.IImageTransformer; import ch.systemsx.cisd.base.image.IImageTransformerFactory; @@ -43,6 +44,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil; /** * @author Franz-Josef Elmer */ +@Friend(toClasses = ImageChannelsUtils.class) public class ImageChannelsUtilsTest extends AssertJUnit { public static final File TEST_IMAGE_FOLDER = new File("../screening/sourceTest/java/" diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageSeriesViewerModelTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageSeriesViewerModelTest.java new file mode 100644 index 0000000000000000000000000000000000000000..04fc91d9732325bbf35a58248ebdf764a45eb229 --- /dev/null +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/LogicalImageSeriesViewerModelTest.java @@ -0,0 +1,86 @@ +/* + * 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.client.web.client.application.detailviewers; + +import java.util.ArrayList; +import java.util.List; + +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.LogicalImageSeriesViewer.ImageSeriesPoint; +import ch.systemsx.cisd.openbis.plugin.screening.client.web.client.application.detailviewers.LogicalImageSeriesViewer.LogicalImageSeriesViewerModel; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelStack; + +/** + * Unit tests of {@link LogicalImageSeriesViewerModel}. + * + * @author Tomasz Pylak + */ +public class LogicalImageSeriesViewerModelTest extends AssertJUnit +{ + + @Test + public void testModel() + { + Float times[] = new Float[] + { 3.4f, null, 1.2f, 6.6f }; + Float depths[] = new Float[] + { 66.6f, 33.4f, null, 11.2f }; + Integer series[] = new Integer[] + { 1, 2, 3, null }; + + LogicalImageSeriesViewerModel model = createModel(times, depths, series); + + List<ImageSeriesPoint> sortedPoints = model.getSortedPoints(); + ImageSeriesPoint firstPoint = new ImageSeriesPoint(null, null, null); + assertEquals(firstPoint, sortedPoints.get(0)); + ImageSeriesPoint lastPoint = new ImageSeriesPoint(6.6f, 66.6f, 3); + assertEquals(lastPoint, sortedPoints.get(sortedPoints.size() - 1)); + + List<List<ImageChannelStack>> stackSeriesPoints = model.getSortedChannelStackSeriesPoints(); + List<ImageChannelStack> firstList = stackSeriesPoints.get(0); + assertEquals(firstPoint, new ImageSeriesPoint(firstList.get(0))); + + List<ImageChannelStack> lastList = stackSeriesPoints.get(stackSeriesPoints.size() - 1); + assertEquals(lastPoint, new ImageSeriesPoint(lastList.get(0))); + } + + private LogicalImageSeriesViewerModel createModel(Float[] times, Float[] depths, + Integer[] series) + { + List<ImageChannelStack> stacks = new ArrayList<ImageChannelStack>(); + for (int time = 0; time < times.length; time++) + { + for (int depth = 0; depth < depths.length; depth++) + { + for (int seriesNum = 0; seriesNum < series.length; seriesNum++) + { + stacks.add(mkStack(1, 1, times[time], depths[depth], series[seriesNum])); + stacks.add(mkStack(2, 2, times[time], depths[depth], series[seriesNum])); + } + } + } + return new LogicalImageSeriesViewerModel(stacks); + } + + private static ImageChannelStack mkStack(int row, int col, Float tOrNull, Float zOrNull, + Integer seriesNumberOrNull) + { + return new ImageChannelStack(0, row, col, tOrNull, zOrNull, seriesNumberOrNull); + } +} diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTOTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTOTest.java index 0748606ef44f2bb63652cc6dc8a0453ed157f59f..fb1e839134b01802e56ea1e4b89e368d336e1378 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTOTest.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelStackDTOTest.java @@ -54,7 +54,7 @@ public class ImgChannelStackDTOTest private ImgChannelStackDTO createStackChannel(Long spotId) { - return new ImgChannelStackDTO(0, 1, 1, 1, spotId, 123F, null); + return new ImgChannelStackDTO(0, 1, 1, 1, spotId, 123F, null, null, false); } }