From d1ec132f159a1c18a58888c25213b4abfa998c99 Mon Sep 17 00:00:00 2001 From: tpylak <tpylak> Date: Tue, 11 Jan 2011 13:42:38 +0000 Subject: [PATCH] LMS-1965 channels optionally on dataset level in HCS, handling a case when some overlays are missing LMS-1983 allow image file to be placed in the dropbox as a zip archive SVN: 19362 --- .../etlserver/AbstractStorageProcessor.java | 30 +---- .../etlserver/DefaultStorageProcessor.java | 26 +++++ .../SmartParentDataSetInfoExtractor.java | 4 +- screening/dist/etc/service.properties | 19 +++- .../dss/etl/AbstractImageFileExtractor.java | 83 +++++++++++++- .../etl/AbstractImageStorageProcessor.java | 103 +++++------------- .../openbis/dss/etl/HCSImageDatasetInfo.java | 11 +- .../dss/etl/HCSImageDatasetUploader.java | 23 +++- .../dss/etl/HCSImageFileExtractor.java | 8 +- ...roscopyBlackboxSeriesStorageProcessor.java | 2 +- .../dss/etl/PlateStorageProcessor.java | 27 ++++- .../etl/dataaccess/ImagingDatasetLoader.java | 15 ++- .../server/images/ImageChannelsUtils.java | 52 +++++---- .../shared/imaging/HCSDatasetLoader.java | 9 +- 14 files changed, 252 insertions(+), 160 deletions(-) diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractStorageProcessor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractStorageProcessor.java index 6ecca0f1ca3..d50bae5825c 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractStorageProcessor.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/AbstractStorageProcessor.java @@ -21,9 +21,7 @@ import java.util.Properties; import org.apache.commons.io.FilenameUtils; -import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.utilities.PropertyUtils; -import ch.systemsx.cisd.etlserver.utils.Unzipper; /** * An <code>abtract</code> implementation of <code>IStorageProcessor</code>. @@ -32,26 +30,16 @@ import ch.systemsx.cisd.etlserver.utils.Unzipper; */ public abstract class AbstractStorageProcessor implements IStorageProcessor { - protected final Properties properties; - - private File storeRootDir; - - static final String UNZIP_CRITERIA_KEY = "unzip"; - - static final String DELETE_UNZIPPED_KEY = "delete_unzipped"; - private static final String[] ZIP_FILE_EXTENSIONS = { "zip" }; - private final boolean unzip; + protected final Properties properties; - private final boolean deleteUnzipped; + private File storeRootDir; protected AbstractStorageProcessor(final Properties properties) { this.properties = properties; - unzip = PropertyUtils.getBoolean(properties, UNZIP_CRITERIA_KEY, false); - deleteUnzipped = PropertyUtils.getBoolean(properties, DELETE_UNZIPPED_KEY, true); } protected final String getMandatoryProperty(final String propertyKey) @@ -85,19 +73,7 @@ public abstract class AbstractStorageProcessor implements IStorageProcessor // do nothing } - /** - * Unzips given archive file to selected output directory. - */ - protected final Status unzipIfMatching(File archiveFile, File outputDirectory) - { - if (unzip && isZipFile(archiveFile)) - { - return Unzipper.unzip(archiveFile, outputDirectory, deleteUnzipped); - } - return Status.OK; - } - - public static boolean isZipFile(File file) + protected static boolean isZipFile(File file) { if (file.isDirectory()) { diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultStorageProcessor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultStorageProcessor.java index 672425e8f0f..01a0c656702 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultStorageProcessor.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/DefaultStorageProcessor.java @@ -21,8 +21,11 @@ import java.util.List; import java.util.Properties; import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.filesystem.FileUtilities; import ch.systemsx.cisd.common.mail.IMailClient; +import ch.systemsx.cisd.common.utilities.PropertyUtils; +import ch.systemsx.cisd.etlserver.utils.Unzipper; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; import ch.systemsx.cisd.openbis.generic.shared.dto.StorageFormat; @@ -38,9 +41,20 @@ public class DefaultStorageProcessor extends AbstractStorageProcessor static final String NO_RENAME = "Couldn't rename '%s' to '%s'."; + static final String UNZIP_CRITERIA_KEY = "unzip"; + + static final String DELETE_UNZIPPED_KEY = "delete_unzipped"; + + private final boolean unzip; + + private final boolean deleteUnzipped; + public DefaultStorageProcessor(final Properties properties) { super(properties); + + unzip = PropertyUtils.getBoolean(properties, UNZIP_CRITERIA_KEY, false); + deleteUnzipped = PropertyUtils.getBoolean(properties, DELETE_UNZIPPED_KEY, true); } // @@ -106,4 +120,16 @@ public class DefaultStorageProcessor extends AbstractStorageProcessor } return files.get(0); } + + /** + * Unzips given archive file to selected output directory. + */ + protected final Status unzipIfMatching(File archiveFile, File outputDirectory) + { + if (unzip && isZipFile(archiveFile)) + { + return Unzipper.unzip(archiveFile, outputDirectory, deleteUnzipped); + } + return Status.OK; + } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/SmartParentDataSetInfoExtractor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/SmartParentDataSetInfoExtractor.java index 0af00d97530..6fa972b2e94 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/SmartParentDataSetInfoExtractor.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/SmartParentDataSetInfoExtractor.java @@ -177,7 +177,9 @@ public class SmartParentDataSetInfoExtractor extends DefaultDataSetInfoExtractor if (failWhenParentMissing) { throw UserFailureException - .fromTemplate("No parent datasets of the type '%s' connected to the same sample/experiment could be found."); + .fromTemplate( + "No parent datasets of the type '%s' connected to the same sample/experiment could be found.", + datasetTypePatternOrNull); } else { return new ArrayList<String>(); // no parents will be set diff --git a/screening/dist/etc/service.properties b/screening/dist/etc/service.properties index 0fa1001749a..38db9f1686d 100644 --- a/screening/dist/etc/service.properties +++ b/screening/dist/etc/service.properties @@ -213,6 +213,7 @@ merged-channels-images.type-extractor.locator-type = RELATIVE_LOCATION merged-channels-images.type-extractor.data-set-type = HCS_IMAGE merged-channels-images.type-extractor.is-measured = true +# Note: this storage processor is able to process folders compressed with zip as well merged-channels-images.storage-processor = ch.systemsx.cisd.openbis.dss.etl.PlateStorageProcessor # How should the original data be stored? Possible values: # unchanged - nothing is changed, the default @@ -226,12 +227,22 @@ merged-channels-images.storage-processor.generate-thumbnails = false # Thumbnails size in pixels # merged-channels-images.storage-processor.thumbnail-max-width = 300 # merged-channels-images.storage-processor.thumbnail-max-height = 200 -# DEPRECATED: use 'channel-codes' and 'channel-labels' instead -#merged-channels-images.storage-processor.channel-names = gfp, dapi -# Codes of the channels in which images have been acquired. Allowed characters: [A-Z0-9_]. Number and order of entries must be consistent with 'channel-labels'. +# Codes of the channels in which images have been acquired. Allowed characters: [A-Z0-9_-]. +# Number and order of entries must be consistent with 'channel-labels'. merged-channels-images.storage-processor.channel-codes = GFP, DAPI -# Labels of the channels in which images have been acquired. Number and order of entries must be consistent with 'channel-codes'. +# Labels of the channels in which images have been acquired. +# Number and order of entries must be consistent with 'channel-codes'. merged-channels-images.storage-processor.channel-labels = Gfp, Dapi + +# Optional, true by default. +# Set to false to allow datasets in one experiment to use different channels. +# In this case 'channel-codes' and 'channel-labels' become optional and are used only to determine the label for each channel code. +# It should be set to 'false' for overlay image datasets. +#merged-channels-images.storage-processor.define-channels-per-experiment = false + +# This is an optional boolean property which defines if all image datasets in one experiment have the same +# channels or if each imported dataset can have different channels. By default true if not specified. +#merged-channels-images.storage-processor.define-channels-per-experiment = false # Format: [width]>x[height], e.g. 3x4. Specifies the grid into which a microscope divided the well to acquire images. merged-channels-images.storage-processor.well_geometry = 3x3 # implementation of the IHCSImageFileExtractor interface which maps images to the location on the plate and particular channel 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 28e69d57882..1774b2ed918 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 @@ -18,6 +18,7 @@ package ch.systemsx.cisd.openbis.dss.etl; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -82,6 +83,18 @@ abstract public class AbstractImageFileExtractor implements IImageFileExtractor protected static final String IMAGE_FILE_ACCEPTED = "Image file '%s' was accepted: %s."; + // -------- + + // comma separated list of channel names, order matters + @Deprecated + public static final String CHANNEL_NAMES = "channel-names"; + + // comma separated list of channel codes, order matters + public static final String CHANNEL_CODES = "channel-codes"; + + // comma separated list of channel labels, order matters + public static final String CHANNEL_LABELS = "channel-labels"; + // optional, list of the color components (RED, GREEN, BLUE), in the same order as channel names protected static final String EXTRACT_SINGLE_IMAGE_CHANNELS_PROPERTY = "extract-single-image-channels"; @@ -104,17 +117,17 @@ abstract public class AbstractImageFileExtractor implements IImageFileExtractor protected AbstractImageFileExtractor(Properties properties, boolean skipChannelsWithoutImages) { - this(extractChannelDescriptions(properties), getWellGeometry(properties), + this(tryExtractChannelDescriptions(properties), getWellGeometry(properties), skipChannelsWithoutImages, properties); } /** * @param skipChannelsWithoutImages if true channel names are derived from a set of channel * codes in the extracted images. In this way we do not restrict available channel - * codes and we have no channels without images. Channel labels are taken from + * codes and each channel has at least one image. Channel labels are taken from * channel descriptions anyway. Should be set to true only for microscopy, in HCS * each dataset of one experiment should have the same set of channels even if they - * are not present in some datasets. + * are not present in some datasets (exceptions: image overlays, test screens). */ protected AbstractImageFileExtractor(List<ChannelDescription> channelDescriptionsOrNull, Geometry wellGeometry, boolean skipChannelsWithoutImages, Properties properties) @@ -165,7 +178,7 @@ abstract public class AbstractImageFileExtractor implements IImageFileExtractor if (skipChannelsWithoutImages == false && channelDescriptionsOrNull == null) { throw ConfigurationFailureException - .fromTemplate("Channel names are not specified and extraction of channels from images is switched off!"); + .fromTemplate("Expected channels are not specified and extraction of channels from images is switched off!"); } if (channelColorComponentsOrNull != null && channelDescriptionsOrNull == null) { @@ -352,7 +365,67 @@ abstract public class AbstractImageFileExtractor implements IImageFileExtractor protected final static List<ChannelDescription> extractChannelDescriptions( final Properties properties) { - return PlateStorageProcessor.extractChannelDescriptions(properties); + List<ChannelDescription> channelDescriptions = tryExtractChannelDescriptions(properties); + if (channelDescriptions == null) + { + throw new ConfigurationFailureException(String.format( + "Both '%s' and '%s' should be configured", CHANNEL_CODES, CHANNEL_LABELS)); + } + return channelDescriptions; + } + + private final static List<ChannelDescription> tryExtractChannelDescriptions( + final Properties properties) + { + List<String> names = PropertyUtils.tryGetList(properties, CHANNEL_NAMES); + List<String> codes = PropertyUtils.tryGetList(properties, CHANNEL_CODES); + List<String> labels = tryGetListOfLabels(properties, CHANNEL_LABELS); + if (names != null && (codes != null || labels != null)) + { + throw new ConfigurationFailureException(String.format( + "Configure either '%s' or ('%s','%s') but not both.", CHANNEL_NAMES, + CHANNEL_CODES, CHANNEL_LABELS)); + } + if (names != null) + { + List<ChannelDescription> descriptions = new ArrayList<ChannelDescription>(); + for (String name : names) + { + descriptions.add(new ChannelDescription(name)); + } + return descriptions; + } + if (codes == null || labels == null) + { + return null; + } + if (codes.size() != labels.size()) + { + throw new ConfigurationFailureException(String.format( + "Number of configured '%s' should be the same as number of '%s'.", + CHANNEL_CODES, CHANNEL_LABELS)); + } + List<ChannelDescription> descriptions = new ArrayList<ChannelDescription>(); + for (int i = 0; i < codes.size(); i++) + { + descriptions.add(new ChannelDescription(codes.get(i), labels.get(i))); + } + return descriptions; + } + + private final static List<String> tryGetListOfLabels(Properties properties, String propertyKey) + { + String itemsList = PropertyUtils.getProperty(properties, propertyKey); + if (itemsList == null) + { + return null; + } + String[] items = itemsList.split(","); + for (int i = 0; i < items.length; i++) + { + items[i] = items[i].trim(); + } + return Arrays.asList(items); } protected final static void ensureChannelExist( 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 bb5ae6b1252..52164f21bfe 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 @@ -19,7 +19,6 @@ package ch.systemsx.cisd.openbis.dss.etl; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; @@ -29,13 +28,14 @@ import javax.sql.DataSource; import net.lemnik.eodsql.QueryTool; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.time.DurationFormatUtils; import org.apache.log4j.Logger; import ch.systemsx.cisd.bds.hcs.Geometry; import ch.systemsx.cisd.common.collections.CollectionUtils; -import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.filesystem.FileOperations; import ch.systemsx.cisd.common.filesystem.FileUtilities; @@ -50,6 +50,7 @@ import ch.systemsx.cisd.etlserver.AbstractStorageProcessor; import ch.systemsx.cisd.etlserver.ITypeExtractor; import ch.systemsx.cisd.etlserver.hdf5.Hdf5Container; import ch.systemsx.cisd.etlserver.hdf5.HierarchicalStructureDuplicatorFileToHdf5; +import ch.systemsx.cisd.etlserver.utils.Unzipper; import ch.systemsx.cisd.openbis.dss.Constants; import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; import ch.systemsx.cisd.openbis.dss.etl.dto.ImageSeriesPoint; @@ -136,16 +137,6 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor protected static final String FILE_EXTRACTOR_PROPERTY = "file-extractor"; - // comma separated list of channel names, order matters - @Deprecated - public static final String CHANNEL_NAMES = "channel-names"; - - // comma separated list of channel codes, order matters - public static final String CHANNEL_CODES = "channel-codes"; - - // comma separated list of channel labels, order matters - public static final String CHANNEL_LABELS = "channel-labels"; - // how the original data should be stored private static enum OriginalDataStorageFormat { @@ -178,8 +169,6 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor protected final Geometry spotGeometry; - protected final List<ChannelDescription> channelDescriptions; - // --- internal state ------------- private IImagingQueryDAO currentTransaction; @@ -188,17 +177,14 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor public AbstractImageStorageProcessor(final Properties properties) { - this(getMandatorySpotGeometry(properties), extractChannelDescriptions(properties), - tryCreateImageExtractor(properties), properties); + this(getMandatorySpotGeometry(properties), tryCreateImageExtractor(properties), properties); } protected AbstractImageStorageProcessor(Geometry spotGeometry, - List<ChannelDescription> channelDescriptions, IImageFileExtractor imageFileExtractor, - Properties properties) + IImageFileExtractor imageFileExtractor, Properties properties) { super(properties); this.spotGeometry = spotGeometry; - this.channelDescriptions = channelDescriptions; this.imageFileExtractor = imageFileExtractor; this.thumbnailMaxWidth = PropertyUtils.getInt(properties, THUMBNAIL_MAX_WIDTH_PROPERTY, @@ -245,61 +231,6 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor return OriginalDataStorageFormat.valueOf(textValue.toUpperCase()); } - private final static List<String> tryGetListOfLabels(Properties properties, String propertyKey) - { - String itemsList = PropertyUtils.getProperty(properties, propertyKey); - if (itemsList == null) - { - return null; - } - String[] items = itemsList.split(","); - for (int i = 0; i < items.length; i++) - { - items[i] = items[i].trim(); - } - return Arrays.asList(items); - } - - public final static List<ChannelDescription> extractChannelDescriptions( - final Properties properties) - { - List<String> names = PropertyUtils.tryGetList(properties, CHANNEL_NAMES); - List<String> codes = PropertyUtils.tryGetList(properties, CHANNEL_CODES); - List<String> labels = tryGetListOfLabels(properties, CHANNEL_LABELS); - if (names != null && (codes != null || labels != null)) - { - throw new ConfigurationFailureException(String.format( - "Configure either '%s' or ('%s','%s') but not both.", CHANNEL_NAMES, - CHANNEL_CODES, CHANNEL_LABELS)); - } - if (names != null) - { - List<ChannelDescription> descriptions = new ArrayList<ChannelDescription>(); - for (String name : names) - { - descriptions.add(new ChannelDescription(name)); - } - return descriptions; - } - if (codes == null || labels == null) - { - throw new ConfigurationFailureException(String.format( - "Both '%s' and '%s' should be configured", CHANNEL_CODES, CHANNEL_LABELS)); - } - if (codes.size() != labels.size()) - { - throw new ConfigurationFailureException(String.format( - "Number of configured '%s' should be the same as number of '%s'.", - CHANNEL_CODES, CHANNEL_LABELS)); - } - List<ChannelDescription> descriptions = new ArrayList<ChannelDescription>(); - for (int i = 0; i < codes.size(); i++) - { - descriptions.add(new ChannelDescription(codes.get(i), labels.get(i))); - } - return descriptions; - } - private IImagingQueryDAO createQuery() { return QueryTool.getQuery(dataSource, IImagingQueryDAO.class); @@ -315,6 +246,13 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor assert incomingDataSetDirectory != null : "Incoming data set directory can not be null."; assert typeExtractor != null : "Unspecified IProcedureAndDataTypeExtractor implementation."; + File unzipedFolder = tryUnzipToFolder(incomingDataSetDirectory); + if (unzipedFolder != null) + { + return storeData(dataSetInformation, typeExtractor, mailClient, unzipedFolder, + rootDirectory); + } + ImageFileExtractionResult extractionResult = extractImages(dataSetInformation, incomingDataSetDirectory); @@ -330,6 +268,23 @@ abstract class AbstractImageStorageProcessor extends AbstractStorageProcessor return rootDirectory; } + private File tryUnzipToFolder(File incomingDataSetDirectory) + { + if (isZipFile(incomingDataSetDirectory) == false) + { + return null; + } + String outputDirName = FilenameUtils.getBaseName(incomingDataSetDirectory.getName()); + File output = new File(incomingDataSetDirectory.getParentFile(), outputDirName); + Status status = Unzipper.unzip(incomingDataSetDirectory, output, true); + if (status.isError()) + { + throw EnvironmentFailureException.fromTemplate("Cannot unzip '%s': %s", + incomingDataSetDirectory.getName(), status.tryGetErrorMessage()); + } + return output; + } + private void processImages(final File rootDirectory, List<AcquiredSingleImage> plateImages, File imagesInStoreFolder) { diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetInfo.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetInfo.java index 6a1e9c56dd5..5dbfb7b4603 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetInfo.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetInfo.java @@ -23,12 +23,15 @@ package ch.systemsx.cisd.openbis.dss.etl; */ public class HCSImageDatasetInfo extends HCSContainerDatasetInfo { + private final boolean storeChannelsOnExperimentLevel; + private final int tileRows, tileColumns; // has any well timepoints or depth stack images? private final boolean hasImageSeries; - public HCSImageDatasetInfo(HCSContainerDatasetInfo info, int tileRows, int tileColumns, + public HCSImageDatasetInfo(HCSContainerDatasetInfo info, + boolean storeChannelsOnExperimentLevel, int tileRows, int tileColumns, boolean hasImageSeries) { super.setContainerRows(info.getContainerRows()); @@ -36,6 +39,7 @@ public class HCSImageDatasetInfo extends HCSContainerDatasetInfo super.setContainerPermId(info.getContainerPermId()); super.setDatasetPermId(info.getDatasetPermId()); super.setExperimentPermId(info.getExperimentPermId()); + this.storeChannelsOnExperimentLevel = storeChannelsOnExperimentLevel; this.tileRows = tileRows; this.tileColumns = tileColumns; this.hasImageSeries = hasImageSeries; @@ -55,4 +59,9 @@ public class HCSImageDatasetInfo extends HCSContainerDatasetInfo { return hasImageSeries; } + + public boolean isStoreChannelsOnExperimentLevel() + { + return storeChannelsOnExperimentLevel; + } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetUploader.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetUploader.java index c516b94d50f..387cc0088be 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetUploader.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/HCSImageDatasetUploader.java @@ -46,16 +46,29 @@ public class HCSImageDatasetUploader extends AbstractImageDatasetUploader private void upload(HCSImageDatasetInfo info, List<AcquiredSingleImage> images, List<ImageFileExtractionResult.Channel> channels) { - ExperimentWithChannelsAndContainer basicStruct = - ImagingDatabaseHelper.getOrCreateExperimentWithChannelsAndContainer( - dao, info, channels); - long contId = basicStruct.getContainerId(); - ImagingChannelsMap channelsMap = basicStruct.getChannelsMap(); + long contId; + ImagingChannelsMap channelsMap = null; + if (info.isStoreChannelsOnExperimentLevel()) + { + ExperimentWithChannelsAndContainer basicStruct = + ImagingDatabaseHelper.getOrCreateExperimentWithChannelsAndContainer(dao, info, + channels); + contId = basicStruct.getContainerId(); + channelsMap = basicStruct.getChannelsMap(); + } else + { + contId = ImagingDatabaseHelper.getOrCreateExperimentAndContainer(dao, info); + } Long[][] spotIds = getOrCreateSpots(contId, info, images); ISpotProvider spotProvider = getSpotProvider(spotIds); long datasetId = createDataset(contId, info); + if (info.isStoreChannelsOnExperimentLevel() == false) + { + channelsMap = ImagingDatabaseHelper.createDatasetChannels(dao, datasetId, channels); + } + assert channelsMap != null; createImages(images, spotProvider, channelsMap, datasetId); } 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 60f98e67ae3..7b55b51818c 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 @@ -49,11 +49,17 @@ public class HCSImageFileExtractor extends AbstractImageFileExtractor public HCSImageFileExtractor(final Properties properties) { - super(properties, false); + super(properties, extractSkipChannelsWithoutImages(properties)); this.shouldValidatePlateName = PropertyUtils.getBoolean(properties, CHECK_PLATE_NAME_FLAG_PROPERTY_NAME, true); } + private static boolean extractSkipChannelsWithoutImages(Properties properties) + { + return PropertyUtils.getBoolean(properties, + PlateStorageProcessor.CHANNELS_PER_EXPERIMENT_PROPERTY, true) == false; + } + /** * Extracts the plate location from argument. Returns <code>null</code> if the operation fails. */ 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 index 35afb96026c..703d95c2406 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyBlackboxSeriesStorageProcessor.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/MicroscopyBlackboxSeriesStorageProcessor.java @@ -59,7 +59,7 @@ public class MicroscopyBlackboxSeriesStorageProcessor extends AbstractImageStora public MicroscopyBlackboxSeriesStorageProcessor(Properties properties) { - super(DEFAULT_WELL_GEOMETRY, DEFAULT_CHANNELS, new BlackboxSeriesImageFileExtractor( + super(DEFAULT_WELL_GEOMETRY, new BlackboxSeriesImageFileExtractor( properties), properties); } 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 c34381e90b4..7c6eb63c052 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 @@ -34,6 +34,7 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.filesystem.FileUtilities; import ch.systemsx.cisd.common.mail.IMailClient; import ch.systemsx.cisd.common.utilities.ClassUtils; +import ch.systemsx.cisd.common.utilities.PropertyUtils; import ch.systemsx.cisd.etlserver.IHCSImageFileAccepter; import ch.systemsx.cisd.openbis.dss.etl.HCSImageCheckList.FullLocation; import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; @@ -54,10 +55,18 @@ public final class PlateStorageProcessor extends AbstractImageStorageProcessor // a class of the old-style image extractor private static final String DEPRECATED_FILE_EXTRACTOR_PROPERTY = "deprecated-file-extractor"; + /** + * Optional boolean property. Defines if all image datasets in one experiment have the same + * channels or if each imported dataset can have different channels. By default true. + */ + static final String CHANNELS_PER_EXPERIMENT_PROPERTY = "define-channels-per-experiment"; + // --- private final ch.systemsx.cisd.etlserver.IHCSImageFileExtractor deprecatedImageFileExtractor; + private final boolean storeChannelsOnExperimentLevel; + public PlateStorageProcessor(Properties properties) { super(properties); @@ -71,6 +80,8 @@ public final class PlateStorageProcessor extends AbstractImageStorageProcessor { this.deprecatedImageFileExtractor = null; } + this.storeChannelsOnExperimentLevel = + PropertyUtils.getBoolean(properties, CHANNELS_PER_EXPERIMENT_PROPERTY, true); } private static final class HCSImageFileAccepter implements IHCSImageFileAccepter @@ -160,7 +171,8 @@ public final class PlateStorageProcessor extends AbstractImageStorageProcessor protected void validateImages(DataSetInformation dataSetInformation, IMailClient mailClient, File incomingDataSetDirectory, ImageFileExtractionResult extractionResult) { - HCSImageCheckList imageCheckList = createImageCheckList(dataSetInformation); + HCSImageCheckList imageCheckList = + createImageCheckList(dataSetInformation, extractionResult.getChannels()); checkImagesForDuplicates(extractionResult, imageCheckList); if (extractionResult.getInvalidFiles().size() > 0) { @@ -193,13 +205,14 @@ public final class PlateStorageProcessor extends AbstractImageStorageProcessor return HCSContainerDatasetInfo.getPlateGeometry(dataSetInformation); } - private HCSImageCheckList createImageCheckList(DataSetInformation dataSetInformation) + private HCSImageCheckList createImageCheckList(DataSetInformation dataSetInformation, + List<ch.systemsx.cisd.openbis.dss.etl.ImageFileExtractionResult.Channel> channels) { PlateDimension plateGeometry = getPlateGeometry(dataSetInformation); List<String> channelCodes = new ArrayList<String>(); - for (ChannelDescription cd : channelDescriptions) + for (ch.systemsx.cisd.openbis.dss.etl.ImageFileExtractionResult.Channel channel : channels) { - channelCodes.add(cd.getCode()); + channelCodes.add(channel.getCode()); } return new HCSImageCheckList(channelCodes, plateGeometry, spotGeometry); } @@ -250,6 +263,8 @@ public final class PlateStorageProcessor extends AbstractImageStorageProcessor IImageFileExtractor extractor = imageFileExtractor; if (extractor == null) { + List<ChannelDescription> channelDescriptions = + AbstractImageFileExtractor.extractChannelDescriptions(properties); extractor = adapt(deprecatedImageFileExtractor, incomingDataSetDirectory, channelDescriptions); @@ -297,7 +312,7 @@ public final class PlateStorageProcessor extends AbstractImageStorageProcessor HCSContainerDatasetInfo info = HCSContainerDatasetInfo.createScreeningDatasetInfo(dataSetInformation); boolean hasImageSeries = hasImageSeries(acquiredImages); - return new HCSImageDatasetInfo(info, spotGeometry.getRows(), spotGeometry.getColumns(), - hasImageSeries); + return new HCSImageDatasetInfo(info, storeChannelsOnExperimentLevel, + spotGeometry.getRows(), spotGeometry.getColumns(), hasImageSeries); } } 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 7b972f37a7f..c5ac54f8df1 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 @@ -125,14 +125,17 @@ public class ImagingDatasetLoader extends HCSDatasetLoader implements IImagingDa private ImgChannelDTO tryLoadChannel(String chosenChannelCode) { - if (containerOrNull != null) + // first we check if there are some channels defined at the dataset level (even for HCS one + // can decide in configuration about that) + ImgChannelDTO channel = query.tryGetChannelForDataset(dataset.getId(), chosenChannelCode); + // if not, we check at the experiment level + if (channel == null && containerOrNull != null) { - return query.tryGetChannelForExperiment(containerOrNull.getExperimentId(), - chosenChannelCode); - } else - { - return query.tryGetChannelForDataset(dataset.getId(), chosenChannelCode); + channel = + query.tryGetChannelForExperiment(containerOrNull.getExperimentId(), + chosenChannelCode); } + return channel; } private void validateChannelStackReference(ImageChannelStackReference channelStackReference) 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 f1b54ba8a10..b16f7d65062 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 @@ -116,7 +116,8 @@ public class ImageChannelsUtils // NOTE: never merges the overlays, draws each channel separately (merging looses // transparency and is slower) List<BufferedImage> overlayImages = - getSingleImages(overlayChannels, overlaySize, datasetDirectoryProvider); + getSingleImagesSkipNonExisting(overlayChannels, overlaySize, + datasetDirectoryProvider); for (BufferedImage overlayImage : overlayImages) { if (image != null) @@ -131,7 +132,7 @@ public class ImageChannelsUtils return createResponseContentStream(image, null); } - private static List<BufferedImage> getSingleImages( + private static List<BufferedImage> getSingleImagesSkipNonExisting( DatasetAcquiredImagesReference imagesReference, RequestedImageSize imageSize, IDatasetDirectoryProvider datasetDirectoryProvider) { @@ -139,7 +140,7 @@ public class ImageChannelsUtils createImageChannelsUtils(imagesReference, datasetDirectoryProvider, imageSize); boolean mergeAllChannels = utils.isMergeAllChannels(imagesReference); List<AbsoluteImageReference> imageContents = - utils.fetchImageContents(imagesReference, mergeAllChannels); + utils.fetchImageContents(imagesReference, mergeAllChannels, true); return calculateSingleImages(imageContents); } @@ -198,7 +199,7 @@ public class ImageChannelsUtils { boolean mergeAllChannels = isMergeAllChannels(imageChannels); List<AbsoluteImageReference> imageContents = - fetchImageContents(imageChannels, mergeAllChannels); + fetchImageContents(imageChannels, mergeAllChannels, false); return mergeChannels(imageContents, transform, mergeAllChannels); } @@ -227,16 +228,30 @@ public class ImageChannelsUtils return true; } + /** + * @param skipNonExisting if true references to non-existing images are ignored, otherwise an + * exception is thrown + */ private List<AbsoluteImageReference> fetchImageContents( - DatasetAcquiredImagesReference imagesReference, boolean mergeAllChannels) + DatasetAcquiredImagesReference imagesReference, boolean mergeAllChannels, + boolean skipNonExisting) { List<String> channelCodes = getChannelCodes(imagesReference); List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>(); for (String channelCode : channelCodes) { + ImageChannelStackReference channelStackReference = + imagesReference.getChannelStackReference(); AbsoluteImageReference image = - getImageReference(imagesReference.getChannelStackReference(), channelCode); - images.add(image); + imageAccessor.tryGetImage(channelCode, channelStackReference, imageSizeLimit); + if (image == null && skipNonExisting == false) + { + throw createImageNotFoundException(channelStackReference, channelCode); + } + if (image != null) + { + images.add(image); + } } // Optimization for a case where all channels are on one image @@ -331,7 +346,7 @@ public class ImageChannelsUtils new ImageChannelsUtils(imageAccessor, imageSizeLimitOrNull); boolean mergeAllChannels = imageChannelsUtils.isMergeAllChannels(imagesReference); List<AbsoluteImageReference> imageContents = - imageChannelsUtils.fetchImageContents(imagesReference, mergeAllChannels); + imageChannelsUtils.fetchImageContents(imagesReference, mergeAllChannels, false); IContent rawContent = tryGetRawContent(convertToPng, imageContents); if (rawContent != null) @@ -594,24 +609,13 @@ public class ImageChannelsUtils // --------- common - /** - * @throw {@link EnvironmentFailureException} when image does not exist - */ - private AbsoluteImageReference getImageReference( + private EnvironmentFailureException createImageNotFoundException( ImageChannelStackReference channelStackReference, String chosenChannelCode) { - AbsoluteImageReference image = - imageAccessor.tryGetImage(chosenChannelCode, channelStackReference, imageSizeLimit); - if (image != null) - { - return image; - } else - { - throw EnvironmentFailureException.fromTemplate( - "No " + (imageSizeLimit.isThumbnailRequired() ? "thumbnail" : "image") - + " found for channel stack %s and channel %s", channelStackReference, - chosenChannelCode); - } + return EnvironmentFailureException.fromTemplate( + "No " + (imageSizeLimit.isThumbnailRequired() ? "thumbnail" : "image") + + " found for channel stack %s and channel %s", channelStackReference, + chosenChannelCode); } /** 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 d0e56ba8747..72422a5d8f0 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 @@ -82,13 +82,12 @@ public class HCSDatasetLoader implements IImageDatasetLoader private List<ImgChannelDTO> loadChannels() { - if (containerOrNull != null) - { - return query.getChannelsByExperimentId(containerOrNull.getExperimentId()); - } else + List<ImgChannelDTO> myChannels = query.getChannelsByDatasetId(dataset.getId()); + if (myChannels.size() == 0 && containerOrNull != null) { - return query.getChannelsByDatasetId(dataset.getId()); + myChannels = query.getChannelsByExperimentId(containerOrNull.getExperimentId()); } + return myChannels; } private String tryGetImageTransformerFactorySignatureForMergedChannels() -- GitLab