/* * #%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.services; import java.io.IOException; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Templates; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import loci.common.services.AbstractService; import loci.common.services.ServiceException; import loci.common.xml.XMLTools; import loci.formats.CoreMetadata; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.Modulo; import loci.formats.meta.IMetadata; import loci.formats.meta.MetadataConverter; import loci.formats.meta.MetadataRetrieve; import loci.formats.meta.MetadataStore; import loci.formats.meta.ModuloAnnotation; import loci.formats.meta.OriginalMetadataAnnotation; import loci.formats.ome.OMEXMLMetadata; import loci.formats.ome.OMEXMLMetadataImpl; import ome.xml.meta.OMEXMLMetadataRoot; import ome.xml.model.BinData; import ome.xml.model.Channel; import ome.xml.model.Image; import ome.xml.model.MetadataOnly; import ome.xml.model.OME; import ome.xml.model.OMEModel; import ome.xml.model.OMEModelImpl; import ome.xml.model.OMEModelObject; import ome.xml.model.Pixels; import ome.xml.model.Annotation; import ome.xml.model.StructuredAnnotations; import ome.xml.model.XMLAnnotation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * * @author callan */ public class OMEXMLServiceImpl extends AbstractService implements OMEXMLService { /** Latest OME-XML version namespace. */ public static final String LATEST_VERSION = "2015-01"; public static final String NO_OME_XML_MSG = "ome-xml.jar is required to read OME-TIFF files. " + "Please download it from " + FormatTools.URL_BIO_FORMATS_LIBRARIES; /** Logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(OMEXMLService.class); // -- Stylesheet names -- private static final String XSLT_PATH = "/transforms/"; private static final String XSLT_2003FC = XSLT_PATH + "2003-FC-to-2008-09.xsl"; private static final String XSLT_200706 = XSLT_PATH + "2007-06-to-2008-09.xsl"; private static final String XSLT_200802 = XSLT_PATH + "2008-02-to-2008-09.xsl"; private static final String XSLT_200809 = XSLT_PATH + "2008-09-to-2009-09.xsl"; private static final String XSLT_200909 = XSLT_PATH + "2009-09-to-2010-04.xsl"; private static final String XSLT_201004 = XSLT_PATH + "2010-04-to-2010-06.xsl"; private static final String XSLT_201006 = XSLT_PATH + "2010-06-to-2011-06.xsl"; private static final String XSLT_201106 = XSLT_PATH + "2011-06-to-2012-06.xsl"; private static final String XSLT_201206 = XSLT_PATH + "2012-06-to-2013-06.xsl"; private static final String XSLT_201306 = XSLT_PATH + "2013-06-to-2015-01.xsl"; // -- Cached stylesheets -- /** Reordering stylesheet. */ private static Templates reorderXSLT; /** Stylesheets for updating from previous schema releases. */ private static Templates update2003FC; private static Templates update200706; private static Templates update200802; private static Templates update200809; private static Templates update200909; private static Templates update201004; private static Templates update201006; private static Templates update201106; private static Templates update201206; private static Templates update201306; private static final String SCHEMA_PATH = "http://www.openmicroscopy.org/Schemas/OME/"; /** * Default constructor. */ public OMEXMLServiceImpl() { checkClassDependency(ome.xml.model.OMEModelObject.class); } /** @see OMEXMLService#getLatestVersion() */ @Override public String getLatestVersion() { return LATEST_VERSION; } /** @see OMEXMLService#transformToLatestVersion(String) */ @Override public String transformToLatestVersion(String xml) throws ServiceException { String version = getOMEXMLVersion(xml); if (version.equals(getLatestVersion())) return xml; LOGGER.debug("Attempting to update XML with version: {}", version); LOGGER.trace("Initial dump: {}", xml); String transformed = null; try { if (version.equals("2003-FC")) { xml = verifyOMENamespace(xml); LOGGER.debug("Running UPDATE_2003FC stylesheet."); if (update2003FC == null) { update2003FC = XMLTools.getStylesheet(XSLT_2003FC, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(xml, update2003FC); } else if (version.equals("2007-06")) { xml = verifyOMENamespace(xml); LOGGER.debug("Running UPDATE_200706 stylesheet."); if (update200706 == null) { update200706 = XMLTools.getStylesheet(XSLT_200706, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(xml, update200706); } else if (version.equals("2008-02")) { xml = verifyOMENamespace(xml); LOGGER.debug("Running UPDATE_200802 stylesheet."); if (update200802 == null) { update200802 = XMLTools.getStylesheet(XSLT_200802, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(xml, update200802); } else transformed = xml; LOGGER.debug("XML updated to at least 2008-09"); LOGGER.trace("At least 2008-09 dump: {}", transformed); if (!version.equals("2009-09") && !version.equals("2010-04") && !version.equals("2010-06") && !version.equals("2011-06") && !version.equals("2012-06") && !version.equals("2013-06") && !version.equals("2015-01") ) { transformed = verifyOMENamespace(transformed); LOGGER.debug("Running UPDATE_200809 stylesheet."); if (update200809 == null) { update200809 = XMLTools.getStylesheet(XSLT_200809, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(transformed, update200809); } LOGGER.debug("XML updated to at least 2009-09"); LOGGER.trace("At least 2009-09 dump: {}", transformed); if (!version.equals("2010-04") && !version.equals("2010-06") && !version.equals("2011-06") && !version.equals("2012-06") && !version.equals("2013-06") && !version.equals("2015-01") ) { transformed = verifyOMENamespace(transformed); LOGGER.debug("Running UPDATE_200909 stylesheet."); if (update200909 == null) { update200909 = XMLTools.getStylesheet(XSLT_200909, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(transformed, update200909); } else transformed = xml; LOGGER.debug("XML updated to at least 2010-04"); LOGGER.trace("At least 2010-04 dump: {}", transformed); if (!version.equals("2010-06") && !version.equals("2011-06") && !version.equals("2012-06") && !version.equals("2013-06") && !version.equals("2015-01") ) { transformed = verifyOMENamespace(transformed); LOGGER.debug("Running UPDATE_201004 stylesheet."); if (update201004 == null) { update201004 = XMLTools.getStylesheet(XSLT_201004, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(transformed, update201004); } else transformed = xml; LOGGER.debug("XML updated to at least 2010-06"); if (!version.equals("2011-06") && !version.equals("2012-06") && !version.equals("2013-06") && !version.equals("2015-01") ) { transformed = verifyOMENamespace(transformed); LOGGER.debug("Running UPDATE_201006 stylesheet."); if (update201006 == null) { update201006 = XMLTools.getStylesheet(XSLT_201006, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(transformed, update201006); } else transformed = xml; LOGGER.debug("XML updated to at least 2011-06"); if (!version.equals("2012-06") && !version.equals("2013-06") && !version.equals("2015-01") ) { transformed = verifyOMENamespace(transformed); LOGGER.debug("Running UPDATE_201106 stylesheet."); if (update201106 == null) { update201106 = XMLTools.getStylesheet(XSLT_201106, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(transformed, update201106); } else transformed = xml; LOGGER.debug("XML updated to at least 2012-06"); if (!version.equals("2013-06") && !version.equals("2015-01") ) { transformed = verifyOMENamespace(transformed); LOGGER.debug("Running UPDATE_201206 stylesheet."); if (update201206 == null) { update201206 = XMLTools.getStylesheet(XSLT_201206, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(transformed, update201206); } else transformed = xml; LOGGER.debug("XML updated to at least 2013-06"); if (!version.equals("2015-01") ) { transformed = verifyOMENamespace(transformed); LOGGER.debug("Running UPDATE_201306 stylesheet."); if (update201306 == null) { update201306 = XMLTools.getStylesheet(XSLT_201306, OMEXMLServiceImpl.class); } transformed = XMLTools.transformXML(transformed, update201306); } else transformed = xml; LOGGER.debug("XML updated to at least 2015-01"); // fix namespaces transformed = transformed.replaceAll("<ns.*?:", "<"); transformed = transformed.replaceAll("xmlns:ns.*?=", "xmlns:OME="); transformed = transformed.replaceAll("</ns.*?:", "</"); LOGGER.trace("Transformed XML dump: {}", transformed); return transformed; } catch (IOException e) { LOGGER.warn("Could not transform version " + version + " OME-XML."); } return null; } /** @see OMEXMLService#createOMEXMLMetadata() */ @Override public OMEXMLMetadata createOMEXMLMetadata() throws ServiceException { return createOMEXMLMetadata(null); } /** @see OMEXMLService#createOMEXMLMetadata(java.lang.String) */ @Override public OMEXMLMetadata createOMEXMLMetadata(String xml) throws ServiceException { return createOMEXMLMetadata(xml, null); } /** * @see OMEXMLService#createOMEXMLMetadata(java.lang.String, java.lang.String) */ @Override public OMEXMLMetadata createOMEXMLMetadata(String xml, String version) throws ServiceException { if (xml != null) { xml = XMLTools.sanitizeXML(xml); } OMEXMLMetadataRoot ome = xml == null ? null : createRoot(transformToLatestVersion(xml)); OMEXMLMetadata meta = new OMEXMLMetadataImpl(); if (ome != null) meta.setRoot(ome); return meta; } /** @see OMEXMLService#createOMEXMLRoot(java.lang.String) */ @Override public OMEModelObject createOMEXMLRoot(String xml) throws ServiceException { return createRoot(transformToLatestVersion(xml)); } /** @see OMEXMLService#isOMEXMLMetadata(java.lang.Object) */ @Override public boolean isOMEXMLMetadata(Object o) { return o instanceof OMEXMLMetadata; } /** @see OMEXMLService#isOMEXMLRoot(java.lang.Object) */ @Override public boolean isOMEXMLRoot(Object o) { return o instanceof OMEModelObject; } /** * Constructs an OME root node. <b>NOTE:</b> This method is mostly here to * ensure type safety of return values as instances of service dependency * classes should not leak out of the interface. * @param xml String of XML to create the root node from. * @return An ome.xml.model.OMEModelObject subclass root node. * @throws IOException If there is an error reading from the string. * @throws SAXException If there is an error parsing the XML. * @throws ParserConfigurationException If there is an error preparing the * parsing infrastructure. */ private OMEXMLMetadataRoot createRoot(String xml) throws ServiceException { try { OMEModel model = new OMEModelImpl(); OMEXMLMetadataRoot ome = new OMEXMLMetadataRoot(XMLTools.parseDOM(xml).getDocumentElement(), model); model.resolveReferences(); return ome; } catch (Exception e) { throw new ServiceException(e); } } /** @see OMEXMLService#getOMEXMLVersion(java.lang.Object) */ @Override public String getOMEXMLVersion(Object o) { if (o == null) return null; if (o instanceof OMEXMLMetadata || o instanceof OMEModelObject) { return LATEST_VERSION; } else if (o instanceof String) { String xml = (String) o; try { Element e = XMLTools.parseDOM(xml).getDocumentElement(); String namespace = e.getAttribute("xmlns"); if (namespace == null || namespace.equals("")) { namespace = e.getAttribute("xmlns:ome"); } if (namespace == null || namespace.equals("")) { namespace = e.getAttribute("xmlns:OME"); } return namespace.endsWith("ome.xsd") ? "2003-FC" : namespace.substring(namespace.lastIndexOf("/") + 1); } catch (ParserConfigurationException pce) { } catch (SAXException se) { } catch (IOException ioe) { } } return null; } /** @see OMEXMLService#getOMEMetadata(loci.formats.meta.MetadataRetrieve) */ @Override public OMEXMLMetadata getOMEMetadata(MetadataRetrieve src) throws ServiceException { // check if the metadata is already an OME-XML metadata object if (src instanceof OMEXMLMetadata) return (OMEXMLMetadata) src; // populate a new OME-XML metadata object with metadata // converted from the non-OME-XML metadata object OMEXMLMetadata omexmlMeta = createOMEXMLMetadata(); convertMetadata(src, omexmlMeta); return omexmlMeta; } /** @see OMEXMLService#getOMEXML(loci.formats.meta.MetadataRetrieve) */ @Override public String getOMEXML(MetadataRetrieve src) throws ServiceException { OMEXMLMetadata omexmlMeta = getOMEMetadata(src); String xml = omexmlMeta.dumpXML(); // make sure that the namespace has been set correctly // convert XML string to DOM Document doc = null; Exception exception = null; try { doc = XMLTools.parseDOM(xml); } catch (ParserConfigurationException exc) { exception = exc; } catch (SAXException exc) { exception = exc; } catch (IOException exc) { exception = exc; } if (exception != null) { LOGGER.info("Malformed OME-XML", exception); return null; } Element root = doc.getDocumentElement(); root.setAttribute("xmlns", SCHEMA_PATH + getLatestVersion()); // convert tweaked DOM back to XML string try { xml = XMLTools.getXML(doc); } catch (TransformerConfigurationException exc) { exception = exc; } catch (TransformerException exc) { exception = exc; } if (exception != null) { LOGGER.info("Internal XML conversion error", exception); return null; } return xml; } /** @see OMEXMLService#validateOMEXML(java.lang.String) */ @Override public boolean validateOMEXML(String xml) { return validateOMEXML(xml, false); } /** @see OMEXMLService#validateOMEXML(java.lang.String, boolean) */ @Override public boolean validateOMEXML(String xml, boolean pixelsHack) { // HACK: Inject a TiffData element beneath any childless Pixels elements. if (pixelsHack) { // convert XML string to DOM Document doc = null; Exception exception = null; try { doc = XMLTools.parseDOM(xml); } catch (ParserConfigurationException exc) { exception = exc; } catch (SAXException exc) { exception = exc; } catch (IOException exc) { exception = exc; } if (exception != null) { LOGGER.info("Malformed OME-XML", exception); return false; } // inject TiffData elements as needed NodeList list = doc.getElementsByTagName("Pixels"); for (int i=0; i<list.getLength(); i++) { Node node = list.item(i); NodeList children = node.getChildNodes(); boolean needsTiffData = true; for (int j=0; j<children.getLength(); j++) { Node child = children.item(j); String name = child.getLocalName(); if ("TiffData".equals(name) || "BinData".equals(name)) { needsTiffData = false; break; } } if (needsTiffData) { // inject TiffData element Node tiffData = doc.createElement("TiffData"); node.insertBefore(tiffData, node.getFirstChild()); } } // convert tweaked DOM back to XML string try { xml = XMLTools.getXML(doc); } catch (TransformerConfigurationException exc) { exception = exc; } catch (TransformerException exc) { exception = exc; } if (exception != null) { LOGGER.info("Internal XML conversion error", exception); return false; } } return XMLTools.validateXML(xml, "OME-XML"); } /** * @see OMEXMLService#getModuloAlongZ(OMEXMLMetadata, int) */ @Override public Modulo getModuloAlongZ(OMEXMLMetadata omexml, int image) { return getModuloAlong(omexml, "ModuloAlongZ", image); } /** * @see OMEXMLService#getModuloAlongC(OMEXMLMetadata, int) */ @Override public Modulo getModuloAlongC(OMEXMLMetadata omexml, int image) { return getModuloAlong(omexml, "ModuloAlongC", image); } /** * @see OMEXMLService#getModuloAlongT(OMEXMLMetadata, int) */ @Override public Modulo getModuloAlongT(OMEXMLMetadata omexml, int image) { return getModuloAlong(omexml, "ModuloAlongT", image); } /** * Create a {@link loci.formats.Modulo} corresponding to the given ModuloAlong* tag. * @param omexml the OMEXMLMetadata from which to retrieve the ModuloAlong* tag * @param tag the tag name (e.g. "ModuloAlongC") * @param image the Image index within the OMEXMLMetadata * @return the corresponding Modulo object */ private Modulo getModuloAlong(OMEXMLMetadata omexml, String tag, int image) { OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omexml.getRoot(); Image img = root.getImage(image); if (img == null) { return null; } for (int i=0; i<img.sizeOfLinkedAnnotationList(); i++) { Annotation annotation = img.getLinkedAnnotation(i); if (!(annotation instanceof XMLAnnotation)) { continue; } String xml = ((XMLAnnotation) annotation).getValue(); try { Document annotationRoot = XMLTools.parseDOM(xml); NodeList nodes = annotationRoot.getElementsByTagName(tag); if (nodes.getLength() > 0) { Element modulo = (Element) nodes.item(0); NamedNodeMap attrs = modulo.getAttributes(); Modulo m = new Modulo(tag.substring(tag.length() - 1)); Node start = attrs.getNamedItem("Start"); Node end = attrs.getNamedItem("End"); Node step = attrs.getNamedItem("Step"); Node type = attrs.getNamedItem("Type"); Node typeDescription = attrs.getNamedItem("TypeDescription"); Node unit = attrs.getNamedItem("Unit"); if (start != null) { m.start = Double.parseDouble(start.getNodeValue()); } if (end != null) { m.end = Double.parseDouble(end.getNodeValue()); } if (step != null) { m.step = Double.parseDouble(step.getNodeValue()); } if (type != null) { m.type = type.getNodeValue(); } if (typeDescription != null) { m.typeDescription = typeDescription.getNodeValue(); } if (unit != null) { m.unit = unit.getNodeValue(); } NodeList labels = modulo.getElementsByTagName("Label"); if (labels != null && labels.getLength() > 0) { m.labels = new String[labels.getLength()]; for (int q=0; q<labels.getLength(); q++) { m.labels[q] = labels.item(q).getTextContent(); } } return m; } } catch (ParserConfigurationException e) { LOGGER.debug("Failed to parse ModuloAlong", e); } catch (SAXException e) { LOGGER.debug("Failed to parse ModuloAlong", e); } catch (IOException e) { LOGGER.debug("Failed to parse ModuloAlong", e); } } return null; } /** * @see OMEXMLService#getOriginalMetadata(loci.formats.ome.OMEXMLMetadata) */ @Override public Hashtable getOriginalMetadata(OMEXMLMetadata omexmlMeta) { OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omexmlMeta.getRoot(); StructuredAnnotations annotations = root.getStructuredAnnotations(); if (annotations == null) { return null; } Hashtable metadata = new Hashtable(); for (int i=0; i<annotations.sizeOfXMLAnnotationList(); i++) { XMLAnnotation annotation = annotations.getXMLAnnotation(i); if (annotation instanceof OriginalMetadataAnnotation) { OriginalMetadataAnnotation original = (OriginalMetadataAnnotation) annotation; metadata.put(original.getKey(), original.getValueForKey()); continue; } String xml = annotation.getValue(); try { Document annotationRoot = XMLTools.parseDOM(xml); NodeList metadataNodes = annotationRoot.getElementsByTagName("OriginalMetadata"); for (int meta=0; meta<metadataNodes.getLength(); meta++) { Element metadataNode = (Element) metadataNodes.item(meta); NodeList keys = metadataNode.getElementsByTagName("Key"); NodeList values = metadataNode.getElementsByTagName("Value"); for (int q=0; q<keys.getLength(); q++) { Node key = keys.item(q); Node value = values.item(q); metadata.put(key.getTextContent(), value.getTextContent()); } } if (metadataNodes.getLength() == 0) { metadataNodes = annotationRoot.getDocumentElement().getChildNodes(); for (int meta=0; meta<metadataNodes.getLength(); meta++) { // Not all nodes will be instances of Element, particularly if // the XML parsing was done by Xerces. In particular, if a // comment is found, it will be an instance of // com.sun.org.apache.xerces.internal.dom.DeferredCommentImpl. if (metadataNodes.item(meta) instanceof Element) { Element node = (Element) metadataNodes.item(meta); String name = node.getNodeName(); NamedNodeMap attrs = node.getAttributes(); Node value = attrs.getNamedItem("Value"); if (value != null) { metadata.put(name, value.getNodeValue()); } } } } } catch (ParserConfigurationException e) { LOGGER.debug("Failed to parse OriginalMetadata", e); } catch (SAXException e) { LOGGER.debug("Failed to parse OriginalMetadata", e); } catch (IOException e) { LOGGER.debug("Failed to parse OriginalMetadata", e); } } return metadata; } /** * @see OMEXMLService#populateOriginalMetadata(loci.formats.ome.OMEXMLMetadata, Hashtable) */ @Override public void populateOriginalMetadata(OMEXMLMetadata omexmlMeta, Hashtable<String, Object> metadata) { omexmlMeta.resolveReferences(); if (metadata.size() == 0) { return; } OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omexmlMeta.getRoot(); StructuredAnnotations annotations = root.getStructuredAnnotations(); if (annotations == null) annotations = new StructuredAnnotations(); int annotationIndex = annotations.sizeOfXMLAnnotationList(); if (annotationIndex > 0) { String lastAnnotationID = omexmlMeta.getXMLAnnotationID(annotationIndex - 1); String lastIndex = lastAnnotationID.substring(lastAnnotationID.lastIndexOf(":") + 1); try { int index = Integer.parseInt(lastIndex); while (index >= annotationIndex) { annotationIndex++; } } catch (NumberFormatException e) { } } for (String key : metadata.keySet()) { OriginalMetadataAnnotation annotation = new OriginalMetadataAnnotation(); annotation.setID(MetadataTools.createLSID("Annotation", annotationIndex)); annotation.setKeyValue(key, metadata.get(key).toString()); annotations.addXMLAnnotation(annotation); annotationIndex++; } root.setStructuredAnnotations(annotations); omexmlMeta.setRoot(root); } /** * @see OMEXMLService#populateOriginalMetadata(loci.formats.ome.OMEXMLMetadata, java.lang.String, java.lang.String) */ @Override public void populateOriginalMetadata(OMEXMLMetadata omexmlMeta, String key, String value) { omexmlMeta.resolveReferences(); OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omexmlMeta.getRoot(); StructuredAnnotations annotations = root.getStructuredAnnotations(); if (annotations == null) annotations = new StructuredAnnotations(); int annotationIndex = annotations.sizeOfXMLAnnotationList(); if (annotationIndex > 0) { String lastAnnotationID = omexmlMeta.getXMLAnnotationID(annotationIndex - 1); String lastIndex = lastAnnotationID.substring(lastAnnotationID.lastIndexOf(":") + 1); try { int index = Integer.parseInt(lastIndex); while (index >= annotationIndex) { annotationIndex++; } } catch (NumberFormatException e) { } } OriginalMetadataAnnotation annotation = new OriginalMetadataAnnotation(); annotation.setID(MetadataTools.createLSID("Annotation", annotationIndex)); annotation.setKeyValue(key, value); annotations.addXMLAnnotation(annotation); root.setStructuredAnnotations(annotations); omexmlMeta.setRoot(root); } /** * @see OMEXMLService#convertMetadata(java.lang.String, loci.formats.meta.MetadataStore) */ @Override public void convertMetadata(String xml, MetadataStore dest) throws ServiceException { OMEXMLMetadataRoot ome = createRoot(transformToLatestVersion(xml)); String rootVersion = getOMEXMLVersion(ome); String storeVersion = getOMEXMLVersion(dest); if (rootVersion.equals(storeVersion)) { // metadata store is already an OME-XML metadata object of the // correct schema version; populate OME-XML string directly if (!(dest instanceof OMEXMLMetadata)) { throw new IllegalArgumentException( "Expecting OMEXMLMetadata instance."); } dest.setRoot(ome); } else { // metadata store is incompatible; create an OME-XML // metadata object and copy it into the destination IMetadata src = createOMEXMLMetadata(xml); convertMetadata(src, dest); } } /** * @see OMEXMLService#convertMetadata(loci.formats.meta.MetadataRetrieve, loci.formats.meta.MetadataStore) */ @Override public void convertMetadata(MetadataRetrieve src, MetadataStore dest) { MetadataConverter.convertMetadata(src, dest); } /** @see OMEXMLService#removeBinData(OMEXMLMetadata) */ @Override public void removeBinData(OMEXMLMetadata omexmlMeta) { omexmlMeta.resolveReferences(); OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omexmlMeta.getRoot(); List<Image> images = root.copyImageList(); for (Image img : images) { Pixels pix = img.getPixels(); List<BinData> binData = pix.copyBinDataList(); for (BinData bin : binData) { pix.removeBinData(bin); } pix.setMetadataOnly(null); } omexmlMeta.setRoot(root); } /** @see OMEXMLService#removeChannels(OMEXMLMetadata, int, int) */ @Override public void removeChannels(OMEXMLMetadata omexmlMeta, int image, int sizeC) { omexmlMeta.resolveReferences(); OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omexmlMeta.getRoot(); Pixels img = root.getImage(image).getPixels(); List<Channel> channels = img.copyChannelList(); for (int c=0; c<channels.size(); c++) { Channel channel = channels.get(c); if (channel.getID() == null || c >= sizeC) { img.removeChannel(channel); } } omexmlMeta.setRoot(root); } /** @see OMEXMLService#addMetadataOnly(OMEXMLMetadata, int) */ @Override public void addMetadataOnly(OMEXMLMetadata omexmlMeta, int image) { addMetadataOnly(omexmlMeta, image, true); } /** @see OMEXMLService#addMetadataOnly(OMEXMLMetadata, int, boolean) */ public void addMetadataOnly(OMEXMLMetadata omexmlMeta, int image, boolean resolve) { if (resolve) { omexmlMeta.resolveReferences(); } MetadataOnly meta = new MetadataOnly(); OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omexmlMeta.getRoot(); Pixels pix = root.getImage(image).getPixels(); pix.setMetadataOnly(meta); } /** @see OMEXMLService#isEqual(OMEXMLMetadata, OMEXMLMetadata) */ @Override public boolean isEqual(OMEXMLMetadata src1, OMEXMLMetadata src2) { src1.resolveReferences(); src2.resolveReferences(); OMEXMLMetadataRoot omeRoot1 = (OMEXMLMetadataRoot) src1.getRoot(); OMEXMLMetadataRoot omeRoot2 = (OMEXMLMetadataRoot) src2.getRoot(); DocumentBuilder builder = null; try { builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { return false; } Document doc1 = builder.newDocument(); Document doc2 = builder.newDocument(); Element root1 = omeRoot1.asXMLElement(doc1); Element root2 = omeRoot2.asXMLElement(doc2); return equals(root1, root2); } @Override public void addModuloAlong(OMEXMLMetadata meta, CoreMetadata core, int imageIdx) { meta.resolveReferences(); if (core.moduloZ.length() == 1 && core.moduloC.length() == 1 && core.moduloT.length() == 1) { // nothing to populate return; } OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) meta.getRoot(); Image image; try { image = root.getImage(imageIdx); } catch (IndexOutOfBoundsException ieeb) { // If there is no image of the given index, // then we are considering the metadata corrupt, // and exiting without doing anything. return; } StructuredAnnotations annotations = root.getStructuredAnnotations(); if (annotations == null) annotations = new StructuredAnnotations(); int annotationIndex = annotations.sizeOfXMLAnnotationList(); final Set<String> knownModulos = new HashSet<String>(); if (annotationIndex > 0) { // Check which modulo annotations are already present. for (int idx = 0; idx < annotationIndex; idx++) { if (ModuloAnnotation.MODULO_NS.equals( meta.getXMLAnnotationNamespace(idx))) { // ignore this annotation if it is not linked to the current Image boolean ignore = true; String xmlID = meta.getXMLAnnotationID(idx); for (int link=0; link<image.sizeOfLinkedAnnotationList(); link++) { if (xmlID.equals(image.getLinkedAnnotation(link).getID())) { ignore = false; break; } } if (ignore) { continue; } String value = meta.getXMLAnnotationValue(idx); try { Document doc = XMLTools.parseDOM(value); NodeList modulos = doc.getElementsByTagName("Modulo"); for (int m = 0; m < modulos.getLength(); m++) { Node modulo = modulos.item(m); NodeList children = modulo.getChildNodes(); for (int c = 0; c < children.getLength(); c++) { Node child = children.item(c); String name = child.getNodeName(); knownModulos.add(name); } } } catch (Exception e) { LOGGER.warn("Could not parse XML from annotation: {}", value, e); } } } // Calculate the next annotation ID that should be used. String lastAnnotationID = meta.getXMLAnnotationID(annotationIndex - 1); String lastIndex = lastAnnotationID.substring(lastAnnotationID.lastIndexOf(":") + 1); try { int index = Integer.parseInt(lastIndex); while (index >= annotationIndex) { annotationIndex++; } } catch (NumberFormatException e) { } } int imageAnnotation = 0; if (core.moduloZ.length() > 1 && !knownModulos.contains("ModuloAlongZ")) { createModulo(meta, core.moduloZ, annotations, image, imageIdx, annotationIndex, imageAnnotation); annotationIndex++; imageAnnotation++; } if (core.moduloC.length() > 1 && !knownModulos.contains("ModuloAlongC")) { createModulo(meta, core.moduloC, annotations, image, imageIdx, annotationIndex, imageAnnotation); annotationIndex++; imageAnnotation++; } if (core.moduloT.length() > 1 && !knownModulos.contains("ModuloAlongT")) { createModulo(meta, core.moduloT, annotations, image, imageIdx, annotationIndex, imageAnnotation); annotationIndex++; imageAnnotation++; } root.setStructuredAnnotations(annotations); meta.setRoot(root); } /** * Create a ModuloAlong* annotation corresponding to the given * {@link loci.formats.Modulo}. * @param meta the OMEXMLMetadata in which to create the annotation * @param modulo the Modulo object that contains the annotation data * @param annotations the list of existing annotations * @param image the Image to which the new annotation should be linked * @param imageIdx the index of the Image * @param annotationIndex the index to be assigned to the new annotation * @param imageAnnotation the index to be assigned to the new annotation link */ private void createModulo( final OMEXMLMetadata meta, final Modulo modulo, final StructuredAnnotations annotations, final Image image, final int imageIdx, final int annotationIndex, final int imageAnnotation) { ModuloAnnotation annotation = new ModuloAnnotation(); annotation.setModulo(meta, modulo); String id = MetadataTools.createLSID("Annotation", annotationIndex); annotation.setID(id); annotations.addXMLAnnotation(annotation); meta.setImageAnnotationRef(id, imageIdx, imageAnnotation); image.linkAnnotation(annotation); } // -- Utility methods - casting -- /** @see OMEXMLService#asStore(loci.formats.meta.MetadataRetrieve) */ @Override public MetadataStore asStore(MetadataRetrieve meta) { return meta instanceof MetadataStore ? (MetadataStore) meta : null; } /** @see OMEXMLService#asRetrieve(loci.formats.meta.MetadataStore) */ @Override public MetadataRetrieve asRetrieve(MetadataStore meta) { return meta instanceof MetadataRetrieve ? (MetadataRetrieve) meta : null; } // -- Helper methods -- /** Ensures that an xmlns:ome element exists. */ private String verifyOMENamespace(String xml) { try { Document doc = XMLTools.parseDOM(xml); Element e = doc.getDocumentElement(); String omeNamespace = e.getAttribute("xmlns:ome"); if (omeNamespace == null || omeNamespace.equals("")) { e.setAttribute("xmlns:ome", e.getAttribute("xmlns")); } return XMLTools.getXML(doc); } catch (ParserConfigurationException pce) { } catch (TransformerConfigurationException tce) { } catch (TransformerException te) { } catch (SAXException se) { } catch (IOException ioe) { } return null; } /** Compares two Elements for equality. */ public boolean equals(Node e1, Node e2) { NodeList children1 = e1.getChildNodes(); NodeList children2 = e2.getChildNodes(); String localName1 = e1.getLocalName(); if (localName1 == null) { localName1 = ""; } String localName2 = e2.getLocalName(); if (localName2 == null) { localName2 = ""; } if (!localName1.equals(localName2)) { return false; } if (localName1.equals("StructuredAnnotations")) { // we don't care about StructuredAnnotations at all return true; } NamedNodeMap attributes1 = e1.getAttributes(); NamedNodeMap attributes2 = e2.getAttributes(); if (attributes1 == null || attributes2 == null) { if ((attributes1 == null && attributes2 != null) || (attributes1 != null && attributes2 == null)) { return false; } } else if (attributes1.getLength() != attributes2.getLength()) { return false; } else { // make sure that all of the attributes are equal, except for IDs int nAttributes = attributes1.getLength(); for (int i=0; i<nAttributes; i++) { Node n1 = attributes1.item(i); String localName = n1.getNodeName(); if (localName != null && !localName.equals("ID")) { Node n2 = attributes2.getNamedItem(localName); if (n2 == null) { return false; } if (!equals(n1, n2)) { return false; } } else if ("ID".equals(localName)) { if (localName1.endsWith("Settings")) { // this is a reference to a different node // the references are equal if the two referenced nodes are equal Node n2 = attributes2.getNamedItem(localName); Node realRoot1 = findRootNode(e1); Node realRoot2 = findRootNode(e2); String refName = localName1.replaceAll("Settings", ""); Node ref1 = findChildWithID(realRoot1, refName, n1.getNodeValue()); Node ref2 = findChildWithID(realRoot2, refName, n2.getNodeValue()); if (ref1 == null && ref2 == null) { return true; } else if ((ref1 == null && ref2 != null) || (ref1 != null && ref2 == null) || !equals(ref1, ref2)) { return false; } } } } } if (children1.getLength() != children2.getLength()) { return false; } Object node1 = e1.getNodeValue(); Object node2 = e2.getNodeValue(); if (node1 == null && node2 != null) { return false; } if (node1 != null && !node1.equals(node2) && !localName1.equals("")) { return false; } for (int i=0; i<children1.getLength(); i++) { if (!equals(children1.item(i), children2.item(i))) { return false; } } return true; } /** Return the absolute root node for the specified child node. */ private Node findRootNode(Node child) { if (child.getParentNode() != null) { return findRootNode(child.getParentNode()); } return child; } /** Return the child node with specified value for the "ID" attribute. */ private Node findChildWithID(Node root, String name, String id) { NamedNodeMap attributes = root.getAttributes(); if (attributes != null) { Node idNode = attributes.getNamedItem("ID"); if (idNode != null && id.equals(idNode.getNodeValue()) && name.equals(root.getNodeName())) { return root; } } NodeList children = root.getChildNodes(); for (int i=0; i<children.getLength(); i++) { Node result = findChildWithID(children.item(i), name, id); if (result != null) { return result; } } return null; } }