/* * #%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; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.Vector; import loci.common.DataTools; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.formats.in.DefaultMetadataOptions; import loci.formats.in.MetadataLevel; import loci.formats.in.MetadataOptions; import loci.formats.meta.MetadataStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Logic to stitch together files with similar names. * Assumes that all files have the same characteristics (e.g., dimensions). */ public class FileStitcher extends ReaderWrapper { // -- Constants -- private static final Logger LOGGER = LoggerFactory.getLogger(FileStitcher.class); private static final int MAX_READERS = 1000; // -- Fields -- /** * Whether string ids given should be treated * as file patterns rather than single file paths. */ private boolean patternIds = false; private boolean doNotChangePattern = false; /** Dimensional axis lengths per file. */ private int[] sizeZ, sizeC, sizeT; /** Component lengths for each axis type. */ private int[][] lenZ, lenC, lenT; /** Core metadata. */ private ArrayList<CoreMetadata> core = new ArrayList<CoreMetadata>(); /** The number of the current series. */ private int coreIndex; /** The number of the current series (non flat). */ private int series; private boolean noStitch; private boolean group = true; private MetadataStore store; private ExternalSeries[] externals; private ClassList<IFormatReader> classList; // -- Constructors -- /** Constructs a FileStitcher around a new image reader. */ public FileStitcher() { this(new ImageReader()); } /** * Constructs a FileStitcher around a new image reader. * @param patternIds Whether string ids given should be treated as file * patterns rather than single file paths. */ public FileStitcher(boolean patternIds) { this(new ImageReader(), patternIds); } /** * Constructs a FileStitcher with the given reader. * @param r The reader to use for reading stitched files. */ public FileStitcher(IFormatReader r) { this(r, false); } /** * Constructs a FileStitcher with the given reader. * @param r The reader to use for reading stitched files. * @param patternIds Whether string ids given should be treated as file * patterns rather than single file paths. */ public FileStitcher(IFormatReader r, boolean patternIds) { super(r); if (r.getClass().getPackage().getName().equals("loci.formats.in")) { ClassList<IFormatReader> classes = new ClassList<IFormatReader>(IFormatReader.class); classes.addClass(r.getClass()); setReaderClassList(classes); } else { reader = DimensionSwapper.makeDimensionSwapper(r); } setUsingPatternIds(patternIds); } // -- FileStitcher API methods -- /** * Set the ClassList object to use when constructing any helper readers. */ public void setReaderClassList(ClassList<IFormatReader> classList) { this.classList = classList; reader = DimensionSwapper.makeDimensionSwapper(new ImageReader(classList)); } /** Gets the wrapped reader prototype. */ @Override public IFormatReader getReader() { return reader; } /** Sets whether the reader is using file patterns for IDs. */ public void setUsingPatternIds(boolean patternIds) { this.patternIds = patternIds; } /** Gets whether the reader is using file patterns for IDs. */ public boolean isUsingPatternIds() { return patternIds; } public void setCanChangePattern(boolean doChange) { doNotChangePattern = !doChange; } public boolean canChangePattern() { return !doNotChangePattern; } /** Gets the reader appropriate for use with the given image plane. */ public IFormatReader getReader(int no) throws FormatException, IOException { if (noStitch) return reader; int[] q = computeIndices(no); int fno = q[0]; return getReader(getCoreIndex(), fno); } /** * Gets the reader that should be used with the given series and image plane. */ public DimensionSwapper getReader(int series, int no) { if (noStitch) return (DimensionSwapper) reader; DimensionSwapper r = externals[getExternalSeries(series)].getReader(no); initReader(series, no); return r; } /** Gets the local reader index for use with the given image plane. */ public int getAdjustedIndex(int no) throws FormatException, IOException { if (noStitch) return no; int[] q = computeIndices(no); int ino = q[1]; return ino; } /** * Gets the axis type for each dimensional block. * @return An array containing values from the enumeration: * <ul> * <li>AxisGuesser.Z_AXIS: focal planes</li> * <li>AxisGuesser.T_AXIS: time points</li> * <li>AxisGuesser.C_AXIS: channels</li> * <li>AxisGuesser.S_AXIS: series</li> * </ul> */ public int[] getAxisTypes() { FormatTools.assertId(getCurrentFile(), true, 2); return externals[getExternalSeries()].getAxisGuesser().getAxisTypes(); } /** * Sets the axis type for each dimensional block. * @param axes An array containing values from the enumeration: * <ul> * <li>AxisGuesser.Z_AXIS: focal planes</li> * <li>AxisGuesser.T_AXIS: time points</li> * <li>AxisGuesser.C_AXIS: channels</li> * <li>AxisGuesser.S_AXIS: series</li> * </ul> */ public void setAxisTypes(int[] axes) throws FormatException { FormatTools.assertId(getCurrentFile(), true, 2); externals[getExternalSeries()].getAxisGuesser().setAxisTypes(axes); computeAxisLengths(); } /** Gets the file pattern object used to build the list of files. */ public FilePattern getFilePattern() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? findPattern(getCurrentFile()) : externals[getExternalSeries()].getFilePattern(); } /** * Gets the axis guesser object used to guess * which dimensional axes are which. */ public AxisGuesser getAxisGuesser() { FormatTools.assertId(getCurrentFile(), true, 2); return externals[getExternalSeries()].getAxisGuesser(); } public FilePattern findPattern(String id) { return new FilePattern(FilePattern.findPattern(id)); } /** * Finds the file pattern for the given ID, based on the state of the file * stitcher. Takes both ID map entries and the patternIds flag into account. */ public String[] findPatterns(String id) { if (!patternIds) { // find the containing patterns HashMap<String, Object> map = Location.getIdMap(); if (map.containsKey(id)) { // search ID map for pattern, rather than files on disk String[] idList = new String[map.size()]; map.keySet().toArray(idList); return FilePattern.findSeriesPatterns(id, null, idList); } else { // id is an unmapped file path; look to similar files on disk return FilePattern.findSeriesPatterns(id); } } if (doNotChangePattern) { return new String[] {id}; } patternIds = false; String[] patterns = findPatterns(new FilePattern(id).getFiles()[0]); if (patterns.length == 0) patterns = new String[] {id}; else { FilePattern test = new FilePattern(patterns[0]); if (test.getFiles().length == 0) patterns = new String[] {id}; } patternIds = true; return patterns; } // -- IFormatReader API methods -- /* @see IFormatReader#getRequiredDirectories() */ @Override public int getRequiredDirectories(String[] files) throws FormatException, IOException { return reader.getRequiredDirectories(files); } /* @see IFormatReader#getImageCount() */ @Override public int getImageCount() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getImageCount() : core.get(getCoreIndex()).imageCount; } /* @see IFormatReader#isRGB() */ @Override public boolean isRGB() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isRGB() : core.get(getCoreIndex()).rgb; } /* @see IFormatReader#getSizeX() */ @Override public int getSizeX() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeX() : core.get(getCoreIndex()).sizeX; } /* @see IFormatReader#getSizeY() */ @Override public int getSizeY() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeY() : core.get(getCoreIndex()).sizeY; } /* @see IFormatReader#getSizeZ() */ @Override public int getSizeZ() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeZ() : core.get(getCoreIndex()).sizeZ; } /* @see IFormatReader#getSizeC() */ @Override public int getSizeC() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeC() : core.get(getCoreIndex()).sizeC; } /* @see IFormatReader#getSizeT() */ @Override public int getSizeT() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeT() : core.get(getCoreIndex()).sizeT; } /* @see IFormatReader#getPixelType() */ @Override public int getPixelType() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getPixelType() : core.get(getCoreIndex()).pixelType; } /* @see IFormatReader#getBitsPerPixel() */ @Override public int getBitsPerPixel() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getBitsPerPixel() : core.get(getCoreIndex()).bitsPerPixel; } /* @see IFormatReader#isIndexed() */ @Override public boolean isIndexed() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isIndexed() : core.get(getCoreIndex()).indexed; } /* @see IFormatReader#isFalseColor() */ @Override public boolean isFalseColor() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isFalseColor() : core.get(getCoreIndex()).falseColor; } /* @see IFormatReader#get8BitLookupTable() */ @Override public byte[][] get8BitLookupTable() throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.get8BitLookupTable() : getReader(getCoreIndex(), 0).get8BitLookupTable(); } /* @see IFormatReader#get16BitLookupTable() */ @Override public short[][] get16BitLookupTable() throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.get16BitLookupTable() : getReader(getCoreIndex(), 0).get16BitLookupTable(); } /* @see IFormatReader#getThumbSizeX() */ @Override public int getThumbSizeX() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getThumbSizeX() : getReader(getCoreIndex(), 0).getThumbSizeX(); } /* @see IFormatReader#getThumbSizeY() */ @Override public int getThumbSizeY() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getThumbSizeY() : getReader(getCoreIndex(), 0).getThumbSizeY(); } /* @see IFormatReader#isLittleEndian() */ @Override public boolean isLittleEndian() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isLittleEndian() : getReader(getCoreIndex(), 0).isLittleEndian(); } /* @see IFormatReader#getDimensionOrder() */ @Override public String getDimensionOrder() { FormatTools.assertId(getCurrentFile(), true, 2); if (noStitch) return reader.getDimensionOrder(); return core.get(getCoreIndex()).dimensionOrder; } /* @see IFormatReader#isOrderCertain() */ @Override public boolean isOrderCertain() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isOrderCertain() : core.get(getCoreIndex()).orderCertain; } /* @see IFormatReader#isThumbnailSeries() */ @Override public boolean isThumbnailSeries() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isThumbnailSeries() : core.get(getCoreIndex()).thumbnail; } /* @see IFormatReader#isInterleaved() */ @Override public boolean isInterleaved() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isInterleaved() : getReader(getCoreIndex(), 0).isInterleaved(); } /* @see IFormatReader#isInterleaved(int) */ @Override public boolean isInterleaved(int subC) { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isInterleaved(subC) : getReader(getCoreIndex(), 0).isInterleaved(subC); } /* @see IFormatReader#openBytes(int) */ @Override public byte[] openBytes(int no) throws FormatException, IOException { return openBytes(no, 0, 0, getSizeX(), getSizeY()); } /* @see IFormatReader#openBytes(int, byte[]) */ @Override public byte[] openBytes(int no, byte[] buf) throws FormatException, IOException { return openBytes(no, buf, 0, 0, getSizeX(), getSizeY()); } /* @see IFormatReader#openBytes(int, int, int, int, int) */ @Override public byte[] openBytes(int no, int x, int y, int w, int h) throws FormatException, IOException { int bpp = FormatTools.getBytesPerPixel(getPixelType()); int ch = getRGBChannelCount(); byte[] buf = DataTools.allocate(w, h, ch, bpp); return openBytes(no, buf, x, y, w, h); } /* @see IFormatReader#openBytes(int, byte[], int, int, int, int) */ @Override public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); int[] pos = computeIndices(no); IFormatReader r = getReader(getCoreIndex(), pos[0]); int ino = pos[1]; if (ino < r.getImageCount()) { byte[] b = r.openBytes(ino, buf, x, y, w, h); if (!noStitch && ino == r.getImageCount() - 1) { r.close(); } return b; } // return a blank image to cover for the fact that // this file does not contain enough image planes Arrays.fill(buf, (byte) 0); return buf; } /* @see IFormatReader#openPlane(int, int, int, int, int) */ @Override public Object openPlane(int no, int x, int y, int w, int h) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); IFormatReader r = getReader(no); int ino = getAdjustedIndex(no); if (ino < r.getImageCount()) return r.openPlane(ino, x, y, w, h); return null; } /* @see IFormatReader#openThumbBytes(int) */ @Override public byte[] openThumbBytes(int no) throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); IFormatReader r = getReader(no); int ino = getAdjustedIndex(no); if (ino < r.getImageCount()) return r.openThumbBytes(ino); // return a blank image to cover for the fact that // this file does not contain enough image planes return externals[getExternalSeries()].getBlankThumbBytes(); } /* @see IFormatReader#close() */ @Override public void close() throws IOException { close(false); } /* @see IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (externals != null) { for (ExternalSeries s : externals) { if (s != null && s.getReaders() != null) { for (DimensionSwapper r : s.getReaders()) { if (r != null) r.close(fileOnly); } } } } if (!fileOnly) { noStitch = false; externals = null; sizeZ = sizeC = sizeT = null; lenZ = lenC = lenT = null; core.clear(); coreIndex = 0; series = 0; store = null; } } /* @see IFormatReader#getSeriesCount() */ @Override public int getSeriesCount() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSeriesCount() : core.size(); } /* @see IFormatReader#setSeries(int) */ @Override public void setSeries(int no) { FormatTools.assertId(getCurrentFile(), true, 2); int n = reader.getSeriesCount(); if (n > 1 || noStitch) reader.setSeries(no); else { coreIndex = no; series = no; } } /* @see IFormatReader#getSeries() */ @Override public int getSeries() { FormatTools.assertId(getCurrentFile(), true, 2); return reader.getSeries() > 0 ? reader.getSeries() : series; } /* @see IFormatReader#seriesToCoreIndex(int) */ @Override public int seriesToCoreIndex(int series) { int n = reader.getSeriesCount(); if (n > 1 || noStitch) return reader.seriesToCoreIndex(series); return series; } /* @see IFormatReader#coreIndexToSeries(int) */ @Override public int coreIndexToSeries(int index) { int n = reader.getSeriesCount(); if (n > 1 || noStitch) return reader.coreIndexToSeries(index); return index; } /* @see IFormatReader#setCoreIndex(int) */ @Override public void setCoreIndex(int no) { FormatTools.assertId(getCurrentFile(), true, 2); int n = reader.getSeriesCount(); if (n > 1 || noStitch) reader.setCoreIndex(no); else { coreIndex = no; series = no; } } /* @see IFormatReader#getCoreIndex() */ @Override public int getCoreIndex() { FormatTools.assertId(getCurrentFile(), true, 2); return reader.getCoreIndex() > 0 ? reader.getCoreIndex() : coreIndex; } /* @see IFormatReader#setGroupFiles(boolean) */ @Override public void setGroupFiles(boolean group) { this.group = group; } /* @see IFormatReader#isGroupFiles(boolean) */ @Override public boolean isGroupFiles() { return group; } /* @see IFormatReader#setNormalized(boolean) */ @Override public void setNormalized(boolean normalize) { FormatTools.assertId(getCurrentFile(), false, 2); if (externals == null) reader.setNormalized(normalize); else { for (ExternalSeries s : externals) { for (DimensionSwapper r : s.getReaders()) { r.setNormalized(normalize); } } } } /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */ @Override public void setOriginalMetadataPopulated(boolean populate) { FormatTools.assertId(getCurrentFile(), false, 1); if (externals == null) reader.setOriginalMetadataPopulated(populate); else { for (ExternalSeries s : externals) { for (DimensionSwapper r : s.getReaders()) { r.setOriginalMetadataPopulated(populate); } } } } /* @see IFormatReader#getUsedFiles() */ @Override public String[] getUsedFiles() { FormatTools.assertId(getCurrentFile(), true, 2); if (noStitch) return reader.getUsedFiles(); // returning the files list directly here is fast, since we do not // have to call initFile on each constituent file; but we can only do so // when each constituent file does not itself have multiple used files Set<String> files = new LinkedHashSet<String>(); for (ExternalSeries s : externals) { String[] f = s.getFiles(); for (String file : f) { String path = new Location(file).getAbsolutePath(); files.add(path); } DimensionSwapper[] readers = s.getReaders(); for (int i=0; i<readers.length; i++) { try { readers[i].setId(f[i]); String[] used = readers[i].getUsedFiles(); for (String file : used) { String path = new Location(file).getAbsolutePath(); files.add(path); } readers[i].close(); } catch (FormatException e) { LOGGER.debug("", e); } catch (IOException e) { LOGGER.debug("", e); } } } return files.toArray(new String[files.size()]); } /* @see IFormatReader#getUsedFiles() */ @Override public String[] getUsedFiles(boolean noPixels) { return noPixels && noStitch ? reader.getUsedFiles(noPixels) : getUsedFiles(); } /* @see IFormatReader#getSeriesUsedFiles() */ @Override public String[] getSeriesUsedFiles() { return getUsedFiles(); } /* @see IFormatReader#getSeriesUsedFiles(boolean) */ @Override public String[] getSeriesUsedFiles(boolean noPixels) { return getUsedFiles(noPixels); } /* @see IFormatReader#getAdvancedUsedFiles(boolean) */ @Override public FileInfo[] getAdvancedUsedFiles(boolean noPixels) { if (noStitch) return reader.getAdvancedUsedFiles(noPixels); String[] files = getUsedFiles(noPixels); if (files == null) return null; FileInfo[] infos = new FileInfo[files.length]; for (int i=0; i<infos.length; i++) { infos[i] = new FileInfo(); infos[i].filename = files[i]; try { infos[i].reader = ((DimensionSwapper) reader).unwrap().getClass(); } catch (FormatException e) { LOGGER.debug("", e); } catch (IOException e) { LOGGER.debug("", e); } infos[i].usedToInitialize = files[i].endsWith(getCurrentFile()); } return infos; } /* @see IFormatReader#getAdvancedSeriesUsedFiles(boolean) */ @Override public FileInfo[] getAdvancedSeriesUsedFiles(boolean noPixels) { if (noStitch) return reader.getAdvancedSeriesUsedFiles(noPixels); String[] files = getSeriesUsedFiles(noPixels); if (files == null) return null; FileInfo[] infos = new FileInfo[files.length]; for (int i=0; i<infos.length; i++) { infos[i] = new FileInfo(); infos[i].filename = files[i]; try { infos[i].reader = ((DimensionSwapper) reader).unwrap().getClass(); } catch (FormatException e) { LOGGER.debug("", e); } catch (IOException e) { LOGGER.debug("", e); } infos[i].usedToInitialize = files[i].endsWith(getCurrentFile()); } return infos; } /* @see IFormatReader#getIndex(int, int, int) */ @Override public int getIndex(int z, int c, int t) { FormatTools.assertId(getCurrentFile(), true, 2); return FormatTools.getIndex(this, z, c, t); } /* @see IFormatReader#getIndex(int, int, int, int, int, int) */ @Override public int getIndex(int z, int c, int t, int moduloZ, int moduloC, int moduloT) { FormatTools.assertId(getCurrentFile(), true, 2); return FormatTools.getIndex(this, z, c, t, moduloZ, moduloC, moduloT); } /* @see IFormatReader#getZCTCoords(int) */ @Override public int[] getZCTCoords(int index) { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getZCTCoords(index) : FormatTools.getZCTCoords(core.get(getCoreIndex()).dimensionOrder, getSizeZ(), getEffectiveSizeC(), getSizeT(), getImageCount(), index); } /* @see IFormatReader#getZCTModuloCoords(int) */ @Override public int[] getZCTModuloCoords(int index) { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getZCTModuloCoords(index) : FormatTools.getZCTCoords(core.get(getCoreIndex()).dimensionOrder, getSizeZ(), getEffectiveSizeC(), getSizeT(), getModuloZ().length(), getModuloC().length(), getModuloT().length(), getImageCount(), index); } /* @see IFormatReader#getSeriesMetadata() */ @Override public Hashtable<String, Object> getSeriesMetadata() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSeriesMetadata() : core.get(getCoreIndex()).seriesMetadata; } /* @see IFormatReader#getCoreMetadataList() */ @Override public List<CoreMetadata> getCoreMetadataList() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getCoreMetadataList() : core; } /* @see IFormatReader#setMetadataStore(MetadataStore) */ @Override public void setMetadataStore(MetadataStore store) { FormatTools.assertId(getCurrentFile(), false, 2); reader.setMetadataStore(store); this.store = store; } /* @see IFormatReader#getMetadataStore() */ @Override public MetadataStore getMetadataStore() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getMetadataStore() : store; } /* @see IFormatReader#getMetadataStoreRoot() */ @Override public Object getMetadataStoreRoot() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getMetadataStoreRoot() : store.getRoot(); } /* @see IFormatReader#getUnderlyingReaders() */ @Override public IFormatReader[] getUnderlyingReaders() { List<IFormatReader> list = new ArrayList<IFormatReader>(); for (ExternalSeries s : externals) { for (DimensionSwapper r : s.getReaders()) { list.add(r); } } return list.toArray(new IFormatReader[0]); } /* @see IFormatReader#reopenFile) */ @Override public void reopenFile() throws IOException { reader.reopenFile(); for (ExternalSeries s : externals) { for (DimensionSwapper r : s.getReaders()) { r.reopenFile(); } } } /* @see IFormatReader#setId(String) */ @Override public void setId(String id) throws FormatException, IOException { if (getCurrentFile() != null && new Location(id).getAbsolutePath().equals(getCurrentFile())) { // already initialized this file return; } close(); initFile(id); } // -- Internal FormatReader API methods -- /** Initializes the given file or file pattern. */ protected void initFile(String id) throws FormatException, IOException { LOGGER.debug("initFile: {}", id); FilePattern fp = new FilePattern(id); if (!patternIds) { patternIds = fp.isValid() && fp.getFiles().length > 1; } else { patternIds = !new Location(id).exists() && Location.getMappedId(id).equals(id); } boolean mustGroup = false; if (patternIds) { mustGroup = fp.isValid() && reader.fileGroupOption(fp.getFiles()[0]) == FormatTools.MUST_GROUP; } else { mustGroup = reader.fileGroupOption(id) == FormatTools.MUST_GROUP; } if (mustGroup || !group) { // reader subclass is handling file grouping noStitch = true; reader.close(); reader.setGroupFiles(group); if (patternIds && fp.isValid()) { reader.setId(fp.getFiles()[0]); } else reader.setId(id); return; } if (fp.isRegex()) { setCanChangePattern(false); } String[] patterns = findPatterns(id); if (patterns.length == 0) patterns = new String[] {id}; externals = new ExternalSeries[patterns.length]; for (int i=0; i<externals.length; i++) { externals[i] = new ExternalSeries(new FilePattern(patterns[i])); } fp = new FilePattern(patterns[0]); reader.close(); reader.setGroupFiles(group); if (!fp.isValid()) { throw new FormatException("Invalid file pattern: " + fp.getPattern()); } reader.setId(fp.getFiles()[0]); String msg = " Please rename your files or disable file stitching."; if (reader.getSeriesCount() > 1 && externals.length > 1) { throw new FormatException("Unsupported grouping: File pattern contains " + "multiple files and each file contains multiple series." + msg); } int nPixelsFiles = reader.getUsedFiles().length - reader.getUsedFiles(true).length; if (nPixelsFiles > 1 || fp.getFiles().length == 1) { noStitch = true; return; } AxisGuesser guesser = new AxisGuesser(fp, reader.getDimensionOrder(), reader.getSizeZ(), reader.getSizeT(), reader.getEffectiveSizeC(), reader.isOrderCertain()); // use the dimension order recommended by the axis guesser ((DimensionSwapper) reader).swapDimensions(guesser.getAdjustedOrder()); // if this is a multi-series dataset, we need some special logic int seriesCount = externals.length; if (externals.length == 1) { seriesCount = reader.getSeriesCount(); } // verify that file pattern is valid and matches existing files if (!fp.isValid()) { throw new FormatException("Invalid " + (patternIds ? "file pattern" : "filename") + " (" + id + "): " + fp.getErrorMessage() + msg); } String[] files = fp.getFiles(); if (files == null) { throw new FormatException("No files matching pattern (" + fp.getPattern() + "). " + msg); } for (int i=0; i<files.length; i++) { String file = files[i]; // HACK: skip file existence check for fake files if (file.toLowerCase().endsWith(".fake")) continue; if (!new Location(file).exists()) { throw new FormatException("File #" + i + " (" + file + ") does not exist."); } } // determine reader type for these files; assume all are the same type Class<? extends IFormatReader> readerClass = ((DimensionSwapper) reader).unwrap(files[0]).getClass(); sizeZ = new int[seriesCount]; sizeC = new int[seriesCount]; sizeT = new int[seriesCount]; boolean[] certain = new boolean[seriesCount]; lenZ = new int[seriesCount][]; lenC = new int[seriesCount][]; lenT = new int[seriesCount][]; // analyze first file; assume each file has the same parameters core.clear(); int oldSeries = getSeries(); for (int i=0; i<seriesCount; i++) { IFormatReader rr = getReader(i, 0); CoreMetadata ms = new CoreMetadata(); core.add(ms); ms.sizeX = rr.getSizeX(); ms.sizeY = rr.getSizeY(); // NB: core.sizeZ populated in computeAxisLengths below // NB: core.sizeC populated in computeAxisLengths below // NB: core.sizeT populated in computeAxisLengths below ms.pixelType = rr.getPixelType(); ExternalSeries external = externals[getExternalSeries(i)]; ms.imageCount = rr.getImageCount() * external.getFiles().length; ms.thumbSizeX = rr.getThumbSizeX(); ms.thumbSizeY = rr.getThumbSizeY(); ms.dimensionOrder = rr.getDimensionOrder(); // NB: core.orderCertain[i] populated below ms.rgb = rr.isRGB(); ms.littleEndian = rr.isLittleEndian(); ms.interleaved = rr.isInterleaved(); ms.seriesMetadata = rr.getSeriesMetadata(); ms.indexed = rr.isIndexed(); ms.falseColor = rr.isFalseColor(); ms.bitsPerPixel = rr.getBitsPerPixel(); sizeZ[i] = rr.getSizeZ(); sizeC[i] = rr.getSizeC(); sizeT[i] = rr.getSizeT(); certain[i] = rr.isOrderCertain(); } // order may need to be adjusted for (int i=0; i<seriesCount; i++) { setSeries(i); AxisGuesser ag = externals[getExternalSeries()].getAxisGuesser(); core.get(i).dimensionOrder = ag.getAdjustedOrder(); core.get(i).orderCertain = ag.isCertain(); computeAxisLengths(); } setSeries(oldSeries); // populate metadata store store = reader.getMetadataStore(); // don't overwrite pixel info if files aren't actually grouped if (!noStitch) { MetadataTools.populatePixels(store, this, false, false); if (reader.getSeriesCount() == 1 && getSeriesCount() > 1) { for (int i=0; i<getSeriesCount(); i++) { int index = getExternalSeries(i); String pattern = externals[index].getFilePattern().getPattern(); pattern = pattern.substring(pattern.lastIndexOf(File.separator) + 1); store.setImageName(pattern, i); } } } } // -- Helper methods -- private int getExternalSeries() { return getExternalSeries(getCoreIndex()); } private int getExternalSeries(int currentSeries) { if (reader.getSeriesCount() > 1) return 0; return currentSeries; } /** Computes axis length arrays, and total axis lengths. */ protected void computeAxisLengths() throws FormatException { int sno = getCoreIndex(); CoreMetadata ms = core.get(sno); ExternalSeries s = externals[getExternalSeries()]; FilePattern p = s.getFilePattern(); int[] count = p.getCount(); initReader(sno, 0); AxisGuesser ag = s.getAxisGuesser(); int[] axes = ag.getAxisTypes(); int numZ = ag.getAxisCountZ(); int numC = ag.getAxisCountC(); int numT = ag.getAxisCountT(); if (axes.length == 0 && s.getFiles().length > 1) { axes = new int[] {AxisGuesser.T_AXIS}; count = new int[] {s.getFiles().length}; numT++; } ms.sizeZ = sizeZ[sno]; ms.sizeC = sizeC[sno]; ms.sizeT = sizeT[sno]; lenZ[sno] = new int[numZ + 1]; lenC[sno] = new int[numC + 1]; lenT[sno] = new int[numT + 1]; lenZ[sno][0] = sizeZ[sno]; lenC[sno][0] = sizeC[sno] / reader.getRGBChannelCount(); lenT[sno][0] = sizeT[sno]; for (int i=0, z=1, c=1, t=1; i<count.length; i++) { switch (axes[i]) { case AxisGuesser.Z_AXIS: ms.sizeZ *= count[i]; lenZ[sno][z++] = count[i]; break; case AxisGuesser.C_AXIS: ms.sizeC *= count[i]; lenC[sno][c++] = count[i]; break; case AxisGuesser.T_AXIS: ms.sizeT *= count[i]; lenT[sno][t++] = count[i]; break; case AxisGuesser.S_AXIS: break; default: throw new FormatException("Unknown axis type for axis #" + i + ": " + axes[i]); } } ms.imageCount = ms.sizeZ * ms.sizeT; ms.imageCount *= (ms.sizeC / reader.getRGBChannelCount()); ms.moduloC = reader.getModuloC(); ms.moduloZ = reader.getModuloZ(); ms.moduloT = reader.getModuloT(); if (ms.moduloC.length() % ms.sizeC != 0) { ms.moduloC.start = 0; ms.moduloC.step = 1; ms.moduloC.end = 0; } if (ms.moduloZ.length() % ms.sizeZ != 0) { ms.moduloZ.start = 0; ms.moduloZ.step = 1; ms.moduloZ.end = 0; } if (ms.moduloT.length() % ms.sizeT != 0) { ms.moduloT.start = 0; ms.moduloT.step = 1; ms.moduloT.end = 0; } } /** * Gets the file index, and image index into that file, * corresponding to the given global image index. * * @return An array of size 2, dimensioned {file index, image index}. */ protected int[] computeIndices(int no) throws FormatException, IOException { if (noStitch) return new int[] {0, no}; int sno = getCoreIndex(); ExternalSeries s = externals[getExternalSeries()]; int[] axes = s.getAxisGuesser().getAxisTypes(); int[] count = s.getFilePattern().getCount(); if (axes.length == 0) { axes = new int[] {AxisGuesser.T_AXIS}; count = new int[] {s.getFiles().length}; } // get Z, C and T positions int[] zct = getZCTCoords(no); int[] posZ = FormatTools.rasterToPosition(lenZ[sno], zct[0]); int[] posC = FormatTools.rasterToPosition(lenC[sno], zct[1]); int[] posT = FormatTools.rasterToPosition(lenT[sno], zct[2]); int[] tmpZ = new int[posZ.length]; System.arraycopy(posZ, 0, tmpZ, 0, tmpZ.length); int[] tmpC = new int[posC.length]; System.arraycopy(posC, 0, tmpC, 0, tmpC.length); int[] tmpT = new int[posT.length]; System.arraycopy(posT, 0, tmpT, 0, tmpT.length); // convert Z, C and T position lists into file index and image index int[] pos = new int[axes.length]; int z = 1, c = 1, t = 1; for (int i=0; i<axes.length; i++) { if (axes[i] == AxisGuesser.Z_AXIS) pos[i] = posZ[z++]; else if (axes[i] == AxisGuesser.C_AXIS) pos[i] = posC[c++]; else if (axes[i] == AxisGuesser.T_AXIS) pos[i] = posT[t++]; else if (axes[i] == AxisGuesser.S_AXIS) { pos[i] = 0; } else { throw new FormatException("Unknown axis type for axis #" + i + ": " + axes[i]); } } int fno = FormatTools.positionToRaster(count, pos); DimensionSwapper r = getReader(sno, fno); int ino; if (posZ[0] < r.getSizeZ() && posC[0] < r.getSizeC() && posT[0] < r.getSizeT()) { if (r.isRGB() && (posC[0] * r.getRGBChannelCount() >= lenC[sno][0])) { posC[0] /= lenC[sno][0]; } ino = FormatTools.getIndex(r, posZ[0], posC[0], posT[0]); } else ino = Integer.MAX_VALUE; // coordinates out of range return new int[] {fno, ino}; } protected void initReader(int sno, int fno) { int external = getExternalSeries(sno); DimensionSwapper r = externals[external].getReader(fno); try { if (r.getCurrentFile() == null) { r.setGroupFiles(false); } r.setId(externals[external].getFiles()[fno]); r.setSeries(reader.getSeriesCount() > 1 ? sno : 0); String newOrder = ((DimensionSwapper) reader).getInputOrder(); if ((externals[external].getFiles().length > 1 || !r.isOrderCertain()) && (r.getRGBChannelCount() == 1 || newOrder.indexOf("C") == r.getDimensionOrder().indexOf("C"))) { r.swapDimensions(newOrder); } r.setOutputOrder(newOrder); } catch (FormatException e) { LOGGER.debug("", e); } catch (IOException e) { LOGGER.debug("", e); } } // -- Helper classes -- class ExternalSeries { private DimensionSwapper[] readers; private String[] files; private FilePattern pattern; private byte[] blankThumbBytes; private String originalOrder; private AxisGuesser ag; private int imagesPerFile; public ExternalSeries(FilePattern pattern) throws FormatException, IOException { this.pattern = pattern; files = this.pattern.getFiles(); int nReaders = files.length > MAX_READERS ? 1 : files.length; readers = new DimensionSwapper[nReaders]; for (int i=0; i<readers.length; i++) { if (classList != null) { readers[i] = new DimensionSwapper(new ImageReader(classList)); } else readers[i] = new DimensionSwapper(); readers[i].setGroupFiles(false); } readers[0].setId(files[0]); ag = new AxisGuesser(this.pattern, readers[0].getDimensionOrder(), readers[0].getSizeZ(), readers[0].getSizeT(), readers[0].getSizeC(), readers[0].isOrderCertain()); blankThumbBytes = new byte[FormatTools.getPlaneSize(readers[0], readers[0].getThumbSizeX(), readers[0].getThumbSizeY())]; originalOrder = readers[0].getDimensionOrder(); imagesPerFile = readers[0].getImageCount(); } public DimensionSwapper getReader(int fno) { if (fno < readers.length) { return readers[fno]; } return readers[0]; } public DimensionSwapper[] getReaders() { return readers; } public FilePattern getFilePattern() { return pattern; } public String getOriginalOrder() { return originalOrder; } public AxisGuesser getAxisGuesser() { return ag; } public byte[] getBlankThumbBytes() { return blankThumbBytes; } public String[] getFiles() { return files; } public int getImagesPerFile() { return imagesPerFile; } } }