diff --git a/screening/resource/test-data/MicroscopyImageDropboxTest/maximum_intensity_projection.png b/screening/resource/test-data/MicroscopyImageDropboxTest/maximum_intensity_projection.png new file mode 100644 index 0000000000000000000000000000000000000000..1b83f88d594ae05c37b0eecbc9d791f9d61e0c07 Binary files /dev/null and b/screening/resource/test-data/MicroscopyImageDropboxTest/maximum_intensity_projection.png differ diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/IImageGenerationAlgorithm.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/IImageGenerationAlgorithm.java index cb88f88cf9b95491f3bb38cb8b395cff2d802200..859c6f77766d22291be50d1a47dcd65047b9b6cf 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/IImageGenerationAlgorithm.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/IImageGenerationAlgorithm.java @@ -4,7 +4,6 @@ import java.awt.image.BufferedImage; import java.util.List; import ch.systemsx.cisd.openbis.dss.etl.dto.api.impl.ImageDataSetInformation; -import ch.systemsx.cisd.openbis.dss.etl.dto.api.impl.ImageDataSetStructure; /** * Algorithm for creating a representative thumbnails. @@ -14,9 +13,9 @@ import ch.systemsx.cisd.openbis.dss.etl.dto.api.impl.ImageDataSetStructure; public interface IImageGenerationAlgorithm { /** - * Creates thumbnails for the specified data set info and structure. + * Creates thumbnails for the specified data set info. */ - public List<BufferedImage> generateImages(ImageDataSetInformation information, ImageDataSetStructure structure); + public List<BufferedImage> generateImages(ImageDataSetInformation information); /** * Returns the code of the data set to be registered containing these representative thumbnails. @@ -25,7 +24,7 @@ public interface IImageGenerationAlgorithm /** * Returns the thumbnail file name for the specified index. The index specifies the corresponding - * image returned by {@link #generateImages(ImageDataSetInformation, ImageDataSetStructure)}. + * image returned by {@link #generateImages(ImageDataSetInformation)}. * Note, all file names generated by this method have to different. */ public String getImageFileName(int index); diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/EmptyImageCreationAlgorithm.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/EmptyImageCreationAlgorithm.java index 9f036dea2745158698e3a280142f80451676c55d..5b5c8e530c7299627f15d9ca9d106660a8a3906e 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/EmptyImageCreationAlgorithm.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/EmptyImageCreationAlgorithm.java @@ -20,7 +20,7 @@ public class EmptyImageCreationAlgorithm implements IImageGenerationAlgorithm, S } @Override - public List<BufferedImage> generateImages(ImageDataSetInformation information, ImageDataSetStructure structure) + public List<BufferedImage> generateImages(ImageDataSetInformation information) { return Collections.emptyList(); } diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/MaximumIntensityProjectionGenerationAlgorithm.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/MaximumIntensityProjectionGenerationAlgorithm.java index 263b75669cbb4cae3c8b50a42f1ede89ffac8f17..cb33ecbbf555399a3f54efbc0164e8c656b1fb49 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/MaximumIntensityProjectionGenerationAlgorithm.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/MaximumIntensityProjectionGenerationAlgorithm.java @@ -6,17 +6,45 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.Serializable; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import ch.rinn.restrictions.Private; import ch.systemsx.cisd.openbis.common.io.FileBasedContentNode; import ch.systemsx.cisd.openbis.dss.etl.Utils; import ch.systemsx.cisd.openbis.dss.etl.dto.ImageLibraryInfo; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.Channel; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.ChannelColorComponent; import ch.systemsx.cisd.openbis.dss.etl.dto.api.IImageGenerationAlgorithm; import ch.systemsx.cisd.openbis.dss.etl.dto.api.ImageFileInfo; +import ch.systemsx.cisd.openbis.dss.generic.server.images.ImageChannelsUtils; import ch.systemsx.cisd.openbis.generic.shared.IServer; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ColorComponent; +/** + * Implementation of {@link IImageGenerationAlgorithm} which generates from a bunch of images + * a representative image where at each pixel the brightest pixel from the images are taken. + * + * @author Antti Luomi + * @author Franz-Josef Elmer + */ public class MaximumIntensityProjectionGenerationAlgorithm implements IImageGenerationAlgorithm, Serializable { + private static final class ChannelAndColorComponent + { + private Channel channel; + private ColorComponent colorComponent; + + ChannelAndColorComponent(Channel channel, ColorComponent colorComponent) + { + this.channel = channel; + this.colorComponent = colorComponent; + } + } + + public static final String DEFAULT_FILE_NAME = "maximum_intensity_projection.png"; + private static final long serialVersionUID = IServer.VERSION; private transient BufferedImage result = null; @@ -29,18 +57,38 @@ public class MaximumIntensityProjectionGenerationAlgorithm implements IImageGene private int height; - public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode) { + /** + * Creates an instance for the specified data set type. The generated image will have the same size as + * the original images. The file name will be <code>maximum_intensity_projection.png</code>. + */ + public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode) + { this(dataSetTypeCode, 0, 0); } - public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode, String filename) { + + /** + * Creates an instance for the specified data set type and file name. + * The generated image will have the same size as the original images. + */ + public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode, String filename) + { this(dataSetTypeCode, 0, 0, filename); } - - public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode, int width, int height) { - this(dataSetTypeCode, width, height, "maximum_intensity_projection"); + + /** + * Creates an instance for the specified data set type and specified image size. + * The file name will be <code>maximum_intensity_projection.png</code>. + */ + public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode, int width, int height) + { + this(dataSetTypeCode, width, height, DEFAULT_FILE_NAME); } - public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode, int width, int height, String filename) { + /** + * Creates an instance for the specified data set type, specified image size and specified file name. + */ + public MaximumIntensityProjectionGenerationAlgorithm(String dataSetTypeCode, int width, int height, String filename) + { this.dataSetTypeCode = dataSetTypeCode; this.width = width; this.height = height; @@ -52,86 +100,136 @@ public class MaximumIntensityProjectionGenerationAlgorithm implements IImageGene { return dataSetTypeCode; } - + @Override - public List<BufferedImage> generateImages(ImageDataSetInformation information, ImageDataSetStructure structure) + public List<BufferedImage> generateImages(ImageDataSetInformation information) { + ImageDataSetStructure structure = information.getImageDataSetStructure(); ImageLibraryInfo library = structure.getImageStorageConfiguraton().tryGetImageLibrary(); List<ImageFileInfo> images = structure.getImages(); + Map<String, ChannelAndColorComponent> channelsByCode = getChannelsByCode(structure); + File incomingDirectory = information.getIncomingDirectory(); int maxIntensity = 0; - for (ImageFileInfo image: images) { - String imagePath = image.getImageRelativePath(); - if (image.tryGetTimepoint() == null || image.tryGetTimepoint() != 0) { + for (ImageFileInfo imageFileInfo : images) + { + if (imageToBeIgnored(imageFileInfo)) + { continue; } - BufferedImage imageData = Utils.loadUnchangedImage(new FileBasedContentNode(new File( - information.getIncomingDirectory(), imagePath)), image.tryGetUniqueStringIdentifier(), library); - maxIntensity = addImage(imageData); + String imagePath = imageFileInfo.getImageRelativePath(); + ChannelAndColorComponent channelAndColorComponent = channelsByCode.get(imageFileInfo.getChannelCode()); + String identifier = imageFileInfo.tryGetUniqueStringIdentifier(); + BufferedImage image = loadImage(incomingDirectory, imagePath, identifier, library); + image = ImageChannelsUtils.rescaleIfNot8Bit(image, 0f); + image = ImageChannelsUtils.extractChannel(image, channelAndColorComponent.colorComponent); + image = ImageChannelsUtils.transformGrayToColor(image, channelAndColorComponent.channel.tryGetChannelColor()); + maxIntensity = addImage(image); } - - if (result == null) { + + if (result == null) + { return Collections.emptyList(); - } else { - for (int y=0; y<result.getHeight(); y++) { - for (int x=0; x<result.getWidth(); x++) { + } else + { + for (int y = 0; y < result.getHeight(); y++) + { + for (int x = 0; x < result.getWidth(); x++) + { result.setRGB(x, y, adjust(result.getRGB(x, y), maxIntensity)); } } - if (width > 0 && height > 0) { + if (width > 0 && height > 0) + { BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); AffineTransform at = new AffineTransform(); - at.scale((double)width / (double)result.getWidth(), (double)height / (double)result.getHeight()); + at.scale((double) width / (double) result.getWidth(), (double) height / (double) result.getHeight()); AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); - result = scaleOp.filter(result, scaled); + result = scaleOp.filter(result, scaled); } - + return Collections.singletonList(result); } + } + @Private BufferedImage loadImage(File incomingDirectory, String imagePath, String identifier, ImageLibraryInfo library) + { + File file = new File(incomingDirectory, imagePath); + return Utils.loadUnchangedImage(new FileBasedContentNode(file), identifier, library); } - private int adjust(int rgb, int maxIntensity) + /** + * Returns <code>true</code> if the specified image should be ignored. Can be overridden. + */ + protected boolean imageToBeIgnored(ImageFileInfo image) { - if (maxIntensity > 255) { - maxIntensity = 255; + return image.tryGetTimepoint() == null || image.tryGetTimepoint() != 0; + } + + private Map<String, ChannelAndColorComponent> getChannelsByCode(ImageDataSetStructure structure) + { + Map<String, ChannelAndColorComponent> channelsByCode = new HashMap<String, ChannelAndColorComponent>(); + List<Channel> channels = structure.getChannels(); + for (int i = 0; i < channels.size(); i++) + { + Channel channel = channels.get(i); + ColorComponent colorComponent = tryGetColorComponent(structure, i); + channelsByCode.put(channel.getCode(), new ChannelAndColorComponent(channel, colorComponent)); + } + return channelsByCode; + } + + private ColorComponent tryGetColorComponent(ImageDataSetStructure structure, int i) + { + List<ChannelColorComponent> channelColorComponents = structure.getChannelColorComponents(); + if (channelColorComponents == null || channelColorComponents.isEmpty()) + { + return null; } - int r = new Double( ((double)getRed(rgb)) / ((double)maxIntensity) * 255).intValue(); - int g = new Double( ((double)getGreen(rgb)) / ((double)maxIntensity) * 255).intValue(); - int b = new Double( ((double)getBlue(rgb)) / ((double)maxIntensity) * 255).intValue(); - + return ChannelColorComponent.getColorComponent(channelColorComponents.get(i)); + } + + private int adjust(int rgb, int maximumIntensity) + { + int maxIntensity = Math.min(255, maximumIntensity); + int r = rescale(getRed(rgb), maxIntensity); + int g = rescale(getGreen(rgb), maxIntensity); + int b = rescale(getBlue(rgb), maxIntensity); return (r << 16) + (g << 8) + b; - } - + + private int rescale(int intensity, int maxIntensity) + { + return (intensity * 255 + maxIntensity / 2) / maxIntensity; + } private int addImage(BufferedImage image) { - if (result == null) { - result = new BufferedImage(image.getWidth(),image.getHeight(), BufferedImage.TYPE_INT_RGB); - for (int y=0; y<image.getHeight(); y++) { - for (int x=0; x<image.getWidth(); x++) { - image.setRGB(x,y, 0); + int w = image.getWidth(); + int h = image.getHeight(); + if (result == null) + { + result = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + result.setRGB(x, y, 0); } } } int maxIntensity = 0; - for (int y=0; y<image.getHeight(); y++) { - for (int x=0; x<image.getWidth(); x++) { - int rgb1 = result.getRGB(x,y); - int rgb2 = image.getRGB(x,y); - + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int rgb1 = result.getRGB(x, y); + int rgb2 = image.getRGB(x, y); + int intensity1 = intensity(rgb1); int intensity2 = intensity(rgb2); - - if (intensity1 > maxIntensity) { - maxIntensity = intensity1; - } - - if (intensity2 > maxIntensity) { - maxIntensity = intensity2; - } - - result.setRGB(x,y, intensity1 > intensity2 ? rgb1 : rgb2); + maxIntensity = Math.max(Math.max(maxIntensity, intensity1), intensity2); + + result.setRGB(x, y, intensity1 > intensity2 ? rgb1 : rgb2); } } return maxIntensity; @@ -142,22 +240,19 @@ public class MaximumIntensityProjectionGenerationAlgorithm implements IImageGene double r = getRed(rgb); double g = getGreen(rgb); double b = getBlue(rgb); - return new Double(Math.sqrt(r*r + g*g + b*b)).intValue(); + return new Double(Math.sqrt(r * r + g * g + b * b)).intValue(); } - private int getBlue(int rgb) { return rgb & 0xff; } - private int getGreen(int rgb) { return (rgb >> 8) & 0xff; } - private int getRed(int rgb) { return (rgb >> 16) & 0xff; diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/jython/v2/ImagingDataSetRegistrationTransaction.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/jython/v2/ImagingDataSetRegistrationTransaction.java index 5a3f323c815056edd0c2660caaee16ed6910328f..2c08e93145687af3afb409799a7deef8201ddf06 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/jython/v2/ImagingDataSetRegistrationTransaction.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/jython/v2/ImagingDataSetRegistrationTransaction.java @@ -347,8 +347,8 @@ public class ImagingDataSetRegistrationTransaction extends DataSetRegistrationTr containedDataSetCodes.add(mainDataset.getDataSetCode()); - createRepresentativeThumbnailByImageGenerationAlgorithm(imageDataSetInformation, imageDataSetStructure, - containedDataSetCodes, thumbnailDatasets); + createRepresentativeThumbnailByImageGenerationAlgorithm(imageDataSetInformation, containedDataSetCodes, + thumbnailDatasets); for (IDataSet thumbnailDataset : thumbnailDatasets) { @@ -366,14 +366,14 @@ public class ImagingDataSetRegistrationTransaction extends DataSetRegistrationTr } private void createRepresentativeThumbnailByImageGenerationAlgorithm(ImageDataSetInformation imageDataSetInformation, - ImageDataSetStructure imageDataSetStructure, List<String> containedDataSetCodes, List<IDataSet> thumbnailDatasets) + List<String> containedDataSetCodes, List<IDataSet> thumbnailDatasets) { IImageGenerationAlgorithm algorithm = imageDataSetInformation.getImageGenerationAlgorithm(); if (algorithm == null) { return; } - List<BufferedImage> images = algorithm.generateImages(imageDataSetInformation, imageDataSetStructure); + List<BufferedImage> images = algorithm.generateImages(imageDataSetInformation); if (images.size() > 0) { IDataSet representative = createNewDataSet(algorithm.getDataSetTypeCode()); for (int i = 0; i < images.size(); i++) diff --git a/screening/sourceTest/core-plugins/MicroscopyImageDropboxTest/1/dss/drop-boxes/MicroscopyImageDropboxTest-drop-box/MicroscopySingleDatasetConfig.py b/screening/sourceTest/core-plugins/MicroscopyImageDropboxTest/1/dss/drop-boxes/MicroscopyImageDropboxTest-drop-box/MicroscopySingleDatasetConfig.py index 149865111cdd22577b48258d1414624cf5097a1c..7dfb4ce3f3dff6bae92f34ecfea045d6006b10d7 100644 --- a/screening/sourceTest/core-plugins/MicroscopyImageDropboxTest/1/dss/drop-boxes/MicroscopyImageDropboxTest-drop-box/MicroscopySingleDatasetConfig.py +++ b/screening/sourceTest/core-plugins/MicroscopyImageDropboxTest/1/dss/drop-boxes/MicroscopyImageDropboxTest-drop-box/MicroscopySingleDatasetConfig.py @@ -13,6 +13,13 @@ from ch.systemsx.cisd.openbis.dss.etl.dto.api import ImageMetadata from ch.systemsx.cisd.openbis.dss.etl.dto.api import OriginalDataStorageFormat from ch.systemsx.cisd.openbis.dss.etl.dto.api import ChannelColorRGB from ch.systemsx.cisd.openbis.dss.etl.dto.api import Channel +from ch.systemsx.cisd.openbis.dss.etl.dto.api.impl import MaximumIntensityProjectionGenerationAlgorithm; + +class TestAlgorithm(MaximumIntensityProjectionGenerationAlgorithm): + def __init__(self): + MaximumIntensityProjectionGenerationAlgorithm.__init__(self, 'MICROSCOPY_REPRESENTATIVE_IMG') + def imageToBeIgnored(self, imageFileInfo): + return False class MicroscopySingleDatasetConfig(SimpleImageContainerDataConfig): """Image data configuration class for multi datasets image files.""" @@ -60,6 +67,7 @@ class MicroscopySingleDatasetConfig(SimpleImageContainerDataConfig): # Set the dataset type self.setDataSetType("MICROSCOPY_IMG") + self.setImageGenerationAlgorithm(TestAlgorithm()) def createChannel(self, channelCode): """Create a channel from the channelCode with the name as read from diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/MaximumIntensityProjectionGenerationAlgorithmTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/MaximumIntensityProjectionGenerationAlgorithmTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c2d5ec5c9ff9897877c632cf76f41243cbdc273e --- /dev/null +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/dto/api/impl/MaximumIntensityProjectionGenerationAlgorithmTest.java @@ -0,0 +1,207 @@ +/* + * Copyright 2014 ETH Zuerich, SIS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.systemsx.cisd.openbis.dss.etl.dto.api.impl; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.rinn.restrictions.Friend; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.image.ImageHistogram; +import ch.systemsx.cisd.openbis.dss.etl.dto.ImageLibraryInfo; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.Channel; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.ChannelColorRGB; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.ImageFileInfo; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.ImageStorageConfiguraton; + + +/** + * + * + * @author Franz-Josef Elmer + */ +@Friend(toClasses = MaximumIntensityProjectionGenerationAlgorithm.class) +public class MaximumIntensityProjectionGenerationAlgorithmTest extends AbstractFileSystemTestCase +{ + private static final class ImageBuilder + { + private BufferedImage image; + private Graphics graphics; + + ImageBuilder(int imageType) + { + image = new BufferedImage(10, 6, BufferedImage.TYPE_INT_RGB); + graphics = image.getGraphics(); + } + + BufferedImage getImage() + { + return image; + } + + ImageBuilder rect(int x, int y, int width, int height, Color color) + { + graphics.setColor(color); + graphics.fillRect(x, y, width, height); + return this; + } + } + + private static final class MaximumIntensityProjectionGenerationAlgorithmWithMockedLoading + extends MaximumIntensityProjectionGenerationAlgorithm + { + private static final long serialVersionUID = 1L; + + private final List<String> recorder = new ArrayList<String>(); + + private final Map<String, BufferedImage> images; + + private final Set<String> imagesToIgnore; + + public MaximumIntensityProjectionGenerationAlgorithmWithMockedLoading(String dataSetTypeCode, + int width, int height, String filename, Map<String, BufferedImage> images, String... imagesToIgnore) + { + super(dataSetTypeCode, width, height, filename); + this.images = images; + this.imagesToIgnore = new HashSet<String>(Arrays.asList(imagesToIgnore)); + } + + @Override + BufferedImage loadImage(File incomingDirectory, String imagePath, String identifier, ImageLibraryInfo library) + { + recorder.add(incomingDirectory.getName() + "/" + imagePath + ": " + identifier + " [" + library + "]"); + BufferedImage bufferedImage = images.get(identifier); + assertNotNull("image " + identifier, bufferedImage); + return bufferedImage; + } + + @Override + protected boolean imageToBeIgnored(ImageFileInfo image) + { + return imagesToIgnore.contains(image.tryGetUniqueStringIdentifier()); + } + } + + private Map<String, BufferedImage> images; + private ImageDataSetInformation information; + private ImageDataSetStructure structure; + + @BeforeMethod + public void createImages() + { + images = new LinkedHashMap<String, BufferedImage>(); + images.put("i1", new ImageBuilder(BufferedImage.TYPE_INT_RGB) + .rect(1, 0, 3, 2, new Color(255, 128, 128)) + .rect(5, 4, 3, 2, new Color(80, 255, 128)).getImage()); + images.put("i2", new ImageBuilder(BufferedImage.TYPE_USHORT_GRAY) + .rect(2, 1, 5, 4, new Color(100, 100, 100)).getImage()); + images.put("i3", new ImageBuilder(BufferedImage.TYPE_USHORT_GRAY) + .rect(3, 2, 5, 4, new Color(30, 30, 30)).getImage()); + information = new ImageDataSetInformation(); + information.setIncomingDirectory(new File(workingDirectory, "incoming")); + structure = new ImageDataSetStructure(); + information.setImageDataSetStructure(structure); + ImageStorageConfiguraton imageStorageConfiguraton = new ImageStorageConfiguraton(); + imageStorageConfiguraton.setImageLibrary(new ImageLibraryInfo("lib", "reader")); + structure.setImageStorageConfiguraton(imageStorageConfiguraton); + Set<String> keySet = images.keySet(); + List<ImageFileInfo> imageFileInfos = new ArrayList<ImageFileInfo>(); + for (String key : keySet) + { + ImageFileInfo fileInfo = new ImageFileInfo("C" + key.toUpperCase(), 1, 1, "images/" + key + ".png"); + fileInfo.setUniqueImageIdentifier(key); + imageFileInfos.add(fileInfo); + } + structure.setImages(imageFileInfos); + } + + @Test + public void test8BitColorImageAnd16BitGrayImage() + { + + MaximumIntensityProjectionGenerationAlgorithmWithMockedLoading generationAlgorithm + = new MaximumIntensityProjectionGenerationAlgorithmWithMockedLoading("ABC", 10, 6, "r.png", images, "i3"); + assertEquals("ABC", generationAlgorithm.getDataSetTypeCode()); + assertEquals("r.png", generationAlgorithm.getImageFileName(0)); + Channel channel1 = new Channel("CI1", "i1", new ChannelColorRGB(200, 100, 80)); + Channel channel2 = new Channel("CI2", "i2", new ChannelColorRGB(0, 120, 180)); + structure.setChannels(Arrays.asList(channel1, channel2)); + + List<BufferedImage> generatedImages = generationAlgorithm.generateImages(information); + + assertEquals("[incoming/images/i1.png: i1 [lib (reader: reader)], " + + "incoming/images/i2.png: i2 [lib (reader: reader)]]", generationAlgorithm.recorder.toString()); + + ImageHistogram histogram = ImageHistogram.calculateHistogram(generatedImages.get(0)); + assertEquals("[0=48, 80=6, 255=6]", renderHistogram(histogram.getRedHistogram())); + assertEquals("[0=32, 47=16, 128=6, 255=6]", renderHistogram(histogram.getGreenHistogram())); + assertEquals("[0=32, 71=16, 128=12]", renderHistogram(histogram.getBlueHistogram())); + assertEquals(1, generatedImages.size()); + } + + @Test + public void testTwo16BitGrayImages() + { + + MaximumIntensityProjectionGenerationAlgorithmWithMockedLoading generationAlgorithm + = new MaximumIntensityProjectionGenerationAlgorithmWithMockedLoading("ABC", 10, 6, "r.png", images, "i1"); + Channel channel1 = new Channel("CI2", "i2", new ChannelColorRGB(200, 100, 80)); + Channel channel2 = new Channel("CI3", "i3", new ChannelColorRGB(0, 120, 180)); + structure.setChannels(Arrays.asList(channel1, channel2)); + + List<BufferedImage> generatedImages = generationAlgorithm.generateImages(information); + + assertEquals("[incoming/images/i2.png: i2 [lib (reader: reader)], " + + "incoming/images/i3.png: i3 [lib (reader: reader)]]", generationAlgorithm.recorder.toString()); + + ImageHistogram histogram = ImageHistogram.calculateHistogram(generatedImages.get(0)); + assertEquals("[0=40, 216=20]", renderHistogram(histogram.getRedHistogram())); + assertEquals("[0=32, 39=8, 108=20]", renderHistogram(histogram.getGreenHistogram())); + assertEquals("[0=32, 58=8, 86=20]", renderHistogram(histogram.getBlueHistogram())); + assertEquals(1, generatedImages.size()); + } + + private String renderHistogram(int[] histogram) + { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < histogram.length; i++) + { + int value = histogram[i]; + if (value > 0) + { + if (builder.length() > 0) + { + builder.append(", "); + } + builder.append(i).append("=").append(value); + } + } + return "[" + builder.toString() + "]"; + } +} diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java index 57436c6d52100d5a8f0a47d3813ab339d4c9498a..8270cb500045290516fca04fe521c80663c6f1ce 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/AbstractScreeningSystemTestCase.java @@ -94,12 +94,17 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase } + private static interface IImageLoader + { + public BufferedImage load(); + + } /** * Helper class to load an image from the image download servlet. * * @author Franz-Josef Elmer */ - protected static final class ImageLoader + protected static final class ImageLoader implements IImageLoader { private final URLMethodWithParameters url; private final List<String> channels = new ArrayList<String>(); @@ -120,6 +125,7 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase url.addParameter(ImageServletUrlParameters.DATASET_CODE_PARAM, dataSet.getCode()); } + @Override public BufferedImage load() { try @@ -241,7 +247,7 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase } /** - * Asserts no failures occurred for all invocations of {@link #check(File, ImageLoader)}. + * Asserts no failures occurred for all invocations of {@link #check(File, IImageLoader)}. * Otherwise an {@link AssertionError} is thrown. */ public void assertNoFailures() @@ -253,11 +259,39 @@ public abstract class AbstractScreeningSystemTestCase extends SystemTestCase } } + /** + * Checks that the specified reference image is equals to the image loaded from the specified file + * in the specified data set. + */ + public void check(File referenceImage, final String sessionToken, final AbstractExternalData dataSet, + final String pathInDataSet) + { + check(referenceImage, new IImageLoader() + { + + @Override + public BufferedImage load() + { + URLMethodWithParameters url = new URLMethodWithParameters(dataSet.getDataStore().getHostUrl() + + "/datastore_server/" + dataSet.getCode() + "/" + pathInDataSet); + url.addParameter(Utils.SESSION_ID_PARAM, sessionToken); + url.addParameter("mode", "simpleHtml"); + try + { + return ImageIO.read(new URL(url.toString())); + } catch (Exception ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + }); + } + /** * Checks that the specified reference image is equals to the image loaded by the specified image loader. * A report is created in case of a failure. */ - public void check(File referenceImage, ImageLoader imageLoader) + public void check(File referenceImage, IImageLoader imageLoader) { FailureReport report = new FailureReport(referenceImage); try diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java index 7c3cbd869e617a5606e1801dd7fbb2b979d6d6dd..a0cdbadaddce386320f76c2e56e82f3cfcc59eee 100644 --- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/screening/systemtests/MicroscopyImageDropboxTest.java @@ -22,6 +22,7 @@ import java.util.Collections; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.impl.MaximumIntensityProjectionGenerationAlgorithm; import ch.systemsx.cisd.openbis.generic.shared.ICommonServer; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetKind; @@ -67,6 +68,9 @@ public class MicroscopyImageDropboxTest extends AbstractImageDropboxTestCase dataSetType = new DataSetType("MICROSCOPY_IMG_OVERVIEW"); dataSetType.setDataSetKind(DataSetKind.PHYSICAL); registerDataSetType(commonServer, dataSetType); + dataSetType = new DataSetType("MICROSCOPY_REPRESENTATIVE_IMG"); + dataSetType.setDataSetKind(DataSetKind.PHYSICAL); + registerDataSetType(commonServer, dataSetType); dataSetType = new DataSetType("MICROSCOPY_IMG_CONTAINER"); dataSetType.setDataSetKind(DataSetKind.CONTAINER); registerDataSetType(commonServer, dataSetType); @@ -119,6 +123,8 @@ public class MicroscopyImageDropboxTest extends AbstractImageDropboxTestCase imageChecker.check(new File(getTestDataFolder(), "Merged_256x256_C0_0_200_C4_150_300.png"), new ImageLoader(dataSet, sessionToken).microscopy().rescaling("SERIES-0_CHANNEL-0", 0, 200) .rescaling("SERIES-0_CHANNEL-4", 150, 300).mode("thumbnail256x256")); + String pathInDataSet = MaximumIntensityProjectionGenerationAlgorithm.DEFAULT_FILE_NAME; + imageChecker.check(new File(getTestDataFolder(), pathInDataSet), sessionToken, dataSet, pathInDataSet); imageChecker.assertNoFailures(); }