From a9f2cd962218f090e846aa08cf68f2ba9a1cf9fe Mon Sep 17 00:00:00 2001 From: tpylak <tpylak> Date: Tue, 13 Sep 2011 14:15:53 +0000 Subject: [PATCH] LMS-2502 image transformations: server side + client URLs SVN: 22922 --- .../AbstractDatasetDownloadServlet.java | 4 +- .../dss/generic/shared/utils/ImageUtil.java | 66 +++-- .../generic/shared/utils/ImageUtilTest.java | 4 +- .../generic/shared/utils/ThumbnailTiming.java | 2 +- .../dss/etl/AbsoluteImageReference.java | 8 +- .../dss/etl/Hdf5ThumbnailGenerator.java | 3 +- .../dss/etl/ImagingDatabaseHelper.java | 27 ++ .../dss/etl/ImagingDatabaseVersionHolder.java | 2 +- .../dss/etl/dataaccess/IImagingQueryDAO.java | 12 +- .../etl/dataaccess/ImagingDatasetLoader.java | 30 ++- .../etl/dto/ImageTransfomationFactories.java | 24 +- .../etl/dto/api/v1/SimpleImageDataConfig.java | 50 +++- .../ImageTransformationBuffer.java | 2 +- .../ImageGenerationDescriptionFactory.java | 28 ++- .../server/images/ImageChannelsUtils.java | 237 +++++++++--------- .../dto/DatasetAcquiredImagesReference.java | 59 ++++- .../dto/ImageGenerationDescription.java | 22 +- .../images/dto/ImageTransformationParams.java | 67 +++++ .../server/plugins/ColorRangeCalculator.java | 1 - ...llBrightnessEqualizerProcessingPlugin.java | 2 +- .../server/DssServiceRpcScreening.java | 108 +++++--- .../shared/api/v1/LoadImageConfiguration.java | 36 ++- .../openbis/dss/shared/DssScreeningUtils.java | 7 + .../v1/ExampleImageTransformerFactory.java | 31 +++ .../dto/LogicalImageReference.java | 6 +- .../detailviewers/utils/ImageUrlUtils.java | 16 +- .../shared/basic/dto/ImageChannel.java | 7 +- .../basic/dto/ImageDatasetParameters.java | 41 ++- .../basic/dto/ImageTransformationInfo.java | 16 +- .../shared/basic/dto/ScreeningConstants.java | 2 + .../shared/imaging/HCSDatasetLoader.java | 92 +++++-- .../dataaccess/IImagingReadonlyQueryDAO.java | 74 ++++-- .../dataaccess/IImagingTransformerDAO.java | 20 +- .../imaging/dataaccess/ImgChannelDTO.java | 2 +- .../dataaccess/ImgImageTransformationDTO.java | 124 +++++++++ .../sql/imaging/postgresql/014/schema-014.sql | 2 +- .../migration/migration-013-014.sql | 2 +- .../etl/dataaccess/ImagingQueryDAOTest.java | 137 ++++++++++ ...ImageGenerationDescriptionFactoryTest.java | 19 +- .../server/images/ImageChannelsUtilsTest.java | 45 +++- .../server/DssServiceRpcScreeningTest.java | 33 ++- 41 files changed, 1162 insertions(+), 308 deletions(-) create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageTransformationParams.java create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgImageTransformationDTO.java diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java index 28502c54b29..fcf3ed43780 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDatasetDownloadServlet.java @@ -295,7 +295,7 @@ abstract public class AbstractDatasetDownloadServlet extends HttpServlet protected static final BufferedImage createThumbnail(IHierarchicalContentNode fileNode, Size thumbnailSize) { - BufferedImage image = ImageUtil.loadImage(fileNode); + BufferedImage image = ImageUtil.loadImageForDisplay(fileNode); return createThumbnail(image, thumbnailSize); } @@ -303,7 +303,7 @@ abstract public class AbstractDatasetDownloadServlet extends HttpServlet { int width = thumbnailSize.getWidth(); int height = thumbnailSize.getHeight(); - return ImageUtil.createThumbnail(image, width, height); + return ImageUtil.createThumbnailForDisplay(image, width, height); } // if display mode describes a thumbnail return its expected size diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java index b1b09962833..10abc68c5ae 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtil.java @@ -24,6 +24,7 @@ import static ch.systemsx.cisd.common.utilities.DataTypeUtil.TIFF_FILE; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.OutputStream; @@ -47,6 +48,8 @@ import ar.com.hjg.pngj.PngWriter; import ch.rinn.restrictions.Private; import ch.systemsx.cisd.base.io.IRandomAccessFile; import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; +import ch.systemsx.cisd.common.image.IntensityRescaling; +import ch.systemsx.cisd.common.image.IntensityRescaling.Levels; import ch.systemsx.cisd.common.io.FileBasedContent; import ch.systemsx.cisd.common.io.IContent; import ch.systemsx.cisd.common.io.hierarchical_content.HierarchicalNodeBasedContent; @@ -57,7 +60,6 @@ import ch.systemsx.cisd.imagereaders.IReadParams; import ch.systemsx.cisd.imagereaders.ImageID; import ch.systemsx.cisd.imagereaders.ImageReaderConstants; import ch.systemsx.cisd.imagereaders.ImageReaderFactory; -import ch.systemsx.cisd.imagereaders.ReadParams; /** * Utility function on images. @@ -83,7 +85,7 @@ public class ImageUtil handle.mark(MAX_READ_AHEAD); try { - return loadJavaAdvancedImagingTiff(handle, imageID, false); + return loadJavaAdvancedImagingTiff(handle, imageID); } catch (RuntimeException ex) { if (imageID.equals(ImageID.NULL)) @@ -110,13 +112,9 @@ public class ImageUtil /** * For experts only! Loads some kinds of TIFF images handled by JAI library. - * - * @param allow16BitGrayscaleModel if true and the image is 16 bit grayscale, then the - * appropriate buffered imaged type will be used, otherwise the image will be - * converted to 24 bits RGB. Useful if access to original pixel values is needed. */ public static BufferedImage loadJavaAdvancedImagingTiff(IRandomAccessFile handle, - ImageID imageID, boolean allow16BitGrayscaleModel) throws EnvironmentFailureException + ImageID imageID) throws EnvironmentFailureException { IImageReader imageReader = ImageReaderFactory.tryGetReader(ImageReaderConstants.JAI_LIBRARY, "tiff"); @@ -126,11 +124,9 @@ public class ImageUtil .fromTemplate("Cannot find JAI image decoder for TIFF files."); } - ReadParams readParams = new ReadParams(); - readParams.setAllow16BitGrayscaleModel(allow16BitGrayscaleModel); try { - return imageReader.readImage(handle, imageID, readParams); + return imageReader.readImage(handle, imageID, null); } catch (Exception ex) { throw EnvironmentFailureException.fromTemplate("Cannot decode image.", ex); @@ -194,11 +190,14 @@ public class ImageUtil * Loads the image specified by <var>imageIdOrNull</var> from the given </var>inputStream</var>. * Supported images formats are GIF, JPG, PNG, and TIFF. The input stream will be closed after * loading. + * <p> + * Note that the original color depth will be kept, so e.g. 12 or 16 bit grayscale images will + * not be converted to RGB. * * @throws IllegalArgumentException if the input stream doesn't start with a magic number * identifying supported image format. */ - public static BufferedImage loadImage(IContent content, String imageIdOrNull, + public static BufferedImage loadUnchangedImage(IContent content, String imageIdOrNull, String imageLibraryNameOrNull, String imageLibraryReaderNameOrNull, IReadParams params) { assert (imageLibraryReaderNameOrNull == null || imageLibraryNameOrNull != null) : "if image reader " @@ -416,7 +415,7 @@ public class ImageUtil @Private static BufferedImage loadImage(IContent content) { - return loadImage(content, null, null, null, null); + return loadUnchangedImage(content, null, null, null, null); } /** @@ -424,26 +423,54 @@ public class ImageUtil * * @throws IllegalArgumentException if the file isn't a valid image file. */ - public static BufferedImage loadImage(IHierarchicalContentNode fileNode) + public static BufferedImage loadImageForDisplay(IHierarchicalContentNode fileNode) { if (fileNode.exists() == false) { throw new IllegalArgumentException("File does not exist: " + fileNode.getRelativePath()); } - return loadImage(new HierarchicalNodeBasedContent(fileNode)); + BufferedImage result = loadImage(new HierarchicalNodeBasedContent(fileNode)); + result = convertForDisplayIfNecessary(result); + return result; } /** * Re-scales the image to be the biggest one which fits into a (0,0,maxWidth, maxHeight) * rectangle. Preserves the aspect ratio. If the rectangle is bigger than the image does - * nothing. Ignores alpha channel of the original image. + * nothing. + * <p> + * If the specified image uses grayscale with color depth larger then 8 bits, conversion to 8 + * bits grayscale is done. + * </p> * * @param maxWidth Maximum width of the result image. * @param maxHeight Maximum height of the result image. */ - public static BufferedImage createThumbnail(BufferedImage image, int maxWidth, int maxHeight) + public static BufferedImage createThumbnailForDisplay(BufferedImage image, int maxWidth, + int maxHeight) + { + BufferedImage result = rescale(image, maxWidth, maxHeight, true, false); + result = convertForDisplayIfNecessary(result); + return result; + } + + /** + * If the specified image uses grayscale with color depth larger then 8 bits, conversion to 8 + * bits grayscale is done. Otherwise the original image is returned. + */ + public static BufferedImage convertForDisplayIfNecessary(BufferedImage image) { - return rescale(image, maxWidth, maxHeight, true, false); + ColorModel colorModel = image.getColorModel(); + // is grayscale? + if (colorModel.getColorSpace().getNumComponents() == 1) + { + if (colorModel.getPixelSize() > 8) + { + Levels intensityRange = IntensityRescaling.computeLevels(image, 0); + return IntensityRescaling.rescaleIntensityLevelTo8Bits(image, intensityRange); + } + } + return image; } /** @@ -480,10 +507,7 @@ public class ImageUtil int thumbnailWidth = (int) (scale * width + 0.5); int thumbnailHeight = (int) (scale * height + 0.5); - // preserve alpha channel if it was present before - int imageType = - image.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB - : BufferedImage.TYPE_INT_RGB; + int imageType = image.getType(); BufferedImage thumbnail = new BufferedImage(thumbnailWidth, thumbnailHeight, imageType); Graphics2D graphics2D = thumbnail.createGraphics(); Object renderingHint = diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java index ae4865d5369..49159712a49 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ImageUtilTest.java @@ -128,8 +128,8 @@ public class ImageUtilTest extends AssertJUnit public void testCreateThumbnail() { BufferedImage image = loadImageByFile("gif-example.gif"); - assertImageSize(79, 100, ImageUtil.createThumbnail(image, 200, 100)); - assertImageSize(100, 127, ImageUtil.createThumbnail(image, 100, 200)); + assertImageSize(79, 100, ImageUtil.createThumbnailForDisplay(image, 200, 100)); + assertImageSize(100, 127, ImageUtil.createThumbnailForDisplay(image, 100, 200)); } @Test diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java index 9ebff8850e3..07c9f281317 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/ThumbnailTiming.java @@ -43,7 +43,7 @@ public class ThumbnailTiming BufferedImage image = ImageUtil.loadImage(file); stopWatch.stop(); stopWatch.start("Convert"); - BufferedImage thumbnail = ImageUtil.createThumbnail(image, 120, 60); + BufferedImage thumbnail = ImageUtil.createThumbnailForDisplay(image, 120, 60); stopWatch.stop(); stopWatch.start("Write"); try diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java index 507bc8cf4cb..8163116a768 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/AbsoluteImageReference.java @@ -95,16 +95,16 @@ public class AbsoluteImageReference extends AbstractImageReference } } - public BufferedImage getImage() + public BufferedImage getUnchangedImage() { if (image == null) { - image = loadImage(content, tryGetImageID(), imageLibraryOrNull); + image = loadUnchangedImage(content, tryGetImageID(), imageLibraryOrNull); } return image; } - static BufferedImage loadImage(IContent content, String imageIdOrNull, + static BufferedImage loadUnchangedImage(IContent content, String imageIdOrNull, ImageLibraryInfo imageLibraryOrNull) { String imageLibraryNameOrNull = null; @@ -114,7 +114,7 @@ public class AbsoluteImageReference extends AbstractImageReference imageLibraryNameOrNull = imageLibraryOrNull.getName(); imageLibraryReaderNameOrNull = imageLibraryOrNull.getReaderName(); } - return ImageUtil.loadImage(content, imageIdOrNull, imageLibraryNameOrNull, + return ImageUtil.loadUnchangedImage(content, imageIdOrNull, imageLibraryNameOrNull, imageLibraryReaderNameOrNull, null); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java index bb018f9b2bd..f3e8e45a8c4 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/Hdf5ThumbnailGenerator.java @@ -201,8 +201,7 @@ class Hdf5ThumbnailGenerator implements IHDF5WriterClient private BufferedImage loadImage(File imageFile, String imageIdOrNull) { - // NOTE 2011-04-20, Tomasz Pylak: support paged tiffs when generating thumbnails - return AbsoluteImageReference.loadImage(new FileBasedContent(imageFile), imageIdOrNull, + return AbsoluteImageReference.loadUnchangedImage(new FileBasedContent(imageFile), imageIdOrNull, imageLibraryOrNull); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java index 7fd1e6a062f..b9c9b8c8256 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseHelper.java @@ -16,6 +16,7 @@ package ch.systemsx.cisd.openbis.dss.etl; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -27,10 +28,12 @@ import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.Channel; import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.ChannelColor; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.ImageTransformation; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgExperimentDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO; /** * Helper class for retrieving and/or creating entities associated with the imaging database: @@ -409,9 +412,33 @@ public class ImagingDatabaseHelper ImgChannelDTO channelDTO = makeChannelDTO(channel, channelOwner); long channelId = dao.addChannel(channelDTO); channelDTO.setId(channelId); + saveChannelImageTransformations(channelId, channel.getAvailableTransformations()); return channelDTO; } + private void saveChannelImageTransformations(long channelId, + ImageTransformation[] transformations) + { + if (transformations.length == 0) + { + return; + } + List<ImgImageTransformationDTO> transformationDTOs = + new ArrayList<ImgImageTransformationDTO>(); + for (ImageTransformation transformation : transformations) + { + transformationDTOs.add(createTransformationDTO(transformation, channelId)); + } + dao.addImageTransformations(transformationDTOs); + } + + private ImgImageTransformationDTO createTransformationDTO(ImageTransformation tr, + long channelId) + { + return new ImgImageTransformationDTO(tr.getCode(), tr.getLabel(), tr.getDescription(), + tr.isEditable(), channelId, tr.getImageTransformerFactory()); + } + private static ImgChannelDTO makeChannelDTO(Channel channel, ChannelOwner channelOwner) { ChannelColor channelColor = channel.tryGetChannelColor(); diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java index c1eb6f00962..af002324059 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java @@ -28,7 +28,7 @@ public class ImagingDatabaseVersionHolder implements IDatabaseVersionHolder public String getDatabaseVersion() { - return "013"; // changed in S108 + return "014"; // changed in S115 } } 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 98ad698efd2..946d40f0b3e 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 @@ -33,6 +33,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFe import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureValuesDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureVocabularyTermDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgSpotDTO; /** @@ -69,8 +70,8 @@ public interface IImagingQueryDAO extends TransactionQuery, IImagingReadonlyQuer @Select("insert into EXPERIMENTS (PERM_ID) values (?{1}) returning ID") public long addExperiment(String experimentPermId); - @Select("insert into CHANNELS (LABEL, CODE, DESCRIPTION, WAVELENGTH, DS_ID, EXP_ID, COLOR) values " - + "(?{1.label}, ?{1.code}, ?{1.description}, ?{1.wavelength}, ?{1.datasetId}, ?{1.experimentId}, ?{1.dbChannelColor}) returning ID") + @Select("insert into CHANNELS (CODE, LABEL, DESCRIPTION, WAVELENGTH, DS_ID, EXP_ID, COLOR) values " + + "(?{1.code}, ?{1.label}, ?{1.description}, ?{1.wavelength}, ?{1.datasetId}, ?{1.experimentId}, ?{1.dbChannelColor}) returning ID") public long addChannel(ImgChannelDTO channel); @Select("insert into CONTAINERS (PERM_ID, SPOTS_WIDTH, SPOTS_HEIGHT, EXPE_ID) values " @@ -85,6 +86,13 @@ public interface IImagingQueryDAO extends TransactionQuery, IImagingReadonlyQuer + "?{1.imageLibraryName}, ?{1.imageReaderName}) returning ID") public long addDataset(ImgDatasetDTO dataset); + @Update(sql = "insert into IMAGE_TRANSFORMATIONS(CODE, LABEL, DESCRIPTION, IMAGE_TRANSFORMER_FACTORY, IS_EDITABLE, CHANNEL_ID) values " + + "(?{1.code}, ?{1.label}, ?{1.description}, ?{1.serializedImageTransformerFactory}, ?{1.isEditable}, ?{1.channelId})", batchUpdate = true) + public void addImageTransformations(List<ImgImageTransformationDTO> imageTransformations); + + @Update(sql = "delete from IMAGE_TRANSFORMATIONS where CODE = ?{1} and CHANNEL_ID = ?{2}") + public void removeImageTransformation(String transformationCode, long channelId); + @Select("insert into SPOTS (X, Y, CONT_ID) values " + "(?{1.column}, ?{1.row}, ?{1.containerId}) returning ID") public long addSpot(ImgSpotDTO spot); 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 402a58583c8..28b691b5855 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 @@ -17,6 +17,9 @@ package ch.systemsx.cisd.openbis.dss.etl.dataaccess; import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.apache.commons.lang.StringUtils; @@ -42,6 +45,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgCh import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO; /** * {@link HCSDatasetLoader} extension with code for handling images. @@ -98,7 +102,7 @@ public class ImagingDatasetLoader extends HCSDatasetLoader implements IImagingDa } AbsoluteImageReference imgRef = createAbsoluteImageReference(imageDTO, channel, imageSize, thumbnailFetched); - if (thumbnailFetched && isThumbnailsTooSmall(imageSize, imgRef.getImage())) + if (thumbnailFetched && isThumbnailsTooSmall(imageSize, imgRef.getUnchangedImage())) { imageDTO = tryGetOriginalImage(channel.getId(), channelStackReference, datasetId); if (imageDTO != null) @@ -143,11 +147,33 @@ public class ImagingDatasetLoader extends HCSDatasetLoader implements IImagingDa ImageTransfomationFactories imageTransfomationFactories = new ImageTransfomationFactories(); imageTransfomationFactories .setForMergedChannels(tryGetImageTransformerFactoryForMergedChannels()); - imageTransfomationFactories.setForChannel(channel.tryGetImageTransformerFactory()); + + Map<String, IImageTransformerFactory> singleChannelMap = + getAvailableImageTransformationsForChannel(channel); + imageTransfomationFactories.setForChannel(singleChannelMap); + imageTransfomationFactories.setForImage(imageDTO.tryGetImageTransformerFactory()); return imageTransfomationFactories; } + private Map<String, IImageTransformerFactory> getAvailableImageTransformationsForChannel( + ImgChannelDTO channel) + { + List<ImgImageTransformationDTO> availableImageTransformations = + availableImageTransformationsMap.get(channel.getId()); + Map<String, IImageTransformerFactory> singleChannelMap = + new HashMap<String, IImageTransformerFactory>(); + if (availableImageTransformations != null) + { + for (ImgImageTransformationDTO transformation : availableImageTransformations) + { + singleChannelMap.put(transformation.getCode(), + transformation.getImageTransformerFactory()); + } + } + return singleChannelMap; + } + private IImageTransformerFactory tryGetImageTransformerFactoryForMergedChannels() { IImageTransformerFactory imageTransformerFactory = dataset.tryGetImageTransformerFactory(); diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java index 3a6aa9f26c6..43caf51acac 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/ImageTransfomationFactories.java @@ -16,6 +16,8 @@ package ch.systemsx.cisd.openbis.dss.etl.dto; +import java.util.Map; + import ch.systemsx.cisd.base.image.IImageTransformerFactory; /** @@ -25,8 +27,8 @@ import ch.systemsx.cisd.base.image.IImageTransformerFactory; */ public class ImageTransfomationFactories { - // applied when a single channel is displayed - private IImageTransformerFactory singleChannel; + // transformation with a specified code can be applied when a single channel is displayed + private Map<String/* transformation code */, IImageTransformerFactory> singleChannelMap; // applied when all channels of the image are merged private IImageTransformerFactory mergedChannels; @@ -34,14 +36,24 @@ public class ImageTransfomationFactories // individual transformation for the image private IImageTransformerFactory image; - public IImageTransformerFactory tryGetForChannel(boolean areChannelsMerged) + public IImageTransformerFactory tryGetForChannel(String transformationCodeOrNull) + { + if (transformationCodeOrNull == null) + { + return null; + } + return singleChannelMap.get(transformationCodeOrNull); + } + + public IImageTransformerFactory tryGetForMerged() { - return areChannelsMerged ? mergedChannels : singleChannel; + return mergedChannels; } - public void setForChannel(IImageTransformerFactory transformerFactoryForChannel) + public void setForChannel( + Map<String/* transformation code */, IImageTransformerFactory> singleChannelMap) { - this.singleChannel = transformerFactoryForChannel; + this.singleChannelMap = singleChannelMap; } public void setForMergedChannels(IImageTransformerFactory transformerFactoryForMergedChannels) diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java index 10c6c9ac0aa..e2d4450e71e 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/SimpleImageDataConfig.java @@ -24,6 +24,8 @@ import ch.systemsx.cisd.base.image.IImageTransformerFactory; import ch.systemsx.cisd.common.shared.basic.utils.StringUtils; import ch.systemsx.cisd.openbis.dss.etl.dto.ImageLibraryInfo; import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.transformations.ConvertToolImageTransformerFactory; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.transformations.ImageTransformationBuffer; +import ch.systemsx.cisd.openbis.generic.shared.basic.CodeNormalizer; import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants; @@ -34,14 +36,18 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConst */ abstract public class SimpleImageDataConfig { - // --- one of the following two methods have to be overridden ----------------- + // --- one of the following two methods has to be overridden ----------------- /** - * Extracts tile number, channel code and well code for a given relative path to an image. + * Extracts tile number, channel code and well code for a given relative path to a single image. + * This method should overridden to deal with files containing single images. It is ignored if + * {@link #extractImagesMetadata(String, List)} is overridden as well. * <p> - * Will be called for each file found in the incoming directory which has the extension returned - * by {@link #getRecognizedImageExtensions()}. + * It will be called for each file found in the incoming directory which has the extension + * returned by {@link #getRecognizedImageExtensions()}. * </p> + * To deal with image containers (like multi-page TIFF files) override + * {@link #extractImagesMetadata(String, List)} instead, otherwise leave that method unchanged. */ public ImageMetadata extractImageMetadata(String imagePath) { @@ -50,12 +56,14 @@ abstract public class SimpleImageDataConfig } /** - * Returns meta-data for each image contained in specified image file path. This method returns - * just the {@link ImageMetadata} object returned by {@link #extractImageMetadata(String)}. + * Returns meta-data for each image in the specified image container. This method should + * overridden to deal with container images (like multi-page TIFF files). * <p> - * In case of a image container file format (like multi-page TIFF) this method should - * overridden. + * This implementation returns the result of {@link #extractImageMetadata(String)} wrapped in an + * array, image identifiers are ignored. + * </p> * + * @param imagePath path to the single image or container of many images * @param imageIdentifiers Identifiers of all images contained in the image file. */ public ImageMetadata[] extractImagesMetadata(String imagePath, @@ -103,13 +111,33 @@ abstract public class SimpleImageDataConfig * <p> * Creates channel description for a given code. Can be overridden in subclasses. * </p> - * By default rhe channel label will be equal to the code. Channel color returned by + * By default the channel label will be equal to the code. Channel color returned by * {@link #getChannelColor(String)} will be used. */ public Channel createChannel(String channelCode) { - ChannelColor channelColor = getChannelColor(channelCode.toUpperCase()); - return new Channel(channelCode, channelCode, channelColor); + ChannelColor channelColor = getChannelColor(channelCode); + ImageTransformation[] availableTransformations = + getAvailableChannelTransformations(channelCode); + String label = channelCode; + String normalizedChannelCode = CodeNormalizer.normalize(channelCode); + Channel channel = new Channel(normalizedChannelCode, label, channelColor); + channel.setAvailableTransformations(availableTransformations); + return channel; + } + + /** + * Sets available transformations which can be applied to images of the specified channel (on + * user's request). + * <p> + * Can be overridden in subclasses. The easiest way to create transformations is to use + * {@link ImageTransformationBuffer} class. + * <p> + * By default returns null. + */ + public ImageTransformation[] getAvailableChannelTransformations(String channelCode) + { + return null; } /** diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java index 75edc13c1b6..b63e6071c25 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/v1/transformations/ImageTransformationBuffer.java @@ -33,7 +33,7 @@ import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.ImageTransformation; */ public class ImageTransformationBuffer { - private static final String PREDEFINED_TRANSFORMATIONS_CODE_PREFIX = "PREDEFINED_"; + private static final String PREDEFINED_TRANSFORMATIONS_CODE_PREFIX = "_"; private final List<ImageTransformation> imageTransformations; diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java index 27a0b7f970d..38c3aee6c83 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactory.java @@ -80,7 +80,11 @@ class ImageGenerationDescriptionFactory throw new UserFailureException( "Neither channels nor segmentation objects have been specified!"); } - return new ImageGenerationDescription(channelsToMerge, overlayChannels, sessionId, + + String singleChannelTransformationCodeOrNull = + request.getParameter(ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM); + return new ImageGenerationDescription(channelsToMerge, + singleChannelTransformationCodeOrNull, overlayChannels, sessionId, thumbnailSizeOrNull); } @@ -95,7 +99,7 @@ class ImageGenerationDescriptionFactory { String datasetCode = entry.getKey(); List<String> channels = entry.getValue(); - overlayChannels.add(new DatasetAcquiredImagesReference(datasetCode, + overlayChannels.add(DatasetAcquiredImagesReference.createForManyChannels(datasetCode, channelStackReference, channels)); } return overlayChannels; @@ -143,17 +147,29 @@ class ImageGenerationDescriptionFactory { return null; } - List<String> channelsOrNull = null; if (isMergedChannels == false) { - channelsOrNull = tryGetParams(request, ImageServletUrlParameters.CHANNEL_PARAM); + List<String> channelsOrNull = + tryGetParams(request, ImageServletUrlParameters.CHANNEL_PARAM); if (channelsOrNull == null) { return null; // no channels to merge at all } + if (channelsOrNull.size() > 1) + { + return DatasetAcquiredImagesReference.createForManyChannels(datasetCode, + channelStackReference, channelsOrNull); + } else + { + String channelCode = channelsOrNull.get(0); + return DatasetAcquiredImagesReference.createForSingleChannel(datasetCode, + channelStackReference, channelCode); + } + } else + { + return DatasetAcquiredImagesReference.createForMergedChannels(datasetCode, + channelStackReference); } - return new DatasetAcquiredImagesReference(datasetCode, channelStackReference, - channelsOrNull); } private static boolean isMergedChannels(HttpServletRequest request) 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 4bef593d90d..5902a3a4dc7 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 @@ -21,15 +21,11 @@ import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; -import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import javax.imageio.ImageIO; - import org.apache.log4j.Logger; import ch.rinn.restrictions.Private; @@ -49,6 +45,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ResponseContentStream; import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.DatasetAcquiredImagesReference; import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageChannelStackReference; import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageGenerationDescription; +import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageTransformationParams; import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.RequestedImageSize; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size; import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil; @@ -109,7 +106,10 @@ public class ImageChannelsUtils if (imageChannels != null) { RequestedImageSize imageSize = new RequestedImageSize(thumbnailSizeOrNull, false); - image = calculateBufferedImage(imageChannels, datasetDirectoryProvider, imageSize); + image = + calculateBufferedImage(imageChannels, + params.tryGetSingleChannelTransformationCode(), + datasetDirectoryProvider, imageSize); } RequestedImageSize overlaySize = calcOverlaySize(image, thumbnailSizeOrNull); @@ -147,7 +147,9 @@ public class ImageChannelsUtils boolean mergeAllChannels = utils.isMergeAllChannels(imagesReference); List<AbsoluteImageReference> imageContents = utils.fetchImageContents(imagesReference, mergeAllChannels, true); - return calculateSingleImages(imageContents, true, mergeAllChannels); + ImageTransformationParams transformationInfo = + new ImageTransformationParams(true, mergeAllChannels, null); + return calculateSingleImagesForDisplay(imageContents, transformationInfo); } private static RequestedImageSize getSize(BufferedImage img, boolean highQuality) @@ -187,11 +189,19 @@ public class ImageChannelsUtils private static BufferedImage calculateBufferedImage( DatasetAcquiredImagesReference imageChannels, + String singleChannelTransformationCodeOrNull, IDatasetDirectoryProvider datasetDirectoryProvider, RequestedImageSize imageSizeLimit) { ImageChannelsUtils imageChannelsUtils = createImageChannelsUtils(imageChannels, datasetDirectoryProvider, imageSizeLimit); - return imageChannelsUtils.calculateBufferedImage(imageChannels, true); + + boolean useMergedChannelsTransformation = + imageChannelsUtils.isMergeAllChannels(imageChannels); + ImageTransformationParams transformationInfo = + new ImageTransformationParams(true, useMergedChannelsTransformation, + singleChannelTransformationCodeOrNull); + + return imageChannelsUtils.calculateBufferedImage(imageChannels, transformationInfo); } private static ImageChannelsUtils createImageChannelsUtils( @@ -205,50 +215,29 @@ public class ImageChannelsUtils @Private BufferedImage calculateBufferedImage(DatasetAcquiredImagesReference imageChannels, - boolean transform) + ImageTransformationParams transformationInfo) { boolean mergeAllChannels = isMergeAllChannels(imageChannels); List<AbsoluteImageReference> imageContents = fetchImageContents(imageChannels, mergeAllChannels, false); - return mergeChannels(imageContents, transform, mergeAllChannels); + return calculateBufferedImage(imageContents, transformationInfo); } - // Check if all exiting channels of the image should be merged (and not a single one or a - // subset). - // We want to treat the case where merged channels were requested in the same way as the case - // where all channel names have been enumerated. private boolean isMergeAllChannels(DatasetAcquiredImagesReference imageChannels) { - if (imageChannels.isMergeAllChannels()) - { - return true; - } - List<String> wantedChannelCodes = imageChannels.getChannelCodes(); - List<String> allChannelsCodes = imageAccessor.getImageParameters().getChannelsCodes(); - if (allChannelsCodes.size() == 1) - { - return false; // there is only one channel in total, single channel transformation - // should be used - } - for (String existingChannelCode : allChannelsCodes) - { - if (wantedChannelCodes.indexOf(existingChannelCode) == -1) - { - return false; - } - } - return true; + return imageChannels.isMergeAllChannels(getAllChannelCodes()); } /** * @param skipNonExisting if true references to non-existing images are ignored, otherwise an * exception is thrown + * @param mergeAllChannels true if all existing channel images should be merged */ private List<AbsoluteImageReference> fetchImageContents( DatasetAcquiredImagesReference imagesReference, boolean mergeAllChannels, boolean skipNonExisting) { - List<String> channelCodes = getChannelCodes(imagesReference); + List<String> channelCodes = imagesReference.getChannelCodes(getAllChannelCodes()); List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>(); for (String channelCode : channelCodes) { @@ -289,17 +278,6 @@ public class ImageChannelsUtils return HCSImageDatasetLoaderFactory.create(datasetRoot, datasetCode); } - private List<String> getChannelCodes(DatasetAcquiredImagesReference imagesReference) - { - if (imagesReference.isMergeAllChannels()) - { - return imageAccessor.getImageParameters().getChannelsCodes(); - } else - { - return imagesReference.getChannelCodes(); - } - } - /** * Returns content of the image which is representative for the given dataset. */ @@ -311,7 +289,9 @@ public class ImageChannelsUtils List<AbsoluteImageReference> imageReferences = new ImageChannelsUtils(imageAccessor, imageSizeLimitOrNull) .getRepresentativeImageReferences(wellLocationOrNull); - BufferedImage image = mergeChannels(imageReferences, true, true); + BufferedImage image = + calculateBufferedImage(imageReferences, new ImageTransformationParams(true, true, + null)); String name = createFileName(datasetCode, wellLocationOrNull, imageSizeLimitOrNull); return createResponseContentStream(image, name); } @@ -344,15 +324,12 @@ public class ImageChannelsUtils */ public static IContent getImage(IImagingDatasetLoader imageAccessor, ImageChannelStackReference channelStackReference, String chosenChannelCode, - Size imageSizeLimitOrNull, boolean convertToPng) + Size imageSizeLimitOrNull, String singleChannelImageTransformationCodeOrNull, + boolean convertToPng, boolean transform) { - String datasetCode = imageAccessor.getImageParameters().getDatasetCode(); - boolean isMergedChannels = - ScreeningConstants.MERGED_CHANNELS.equalsIgnoreCase(chosenChannelCode); - List<String> channelCodes = isMergedChannels ? null : Arrays.asList(chosenChannelCode); - DatasetAcquiredImagesReference imagesReference = - new DatasetAcquiredImagesReference(datasetCode, channelStackReference, channelCodes); + createDatasetAcquiredImagesReference(imageAccessor, channelStackReference, + chosenChannelCode); ImageChannelsUtils imageChannelsUtils = new ImageChannelsUtils(imageAccessor, imageSizeLimitOrNull); @@ -365,38 +342,29 @@ public class ImageChannelsUtils { return rawContent; } - BufferedImage image = mergeChannels(imageContents, false, mergeAllChannels); + ImageTransformationParams transformationInfo = + new ImageTransformationParams(transform, mergeAllChannels, + singleChannelImageTransformationCodeOrNull); + BufferedImage image = calculateBufferedImage(imageContents, transformationInfo); return createPngContent(image, null); } - /** - * @return an image for the specified tile in the specified size and for the requested channel. - */ - public static IContent getImage(IImagingDatasetLoader imageAccessor, - ImageChannelStackReference channelStackReference, String chosenChannelCode, - Size imageSizeLimitOrNull, boolean convertToPng, boolean transform) + private static DatasetAcquiredImagesReference createDatasetAcquiredImagesReference( + IImagingDatasetLoader imageAccessor, ImageChannelStackReference channelStackReference, + String chosenChannelCode) { String datasetCode = imageAccessor.getImageParameters().getDatasetCode(); boolean isMergedChannels = ScreeningConstants.MERGED_CHANNELS.equalsIgnoreCase(chosenChannelCode); - List<String> channelCodes = isMergedChannels ? null : Arrays.asList(chosenChannelCode); - - DatasetAcquiredImagesReference imagesReference = - new DatasetAcquiredImagesReference(datasetCode, channelStackReference, channelCodes); - - ImageChannelsUtils imageChannelsUtils = - new ImageChannelsUtils(imageAccessor, imageSizeLimitOrNull); - boolean mergeAllChannels = imageChannelsUtils.isMergeAllChannels(imagesReference); - List<AbsoluteImageReference> imageContents = - imageChannelsUtils.fetchImageContents(imagesReference, mergeAllChannels, false); - - IContent rawContent = tryGetRawContent(convertToPng, imageContents); - if (rawContent != null) + if (isMergedChannels) { - return rawContent; + return DatasetAcquiredImagesReference.createForMergedChannels(datasetCode, + channelStackReference); + } else + { + return DatasetAcquiredImagesReference.createForSingleChannel(datasetCode, + channelStackReference, chosenChannelCode); } - BufferedImage image = mergeChannels(imageContents, transform, mergeAllChannels); - return createPngContent(image, null); } // optimization: if there is exactly one image reference, maybe its original raw content is the @@ -417,7 +385,7 @@ public class ImageChannelsUtils { List<AbsoluteImageReference> images = new ArrayList<AbsoluteImageReference>(); - for (String chosenChannel : imageAccessor.getImageParameters().getChannelsCodes()) + for (String chosenChannel : getAllChannelCodes()) { AbsoluteImageReference image = getRepresentativeImageReference(chosenChannel, wellLocationOrNull); @@ -426,6 +394,11 @@ public class ImageChannelsUtils return images; } + private List<String> getAllChannelCodes() + { + return imageAccessor.getImageParameters().getChannelsCodes(); + } + /** * @throw {@link EnvironmentFailureException} when image does not exist */ @@ -451,18 +424,19 @@ public class ImageChannelsUtils * @param useMergedChannelsTransformation sometimes we can have a single image which contain all * channels merged. In this case a different transformation will be applied to it. */ - private static BufferedImage calculateAndTransformSingleImage( - AbsoluteImageReference imageReference, boolean transform, - boolean useMergedChannelsTransformation) + private static BufferedImage calculateAndTransformSingleImageForDisplay( + AbsoluteImageReference imageReference, ImageTransformationParams transformationInfo) { BufferedImage image = calculateSingleImage(imageReference); - return transform(image, imageReference, transform, useMergedChannelsTransformation); + image = transform(image, imageReference, transformationInfo); + image = ImageUtil.convertForDisplayIfNecessary(image); + return image; } private static BufferedImage calculateSingleImage(AbsoluteImageReference imageReference) { long start = operationLog.isDebugEnabled() ? System.currentTimeMillis() : 0; - BufferedImage image = imageReference.getImage(); + BufferedImage image = imageReference.getUnchangedImage(); if (operationLog.isDebugEnabled()) { operationLog.debug("Load original image: " + (System.currentTimeMillis() - start)); @@ -504,29 +478,51 @@ public class ImageChannelsUtils * @param allChannelsMerged if true then we use one special transformation on the merged images * instead of transforming every single image. */ - private static BufferedImage mergeChannels(List<AbsoluteImageReference> imageReferences, - boolean transform, boolean allChannelsMerged) + private static BufferedImage calculateBufferedImage( + List<AbsoluteImageReference> imageReferences, + ImageTransformationParams transformationInfo) { AbsoluteImageReference singleImageReference = imageReferences.get(0); if (imageReferences.size() == 1) { - return calculateAndTransformSingleImage(singleImageReference, transform, - allChannelsMerged); + return calculateAndTransformSingleImageForDisplay(singleImageReference, + transformationInfo); } else { - // We do not transform single images here. - // The 'merged channels' transformation will be applied later. - List<ImageWithReference> images = - calculateSingleImages(imageReferences, false, allChannelsMerged); - BufferedImage mergedImage = mergeImages(images); - // NOTE: even if we are not merging all the channels but just few of them we use the - // merged-channel transformation - return transform(mergedImage, singleImageReference, transform, true); + return mergeChannels(imageReferences, transformationInfo, singleImageReference); } } + private static BufferedImage mergeChannels(List<AbsoluteImageReference> imageReferences, + ImageTransformationParams transformationInfo, + AbsoluteImageReference singleImageReference) + { + // We do not transform single images here. + // The 'merged channels' transformation will be applied later. + List<ImageWithReference> images = calculateSingleImagesForDisplay(imageReferences, null); + BufferedImage mergedImage = mergeImages(images); + // NOTE: even if we are not merging all the channels but just few of them we use the + // merged-channel transformation + + // TODO 2011-09-13, Tomasz Pylak: it looks like we are applying image level + // transformation from a random single channel to merged images. Replace with following + // code after testing: + + // if (transformationInfo.isApplyNonImageLevelTransformation()) + // { + // IImageTransformerFactory transformationOrNull = + // singleImageReference.getImageTransfomationFactories().tryGetForMerged(); + // mergedImage = + // applyImageTransformation(mergedImage, transformationOrNull); + // } + // return mergedImage; + + return transform(mergedImage, singleImageReference, + transformationInfo.cloneAndSetUseMergedChannelsTransformation()); + } + private static BufferedImage transform(BufferedImage image, - AbsoluteImageReference imageReference, boolean transform, boolean allChannelsMerged) + AbsoluteImageReference imageReference, ImageTransformationParams transformationInfo) { BufferedImage resultImage = image; ImageTransfomationFactories transfomations = @@ -535,13 +531,21 @@ public class ImageChannelsUtils // external image viewer resultImage = applyImageTransformation(resultImage, transfomations.tryGetForImage()); - if (transform == false) + if (transformationInfo.isApplyNonImageLevelTransformation() == false) { return resultImage; } - IImageTransformerFactory channelLevelTransformation = - transfomations.tryGetForChannel(allChannelsMerged); - return applyImageTransformation(resultImage, channelLevelTransformation); + IImageTransformerFactory channelLevelTransformationOrNull; + if (transformationInfo.isUseMergedChannelsTransformation()) + { + channelLevelTransformationOrNull = transfomations.tryGetForMerged(); + } else + { + channelLevelTransformationOrNull = + transfomations.tryGetForChannel(transformationInfo + .tryGetSingleChannelTransformationCode()); + } + return applyImageTransformation(resultImage, channelLevelTransformationOrNull); } private static BufferedImage applyImageTransformation(BufferedImage image, @@ -577,24 +581,28 @@ public class ImageChannelsUtils } } - /** @param useMergedChannelsTransformation used only if transformEachImage is true */ - private static List<ImageWithReference> calculateSingleImages( - List<AbsoluteImageReference> imageReferences, boolean transformEachImage, - boolean useMergedChannelsTransformation) + /** + * @param transformationInfoOrNull if null all transformations (including image-level) will be + * skipped + */ + private static List<ImageWithReference> calculateSingleImagesForDisplay( + List<AbsoluteImageReference> imageReferences, + ImageTransformationParams transformationInfoOrNull) { List<ImageWithReference> images = new ArrayList<ImageWithReference>(); for (AbsoluteImageReference imageRef : imageReferences) { BufferedImage image; - if (transformEachImage) + if (transformationInfoOrNull != null) { image = - calculateAndTransformSingleImage(imageRef, true, - useMergedChannelsTransformation); + calculateAndTransformSingleImageForDisplay(imageRef, + transformationInfoOrNull); } else { + // NOTE: here we skip image level transformations as well image = calculateSingleImage(imageRef); - + image = ImageUtil.convertForDisplayIfNecessary(image); } images.add(new ImageWithReference(image, imageRef)); } @@ -636,11 +644,12 @@ public class ImageChannelsUtils return (i1OrNull == null) ? (i2OrNull == null) : i1OrNull.equals(i2OrNull); } + // this method always returns RGB images, even if the input was in grayscale private static BufferedImage mergeImages(List<ImageWithReference> images) { assert images.size() > 1 : "more than 1 image expected, but found: " + images.size(); - BufferedImage newImage = createNewImage(images.get(0).getBufferedImage()); + BufferedImage newImage = createNewRGBImage(images.get(0).getBufferedImage()); int width = newImage.getWidth(); int height = newImage.getHeight(); int colorBuffer[] = new int[4]; @@ -795,7 +804,7 @@ public class ImageChannelsUtils private static BufferedImage transformToChannel(BufferedImage bufferedImage, ColorComponent colorComponent) { - BufferedImage newImage = createNewImage(bufferedImage); + BufferedImage newImage = createNewRGBImage(bufferedImage); int width = bufferedImage.getWidth(); int height = bufferedImage.getHeight(); for (int x = 0; x < width; x++) @@ -803,7 +812,7 @@ public class ImageChannelsUtils for (int y = 0; y < height; y++) { int rgb = bufferedImage.getRGB(x, y); - int channelColor = getGrayscaleAsChannel(rgb, colorComponent); + int channelColor = extractSingleComponent(rgb, colorComponent); newImage.setRGB(x, y, channelColor); } } @@ -812,7 +821,7 @@ public class ImageChannelsUtils // NOTE: drawing on this image will not preserve transparency - but we do not need it and the // image is smaller - private static BufferedImage createNewImage(RenderedImage bufferedImage) + private static BufferedImage createNewRGBImage(RenderedImage bufferedImage) { BufferedImage newImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), @@ -848,9 +857,9 @@ public class ImageChannelsUtils } } - // we assume that the color was in a grayscale - // we reset all ingredients besides the one which should be shown - private static int getGrayscaleAsChannel(int rgb, ColorComponent colorComponent) + // We reset all ingredients besides the one which is specified by color component. + // The result is the rgb value with only one component which is non-zero. + private static int extractSingleComponent(int rgb, ColorComponent colorComponent) { return colorComponent.extractSingleComponent(rgb).getRGB(); } @@ -866,10 +875,10 @@ public class ImageChannelsUtils return new Color(rgb[0], rgb[1], rgb[2], rgb[3]).getRGB(); } - private static IContent createPngContent(BufferedImage image, String nameOrNull) { final byte[] output = ImageUtil.imageToPngFast(image); return new ByteArrayBasedContent(output, nameOrNull); } + } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java index 10d3e10d786..4113176606c 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/DatasetAcquiredImagesReference.java @@ -16,6 +16,7 @@ package ch.systemsx.cisd.openbis.dss.generic.server.images.dto; +import java.util.Arrays; import java.util.List; /** @@ -29,10 +30,30 @@ public class DatasetAcquiredImagesReference private final ImageChannelStackReference channelStackReference; + /** Can be null if merged channels are requested. */ private final List<String> channelCodesOrNull; + public static DatasetAcquiredImagesReference createForMergedChannels(String datasetCode, + ImageChannelStackReference channelStackReference) + { + return new DatasetAcquiredImagesReference(datasetCode, channelStackReference, null); + } + + public static DatasetAcquiredImagesReference createForSingleChannel(String datasetCode, + ImageChannelStackReference channelStackReference, String channelCode) + { + return new DatasetAcquiredImagesReference(datasetCode, channelStackReference, + Arrays.asList(channelCode)); + } + + public static DatasetAcquiredImagesReference createForManyChannels(String datasetCode, + ImageChannelStackReference channelStackReference, List<String> channelCodes) + { + return new DatasetAcquiredImagesReference(datasetCode, channelStackReference, channelCodes); + } + /** @param channelCodesOrNull null if channels should be merged */ - public DatasetAcquiredImagesReference(String datasetCode, + private DatasetAcquiredImagesReference(String datasetCode, ImageChannelStackReference channelStackReference, List<String> channelCodesOrNull) { assert datasetCode != null; @@ -53,14 +74,42 @@ public class DatasetAcquiredImagesReference return channelStackReference; } - public boolean isMergeAllChannels() + /** + * Check if all exiting channels of the image should be merged (and not a single one or a + * subset). We want to treat the case where merged channels were requested in the same way as + * the case where all channel names have been enumerated. + */ + public boolean isMergeAllChannels(List<String> allChannelsCodes) { - return channelCodesOrNull == null; + if (channelCodesOrNull == null) + { + return true; + } + List<String> wantedChannelCodes = channelCodesOrNull; + if (allChannelsCodes.size() == 1) + { + return false; // there is only one channel in total, single channel transformation + // should be used + } + for (String existingChannelCode : allChannelsCodes) + { + if (wantedChannelCodes.indexOf(existingChannelCode) == -1) + { + return false; + } + } + return true; } - public List<String> getChannelCodes() + public List<String> getChannelCodes(List<String> allChannelsCodes) { - return channelCodesOrNull; + if (channelCodesOrNull == null) + { + return allChannelsCodes; + } else + { + return channelCodesOrNull; + } } @Override diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java index a397574ed06..959b93a38a5 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageGenerationDescription.java @@ -32,6 +32,8 @@ public class ImageGenerationDescription { private final DatasetAcquiredImagesReference imageChannelsOrNull; + private final String singleChannelTransformationCodeOrNull; + private final List<DatasetAcquiredImagesReference> overlayChannels; private final String sessionId; @@ -40,10 +42,12 @@ public class ImageGenerationDescription private final Size thumbnailSizeOrNull; public ImageGenerationDescription(DatasetAcquiredImagesReference imageChannelsOrNull, + String singleChannelTransformationCodeOrNull, List<DatasetAcquiredImagesReference> overlayChannels, String sessionId, Size thumbnailSizeOrNull) { this.imageChannelsOrNull = imageChannelsOrNull; + this.singleChannelTransformationCodeOrNull = singleChannelTransformationCodeOrNull; this.overlayChannels = overlayChannels; this.sessionId = sessionId; this.thumbnailSizeOrNull = thumbnailSizeOrNull; @@ -54,6 +58,11 @@ public class ImageGenerationDescription return imageChannelsOrNull; } + public String tryGetSingleChannelTransformationCode() + { + return singleChannelTransformationCodeOrNull; + } + public List<DatasetAcquiredImagesReference> getOverlayChannels() { return overlayChannels; @@ -116,10 +125,17 @@ public class ImageGenerationDescription private static void appendChannels(StringBuffer sb, DatasetAcquiredImagesReference imagesRef) { - for (String channel : imagesRef.getChannelCodes()) + List<String> channelCodes = imagesRef.getChannelCodes(null); + if (channelCodes == null) { - sb.append("_"); - sb.append(channel); + sb.append("merged"); + } else + { + for (String channel : channelCodes) + { + sb.append("_"); + sb.append(channel); + } } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageTransformationParams.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageTransformationParams.java new file mode 100644 index 00000000000..2cc933d370a --- /dev/null +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/images/dto/ImageTransformationParams.java @@ -0,0 +1,67 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.dss.generic.server.images.dto; + +/** + * Describes which image transformations should be applied. Note that image-level transformation is + * always applied for single channels. + * + * @author Tomasz Pylak + */ +public class ImageTransformationParams +{ + private final boolean applyNonImageLevelTransformation; + + /** ignored if {@link #applyNonImageLevelTransformation} is false */ + private final boolean useMergedChannelsTransformation; + + /** + * ignored if {@link #applyNonImageLevelTransformation} is false or if + * {@link #useMergedChannelsTransformation} is false + */ + private final String singleChannelTransformationCodeOrNull; + + public ImageTransformationParams(boolean applyNonImageLevelTransformation, + boolean useMergedChannelsTransformation, String singleChannelTransformationCodeOrNull) + { + this.applyNonImageLevelTransformation = applyNonImageLevelTransformation; + this.useMergedChannelsTransformation = useMergedChannelsTransformation; + this.singleChannelTransformationCodeOrNull = singleChannelTransformationCodeOrNull; + } + + public boolean isApplyNonImageLevelTransformation() + { + return applyNonImageLevelTransformation; + } + + public boolean isUseMergedChannelsTransformation() + { + return useMergedChannelsTransformation; + } + + public String tryGetSingleChannelTransformationCode() + { + return singleChannelTransformationCodeOrNull; + } + + public ImageTransformationParams cloneAndSetUseMergedChannelsTransformation() + { + return new ImageTransformationParams(applyNonImageLevelTransformation, true, + singleChannelTransformationCodeOrNull); + } + +} diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java index 61c80b2b2e8..84be648d935 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/ColorRangeCalculator.java @@ -153,7 +153,6 @@ public class ColorRangeCalculator } ReadParams params = new ReadParams(); - params.setAllow16BitGrayscaleModel(true); return reader.readImage(file, ImageID.NULL, params); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java index 944ecccc430..4842599c0c6 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/DynamixWellBrightnessEqualizerProcessingPlugin.java @@ -91,7 +91,7 @@ public class DynamixWellBrightnessEqualizerProcessingPlugin extends { IContent content = contentRepository.getContent(image.getFilePath()); return ImageUtil.loadJavaAdvancedImagingTiff(content.getReadOnlyRandomAccessFile(), - ImageUtil.parseImageID(image.getImageID()), true); + ImageUtil.parseImageID(image.getImageID())); } private static IImageTransformerFactoryProvider createImageTransformerFactoryProvider( diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java index 2ccee7a932e..97716ac0060 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreening.java @@ -28,8 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import net.lemnik.eodsql.QueryTool; - import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter; import ch.systemsx.cisd.base.image.IImageTransformerFactory; @@ -84,10 +82,11 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.FeatureVectorLoa import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.FeatureVectorLoader.WellFeatureCollection; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingTransformerDAO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureDefDTO; -import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.TransformerFactoryMapper; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO; /** * Implementation of the screening API interface using RPC. The instance will be created in spring @@ -104,12 +103,6 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc */ public static final int MINOR_VERSION = 8; - - static - { - QueryTool.getTypeMap().put(IImageTransformerFactory.class, new TransformerFactoryMapper()); - } - // this dao will hold one connection to the database private IImagingReadonlyQueryDAO dao; @@ -282,7 +275,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc imageAccessor.tryGetRepresentativeImage(channelCode, null, originalOrThumbnail); if (image != null) { - return image.getImage(); + return image.getUnchangedImage(); } } throw new IllegalStateException("Cannot find any image in a dataset: " + dataset); @@ -298,7 +291,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc imageAccessor.tryGetRepresentativeThumbnail(channelCode, null); if (image != null) { - return image.getImage(); + return image.getUnchangedImage(); } } return null; @@ -319,7 +312,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc col, row)); if (image != null) { - return image.getImage(); + return image.getUnchangedImage(); } } } @@ -343,7 +336,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc row), originalOrThumbnail); if (image != null) { - return image.getImage(); + return image.getUnchangedImage(); } } } @@ -461,13 +454,13 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc public InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences, boolean convertToPng) { - return loadImages(sessionToken, imageReferences, null, convertToPng); + return loadImages(sessionToken, imageReferences, null, null, convertToPng); } public InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences, ImageSize thumbnailSizeOrNull) { - return loadImages(sessionToken, imageReferences, tryAsSize(thumbnailSizeOrNull), true); + return loadImages(sessionToken, imageReferences, tryAsSize(thumbnailSizeOrNull), null, true); } public InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences, @@ -476,20 +469,24 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc final Map<String, IImagingDatasetLoader> imageLoadersMap = getImageDatasetsMap(sessionToken, imageReferences); return loadImages(imageReferences, tryAsSize(configuration.getDesiredImageSize()), + configuration.getSingleChannelImageTransformationCode(), configuration.isDesiredImageFormatPng(), configuration.isOpenBisImageTransformationApplied(), imageLoadersMap); } private InputStream loadImages(String sessionToken, List<PlateImageReference> imageReferences, - final Size sizeOrNull, final boolean convertToPng) + final Size sizeOrNull, String singleChannelImageTransformationCodeOrNull, + final boolean convertToPng) { final Map<String, IImagingDatasetLoader> imageLoadersMap = getImageDatasetsMap(sessionToken, imageReferences); - return loadImages(imageReferences, sizeOrNull, convertToPng, false, imageLoadersMap); + return loadImages(imageReferences, sizeOrNull, singleChannelImageTransformationCodeOrNull, + convertToPng, false, imageLoadersMap); } private InputStream loadImages(List<PlateImageReference> imageReferences, - final Size sizeOrNull, final boolean convertToPng, final boolean transform, + final Size sizeOrNull, final String singleChannelImageTransformationCodeOrNull, + final boolean convertToPng, final boolean transform, final Map<String, IImagingDatasetLoader> imageLoadersMap) { final List<IContent> imageContents = new ArrayList<IContent>(); @@ -508,7 +505,8 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc public IContent getContent() { return tryGetImageContent(imageAccessor, channelStackRef, channelCode, - sizeOrNull, convertToPng, transform); + sizeOrNull, singleChannelImageTransformationCodeOrNull, + convertToPng, transform); } })); } @@ -558,7 +556,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc new HashMap<String, IImagingDatasetLoader>(); imageLoadersMap.put(dataSetIdentifier.getDatasetCode(), imageAccessor); - return loadImages(imageReferences, size, true, false, imageLoadersMap); + return loadImages(imageReferences, size, null, true, false, imageLoadersMap); } public InputStream loadThumbnailImages(String sessionToken, @@ -590,7 +588,7 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc public IContent getContent() { return tryGetImageContent(imageAccessor, channelStackRef, channelCode, - sizeOrNull, true, false); + sizeOrNull, null, true, false); } })); } @@ -720,6 +718,13 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc return ScreeningConstants.MERGED_CHANNELS.equals(channel); } + static final String IMAGE_VIEWER_TRANSFORMATION_CODE = "_CUSTOM"; + + private static final String IMAGE_VIEWER_TRANSFORMATION_LABEL = "Custom"; + + private static final String IMAGE_VIEWER_TRANSFORMATION_DESCRIPTION = + "Custom image transformation defined with the Color Adjustment tool."; + private void saveImageTransformerFactoryForExperiment(long experimentId, String channel, IImageTransformerFactory transformerFactory) { @@ -738,11 +743,36 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc operationLog.info("save image transformer factory " + transformerFactory + " for experiment " + experimentId + " and channel '" + channel + "'."); } - transformerDAO.saveTransformerFactoryForExperimentChannel(experimentId, channel, - transformerFactory); + Long channelId = transformerDAO.getExperimentChannelId(experimentId, channel); + createOrUpdateImageViewerTransformation(channelId, transformerFactory); + } + } + + private void createOrUpdateImageViewerTransformation(long channelId, + IImageTransformerFactory transformerFactory) + { + Long transformationIdOrNull = + transformerDAO.tryGetImageTransformationId(channelId, + IMAGE_VIEWER_TRANSFORMATION_CODE); + if (transformationIdOrNull == null) + { + transformerDAO.addImageTransformation(createImageViewerTransformation(channelId, + transformerFactory)); + } else + { + transformerDAO + .updateImageTransformerFactory(transformationIdOrNull, transformerFactory); } } + private ImgImageTransformationDTO createImageViewerTransformation(long channelId, + IImageTransformerFactory transformerFactory) + { + return new ImgImageTransformationDTO(IMAGE_VIEWER_TRANSFORMATION_CODE, + IMAGE_VIEWER_TRANSFORMATION_LABEL, IMAGE_VIEWER_TRANSFORMATION_DESCRIPTION, true, + channelId, transformerFactory); + } + private void saveImageTransformerFactoryForDataset(long datasetId, String channel, IImageTransformerFactory transformerFactory) { @@ -761,8 +791,8 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc operationLog.info("save image transformer factory " + transformerFactory + " for dataset " + datasetId + " and channel '" + channel + "'."); } - transformerDAO.saveTransformerFactoryForDatasetChannel(datasetId, channel, - transformerFactory); + long channelId = transformerDAO.getDatasetChannelId(datasetId, channel); + createOrUpdateImageViewerTransformation(channelId, transformerFactory); } } @@ -818,11 +848,22 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc return dataset.tryGetImageTransformerFactory(); } else { - return getDAO().tryGetChannelForDataset(dataset.getId(), channel) - .tryGetImageTransformerFactory(); + long channelId = getDAO().getDatasetChannelId(dataset.getId(), channel); + return tryGetImageViewerTransformation(channelId); } } + private IImageTransformerFactory tryGetImageViewerTransformation(long channelId) + { + ImgImageTransformationDTO transformation = + getDAO().tryGetImageTransformation(channelId, IMAGE_VIEWER_TRANSFORMATION_CODE); + if (transformation == null) + { + return null; + } + return transformation.getImageTransformerFactory(); + } + private IImageTransformerFactory tryGetImageTransformerFactoryForExperiment( String experimentPermID, String channel) { @@ -832,8 +873,11 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc .tryGetImageTransformerFactory(); } else { - return getDAO().tryGetChannelForExperimentPermId(experimentPermID, channel) - .tryGetImageTransformerFactory(); + ImgChannelDTO channelDTO = + getDAO().tryGetChannelForExperimentPermId(experimentPermID, channel); + assert channelDTO != null : String.format("No channel '%s' for experiment '%s'.", + channel, experimentPermID); + return tryGetImageViewerTransformation(channelDTO.getId()); } } @@ -982,12 +1026,14 @@ public class DssServiceRpcScreening extends AbstractDssServiceRpc<IDssServiceRpc private IContent tryGetImageContent(IImagingDatasetLoader imageAccessor, final ImageChannelStackReference channelStackReference, String channelCode, - Size thumbnailSizeOrNull, boolean convertToPng, boolean transform) + Size thumbnailSizeOrNull, String singleChannelImageTransformationCodeOrNull, + boolean convertToPng, boolean transform) { try { return ImageChannelsUtils.getImage(imageAccessor, channelStackReference, channelCode, - thumbnailSizeOrNull, convertToPng, transform); + thumbnailSizeOrNull, singleChannelImageTransformationCodeOrNull, convertToPng, + transform); } catch (EnvironmentFailureException e) { operationLog.error("Error reading image.", e); diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java index 769f472d035..e27b7078e43 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/screening/shared/api/v1/LoadImageConfiguration.java @@ -43,6 +43,8 @@ public class LoadImageConfiguration implements Serializable private boolean openBisImageTransformationApplied = false; + private String singleChannelImageTransformationCodeOrNull = null; + /** * The desired size of the image. Null if the original size is requested. * <p> @@ -93,7 +95,8 @@ public class LoadImageConfiguration implements Serializable } /** - * Should the image transformation stored in openBIS be applied to the image? + * Should the image transformation stored in openBIS for a channel or merged channel be applied + * to the image? * * @return True if the image transformation should be applied; false if the original image * should be returned. @@ -104,7 +107,8 @@ public class LoadImageConfiguration implements Serializable } /** - * Set whether the image transformation stored in openBIS should be applied. + * Set whether the image transformation stored in openBIS for a channel or merged channel should + * be applied. * * @param openBisImageTransformationApplied Pass in true if the transformation should be * applied; false otherwise. @@ -112,6 +116,34 @@ public class LoadImageConfiguration implements Serializable public void setOpenBisImageTransformationApplied(boolean openBisImageTransformationApplied) { this.openBisImageTransformationApplied = openBisImageTransformationApplied; + if (openBisImageTransformationApplied && getSingleChannelImageTransformationCode() == null) + { + // for backward compatibility - apply Color Adjustment tool transformation + setSingleChannelImageTransformationCode("_CUSTOM"); + } + } + + /** Code of the transformation, which should be applied to the image. Can return null. */ + public String getSingleChannelImageTransformationCode() + { + return singleChannelImageTransformationCodeOrNull; + } + + /** + * Sets the code of the transformation, which should be applied to the image. This parameter + * will be ignored if {@link #isOpenBisImageTransformationApplied()} is false. To keep backward + * compatibility if {@link #isOpenBisImageTransformationApplied()} is true and no image + * transformation code is specified, then the transformation defined by the Color Adjustment + * tool will be applied. + * <p> + * Transformation has to be assigned (usually during dataset registration) to the channel which + * will be fetched. Note that such transformations can be channel-specific. If merged channels + * image will be requested or the transformation does not exist then untransformed image will be + * returned. + */ + public void setSingleChannelImageTransformationCode(String transformationCodeOrNull) + { + this.singleChannelImageTransformationCodeOrNull = transformationCodeOrNull; } @Override diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java index 20e40fac3e1..97f2e0987e4 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/shared/DssScreeningUtils.java @@ -20,10 +20,12 @@ import javax.sql.DataSource; import net.lemnik.eodsql.QueryTool; +import ch.systemsx.cisd.base.image.IImageTransformerFactory; import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingTransformerDAO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.TransformerFactoryMapper; /** * Utility methods for DSS. @@ -34,6 +36,11 @@ public class DssScreeningUtils { private static final IImagingReadonlyQueryDAO query = createQuery(); + static + { + QueryTool.getTypeMap().put(IImageTransformerFactory.class, new TransformerFactoryMapper()); + } + /** * Returned query is reused and should NOT be closed. */ diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java index 57253aea57e..4812ae65a19 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/api/v1/ExampleImageTransformerFactory.java @@ -116,4 +116,35 @@ public class ExampleImageTransformerFactory implements IImageTransformerFactory return getClass().getSimpleName() + "[" + colorPattern + "]"; } + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + brightnessDelta; + result = prime * result + ((colorPattern == null) ? 0 : colorPattern.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; + ExampleImageTransformerFactory other = (ExampleImageTransformerFactory) obj; + if (brightnessDelta != other.brightnessDelta) + return false; + if (colorPattern == null) + { + if (other.colorPattern != null) + return false; + } else if (!colorPattern.equals(other.colorPattern)) + return false; + return true; + } + } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java index 51115c7aab8..2bed12ee140 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/dto/LogicalImageReference.java @@ -124,9 +124,11 @@ public class LogicalImageReference return datastoreCode; } - public String getTransformerFactorySignatureOrNull(String channelCode) + public String tryGetTransformerFactorySignature(String channelCodeOrNull, + String transformationCode) { - return imageParameters.getTransformerFactorySignatureOrNull(channelCode); + return imageParameters.tryGetTransformerFactorySignature(channelCodeOrNull, + transformationCode); } public List<DatasetOverlayImagesReference> getOverlayDatasets() diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java index 529dc03ff10..91534478d3f 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/client/web/client/application/detailviewers/utils/ImageUrlUtils.java @@ -146,6 +146,12 @@ public class ImageUrlUtils } methodWithParameters.addParameter(ImageServletUrlParameters.TILE_ROW_PARAM, tileRow); methodWithParameters.addParameter(ImageServletUrlParameters.TILE_COL_PARAM, tileCol); + if (channelReferences.tryGetImageTransformationCode() != null) + { + methodWithParameters.addParameter( + ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM, + channelReferences.tryGetImageTransformationCode()); + } addImageTransformerSignature(methodWithParameters, channelReferences); String linkURLOrNull = createImageLinks ? methodWithParameters.toString() : null; addThumbnailSize(methodWithParameters, width, height); @@ -170,7 +176,14 @@ public class ImageUrlUtils { for (String channel : channels) { - String signature = images.getTransformerFactorySignatureOrNull(channel); + String channelOrNull = channel; + if (channelOrNull.equalsIgnoreCase(ScreeningConstants.MERGED_CHANNELS)) + { + channelOrNull = null; + } + String signature = + images.tryGetTransformerFactorySignature(channelOrNull, + channelReferences.tryGetImageTransformationCode()); if (signature != null) { url.addParameter("transformerFactorySignature", signature); @@ -203,7 +216,6 @@ public class ImageUrlUtils methodWithParameters.addParameter(ImageServletUrlParameters.CHANNEL_PARAM, channel); } } - addOverlayParameters(channelReferences.getOverlayChannels(), methodWithParameters); return methodWithParameters; diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java index e526f08518b..c3fd5d26526 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageChannel.java @@ -16,7 +16,6 @@ package ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto; -import java.util.ArrayList; import java.util.List; import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable; @@ -51,9 +50,9 @@ public class ImageChannel implements ISerializable { } - // FIXME add List<ImageTransformationInfo> availableImageTransformations param public ImageChannel(String code, String label, String description, Integer wavelength, - ImageChannelColor channelColor) + ImageChannelColor channelColor, + List<ImageTransformationInfo> availableImageTransformations) { assert code != null; // assert availableImageTransformations != null; @@ -63,7 +62,7 @@ public class ImageChannel implements ISerializable this.description = description; this.wavelength = wavelength; this.channelColor = channelColor; - this.availableImageTransformations = new ArrayList<ImageTransformationInfo>(); + this.availableImageTransformations = availableImageTransformations; } public String getCode() diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java index c98e9a0bbaa..c00644bd8a0 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageDatasetParameters.java @@ -17,9 +17,7 @@ package ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ServiceVersionHolder; @@ -47,12 +45,11 @@ public class ImageDatasetParameters implements ISerializable private List<ImageChannel> channels; - private Map<String/* channel code */, String/* signature */> channelsTransformerFactorySignatures = - new HashMap<String, String>(); - // true if any well in the dataset has a time series (or depth stack) of images private boolean isMultidimensional; + private String mergedChannelTransformerFactorySignatureOrNull; + public Integer tryGetRowsNum() { return rowsNumOrNull; @@ -161,13 +158,39 @@ public class ImageDatasetParameters implements ISerializable return channels; } - public void addTransformerFactorySignatureFor(String channelCode, String signatureOrNull) + /** + * @param channelCodeOrNull null for merged channels (transformationCode is ignored in that + * case) + */ + public String tryGetTransformerFactorySignature(String channelCodeOrNull, + String transformationCode) + { + if (channelCodeOrNull == null) + { + return mergedChannelTransformerFactorySignatureOrNull; + } + List<ImageTransformationInfo> transformations = + getAvailableImageTransformationsFor(channelCodeOrNull); + for (ImageTransformationInfo transformation : transformations) + { + if (transformation.getCode().equalsIgnoreCase(transformationCode)) + { + return transformation.getTransformationSignature(); + } + } + return null; + } + + public String tryGetMergedChannelTransformerFactorySignature() { - channelsTransformerFactorySignatures.put(channelCode, signatureOrNull); + return mergedChannelTransformerFactorySignatureOrNull; } - public String getTransformerFactorySignatureOrNull(String channelCode) + public void setMergedChannelTransformerFactorySignature( + String mergedChannelTransformerFactorySignatureOrNull) { - return channelsTransformerFactorySignatures.get(channelCode); + this.mergedChannelTransformerFactorySignatureOrNull = + mergedChannelTransformerFactorySignatureOrNull; } + } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java index 625c47e1006..f1a4e3a326e 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ImageTransformationInfo.java @@ -35,6 +35,8 @@ public class ImageTransformationInfo implements ISerializable // can be null private String description; + private String transformationSignature; + private boolean isDefault; // GWT only @@ -43,7 +45,8 @@ public class ImageTransformationInfo implements ISerializable { } - public ImageTransformationInfo(String code, String label, String description, boolean isDefault) + public ImageTransformationInfo(String code, String label, String description, + String transformationSignature, boolean isDefault) { assert code != null : "code is null"; assert label != null : " label is null"; @@ -51,6 +54,7 @@ public class ImageTransformationInfo implements ISerializable this.code = code; this.label = label; this.description = description; + this.transformationSignature = transformationSignature; this.isDefault = isDefault; } @@ -69,8 +73,18 @@ public class ImageTransformationInfo implements ISerializable return description; } + public String getTransformationSignature() + { + return transformationSignature; + } + public boolean isDefault() { return isDefault; } + + public void setDefault(boolean isDefault) + { + this.isDefault = isDefault; + } } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java index b404115ca6d..4cbe2e1c84d 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/basic/dto/ScreeningConstants.java @@ -178,6 +178,8 @@ public class ScreeningConstants public final static String WELL_COLUMN_PARAM = "wellCol"; + public final static String SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM = "transformation"; + // -- mandatory servlet parameters public final static String DATASET_CODE_PARAM = "dataset"; 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 a127fd77649..a3ec2750365 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 @@ -18,13 +18,17 @@ package ch.systemsx.cisd.openbis.plugin.screening.shared.imaging; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import ch.systemsx.cisd.common.collections.CollectionUtils; import ch.systemsx.cisd.common.utilities.MD5ChecksumCalculator; +import ch.systemsx.cisd.openbis.generic.shared.basic.utils.GroupByMap; +import ch.systemsx.cisd.openbis.generic.shared.basic.utils.IGroupKeyExtractor; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannel; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelStack; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters; -import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageTransformationInfo; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.WellLocation; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgChannelDTO; @@ -32,6 +36,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgCh import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgContainerDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgExperimentDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO; /** * Helper class for easy handling of HCS image dataset standard structure with no code for handling @@ -46,8 +51,6 @@ public class HCSDatasetLoader implements IImageDatasetLoader protected final ImgDatasetDTO dataset; - private final String mergedChannelTransformerFactorySignatureOrNull; - protected ImgContainerDTO containerOrNull; protected ImgExperimentDTO experimentOrNull; @@ -56,6 +59,10 @@ public class HCSDatasetLoader implements IImageDatasetLoader protected List<ImgChannelDTO> channels; + protected Map<Long/* channel id */, List<ImgImageTransformationDTO>> availableImageTransformationsMap; + + private final String mergedChannelTransformerFactorySignatureOrNull; + public HCSDatasetLoader(IImagingReadonlyQueryDAO query, String datasetPermId) { this.query = query; @@ -80,6 +87,26 @@ public class HCSDatasetLoader implements IImageDatasetLoader this.mergedChannelTransformerFactorySignatureOrNull = tryGetImageTransformerFactorySignatureForMergedChannels(); this.channels = loadChannels(); + this.availableImageTransformationsMap = loadAvailableImageTransformations(); + } + + private Map<Long, List<ImgImageTransformationDTO>> loadAvailableImageTransformations() + { + List<ImgImageTransformationDTO> imageTransformations = + query.listImageTransformationsByDatasetId(dataset.getId()); + if (imageTransformations.size() == 0 && containerOrNull != null) + { + imageTransformations = + query.listImageTransformationsByExperimentId(containerOrNull.getExperimentId()); + } + return GroupByMap.create(imageTransformations, + new IGroupKeyExtractor<Long, ImgImageTransformationDTO>() + { + public Long getKey(ImgImageTransformationDTO transformation) + { + return transformation.getChannelId(); + } + }).getMap(); } private List<ImgChannelDTO> loadChannels() @@ -160,32 +187,69 @@ public class HCSDatasetLoader implements IImageDatasetLoader params.setTileRowsNum(getDataset().getFieldNumberOfRows()); params.setTileColsNum(getDataset().getFieldNumberOfColumns()); params.setIsMultidimensional(dataset.getIsMultidimensional()); - params.addTransformerFactorySignatureFor(ScreeningConstants.MERGED_CHANNELS, - mergedChannelTransformerFactorySignatureOrNull); + params.setMergedChannelTransformerFactorySignature(mergedChannelTransformerFactorySignatureOrNull); + params.setChannels(convertChannels()); + return params; + } + private List<ImageChannel> convertChannels() + { List<ImageChannel> convertedChannels = new ArrayList<ImageChannel>(); for (ImgChannelDTO channelDTO : channels) { ImageChannel channel = convert(channelDTO); convertedChannels.add(channel); - - String transformationSignature = - tryGetSignature(channelDTO.getSerializedImageTransformerFactory()); - params.addTransformerFactorySignatureFor(channelDTO.getCode(), transformationSignature); } - params.setChannels(convertedChannels); - return params; + return convertedChannels; } - private static ImageChannel convert(ImgChannelDTO channelDTO) + private ImageChannel convert(ImgChannelDTO channelDTO) { ImageChannelColor imageChannelColor = ImageChannelColor.valueOf(channelDTO.getDbChannelColor()); + List<ImageTransformationInfo> availableImageTransformations = + convertTransformations(availableImageTransformationsMap.get(channelDTO.getId())); return new ImageChannel(channelDTO.getCode(), channelDTO.getLabel(), - channelDTO.getDescription(), channelDTO.getWavelength(), imageChannelColor); + channelDTO.getDescription(), channelDTO.getWavelength(), imageChannelColor, + availableImageTransformations); + } + + private static List<ImageTransformationInfo> convertTransformations( + List<ImgImageTransformationDTO> transformationsOrNull) + { + if (transformationsOrNull == null) + { + return new ArrayList<ImageTransformationInfo>(); + } else + { + List<ImageTransformationInfo> transformations = + CollectionUtils + .map(transformationsOrNull, + new CollectionUtils.ICollectionMappingFunction<ImageTransformationInfo, ImgImageTransformationDTO>() + { + public ImageTransformationInfo map( + ImgImageTransformationDTO transformation) + { + return convert(transformation); + } + }); + if (transformations.size() > 0) + { + transformations.get(0).setDefault(true); + } + return transformations; + } + } + + private static ImageTransformationInfo convert(ImgImageTransformationDTO transformation) + { + String transformationSignature = + tryGetSignature(transformation.getSerializedImageTransformerFactory()); + return new ImageTransformationInfo(transformation.getCode(), transformation.getLabel(), + transformation.getDescription(), transformationSignature, false); } - private String tryGetSignature(byte[] bytesOrNull) + private static String tryGetSignature(byte[] bytesOrNull) { if (bytesOrNull == null) { 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 0f5fba7ee47..4501533d29e 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 @@ -176,16 +176,6 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery + "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); - - @Select("select count(*) > 0 from CHANNELS ch " - + "join DATA_SETS d on ch.ds_id = d.id where d.PERM_ID = ?{1}") - public boolean hasDatasetChannels(String datasetPermId); - // ---------------- Generic --------------------------------- /** @return an image for the specified channel and channel stack or null */ @@ -221,7 +211,58 @@ public interface IImagingReadonlyQueryDAO extends BaseQuery { StringArrayMapper.class }, fetchSize = FETCH_SIZE) public List<ImgDatasetDTO> listDatasetsByPermId(String... datasetPermIds); - // ---------------- HCS - experiments, containers, channels --------------------------------- + // ------------ dataset and experiment channels + + @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 CHANNELS where (DS_ID = ?{1}) and CODE = upper(?{2})") + public ImgChannelDTO tryGetChannelForDataset(long datasetId, String channelCode); + + @Select("select * from CHANNELS where (EXP_ID = ?{1}) and CODE = upper(?{2})") + public ImgChannelDTO tryGetChannelForExperiment(long experimentId, String channelCode); + + @Select("select id from channels where ds_id = ?{1} and code = upper(?{2})") + public long getDatasetChannelId(long datasetId, String channelCode); + + @Select("select id from channels where exp_id = ?{1} and code = upper(?{2})") + public long getExperimentChannelId(long experimentId, String channelCode); + + @Select("select count(*) > 0 from CHANNELS ch " + + "join DATA_SETS d on ch.ds_id = d.id where d.PERM_ID = ?{1}") + public boolean hasDatasetChannels(String datasetPermId); + + @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 channelCode); + + // ----------- image transformations + + @Select("select * from IMAGE_TRANSFORMATIONS tr where tr.channel_id = ?{1} order by tr.ID") + public List<ImgImageTransformationDTO> listImageTransformations(long channelId); + + @Select("select id from image_transformations where channel_id = ?{1} and code = ?{2}") + public Long tryGetImageTransformationId(long channelId, String transformationCode); + + @Select("select * from image_transformations where channel_id = ?{1} and code = ?{2}") + public ImgImageTransformationDTO tryGetImageTransformation(long channelId, + String transformationCode); + + @Select("select tr.* from IMAGE_TRANSFORMATIONS tr " + + " join channels ch on tr.channel_id = ch.id " + + " where ch.ds_id = ?{1} order by tr.ID ") + public List<ImgImageTransformationDTO> listImageTransformationsByDatasetId(long datasetId); + + @Select("select tr.* from IMAGE_TRANSFORMATIONS tr " + + " join channels ch on tr.channel_id = ch.id " + + " where ch.exp_id = ?{1} order by tr.ID ") + public List<ImgImageTransformationDTO> listImageTransformationsByExperimentId(long experimentId); + + // ---------------- HCS - experiments, containers --------------------------------- @Select("select * from EXPERIMENTS where PERM_ID = ?{1}") public ImgExperimentDTO tryGetExperimentByPermId(String experimentPermId); @@ -246,20 +287,9 @@ 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(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(sql = "select * from FEATURE_DEFS where DS_ID = any(?{1})", parameterBindings = diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java index c9c45d90616..b30820274d7 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/IImagingTransformerDAO.java @@ -23,9 +23,11 @@ import net.lemnik.eodsql.Update; import ch.systemsx.cisd.base.image.IImageTransformerFactory; /** + * Write operations on {@link IImageTransformerFactory} + all inherited read operations. + * * @author Franz-Josef Elmer */ -public interface IImagingTransformerDAO extends TransactionQuery +public interface IImagingTransformerDAO extends TransactionQuery, IImagingReadonlyQueryDAO { @Update(sql = "update experiments set image_transformer_factory = ?{2} where id = ?{1}", parameterBindings = { TransformerFactoryMapper.class, TypeMapper.class /* default */}) @@ -41,16 +43,12 @@ public interface IImagingTransformerDAO extends TransactionQuery public void saveTransformerFactoryForImage(long acquiredImageId, IImageTransformerFactory factory); - @Update(sql = "update channels set image_transformer_factory = ?{3} " - + "where code = ?{2} and exp_id = ?{1}", parameterBindings = - { TransformerFactoryMapper.class, TypeMapper.class/* default */, TypeMapper.class /* default */}) - public void saveTransformerFactoryForExperimentChannel(long experimentId, String channel, - IImageTransformerFactory factory); + @Update(sql = "insert into IMAGE_TRANSFORMATIONS(CODE, LABEL, DESCRIPTION, IMAGE_TRANSFORMER_FACTORY, IS_EDITABLE, CHANNEL_ID) values " + + "(?{1.code}, ?{1.label}, ?{1.description}, ?{1.serializedImageTransformerFactory}, ?{1.isEditable}, ?{1.channelId})") + public void addImageTransformation(ImgImageTransformationDTO imageTransformation); - @Update(sql = "update channels set image_transformer_factory = ?{3} " - + "where code = ?{2} and ds_id = ?{1}", parameterBindings = - { TransformerFactoryMapper.class, TypeMapper.class/* default */, TypeMapper.class /* default */}) - public void saveTransformerFactoryForDatasetChannel(long datasetId, String channel, + @Update(sql = "update IMAGE_TRANSFORMATIONS set IMAGE_TRANSFORMER_FACTORY = ?{2} where id = ?{1}", parameterBindings = + { TransformerFactoryMapper.class, TypeMapper.class /* default */}) + public void updateImageTransformerFactory(long imageTransformationId, IImageTransformerFactory factory); - } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java index ff2d9e6a57a..1c30c8fba51 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgChannelDTO.java @@ -24,7 +24,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelCo /** * @author Tomasz Pylak */ -public class ImgChannelDTO extends AbstractImageTransformerFactoryHolder +public class ImgChannelDTO extends AbstractImgIdentifiable { @ResultColumn("CODE") private String code; diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgImageTransformationDTO.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgImageTransformationDTO.java new file mode 100644 index 00000000000..c609382d9c6 --- /dev/null +++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/shared/imaging/dataaccess/ImgImageTransformationDTO.java @@ -0,0 +1,124 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess; + +import net.lemnik.eodsql.ResultColumn; + +import ch.systemsx.cisd.base.image.IImageTransformerFactory; +import ch.systemsx.cisd.openbis.generic.shared.basic.CodeNormalizer; + +/** + * @author Tomasz Pylak + */ +public class ImgImageTransformationDTO extends AbstractImageTransformerFactoryHolder +{ + @ResultColumn("CODE") + private String code; + + @ResultColumn("LABEL") + private String label; + + @ResultColumn("DESCRIPTION") + private String descriptionOrNull; + + @ResultColumn("IS_EDITABLE") + private boolean isEditable; + + @ResultColumn("CHANNEL_ID") + private long channelId; + + // EODSQL only + @SuppressWarnings("unused") + private ImgImageTransformationDTO() + { + // All Data-Object classes must have a default constructor. + } + + public ImgImageTransformationDTO(String code, String label, String descriptionOrNull, + boolean isEditable, long channelId, IImageTransformerFactory imageTransformationFactory) + { + assert code != null : "code is null"; + assert label != null : "label is null"; + assert imageTransformationFactory != null : "transformationFactory is null"; + + this.code = CodeNormalizer.normalize(code); + this.label = label; + this.descriptionOrNull = descriptionOrNull; + this.isEditable = isEditable; + this.channelId = channelId; + setImageTransformerFactory(imageTransformationFactory); + } + + public String getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + /** Can be null. */ + public String getDescription() + { + return descriptionOrNull; + } + + public void setDescription(String descriptionOrNull) + { + this.descriptionOrNull = descriptionOrNull; + } + + public boolean getIsEditable() + { + return isEditable; + } + + public void setEditable(boolean isEditable) + { + this.isEditable = isEditable; + } + + public long getChannelId() + { + return channelId; + } + + public void setChannelId(long channelId) + { + this.channelId = channelId; + } + + public final IImageTransformerFactory getImageTransformerFactory() + { + IImageTransformerFactory factory = super.tryGetImageTransformerFactory(); + assert factory != null : "image factory is null"; + return factory; + } + +} diff --git a/screening/source/sql/imaging/postgresql/014/schema-014.sql b/screening/source/sql/imaging/postgresql/014/schema-014.sql index 7b2517dd8b9..cb225d7d6cf 100644 --- a/screening/source/sql/imaging/postgresql/014/schema-014.sql +++ b/screening/source/sql/imaging/postgresql/014/schema-014.sql @@ -121,7 +121,7 @@ CREATE TABLE IMAGE_TRANSFORMATIONS ( CODE NAME NOT NULL, LABEL NAME NOT NULL, - DESCRIPTION DESCRIPTION, + DESCRIPTION character varying(1000), IS_EDITABLE BOOLEAN_CHAR NOT NULL, IMAGE_TRANSFORMER_FACTORY BYTEA NOT NULL, diff --git a/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql b/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql index 10d6b0ec12d..c15735ca959 100644 --- a/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql +++ b/screening/source/sql/imaging/postgresql/migration/migration-013-014.sql @@ -5,7 +5,7 @@ CREATE TABLE IMAGE_TRANSFORMATIONS ( CODE NAME NOT NULL, LABEL NAME NOT NULL, - DESCRIPTION DESCRIPTION, + DESCRIPTION character varying(1000), IS_EDITABLE BOOLEAN_CHAR NOT NULL, IMAGE_TRANSFORMER_FACTORY BYTEA NOT NULL, 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 37894657d9a..96bf69b4bbb 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 @@ -30,7 +30,10 @@ import net.lemnik.eodsql.QueryTool; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import ch.systemsx.cisd.base.image.IImageTransformerFactory; import ch.systemsx.cisd.bds.hcs.Location; +import ch.systemsx.cisd.openbis.generic.shared.basic.CodeNormalizer; +import ch.systemsx.cisd.openbis.plugin.screening.client.api.v1.ExampleImageTransformerFactory; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.AbstractDBTest; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ColorComponent; @@ -41,6 +44,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgCo import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgDatasetDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageEnrichedDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgSpotDTO; /** @@ -53,6 +57,14 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgSp public class ImagingQueryDAOTest extends AbstractDBTest { + private static final boolean IMAGE_TRANSFORMATION_IS_EDITABLE = false; + + private static final String IMAGE_TRANSFORMATION_DESC = "tr_desc"; + + private static final String IMAGE_TRANSFORMATION_LABEL = "tr_label"; + + private static final String IMAGE_TRANSFORMATION_CODE = "tr_code"; + private static final String CHANNEL_LABEL = "Channel Label"; private static final ImageChannelColor CHANNEL_COLOR = ImageChannelColor.RED; @@ -216,8 +228,14 @@ public class ImagingQueryDAOTest extends AbstractDBTest final long datasetId = addDataset(DS_PERM_ID, containerId); final long spotId = addSpot(containerId); final long datasetChannelId1 = addDatasetChannel(datasetId); + addImageTransformationsAndTestListing(datasetChannelId1); + testAddingAndRemovingImageTransformation(datasetChannelId1, DS_CHANNEL, datasetId, null); + assertTrue(dao.hasDatasetChannels(DS_PERM_ID)); final long experimentChannelId2 = addExperimentChannel(experimentId); + addImageTransformationsAndTestListing(experimentChannelId2); + testAddingAndRemovingImageTransformation(experimentChannelId2, EXP_CHANNEL, null, + experimentId); testChannelMethods(experimentId, datasetId, datasetChannelId1, experimentChannelId2); @@ -409,6 +427,125 @@ public class ImagingQueryDAOTest extends AbstractDBTest return dao.addChannel(channel); } + private void testAddingAndRemovingImageTransformation(long channelId, String channelCode, + Long datasetId, Long experimentId) + { + ImgImageTransformationDTO transformation = addImageTransformation(channelId, 312); + + String transformationCode = transformation.getCode(); + Long testedTransformationId = + tryGetTransformationIdForDatasetOrExperimentChannel(datasetId, experimentId, + transformationCode); + assertNotNull("Cannot get the transformation id back", testedTransformationId); + + ImgImageTransformationDTO testedTransformation = + dao.tryGetImageTransformation(channelId, transformationCode); + assertNotNull(testedTransformation); + assertEquals(testedTransformationId.longValue(), testedTransformation.getId()); + + List<ImgImageTransformationDTO> allTransformations = + listImageTransformations(datasetId, experimentId); + assertEquals(1, allTransformations.size()); + ImgImageTransformationDTO testedTransformationListed = allTransformations.get(0); + assertEquals("list transformations failed", testedTransformation, + testedTransformationListed); + + // test removal of transformation + dao.removeImageTransformation(transformationCode, channelId); + testedTransformationId = + tryGetTransformationIdForDatasetOrExperimentChannel(datasetId, experimentId, + transformationCode); + assertNull(testedTransformationId); + + } + + private List<ImgImageTransformationDTO> listImageTransformations(Long datasetIdOrNull, + Long experimentIdOrNull) + { + if (datasetIdOrNull != null) + { + return dao.listImageTransformationsByDatasetId(datasetIdOrNull); + } else + { + assert experimentIdOrNull != null : "both dataset and exp id are null"; + return dao.listImageTransformationsByExperimentId(experimentIdOrNull); + } + } + + private Long tryGetTransformationIdForDatasetOrExperimentChannel(Long datasetId, + Long experimentId, String transformationCode) + { + long channelId = getChannelId(datasetId, experimentId); + return dao.tryGetImageTransformationId(channelId, transformationCode); + } + + private long getChannelId(Long datasetIdOrNull, Long experimentIdOrNull) + { + if (datasetIdOrNull != null) + { + return dao.getDatasetChannelId(datasetIdOrNull, DS_CHANNEL); + } else + { + assert experimentIdOrNull != null : "both dataset and exp id are null"; + return dao.getExperimentChannelId(experimentIdOrNull, EXP_CHANNEL); + } + } + + private ImgImageTransformationDTO addImageTransformation(long channelId, long codeSuffix) + { + IImageTransformerFactory transformerFactory1 = new ExampleImageTransformerFactory(123); + ImgImageTransformationDTO transformation1 = + createImageTransformation(channelId, codeSuffix, transformerFactory1); + dao.addImageTransformations(Arrays.asList(transformation1)); + return transformation1; + } + + // adds two transformtion for the channel and tests that they are written correctly + private void addImageTransformationsAndTestListing(long channelId) + { + IImageTransformerFactory transformerFactory1 = new ExampleImageTransformerFactory(123); + ImgImageTransformationDTO transformation1 = + createImageTransformation(channelId, 0, transformerFactory1); + IImageTransformerFactory transformerFactory2 = new ExampleImageTransformerFactory(321); + ImgImageTransformationDTO transformation2 = + createImageTransformation(channelId, 1, transformerFactory2); + + dao.addImageTransformations(Arrays.asList(transformation1, transformation2)); + + // read back and test + + // listImageTransformations() test + List<ImgImageTransformationDTO> readTransformations = + dao.listImageTransformations(channelId); + + assertEquals(2, readTransformations.size()); + assertEquals(transformerFactory1, readTransformations.get(0) + .tryGetImageTransformerFactory()); + assertEquals(transformerFactory2, readTransformations.get(1) + .tryGetImageTransformerFactory()); + + for (int i = 0; i < 2; i++) + { + ImgImageTransformationDTO readTransformation = readTransformations.get(i); + assertEquals(CodeNormalizer.normalize(IMAGE_TRANSFORMATION_CODE + i), + readTransformation.getCode()); + assertEquals(IMAGE_TRANSFORMATION_LABEL, readTransformation.getLabel()); + assertEquals(IMAGE_TRANSFORMATION_DESC, readTransformation.getDescription()); + assertEquals(IMAGE_TRANSFORMATION_IS_EDITABLE, readTransformation.getIsEditable()); + // clean the db + dao.removeImageTransformation(readTransformation.getCode(), channelId); + + } + } + + private ImgImageTransformationDTO createImageTransformation(long channelId, long codeSuffix, + IImageTransformerFactory transformerFactory) + { + return new ImgImageTransformationDTO(IMAGE_TRANSFORMATION_CODE + codeSuffix, + IMAGE_TRANSFORMATION_LABEL, IMAGE_TRANSFORMATION_DESC, + IMAGE_TRANSFORMATION_IS_EDITABLE, channelId, transformerFactory); + } + private long addExperimentChannel(long experimentId) { final ImgChannelDTO channel = diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java index fdf8afbc270..1b38791510b 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ImageGenerationDescriptionFactoryTest.java @@ -29,6 +29,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageChannelStackR import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageGenerationDescription; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size; import ch.systemsx.cisd.openbis.generic.shared.ServletParamsParsingTestUtils; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants.ImageServletUrlParameters; /** * Tests of {@link ImageGenerationDescriptionFactory}. @@ -62,7 +63,7 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT DatasetAcquiredImagesReference channelsToMerge = desc.tryGetImageChannels(); assertEquals(channelStackRef, channelsToMerge.getChannelStackReference()); - assertEquals(Arrays.asList("DAPI", "GFP"), channelsToMerge.getChannelCodes()); + assertEquals(Arrays.asList("DAPI", "GFP"), channelsToMerge.getChannelCodes(null)); assertEquals(BASIC_DATASET_CODE, channelsToMerge.getDatasetCode()); List<DatasetAcquiredImagesReference> overlayChannels = desc.getOverlayChannels(); @@ -73,7 +74,7 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT DatasetAcquiredImagesReference firstChannel = overlayChannels.get(firstChannelIx); assertEquals(channelStackRef, firstChannel.getChannelStackReference()); - assertEquals(Arrays.asList("X", "Y"), firstChannel.getChannelCodes()); + assertEquals(Arrays.asList("X", "Y"), firstChannel.getChannelCodes(null)); assertEquals(firstOverlayDatasetCode, firstChannel.getDatasetCode()); DatasetAcquiredImagesReference secondChannel = overlayChannels.get(1 - firstChannelIx); @@ -86,16 +87,21 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT Map<String, String[]> paramsMap = createBasicParamsMap(); addListParams(paramsMap, "dataset", BASIC_DATASET_CODE); addListParams(paramsMap, "mergeChannels", "true"); + String myImageTransformationCode = "make-darker"; + addListParams(paramsMap, + ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM, + myImageTransformationCode); + addRequestParamsExpectations(paramsMap); ImageGenerationDescription desc = ImageGenerationDescriptionFactory.create(request); DatasetAcquiredImagesReference channelsToMerge = desc.tryGetImageChannels(); assertNotNull(channelsToMerge); - assertNull(channelsToMerge.getChannelCodes()); - assertTrue(channelsToMerge.isMergeAllChannels()); + assertNull(channelsToMerge.getChannelCodes(null)); + assertTrue(channelsToMerge.isMergeAllChannels(null)); assertEquals(BASIC_DATASET_CODE, channelsToMerge.getDatasetCode()); - + assertEquals(myImageTransformationCode, desc.tryGetSingleChannelTransformationCode()); assertEquals(0, desc.getOverlayChannels().size()); } @@ -120,7 +126,8 @@ public class ImageGenerationDescriptionFactoryTest extends ServletParamsParsingT final Map<String, String[]> paramsMap = new HashMap<String, String[]>(); addSingleParams(paramsMap, "sessionID", "sessionXXX", "wellRow", "1", "wellCol", "4", "tileRow", "7", "tileCol", "2", "mode", "thumbnail200x120", "channelStackId", null, - "mergeChannels", null, "dataset", null); + "mergeChannels", null, "dataset", null, + ImageServletUrlParameters.SINGLE_CHANNEL_TRANSFORMATION_CODE_PARAM, null); return paramsMap; } 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 69614af24fa..52983bd109d 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 @@ -20,7 +20,10 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.io.IOUtils; import org.jmock.Expectations; @@ -44,12 +47,14 @@ import ch.systemsx.cisd.openbis.dss.etl.IImagingDatasetLoader; import ch.systemsx.cisd.openbis.dss.etl.dto.ImageTransfomationFactories; import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.DatasetAcquiredImagesReference; import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageChannelStackReference; +import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.ImageTransformationParams; import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.RequestedImageSize; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.Size; import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtilTest; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannel; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageTransformationInfo; /** * @author Franz-Josef Elmer @@ -115,7 +120,8 @@ public class ImageChannelsUtilsTest extends AssertJUnit try { - createImageChannelsUtils(null).calculateBufferedImage(imageRef, true); + createImageChannelsUtils(null).calculateBufferedImage(imageRef, + createSingleChannelTransformationParams()); fail("EnvironmentFailureException expected"); } catch (EnvironmentFailureException ex) { @@ -126,6 +132,11 @@ public class ImageChannelsUtilsTest extends AssertJUnit context.assertIsSatisfied(); } + private ImageTransformationParams createSingleChannelTransformationParams() + { + return new ImageTransformationParams(true, false, null); + } + @Test public void testGetTiffImage() { @@ -152,8 +163,8 @@ public class ImageChannelsUtilsTest extends AssertJUnit prepareExpectations(absoluteImageReference, imageRef); BufferedImage image = - createImageChannelsUtils(thumbnailSizeOrNull) - .calculateBufferedImage(imageRef, true); + createImageChannelsUtils(thumbnailSizeOrNull).calculateBufferedImage(imageRef, + createSingleChannelTransformationParams()); assertEquals(expectedImageContentDescription, getImageContentDescription(image)); context.assertIsSatisfied(); @@ -165,17 +176,17 @@ public class ImageChannelsUtilsTest extends AssertJUnit context.checking(new Expectations() { { - one(loader).getImageParameters(); + allowing(loader).getImageParameters(); ImageDatasetParameters imgParams = new ImageDatasetParameters(); imgParams.setChannels(Arrays.asList(new ImageChannel(CHANNEL, CHANNEL, null, - null, null))); + null, null, new ArrayList<ImageTransformationInfo>()))); will(returnValue(imgParams)); RequestedImageSize requestedSize = absoluteImageReferenceOrNull == null ? RequestedImageSize .createOriginal() : absoluteImageReferenceOrNull .getRequestedSize(); - one(loader).tryGetImage(imageRef.getChannelCodes().get(0), + one(loader).tryGetImage(imageRef.getChannelCodes(null).get(0), imageRef.getChannelStackReference(), requestedSize); will(returnValue(absoluteImageReferenceOrNull)); } @@ -187,8 +198,8 @@ public class ImageChannelsUtilsTest extends AssertJUnit ImageChannelStackReference channelStackReference = ImageChannelStackReference.createFromId(4711); final DatasetAcquiredImagesReference imageRef = - new DatasetAcquiredImagesReference(DATASET_CODE, channelStackReference, - Arrays.asList(CHANNEL)); + DatasetAcquiredImagesReference.createForSingleChannel(DATASET_CODE, + channelStackReference, CHANNEL); return imageRef; } @@ -198,7 +209,10 @@ public class ImageChannelsUtilsTest extends AssertJUnit final DatasetAcquiredImagesReference imageRef = createDatasetAcquiredImagesReference(); AbsoluteImageReference imgRef = createAbsoluteImageReference("img1.gif", RequestedImageSize.createOriginal()); - imgRef.getImageTransfomationFactories().setForChannel(transformerFactory); + String transformationCode = "MY_TRANSFORMATION"; + Map<String, IImageTransformerFactory> transformations = + createImageTransformationsMap(transformationCode, transformerFactory); + imgRef.getImageTransfomationFactories().setForChannel(transformations); prepareExpectations(imgRef, imageRef); context.checking(new Expectations() @@ -209,13 +223,24 @@ public class ImageChannelsUtilsTest extends AssertJUnit } }); - BufferedImage image = createImageChannelsUtils(null).calculateBufferedImage(imageRef, true); + BufferedImage image = + createImageChannelsUtils(null).calculateBufferedImage(imageRef, + new ImageTransformationParams(true, false, transformationCode)); assertEquals("e00 f00 f00 e00\n" + "f00 c00 c00 f00\n" + "f00 c00 c00 f00\n" + "e00 f00 f00 e00\n", getImageContentDescription(image)); context.assertIsSatisfied(); } + private static Map<String, IImageTransformerFactory> createImageTransformationsMap( + String transformationCode, IImageTransformerFactory transformerFactory) + { + Map<String, IImageTransformerFactory> transformations = + new HashMap<String, IImageTransformerFactory>(); + transformations.put(transformationCode, transformerFactory); + return transformations; + } + private static AbsoluteImageReference createAbsoluteImageReference(String fileName, RequestedImageSize imageSize) { diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java index 03df1842ff2..1f6f50e8f46 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/screening/server/DssServiceRpcScreeningTest.java @@ -83,6 +83,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.WellPosition; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannel; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageChannelColor; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageDatasetParameters; +import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ImageTransformationInfo; import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants; import ch.systemsx.cisd.openbis.plugin.screening.shared.dto.PlateFeatureValues; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.IImagingReadonlyQueryDAO; @@ -94,6 +95,7 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgEx import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureDefDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureValuesDTO; import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgFeatureVocabularyTermDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageTransformationDTO; /** * Test cases for the {@link DssServiceRpcScreening}. @@ -175,8 +177,10 @@ public class DssServiceRpcScreeningTest extends AssertJUnit imageParameters.setTileRowsNum(1); imageParameters.setTileColsNum(2); imageParameters.setDatasetCode(DATASET_CODE); - imageParameters.setChannels(Arrays.asList(new ImageChannel(CHANNEL_CODE, CHANNEL_CODE, - null, null, null))); + ImageChannel imageChannel = + new ImageChannel(CHANNEL_CODE, CHANNEL_CODE, null, null, null, + new ArrayList<ImageTransformationInfo>()); + imageParameters.setChannels(Arrays.asList(imageChannel)); context.checking(new Expectations() { { @@ -395,9 +399,16 @@ public class DssServiceRpcScreeningTest extends AssertJUnit ImgChannelDTO channelDTO = new ImgChannelDTO("dapi", null, null, new Long(42), null, "dapi", ImageChannelColor.BLUE); - channelDTO.setSerializedImageTransformerFactory(SerializationUtils - .serialize(transformerFactory)); + long channelId = 444; + channelDTO.setId(channelId); will(returnValue(channelDTO)); + + one(dao).tryGetImageTransformation(channelId, + DssServiceRpcScreening.IMAGE_VIEWER_TRANSFORMATION_CODE); + ImgImageTransformationDTO transformationDTO = + new ImgImageTransformationDTO("tr_code", "tr_labal", null, true, + channelId, transformerFactory); + will(returnValue(transformationDTO)); } }); @@ -494,8 +505,18 @@ public class DssServiceRpcScreeningTest extends AssertJUnit allowing(dao).hasDatasetChannels(DATASET_CODE); will(returnValue(true)); - exactly(2).of(transformerDAO).saveTransformerFactoryForDatasetChannel( - datasetId, channel, transformerFactory); + long channelId = 5; + long transactionId = 123; + exactly(2).of(transformerDAO).getDatasetChannelId(datasetId, channel); + will(returnValue(channelId)); + + exactly(2).of(transformerDAO).tryGetImageTransformationId(channelId, + DssServiceRpcScreening.IMAGE_VIEWER_TRANSFORMATION_CODE); + + will(returnValue(transactionId)); + + exactly(2).of(transformerDAO).updateImageTransformerFactory(transactionId, + transformerFactory); one(transformerDAO).commit(); } -- GitLab