/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2015 University of Dundee. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package ome.formats.importer; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.List; import java.util.Set; import loci.formats.ChannelFiller; import loci.formats.ChannelSeparator; import loci.formats.ClassList; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.IFormatReader; import loci.formats.ImageReader; import loci.formats.Memoizer; import loci.formats.MinMaxCalculator; import loci.formats.in.LeicaReader; import loci.formats.in.MetadataLevel; import loci.formats.in.MetadataOptions; import loci.formats.in.ZipReader; import loci.formats.meta.MetadataStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ome.formats.OMEXMLModelComparator; import ome.util.PixelData; import omero.model.Channel; import omero.model.Pixels; /** * @author Brian Loranger brain at lifesci.dundee.ac.uk * @author Chris Allan callan at blackcat.ca * */ public class OMEROWrapper extends MinMaxCalculator { @SuppressWarnings("unused") private final static Logger log = LoggerFactory.getLogger(OMEROWrapper.class); private ChannelSeparator separator; private ChannelFiller filler; private Memoizer memoizer; public Boolean minMaxSet = null; /** * Reference copy of <i>reader</i> so that we can be compatible with the * IFormatReader/ReaderWrapper interface but still maintain functionality * that we require. */ private ImageReader iReader; private ImportConfig config; /** * Wrapper for bio-formats * * @param config - ImportConfit */ public OMEROWrapper(ImportConfig config) { this(config, -1, null); // Disabled memoization } public OMEROWrapper(ImportConfig config, long elapsedTime, File cacheDirectory) { super(createReader(config)); this.config = config; this.iReader = (ImageReader) reader; // Save old value this.reader = null; filler = new ChannelFiller(iReader); separator = new ChannelSeparator(filler); memoizer = new Memoizer(separator, elapsedTime, cacheDirectory) { public Deser getDeser() { KryoDeser k = new KryoDeser(); k.kryo.register(OMEXMLModelComparator.class); return k; } }; reader = memoizer; // Force unreadable characters to be removed from metadata key/value pairs iReader.setMetadataFiltered(true); filler.setMetadataFiltered(true); separator.setMetadataFiltered(true); // Force images with multiple sub-resolutions to not "duplicate" their // series. iReader.setFlattenedResolutions(false); }; private static ImageReader createReader(ImportConfig config) { if (config == null) { throw new IllegalArgumentException( "An ImportConfig must be instantitated \n " + "in order to properly configure all readers."); } String readersPath = config.readersPath.get(); // Since we now use all readers apart from the ZipReader, just // initialize in this manner which helps us by not requiring // us to keep up with all changes in readers.txt and removes // the requirement for importer_readers.txt (See #2859). // Chris Allan <callan@blackcat.ca> -- Fri 10 Sep 2010 17:24:49 BST ClassList<IFormatReader> readers = ImageReader.getDefaultReaderClasses(); readers.removeClass(ZipReader.class); if (readersPath != null) { Class<?> k = OMEROWrapper.class; if (new File(readersPath).exists()) { k = null; } try { return new ImageReader(new ClassList<IFormatReader>( readersPath, IFormatReader.class, k)); } catch (IOException e) { throw new RuntimeException("Unable to load readers.txt."); } } return new ImageReader(); } public ImportConfig getConfig() { return this.config; } /** * Obtains an object which represents a given sub-image of a plane within * the file. * @param id The path to the file. * @param planeNumber The plane or section within the file to obtain. * @param buf Pre-allocated buffer which has a <i>length</i> that can fit * the entire plane. * @return an object which represents the plane. * @throws FormatException If there is an error parsing the file. * @throws IOException If there is an error reading from the file or * acquiring permissions to read the file. */ public PixelData openPlane2D(String id, int planeNumber, byte[] buf) throws FormatException, IOException { return openPlane2D(id, planeNumber, buf, 0, 0, getSizeX(), getSizeY()); } /** * Obtains an object which represents a given sub-image of a plane within * the file. * @param id The path to the file. * @param planeNumber The plane or section within the file to obtain. * @param buf Pre-allocated buffer which has a <i>length</i> that can fit * the byte count of the sub-image. * @param x X coordinate of the upper-left corner of the sub-image * @param y Y coordinate of the upper-left corner of the sub-image * @param w width of the sub-image * @param h height of the sub-image * @return an object which represents the sub-image of the plane. * @throws FormatException If there is an error parsing the file. * @throws IOException If there is an error reading from the file or * acquiring permissions to read the file. */ public PixelData openPlane2D(String id, int planeNumber, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { // FIXME: HACK! The ChannelSeparator isn't exactly what one would call // "complete" so we have to work around the fact that it still copies // all of the plane data (all three channels) from the file if the file // is RGB. ByteBuffer plane; if (iReader.isRGB() || isLeicaReader()) { // System.err.println("RGB, not using cached buffer."); byte[] bytePlane = openBytes(planeNumber, x, y, w, h); plane = ByteBuffer.wrap(bytePlane); } else { // System.err.println("Not RGB, using cached buffer."); plane = ByteBuffer.wrap(openBytes(planeNumber, buf, x, y, w, h)); } plane.order(isLittleEndian()? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); return new PixelData( FormatTools.getPixelTypeString(getPixelType()), plane); } /** * @return true if reader being used is LeicaReader */ public boolean isLeicaReader() { return iReader.getReader() instanceof LeicaReader; } /** * @return true if min-max is set * @throws FormatException * @throws IOException */ @SuppressWarnings("unchecked") public boolean isMinMaxSet() throws FormatException, IOException { if (minMaxSet == null) { MetadataStore store = reader.getMetadataStore(); int series = reader.getSeries(); List<Pixels> pixels = (List<Pixels>) store.getRoot(); if (pixels == null) { return minMaxSet = true; } Pixels p = pixels.get(series); Channel c = p.getChannel(p.getSizeC().getValue() - 1); minMaxSet = c.getStatsInfo() != null; } return minMaxSet; } /* (non-Javadoc) * @see loci.formats.MinMaxCalculator#updateMinMax(int, byte[], int) */ @Override protected void updateMinMax(int no, byte[] buf, int len) throws FormatException, IOException { if (!isMinMaxSet()) super.updateMinMax(no, buf, len); } /* (non-Javadoc) * @see loci.formats.ReaderWrapper#close() */ public void close() throws IOException { minMaxSet = null; super.close(false); } /** * Return the base image reader * * @return See above. */ public ImageReader getImageReader() { return iReader; } /** * @return true if using SPW reader */ public boolean isSPWReader() { String[] domains = reader.getDomains(); return Arrays.asList(domains).contains(FormatTools.HCS_DOMAIN); } /* (non-Javadoc) * @see loci.formats.ReaderWrapper#getMetadataOptions() */ @Override public MetadataOptions getMetadataOptions() { return iReader.getMetadataOptions(); } /* (non-Javadoc) * @see loci.formats.ReaderWrapper#setMetadataOptions(loci.formats.in.MetadataOptions) */ @Override public void setMetadataOptions(MetadataOptions options) { iReader.setMetadataOptions(options); } /* (non-Javadoc) * @see loci.formats.ReaderWrapper#getSupportedMetadataLevels() */ @Override public Set<MetadataLevel> getSupportedMetadataLevels() { return iReader.getSupportedMetadataLevels(); } } /** * @author Chris Allan * */ class ReaderInvocationHandler implements InvocationHandler { private final IFormatReader reader; public ReaderInvocationHandler(IFormatReader reader) { reader.toString(); // NPE this.reader = reader; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { throw new UnsupportedOperationException(); } else if ("hashCode".equals(method.getName())) { return Integer.valueOf(reader.hashCode()); } else if ("toString".equals(method.getName())) { return "ReaderHandler [" + reader + "]"; } else { try { return method.invoke(proxy, args); } catch (Exception e) { throw new FormatException(e); } } } }