diff --git a/screening/.classpath b/screening/.classpath index c02a69ae620240b1f757651bd92614b6eca14657..314d7bc3a608d1dfee5cf1486f3d9996ce55d894 100644 --- a/screening/.classpath +++ b/screening/.classpath @@ -47,5 +47,6 @@ <classpathentry kind="lib" path="/libraries/sybit-image-viewer/image-viewer.jar"/> <classpathentry kind="lib" path="/libraries/jython/jython.jar" sourcepath="/libraries/jython/src.zip"/> <classpathentry kind="lib" path="/libraries/hibernate/hibernate3.jar" sourcepath="/libraries/hibernate/src.zip"/> + <classpathentry kind="lib" path="/libraries/bioformats/bioformats.jar" sourcepath="/libraries/bioformats/bioformats_src.zip"/> <classpathentry kind="output" path="targets/www/WEB-INF/classes"/> </classpath> diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/biozentrum/ImageMetadataExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/biozentrum/ImageMetadataExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..86171607e97f2174c21137bf661012243fc8cb3e --- /dev/null +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/biozentrum/ImageMetadataExtractor.java @@ -0,0 +1,158 @@ +/* + * 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.etl.biozentrum; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; + +import loci.formats.IFormatReader; +import loci.formats.ImageReader; +import loci.formats.gui.BufferedImageReader; +import loci.formats.in.DefaultMetadataOptions; +import loci.formats.in.MetadataLevel; +import loci.formats.in.MetadataOptions; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; +import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.Location; + +/** + * @author Kaloyan Enimanev + */ +public class ImageMetadataExtractor +{ + + static final String POSITION_X_PROP = "stage-position-x"; + + static final String POSITION_Y_PROP = "stage-position-y"; + + public static Map<String, Object> extractMetadata(File imageFile) + { + HashMap<String, Object> result = new HashMap<String, Object>(); + // initialize the reader + IFormatReader reader = new BufferedImageReader(new ImageReader()); + MetadataOptions metaOptions = new DefaultMetadataOptions(MetadataLevel.ALL); + reader.setMetadataOptions(metaOptions); + + try + { + reader.setId(imageFile.getAbsolutePath()); + nullSafeAddAll(result, reader.getGlobalMetadata()); + nullSafeAddAll(result, reader.getSeriesMetadata()); + } catch (Exception ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } finally + { + close(reader); + } + + return result; + } + + /** + * @param tileToMetadataMap mapping from tile number to image metadata + */ + public static Map<Integer/* tile number */, Location> tryGetTileMapping( + Map<Integer/* tile number */, Map<String/* name */, Object/* value */>> tileToMetadataMap) + { + Set<Number> xCoords = new TreeSet<Number>(); + Set<Number> yCoords = new TreeSet<Number>(); + for (Map<String, Object> metadata : tileToMetadataMap.values()) + { + xCoords.add(extractXCoord(metadata)); + yCoords.add(extractYCoord(metadata)); + } + + List<Number> sortedXCoords = new ArrayList<Number>(xCoords); + List<Number> sortedYCoords = new ArrayList<Number>(yCoords); + + Map<Integer, Location> result = new HashMap<Integer, Location>(); + for (Entry<Integer, Map<String, Object>> entry : tileToMetadataMap.entrySet()) + { + Integer tileNumber = entry.getKey(); + Location location = extractLocation(entry.getValue(), sortedXCoords, sortedYCoords); + result.put(tileNumber, location); + } + return result; + } + + private static void nullSafeAddAll(HashMap<String, Object> accumulator, + Hashtable<String, Object> toAdd) + { + if (toAdd != null) + { + accumulator.putAll(toAdd); + } + + } + + private static void close(IFormatReader reader) + { + try + { + reader.close(); + } catch (Exception ex) + { + // do not throw exception + } + } + + private static Location extractLocation(Map<String, Object> metadata, List<Number> xCoords, + List<Number> yCoords) + { + Number x = extractXCoord(metadata); + Number y = extractYCoord(metadata); + + int locationX = xCoords.indexOf(x); + int locationY = yCoords.indexOf(y); + + Location location = new Location(locationY, locationX); + return location; + } + + private static Number extractXCoord(Map<String, Object> metadata) + { + return extractNumber(metadata, POSITION_X_PROP); + } + + private static Number extractYCoord(Map<String, Object> metadata) + { + return extractNumber(metadata, POSITION_Y_PROP); + } + + private static Number extractNumber(Map<String, Object> metadata, String propName) + { + String numberAsString = (String) metadata.get(propName); + try + { + return Double.valueOf(numberAsString); + } catch (NumberFormatException nfe) + { + throw UserFailureException.fromTemplate( + "Cannot parse number for property %s with value %s", propName, numberAsString); + } + } + +} diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/biozentrum/ImageMetadataExtractorTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/biozentrum/ImageMetadataExtractorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..cc52ffe1cc9227bdaaafb4b0ceb5138117e0491b --- /dev/null +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/biozentrum/ImageMetadataExtractorTest.java @@ -0,0 +1,179 @@ +/* + * 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.etl.biozentrum; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.openbis.dss.etl.dto.api.v1.Location; + +/** + * @author Kaloyan Enimanev + */ +public class ImageMetadataExtractorTest extends AssertJUnit +{ + + public final String FILE_NAME = + "./sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/biozentrum/demo.tif"; + + @Test + public void testMetaDataExtraction() throws Exception + { + File imageFile = new File(FILE_NAME); + Map<String, Object> metaData = ImageMetadataExtractor.extractMetadata(imageFile); + List<String> sortedKeys = new ArrayList<String>(metaData.keySet()); + Collections.sort(sortedKeys); + + StringBuilder output = new StringBuilder(); + for (String key : sortedKeys) + { + Object value = metaData.get(key); + output.append(key + ": " + value); + output.append("\n"); + } + + assertEquals( + "<prop id=\"Description\" type=\"string\" value=\"Experiment base name:ME20101116METADATA03&#13;&#10;Experiment set:ME20101116METADATA03&#13;&#10;Plate Screen&#13;&#10;Exposure: 10 ms&#13;&#10;Binning: 1 x 1&#13;&#10;Region: 1392 x 1040, offset at (0, 0)&#13;&#10;Acquired from Photometrics&#13;&#10;Subtract: Off&#13;&#10;Shading: Off&#13;&#10;Digitizer: 20 MHz&#13;&#10;Gain: Gain 1 (1x)&#13;&#10;Camera Shutter: Always Open&#13;&#10;Clear Count: 2&#13;&#10;Clear Mode: CLEAR PRE SEQUENCE&#13;&#10;Frames to Average: 1&#13;&#10;Trigger Mode: Normal (TIMED)&#13;&#10;Temperature: -29.95&#13;&#10;\">\n" + + "<prop id=\"stage-label\" type=\"string\" value=\"F09: Site 1\">\n" + + "ApplicationName: MetaMorph\n" + + "ApplicationVersion: 3.1.0.79\n" + + "Binning: 1 x 1\n" + + "BitsPerSample: 16\n" + + "Camera Bit Depth: 12\n" + + "Camera Shutter: Always Open\n" + + "Clear Count: 2\n" + + "Clear Mode: CLEAR PRE SEQUENCE\n" + + "Compression: Uncompressed\n" + + "DateTime: 20101121 16:52:52.986\n" + + "Digitizer: 20 MHz\n" + + "Experiment base name: ME20101116METADATA03\n" + + "Experiment set: ME20101116METADATA03\n" + + "Exposure: 10 ms\n" + + "Frames to Average: 1\n" + + "Gain: Gain 1 (1x)\n" + + "ImageLength: 1040\n" + + "ImageWidth: 1392\n" + + "ImageXpress Micro Filter Cube: Texas Red\n" + + "ImageXpress Micro Objective: 10X S Fluor\n" + + "ImageXpress Micro Shutter: Closed\n" + + "Laser focus score: 36.4006\n" + + "MetaDataPhotometricInterpretation: Monochrome\n" + + "MetaDataVersion: 1\n" + + "NewSubfileType: 2\n" + + "NumberOfChannels: 1\n" + + "PhotometricInterpretation: BlackIsZero\n" + + "Region: 1392 x 1040, offset at (0, 0)\n" + + "SamplesPerPixel: 1\n" + + "Shading: Off\n" + + "Software: MetaSeries\n" + + "Subtract: Off\n" + + "Temperature: -29.95\n" + + "Trigger Mode: Normal (TIMED)\n" + + "X position for position #1: 73296.5\n" + + "Y position for position #1: 25554.2\n" + + "_IllumSetting_: Cy3\n" + + "_MagNA_: 0.5\n" + + "_MagRI_: 1\n" + + "_MagSetting_: 10X S Fluor\n" + + "acquisition-time-local: 20101121 16:52:52.986\n" + + "autoscale-max-percent: 0\n" + + "autoscale-min-percent: 0\n" + + "autoscale-state: off\n" + + "bits-per-pixel: 16\n" + + "camera-binning-x: 1\n" + + "camera-binning-y: 1\n" + + "camera-chip-offset-x: 0\n" + + "camera-chip-offset-y: 0\n" + + "gamma: 1\n" + + "gray-calibration-curve-fit-algorithm: 4\n" + + "gray-calibration-max: -1\n" + + "gray-calibration-min: -1\n" + + "gray-calibration-units: \n" + + "gray-calibration-values: \n" + + "image-name: Cy3\n" + + "look-up-table-name: Set By Wavelength\n" + + "look-up-table-type: by-wavelength\n" + + "modification-time-local: 20101121 16:52:53.50\n" + + "number-of-planes: 1\n" + + "photonegative-mode: off\n" + + "pixel-size-x: 1392\n" + + "pixel-size-y: 1040\n" + + "plane-guid: {70EAFB69-A568-464E-9EE2-4479767F96EF}\n" + + "plane-type: plane\n" + + "scale-max: 1058\n" + + "scale-min: 96\n" + + "spatial-calibration-state: on\n" + + "spatial-calibration-units: um\n" + + "spatial-calibration-x: 0.645\n" + + "spatial-calibration-y: 0.645\n" + + "stage-label: F09 : Site 1\n" + + "stage-position-x: 73296.5\n" + + "stage-position-y: 25554.2\n" + + "threshold-color: 4080ff\n" + + "threshold-high: 65535\n" + + "threshold-low: 0\n" + + "threshold-state: ThresholdOff\n" + + "timestamp 0: 2010-11-21T16:52:52\n" + + "wavelength: 624\n" + "z-position: 11145.7\n" + "zoom-percent: 50\n", + output.toString()); + } + + @Test + public void testTileMapping() throws Exception + { + Map<Integer, Map<String, Object>> tileMetadata = + new HashMap<Integer, Map<String, Object>>(); + + tileMetadata.put(1, createTileMetadata(1, 1)); + tileMetadata.put(2, createTileMetadata(2, 2)); + tileMetadata.put(3, createTileMetadata(3, 3)); + tileMetadata.put(4, createTileMetadata(1, 2)); + tileMetadata.put(5, createTileMetadata(3, 2)); + + Map<Integer, Location> locations = ImageMetadataExtractor.tryGetTileMapping(tileMetadata); + + assertLocation(0, 0, 1, locations); + assertLocation(1, 1, 2, locations); + assertLocation(2, 2, 3, locations); + assertLocation(0, 1, 4, locations); + assertLocation(2, 1, 5, locations); + + } + + private void assertLocation(int column, int row, int tileNumber, Map<Integer, Location> locations) + { + Location location = locations.get(tileNumber); + assertEquals(column, location.getColumn()); + assertEquals(row, location.getRow()); + } + + private Map<String, Object> createTileMetadata(double x, double y) + { + Map<String, Object> metadata = new HashMap<String, Object>(); + metadata.put(ImageMetadataExtractor.POSITION_X_PROP, String.valueOf(x)); + metadata.put(ImageMetadataExtractor.POSITION_Y_PROP, String.valueOf(y)); + return metadata; + } + +}