/* * #%L * BSD implementations of Bio-Formats readers and writers * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * #L% */ package loci.formats.out; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveInteger; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.common.RandomAccessOutputStream; import loci.common.services.DependencyException; import loci.common.services.ServiceException; import loci.common.services.ServiceFactory; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.meta.MetadataRetrieve; import loci.formats.ome.OMEXMLMetadata; import loci.formats.ome.OMEXMLMetadataImpl; import loci.formats.services.OMEXMLService; import loci.formats.tiff.IFD; import loci.formats.tiff.TiffSaver; /** * OMETiffWriter is the file format writer for OME-TIFF files. */ public class OMETiffWriter extends TiffWriter { // -- Constants -- private static final String WARNING_COMMENT = "<!-- Warning: this comment is an OME-XML metadata block, which " + "contains crucial dimensional parameters and other important metadata. " + "Please edit cautiously (if at all), and back up the original data " + "before doing so. For more information, see the OME-TIFF web site: " + FormatTools.URL_OME_TIFF + ". -->"; // -- Fields -- private String[][] imageLocations; private OMEXMLMetadata omeMeta; private OMEXMLService service; private Map<String, Integer> ifdCounts = new HashMap<String, Integer>(); private Map<String, String> uuids = new HashMap<String, String>(); // -- Constructor -- public OMETiffWriter() { super("OME-TIFF", new String[] {"ome.tif", "ome.tiff", "ome.tf2", "ome.tf8", "ome.btf"}); } // -- IFormatHandler API methods -- /* @see loci.formats.IFormatHandler#close() */ @Override public void close() throws IOException { try { if (currentId != null) { setupServiceAndMetadata(); // remove any BinData elements from the OME-XML service.removeBinData(omeMeta); for (int series=0; series<omeMeta.getImageCount(); series++) { setSeries(series); populateImage(omeMeta, series); } List<String> files = new ArrayList<String>(); for (String[] s : imageLocations) { for (String f : s) { if (!files.contains(f) && f != null) { files.add(f); String xml = getOMEXML(f); // write OME-XML to the first IFD's comment saveComment(f, xml); } } } } } catch (DependencyException de) { throw new RuntimeException(de); } catch (ServiceException se) { throw new RuntimeException(se); } catch (FormatException fe) { throw new RuntimeException(fe); } catch (IllegalArgumentException iae) { throw new RuntimeException(iae); } finally { super.close(); boolean canReallyClose = omeMeta == null || ifdCounts.size() == omeMeta.getImageCount(); if (omeMeta != null && canReallyClose) { int omePlaneCount = 0; for (int i=0; i<omeMeta.getImageCount(); i++) { int sizeZ = omeMeta.getPixelsSizeZ(i).getValue(); int sizeC = omeMeta.getPixelsSizeC(i).getValue(); int sizeT = omeMeta.getPixelsSizeT(i).getValue(); omePlaneCount += sizeZ * sizeC * sizeT; } int ifdCount = 0; for (String key : ifdCounts.keySet()) { ifdCount += ifdCounts.get(key); } canReallyClose = omePlaneCount == ifdCount; } if (canReallyClose) { imageLocations = null; omeMeta = null; service = null; ifdCounts.clear(); } else { for(String k : ifdCounts.keySet()) ifdCounts.put(k, 0); } } } // -- IFormatWriter API methods -- /** * @see loci.formats.IFormatWriter#saveBytes(int, byte[], int, int, int, int) */ @Override public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { saveBytes(no, buf, null, x, y, w, h); } /** * @see loci.formats.IFormatWriter#saveBytes(int, byte[], int, int, int, int) */ @Override public void saveBytes(int no, byte[] buf, IFD ifd, int x, int y, int w, int h) throws FormatException, IOException { super.saveBytes(no, buf, ifd, x, y, w, h); int index = no; while (imageLocations[series][index] != null) { if (index < imageLocations[series].length - 1) { index++; } else { break; } } imageLocations[series][index] = currentId; } // -- FormatWriter API methods -- /* @see FormatWriter#setId(String) */ @Override public void setId(String id) throws FormatException, IOException { if (id.equals(currentId)) return; super.setId(id); if (imageLocations == null) { MetadataRetrieve r = getMetadataRetrieve(); imageLocations = new String[r.getImageCount()][]; for (int i=0; i<imageLocations.length; i++) { setSeries(i); imageLocations[i] = new String[planeCount()]; } setSeries(0); } } // -- Helper methods -- /** Gets the UUID corresponding to the given filename. */ private String getUUID(String filename) { String uuid = uuids.get(filename); if (uuid == null) { uuid = UUID.randomUUID().toString(); uuids.put(filename, uuid); } return uuid; } private void setupServiceAndMetadata() throws DependencyException, ServiceException { // extract OME-XML string from metadata object MetadataRetrieve retrieve = getMetadataRetrieve(); ServiceFactory factory = new ServiceFactory(); service = factory.getInstance(OMEXMLService.class); OMEXMLMetadata originalOMEMeta = service.getOMEMetadata(retrieve); originalOMEMeta.resolveReferences(); String omexml = service.getOMEXML(originalOMEMeta); omeMeta = service.createOMEXMLMetadata(omexml); } private String getOMEXML(String file) throws FormatException, IOException { // generate UUID and add to OME element String uuid = "urn:uuid:" + getUUID(new Location(file).getName()); omeMeta.setUUID(uuid); String xml; try { xml = service.getOMEXML(omeMeta); } catch (ServiceException se) { throw new FormatException(se); } // insert warning comment String prefix = xml.substring(0, xml.indexOf(">") + 1); String suffix = xml.substring(xml.indexOf(">") + 1); return prefix + WARNING_COMMENT + suffix; } private void saveComment(String file, String xml) throws IOException { if (out != null) out.close(); out = new RandomAccessOutputStream(file); RandomAccessInputStream in = null; try { TiffSaver saver = new TiffSaver(out, file); saver.setBigTiff(isBigTiff); in = new RandomAccessInputStream(file); saver.overwriteLastIFDOffset(in); saver.overwriteComment(in, xml); } catch (FormatException exc) { IOException io = new IOException("Unable to append OME-XML comment"); io.initCause(exc); throw io; } finally { if (out != null) out.close(); if (in != null) in.close(); } } private void populateTiffData(OMEXMLMetadata omeMeta, int[] zct, int ifd, int series, int plane) { omeMeta.setTiffDataFirstZ(new NonNegativeInteger(zct[0]), series, plane); omeMeta.setTiffDataFirstC(new NonNegativeInteger(zct[1]), series, plane); omeMeta.setTiffDataFirstT(new NonNegativeInteger(zct[2]), series, plane); omeMeta.setTiffDataIFD(new NonNegativeInteger(ifd), series, plane); omeMeta.setTiffDataPlaneCount(new NonNegativeInteger(1), series, plane); } private void populateImage(OMEXMLMetadata omeMeta, int series) { String dimensionOrder = omeMeta.getPixelsDimensionOrder(series).toString(); int sizeZ = omeMeta.getPixelsSizeZ(series).getValue().intValue(); int sizeC = omeMeta.getPixelsSizeC(series).getValue().intValue(); int sizeT = omeMeta.getPixelsSizeT(series).getValue().intValue(); int imageCount = getPlaneCount(); if (imageCount == 0) { omeMeta.setTiffDataPlaneCount(new NonNegativeInteger(0), series, 0); return; } PositiveInteger samplesPerPixel = new PositiveInteger((sizeZ * sizeC * sizeT) / imageCount); for (int c=0; c<omeMeta.getChannelCount(series); c++) { omeMeta.setChannelSamplesPerPixel(samplesPerPixel, series, c); } sizeC /= samplesPerPixel.getValue(); int nextPlane = 0; for (int plane=0; plane<imageCount; plane++) { int[] zct = FormatTools.getZCTCoords(dimensionOrder, sizeZ, sizeC, sizeT, imageCount, plane); int planeIndex = plane; if (imageLocations[series].length < imageCount) { planeIndex /= (imageCount / imageLocations[series].length); } String filename = imageLocations[series][planeIndex]; if (filename != null) { filename = new Location(filename).getName(); Integer ifdIndex = ifdCounts.get(filename); int ifd = ifdIndex == null ? 0 : ifdIndex.intValue(); omeMeta.setUUIDFileName(filename, series, nextPlane); String uuid = "urn:uuid:" + getUUID(filename); omeMeta.setUUIDValue(uuid, series, nextPlane); // fill in any non-default TiffData attributes populateTiffData(omeMeta, zct, ifd, series, nextPlane); ifdCounts.put(filename, ifd + 1); nextPlane++; } } } private int planeCount() { MetadataRetrieve r = getMetadataRetrieve(); int z = r.getPixelsSizeZ(series).getValue().intValue(); int t = r.getPixelsSizeT(series).getValue().intValue(); int c = r.getChannelCount(series); String pixelType = r.getPixelsType(series).getValue(); int bytes = FormatTools.getBytesPerPixel(pixelType); if (bytes > 1 && c == 1) { c = r.getChannelSamplesPerPixel(series, 0).getValue(); } return z * c * t; } }