// // FileStitcher.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ 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.Hashtable; import java.util.List; import java.util.Set; import java.util.Vector; 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). * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/FileStitcher.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/FileStitcher.java;hb=HEAD">Gitweb</a></dd></dl> */ public class FileStitcher extends ReaderWrapper { // -- Constants -- private static final Logger LOGGER = LoggerFactory.getLogger(FileStitcher.class); // -- 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 CoreMetadata[] core; /** Current series number. */ 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) { 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. */ 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(getSeries(), 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)].getReaders()[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#getImageCount() */ public int getImageCount() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getImageCount() : core[getSeries()].imageCount; } /* @see IFormatReader#isRGB() */ public boolean isRGB() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isRGB() : core[getSeries()].rgb; } /* @see IFormatReader#getSizeX() */ public int getSizeX() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeX() : core[getSeries()].sizeX; } /* @see IFormatReader#getSizeY() */ public int getSizeY() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeY() : core[getSeries()].sizeY; } /* @see IFormatReader#getSizeZ() */ public int getSizeZ() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeZ() : core[getSeries()].sizeZ; } /* @see IFormatReader#getSizeC() */ public int getSizeC() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeC() : core[getSeries()].sizeC; } /* @see IFormatReader#getSizeT() */ public int getSizeT() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSizeT() : core[getSeries()].sizeT; } /* @see IFormatReader#getPixelType() */ public int getPixelType() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getPixelType() : core[getSeries()].pixelType; } /* @see IFormatReader#getBitsPerPixel() */ public int getBitsPerPixel() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getBitsPerPixel() : core[getSeries()].bitsPerPixel; } /* @see IFormatReader#isIndexed() */ public boolean isIndexed() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isIndexed() : core[getSeries()].indexed; } /* @see IFormatReader#isFalseColor() */ public boolean isFalseColor() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isFalseColor() : core[getSeries()].falseColor; } /* @see IFormatReader#get8BitLookupTable() */ public byte[][] get8BitLookupTable() throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.get8BitLookupTable() : getReader(getSeries(), 0).get8BitLookupTable(); } /* @see IFormatReader#get16BitLookupTable() */ public short[][] get16BitLookupTable() throws FormatException, IOException { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.get16BitLookupTable() : getReader(getSeries(), 0).get16BitLookupTable(); } /* @see IFormatReader#getChannelDimLengths() */ public int[] getChannelDimLengths() { FormatTools.assertId(getCurrentFile(), true, 2); if (noStitch) return reader.getChannelDimLengths(); if (core[getSeries()].cLengths == null) { return new int[] {core[getSeries()].sizeC}; } return core[getSeries()].cLengths; } /* @see IFormatReader#getChannelDimTypes() */ public String[] getChannelDimTypes() { FormatTools.assertId(getCurrentFile(), true, 2); if (noStitch) return reader.getChannelDimTypes(); if (core[getSeries()].cTypes == null) { return new String[] {FormatTools.CHANNEL}; } return core[getSeries()].cTypes; } /* @see IFormatReader#getThumbSizeX() */ public int getThumbSizeX() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getThumbSizeX() : getReader(getSeries(), 0).getThumbSizeX(); } /* @see IFormatReader#getThumbSizeY() */ public int getThumbSizeY() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getThumbSizeY() : getReader(getSeries(), 0).getThumbSizeY(); } /* @see IFormatReader#isLittleEndian() */ public boolean isLittleEndian() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isLittleEndian() : getReader(getSeries(), 0).isLittleEndian(); } /* @see IFormatReader#getDimensionOrder() */ public String getDimensionOrder() { FormatTools.assertId(getCurrentFile(), true, 2); if (noStitch) return reader.getDimensionOrder(); return core[getSeries()].dimensionOrder; } /* @see IFormatReader#isOrderCertain() */ public boolean isOrderCertain() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isOrderCertain() : core[getSeries()].orderCertain; } /* @see IFormatReader#isThumbnailSeries() */ public boolean isThumbnailSeries() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isThumbnailSeries() : core[getSeries()].thumbnail; } /* @see IFormatReader#isInterleaved() */ public boolean isInterleaved() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isInterleaved() : getReader(getSeries(), 0).isInterleaved(); } /* @see IFormatReader#isInterleaved(int) */ public boolean isInterleaved(int subC) { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.isInterleaved(subC) : getReader(getSeries(), 0).isInterleaved(subC); } /* @see IFormatReader#openBytes(int) */ public byte[] openBytes(int no) throws FormatException, IOException { return openBytes(no, 0, 0, getSizeX(), getSizeY()); } /* @see IFormatReader#openBytes(int, byte[]) */ 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) */ 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 = new byte[w * h * ch * bpp]; return openBytes(no, buf, x, y, w, h); } /* @see IFormatReader#openBytes(int, byte[], int, int, int, int) */ 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(getSeries(), pos[0]); int ino = pos[1]; if (ino < r.getImageCount()) return r.openBytes(ino, buf, x, y, w, h); // 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) */ 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) */ 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() */ public void close() throws IOException { close(false); } /* @see IFormatReader#close(boolean) */ 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 = null; series = 0; store = null; } } /* @see IFormatReader#getSeriesCount() */ public int getSeriesCount() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSeriesCount() : core.length; } /* @see IFormatReader#setSeries(int) */ public void setSeries(int no) { FormatTools.assertId(getCurrentFile(), true, 2); int n = reader.getSeriesCount(); if (n > 1) reader.setSeries(no); else series = no; } /* @see IFormatReader#getSeries() */ public int getSeries() { FormatTools.assertId(getCurrentFile(), true, 2); return reader.getSeries() > 0 ? reader.getSeries() : series; } /* @see IFormatReader#setGroupFiles(boolean) */ public void setGroupFiles(boolean group) { this.group = group; } /* @see IFormatReader#isGroupFiles(boolean) */ public boolean isGroupFiles() { return group; } /* @see IFormatReader#setNormalized(boolean) */ 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); } } } } /** * @deprecated * @see IFormatReader#setMetadataCollected(boolean) */ public void setMetadataCollected(boolean collect) { FormatTools.assertId(getCurrentFile(), false, 2); if (externals == null) reader.setMetadataCollected(collect); else { for (ExternalSeries s : externals) { for (DimensionSwapper r : s.getReaders()) { r.setMetadataCollected(collect); } } } } /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */ 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() */ 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 Vector<String> files = new Vector<String>(); for (ExternalSeries s : externals) { String[] f = s.getFiles(); for (String file : f) { if (!files.contains(file)) files.add(file); } } return files.toArray(new String[files.size()]); } /* @see IFormatReader#getUsedFiles() */ public String[] getUsedFiles(boolean noPixels) { return noPixels && noStitch ? reader.getUsedFiles(noPixels) : getUsedFiles(); } /* @see IFormatReader#getSeriesUsedFiles() */ public String[] getSeriesUsedFiles() { return getUsedFiles(); } /* @see IFormatReader#getSeriesUsedFiles(boolean) */ public String[] getSeriesUsedFiles(boolean noPixels) { return getUsedFiles(noPixels); } /* @see IFormatReader#getAdvancedUsedFiles(boolean) */ 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) */ 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) */ public int getIndex(int z, int c, int t) { FormatTools.assertId(getCurrentFile(), true, 2); return FormatTools.getIndex(this, z, c, t); } /* @see IFormatReader#getZCTCoords(int) */ public int[] getZCTCoords(int index) { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getZCTCoords(index) : FormatTools.getZCTCoords(core[getSeries()].dimensionOrder, getSizeZ(), getEffectiveSizeC(), getSizeT(), getImageCount(), index); } /* @see IFormatReader#getSeriesMetadata() */ public Hashtable<String, Object> getSeriesMetadata() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getSeriesMetadata() : core[getSeries()].seriesMetadata; } /* @see IFormatReader#getCoreMetadata() */ public CoreMetadata[] getCoreMetadata() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getCoreMetadata() : core; } /* @see IFormatReader#setMetadataStore(MetadataStore) */ public void setMetadataStore(MetadataStore store) { FormatTools.assertId(getCurrentFile(), false, 2); reader.setMetadataStore(store); this.store = store; } /* @see IFormatReader#getMetadataStore() */ public MetadataStore getMetadataStore() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getMetadataStore() : store; } /* @see IFormatReader#getMetadataStoreRoot() */ public Object getMetadataStoreRoot() { FormatTools.assertId(getCurrentFile(), true, 2); return noStitch ? reader.getMetadataStoreRoot() : store.getRoot(); } /* @see IFormatReader#getUnderlyingReaders() */ 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#setId(String) */ public void setId(String id) throws FormatException, IOException { 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(true); 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(false); 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); } if (reader.getUsedFiles().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 = new CoreMetadata[seriesCount]; int oldSeries = getSeries(); for (int i=0; i<seriesCount; i++) { IFormatReader rr = getReader(i, 0); core[i] = new CoreMetadata(); core[i].sizeX = rr.getSizeX(); core[i].sizeY = rr.getSizeY(); // NB: core.sizeZ populated in computeAxisLengths below // NB: core.sizeC populated in computeAxisLengths below // NB: core.sizeT populated in computeAxisLengths below core[i].pixelType = rr.getPixelType(); ExternalSeries external = externals[getExternalSeries(i)]; core[i].imageCount = rr.getImageCount() * external.getFiles().length; core[i].thumbSizeX = rr.getThumbSizeX(); core[i].thumbSizeY = rr.getThumbSizeY(); // NB: core.cLengths[i] populated in computeAxisLengths below // NB: core.cTypes[i] populated in computeAxisLengths below core[i].dimensionOrder = rr.getDimensionOrder(); // NB: core.orderCertain[i] populated below core[i].rgb = rr.isRGB(); core[i].littleEndian = rr.isLittleEndian(); core[i].interleaved = rr.isInterleaved(); core[i].seriesMetadata = rr.getSeriesMetadata(); core[i].indexed = rr.isIndexed(); core[i].falseColor = rr.isFalseColor(); core[i].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[i].dimensionOrder = ag.getAdjustedOrder(); core[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(getSeries()); } 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 = getSeries(); 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++; } core[sno].sizeZ = sizeZ[sno]; core[sno].sizeC = sizeC[sno]; core[sno].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]; 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: core[sno].sizeZ *= count[i]; lenZ[sno][z++] = count[i]; break; case AxisGuesser.C_AXIS: core[sno].sizeC *= count[i]; lenC[sno][c++] = count[i]; break; case AxisGuesser.T_AXIS: core[sno].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]); } } core[sno].imageCount = core[sno].sizeZ * core[sno].sizeT; if (!isRGB()) { core[sno].imageCount *= core[sno].sizeC; } else core[sno].imageCount *= reader.getEffectiveSizeC(); int[] cLengths = reader.getChannelDimLengths(); String[] cTypes = reader.getChannelDimTypes(); int cCount = 0; for (int i=0; i<cLengths.length; i++) { if (cLengths[i] > 1) cCount++; } for (int i=1; i<lenC[sno].length; i++) { if (lenC[sno][i] > 1) cCount++; } if (cCount == 0) { core[sno].cLengths = new int[] {1}; core[sno].cTypes = new String[] {FormatTools.CHANNEL}; } else { core[sno].cLengths = new int[cCount]; core[sno].cTypes = new String[cCount]; } int c = 0; for (int i=0; i<cLengths.length; i++) { if (cLengths[i] == 1) continue; core[sno].cLengths[c] = cLengths[i]; core[sno].cTypes[c] = cTypes[i]; c++; } for (int i=1; i<lenC[sno].length; i++) { if (lenC[sno][i] == 1) continue; core[sno].cLengths[c] = lenC[sno][i]; core[sno].cTypes[c] = FormatTools.CHANNEL; } } /** * 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 = getSeries(); ExternalSeries s = externals[getExternalSeries()]; int[] axes = s.getAxisGuesser().getAxisTypes(); int[] count = s.getFilePattern().getCount(); // 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].getReaders()[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(); readers = new DimensionSwapper[files.length]; 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[] 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; } } }