Changeset 7021


Ignore:
Timestamp:
10/04/10 13:55:28 (9 years ago)
Author:
melissa
Message:

Updated FileStitcher and FilePattern to better handle multi-series datasets in which the series axis is spread across multiple files. Closes #296, closes #542.

Note that datasets such as S<0-2>_T<0-10>.lif with each file containing multiple series are still unsupported. However, attempting to stitch datasets of this type will result in an informative exception.

Location:
trunk/components
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/components/bio-formats/src/loci/formats/AxisGuesser.java

    r6881 r7021  
    305305    } 
    306306    return num; 
     307  } 
     308 
     309  // -- Static API methods -- 
     310 
     311  /** Returns a best guess of the given label's axis type. */ 
     312  public static int getAxisType(String label) { 
     313    String lowerLabel = label.toLowerCase(); 
     314    for (String p : Z) { 
     315      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return Z_AXIS; 
     316    } 
     317    for (String p : C) { 
     318      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return C_AXIS; 
     319    } 
     320    for (String p : T) { 
     321      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return T_AXIS; 
     322    } 
     323    for (String p : S) { 
     324      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return S_AXIS; 
     325    } 
     326    return UNKNOWN_AXIS; 
    307327  } 
    308328 
  • trunk/components/bio-formats/src/loci/formats/FilePattern.java

    r6881 r7021  
    3030import java.util.List; 
    3131 
     32import loci.common.DataTools; 
    3233import loci.common.Location; 
    3334 
     
    349350   */ 
    350351  public static String findPattern(String name, String dir, String[] nameList) { 
     352    return findPattern(name, dir, nameList, null); 
     353  } 
     354 
     355  /** 
     356   * Identifies the group pattern from a given file within that group. 
     357   * @param name The filename to use as a template for the match. 
     358   * @param dir The directory prefix to use for matching files. 
     359   * @param nameList The names through which to search for matching files. 
     360   * @param excludeAxes The list of axis types which should be excluded from the 
     361   *  pattern. 
     362   */ 
     363  public static String findPattern(String name, String dir, String[] nameList, 
     364    int[] excludeAxes) 
     365  { 
     366    if (excludeAxes == null) excludeAxes = new int[0]; 
     367 
    351368    if (dir == null) dir = ""; // current directory 
    352369    else if (!dir.equals("") && !dir.endsWith(File.separator)) { 
     
    390407    for (int i=0; i<q; i++) { 
    391408      int last = i > 0 ? endList[i - 1] : 0; 
    392       sb.append(name.substring(last, indexList[i])); 
     409      String prefix = name.substring(last, indexList[i]); 
     410      int axisType = AxisGuesser.getAxisType(prefix); 
     411      if (DataTools.containsValue(excludeAxes, axisType)) { 
     412        sb.append(name.substring(last, endList[i])); 
     413        continue; 
     414      } 
     415 
     416      sb.append(prefix); 
    393417      String pre = name.substring(0, indexList[i]); 
    394418      String post = name.substring(endList[i]); 
     
    403427      } 
    404428      boolean fix = true; 
    405       for (int j=0; j<list.length; j++) { 
    406         if (list[j].length() != len) { 
     429      for (String s : list) { 
     430        if (s.length() != len) { 
    407431          fix = false; 
    408432          break; 
     
    419443          int jx = indexList[i] + j; 
    420444          char c = name.charAt(jx); 
    421           for (int k=0; k<list.length; k++) { 
    422             if (list[k].charAt(jx) != c) { 
     445          for (String s : list) { 
     446            if (s.charAt(jx) != c) { 
    423447              same[j] = false; 
    424448              break; 
     
    464488    } 
    465489    sb.append(q > 0 ? name.substring(endList[q - 1]) : name); 
    466     String pat = sb.toString(); 
    467  
    468     // NB: Due to variations in axis length, the file pattern detected can 
    469     // depend on the file name given as the basis of detection. 
    470     // 
    471     // To work around this problem, we redetect the pattern using the first 
    472     // file in the pattern if it differs from the current base file name. 
    473     // 
    474     // For details, see Trac ticket #19: 
    475     // http://dev.loci.wisc.edu/trac/java/ticket/19 
    476     String first = pat.substring(dir.length()); 
    477     first = first.replaceAll("<([0-9]+)-[0-9]+(:[0-9]+)?>", "$1"); 
    478     if (!name.equals(first)) { 
    479       String pattern = findPattern(first, dir, nameList); 
    480       if (pattern != null) return pattern; 
    481     } 
    482  
    483     return pat; 
     490    return sb.toString(); 
     491  } 
     492 
     493  public static String[] findSeriesPatterns(String base) { 
     494    Location file = new Location(base).getAbsoluteFile(); 
     495    Location parent = file.getParentFile(); 
     496    String[] list = parent.list(true); 
     497    return findSeriesPatterns(base, parent.getAbsolutePath(), list); 
     498  } 
     499 
     500  public static String[] findSeriesPatterns(String base, String dir, 
     501    String[] nameList) 
     502  { 
     503    String baseSuffix = new Location(base).getName(); 
     504    baseSuffix = baseSuffix.substring(baseSuffix.indexOf(".") + 1); 
     505 
     506    ArrayList<String> patterns = new ArrayList<String>(); 
     507    int[] exclude = new int[] {AxisGuesser.S_AXIS}; 
     508    for (String name : nameList) { 
     509      String pattern = findPattern(name, dir, nameList, exclude); 
     510      int start = pattern.lastIndexOf(File.separator) + 1; 
     511      if (start < 0) start = 0; 
     512      String patternSuffix = pattern.substring(start); 
     513      patternSuffix = patternSuffix.substring(patternSuffix.indexOf(".") + 1); 
     514 
     515      if (!patterns.contains(pattern) && (!new Location(pattern).exists() || 
     516        base.equals(pattern)) && patternSuffix.equals(baseSuffix)) 
     517      { 
     518        patterns.add(pattern); 
     519      } 
     520    } 
     521    String[] s = patterns.toArray(new String[patterns.size()]); 
     522    Arrays.sort(s); 
     523    return s; 
    484524  } 
    485525 
  • trunk/components/bio-formats/src/loci/formats/FileStitcher.java

    r6881 r7021  
    5252 * <a href="http://dev.loci.wisc.edu/svn/java/trunk/components/bio-formats/src/loci/formats/FileStitcher.java">SVN</a></dd></dl> 
    5353 */ 
    54 public class FileStitcher implements IFormatReader { 
     54public class FileStitcher extends ReaderWrapper { 
    5555 
    5656  // -- Constants -- 
     
    6060 
    6161  // -- Fields -- 
    62  
    63   /** 
    64    * FormatReader to use as a template for constituent readers. 
    65    * 
    66    * The constituent readers must be dimension swappers so that the 
    67    * file stitcher can reorganize the dimension order as needed based on 
    68    * the result of the axis guessing algorithm. 
    69    * 
    70    * @see AxisGuesser#getAdjustedOrder() 
    71    */ 
    72   private DimensionSwapper reader; 
    7362 
    7463  /** 
     
    7867  private boolean patternIds = false; 
    7968 
    80   /** Current file pattern string. */ 
    81   private String currentId; 
    82  
    83   /** File pattern object used to build the list of files. */ 
    84   private FilePattern fp; 
    85  
    86   /** Axis guesser object used to guess which dimensional axes are which. */ 
    87   private AxisGuesser[] ag; 
    88  
    89   /** The matching files. */ 
    90   private String[][] files; 
    91  
    92   /** Used files list. Only initialized in certain cases, upon request. */ 
    93   private String[] usedFiles; 
    94  
    95   /** Reader used for each file. */ 
    96   private DimensionSwapper[][] readers; 
    97  
    98   /** Blank image bytes, for use when image counts vary between files. */ 
    99   private byte[][] blankBytes; 
    100  
    101   /** Blank thumbnail bytes, for use when image counts vary between files. */ 
    102   private byte[][] blankThumbBytes; 
    103  
    104   /** Number of images per file. */ 
    105   private int[] imagesPerFile; 
    106  
    10769  /** Dimensional axis lengths per file. */ 
    10870  private int[] sizeZ, sizeC, sizeT; 
     
    11779  private int series; 
    11880 
    119   private String[] originalOrder; 
    120  
    121   private String[] seriesBlocks; 
    122   private Vector<String[]> fileVector; 
    123   private Vector<String> seriesNames; 
    124   private boolean seriesInFile; 
    125  
    12681  private boolean noStitch; 
    12782 
    12883  private MetadataStore store; 
     84 
     85  private ExternalSeries[] externals; 
    12986 
    13087  // -- Constructors -- 
     
    176133    if (noStitch) return reader; 
    177134    int[] q = computeIndices(no); 
    178     int sno = seriesInFile ? 0 : getSeries(); 
    179135    int fno = q[0]; 
    180     IFormatReader r = readers[sno][fno]; 
    181     if (seriesInFile) r.setSeries(getSeries()); 
     136    return getReader(getSeries(), fno); 
     137  } 
     138 
     139  /** 
     140   * Gets the reader that should be used with the given series and image plane. 
     141   */ 
     142  public DimensionSwapper getReader(int series, int no) { 
     143    if (noStitch) return (DimensionSwapper) reader; 
     144    DimensionSwapper r = externals[getExternalSeries(series)].getReaders()[no]; 
     145    initReader(series, no); 
    182146    return r; 
    183147  } 
     
    202166   */ 
    203167  public int[] getAxisTypes() { 
    204     FormatTools.assertId(currentId, true, 2); 
    205     return ag[getSeries()].getAxisTypes(); 
     168    FormatTools.assertId(getCurrentFile(), true, 2); 
     169    return externals[getExternalSeries()].getAxisGuesser().getAxisTypes(); 
    206170  } 
    207171 
     
    217181   */ 
    218182  public void setAxisTypes(int[] axes) throws FormatException { 
    219     FormatTools.assertId(currentId, true, 2); 
    220     ag[getSeries()].setAxisTypes(axes); 
     183    FormatTools.assertId(getCurrentFile(), true, 2); 
     184    externals[getExternalSeries()].getAxisGuesser().setAxisTypes(axes); 
    221185    computeAxisLengths(); 
    222186  } 
     
    224188  /** Gets the file pattern object used to build the list of files. */ 
    225189  public FilePattern getFilePattern() { 
    226     FormatTools.assertId(currentId, true, 2); 
    227     return fp; 
     190    FormatTools.assertId(getCurrentFile(), true, 2); 
     191    return externals[getExternalSeries()].getFilePattern(); 
    228192  } 
    229193 
     
    233197   */ 
    234198  public AxisGuesser getAxisGuesser() { 
    235     FormatTools.assertId(currentId, true, 2); 
    236     return ag[getSeries()]; 
     199    FormatTools.assertId(getCurrentFile(), true, 2); 
     200    return externals[getExternalSeries()].getAxisGuesser(); 
     201  } 
     202 
     203  public FilePattern findPattern(String id) { 
     204    return new FilePattern(FilePattern.findPattern(id)); 
    237205  } 
    238206 
     
    241209   * stitcher. Takes both ID map entries and the patternIds flag into account. 
    242210   */ 
    243   public FilePattern findPattern(String id) { 
     211  public String[] findPatterns(String id) { 
    244212    if (!patternIds) { 
    245       // find the containing pattern 
     213      // find the containing patterns 
    246214      HashMap<String, Object> map = Location.getIdMap(); 
    247       String pattern = null; 
    248215      if (map.containsKey(id)) { 
    249216        // search ID map for pattern, rather than files on disk 
    250217        String[] idList = new String[map.size()]; 
    251218        map.keySet().toArray(idList); 
    252         pattern = FilePattern.findPattern(id, null, idList); 
     219        return FilePattern.findSeriesPatterns(id, null, idList); 
    253220      } 
    254221      else { 
    255222        // id is an unmapped file path; look to similar files on disk 
    256         pattern = FilePattern.findPattern(new Location(id)); 
    257       } 
    258       if (pattern != null) id = pattern; 
    259     } 
    260     return new FilePattern(id); 
    261   } 
    262  
    263   // -- IMetadataConfigurable API methods -- 
    264  
    265   /* (non-Javadoc) 
    266    * @see loci.formats.IMetadataConfigurable#getSupportedMetadataLevels() 
    267    */ 
    268   public Set<MetadataLevel> getSupportedMetadataLevels() { 
    269     return reader.getSupportedMetadataLevels(); 
    270   } 
    271  
    272   /* (non-Javadoc) 
    273    * @see loci.formats.IMetadataConfigurable#getMetadataOptions() 
    274    */ 
    275   public MetadataOptions getMetadataOptions() { 
    276     return reader.getMetadataOptions(); 
    277   } 
    278  
    279   /* (non-Javadoc) 
    280    * @see loci.formats.IMetadataConfigurable#setMetadataOptions(loci.formats.in.MetadataOptions) 
    281    */ 
    282   public void setMetadataOptions(MetadataOptions options) { 
    283     reader.setMetadataOptions(options); 
     223        return FilePattern.findSeriesPatterns(id); 
     224      } 
     225    } 
     226    patternIds = false; 
     227    String[] patterns = findPatterns(new FilePattern(id).getFiles()[0]); 
     228    patternIds = true; 
     229    return patterns; 
    284230  } 
    285231 
    286232  // -- IFormatReader API methods -- 
    287  
    288   /* @see IFormatReader#isThisType(String, boolean) */ 
    289   public boolean isThisType(String name, boolean open) { 
    290     return reader.isThisType(name, open); 
    291   } 
    292  
    293   /* @see IFormatReader#isThisType(byte[]) */ 
    294   public boolean isThisType(byte[] block) { 
    295     return reader.isThisType(block); 
    296   } 
    297  
    298   /* @see IFormatReader#isThisType(RandomAccessInputStream) */ 
    299   public boolean isThisType(RandomAccessInputStream stream) throws IOException { 
    300     return reader.isThisType(stream); 
    301   } 
    302233 
    303234  /* @see IFormatReader#getImageCount() */ 
    304235  public int getImageCount() { 
    305     FormatTools.assertId(currentId, true, 2); 
     236    FormatTools.assertId(getCurrentFile(), true, 2); 
    306237    return noStitch ? reader.getImageCount() : core[getSeries()].imageCount; 
    307238  } 
     
    309240  /* @see IFormatReader#isRGB() */ 
    310241  public boolean isRGB() { 
    311     FormatTools.assertId(currentId, true, 2); 
     242    FormatTools.assertId(getCurrentFile(), true, 2); 
    312243    return noStitch ? reader.isRGB() : core[getSeries()].rgb; 
    313244  } 
     
    315246  /* @see IFormatReader#getSizeX() */ 
    316247  public int getSizeX() { 
    317     FormatTools.assertId(currentId, true, 2); 
     248    FormatTools.assertId(getCurrentFile(), true, 2); 
    318249    return noStitch ? reader.getSizeX() : core[getSeries()].sizeX; 
    319250  } 
     
    321252  /* @see IFormatReader#getSizeY() */ 
    322253  public int getSizeY() { 
    323     FormatTools.assertId(currentId, true, 2); 
     254    FormatTools.assertId(getCurrentFile(), true, 2); 
    324255    return noStitch ? reader.getSizeY() : core[getSeries()].sizeY; 
    325256  } 
     
    327258  /* @see IFormatReader#getSizeZ() */ 
    328259  public int getSizeZ() { 
    329     FormatTools.assertId(currentId, true, 2); 
     260    FormatTools.assertId(getCurrentFile(), true, 2); 
    330261    return noStitch ? reader.getSizeZ() : core[getSeries()].sizeZ; 
    331262  } 
     
    333264  /* @see IFormatReader#getSizeC() */ 
    334265  public int getSizeC() { 
    335     FormatTools.assertId(currentId, true, 2); 
     266    FormatTools.assertId(getCurrentFile(), true, 2); 
    336267    return noStitch ? reader.getSizeC() : core[getSeries()].sizeC; 
    337268  } 
     
    339270  /* @see IFormatReader#getSizeT() */ 
    340271  public int getSizeT() { 
    341     FormatTools.assertId(currentId, true, 2); 
     272    FormatTools.assertId(getCurrentFile(), true, 2); 
    342273    return noStitch ? reader.getSizeT() : core[getSeries()].sizeT; 
    343274  } 
     
    345276  /* @see IFormatReader#getPixelType() */ 
    346277  public int getPixelType() { 
    347     FormatTools.assertId(currentId, true, 2); 
     278    FormatTools.assertId(getCurrentFile(), true, 2); 
    348279    return noStitch ? reader.getPixelType() : core[getSeries()].pixelType; 
    349280  } 
     
    351282  /* @see IFormatReader#getBitsPerPixel() */ 
    352283  public int getBitsPerPixel() { 
    353     FormatTools.assertId(currentId, true, 2); 
     284    FormatTools.assertId(getCurrentFile(), true, 2); 
    354285    return noStitch ? reader.getBitsPerPixel() : core[getSeries()].bitsPerPixel; 
    355   } 
    356  
    357   /* @see IFormatReader#getEffectiveSizeC() */ 
    358   public int getEffectiveSizeC() { 
    359     FormatTools.assertId(currentId, true, 2); 
    360     return getImageCount() / (getSizeZ() * getSizeT()); 
    361   } 
    362  
    363   /* @see IFormatReader#getRGBChannelCount() */ 
    364   public int getRGBChannelCount() { 
    365     FormatTools.assertId(currentId, true, 2); 
    366     return getSizeC() / getEffectiveSizeC(); 
    367286  } 
    368287 
    369288  /* @see IFormatReader#isIndexed() */ 
    370289  public boolean isIndexed() { 
    371     FormatTools.assertId(currentId, true, 2); 
     290    FormatTools.assertId(getCurrentFile(), true, 2); 
    372291    return noStitch ? reader.isIndexed() : core[getSeries()].indexed; 
    373292  } 
     
    375294  /* @see IFormatReader#isFalseColor() */ 
    376295  public boolean isFalseColor() { 
    377     FormatTools.assertId(currentId, true, 2); 
     296    FormatTools.assertId(getCurrentFile(), true, 2); 
    378297    return noStitch ? reader.isFalseColor() : core[getSeries()].falseColor; 
    379298  } 
     
    381300  /* @see IFormatReader#get8BitLookupTable() */ 
    382301  public byte[][] get8BitLookupTable() throws FormatException, IOException { 
    383     FormatTools.assertId(currentId, true, 2); 
     302    FormatTools.assertId(getCurrentFile(), true, 2); 
    384303    return noStitch ? reader.get8BitLookupTable() : 
    385       readers[seriesInFile ? 0 : getSeries()][0].get8BitLookupTable(); 
     304      getReader(getSeries(), 0).get8BitLookupTable(); 
    386305  } 
    387306 
    388307  /* @see IFormatReader#get16BitLookupTable() */ 
    389308  public short[][] get16BitLookupTable() throws FormatException, IOException { 
    390     FormatTools.assertId(currentId, true, 2); 
     309    FormatTools.assertId(getCurrentFile(), true, 2); 
    391310    return noStitch ? reader.get16BitLookupTable() : 
    392       readers[seriesInFile ? 0 : getSeries()][0].get16BitLookupTable(); 
     311      getReader(getSeries(), 0).get16BitLookupTable(); 
    393312  } 
    394313 
    395314  /* @see IFormatReader#getChannelDimLengths() */ 
    396315  public int[] getChannelDimLengths() { 
    397     FormatTools.assertId(currentId, true, 2); 
     316    FormatTools.assertId(getCurrentFile(), true, 2); 
    398317    if (noStitch) return reader.getChannelDimLengths(); 
    399318    if (core[getSeries()].cLengths == null) { 
     
    405324  /* @see IFormatReader#getChannelDimTypes() */ 
    406325  public String[] getChannelDimTypes() { 
    407     FormatTools.assertId(currentId, true, 2); 
     326    FormatTools.assertId(getCurrentFile(), true, 2); 
    408327    if (noStitch) return reader.getChannelDimTypes(); 
    409328    if (core[getSeries()].cTypes == null) { 
     
    415334  /* @see IFormatReader#getThumbSizeX() */ 
    416335  public int getThumbSizeX() { 
    417     FormatTools.assertId(currentId, true, 2); 
     336    FormatTools.assertId(getCurrentFile(), true, 2); 
    418337    return noStitch ? reader.getThumbSizeX() : 
    419       readers[seriesInFile ? 0 : getSeries()][0].getThumbSizeX(); 
     338      getReader(getSeries(), 0).getThumbSizeX(); 
    420339  } 
    421340 
    422341  /* @see IFormatReader#getThumbSizeY() */ 
    423342  public int getThumbSizeY() { 
    424     FormatTools.assertId(currentId, true, 2); 
     343    FormatTools.assertId(getCurrentFile(), true, 2); 
    425344    return noStitch ? reader.getThumbSizeY() : 
    426       readers[seriesInFile ? 0 : getSeries()][0].getThumbSizeY(); 
     345      getReader(getSeries(), 0).getThumbSizeY(); 
    427346  } 
    428347 
    429348  /* @see IFormatReader#isLittleEndian() */ 
    430349  public boolean isLittleEndian() { 
    431     FormatTools.assertId(currentId, true, 2); 
     350    FormatTools.assertId(getCurrentFile(), true, 2); 
    432351    return noStitch ? reader.isLittleEndian() : 
    433       readers[seriesInFile ? 0 : getSeries()][0].isLittleEndian(); 
     352      getReader(getSeries(), 0).isLittleEndian(); 
    434353  } 
    435354 
    436355  /* @see IFormatReader#getDimensionOrder() */ 
    437356  public String getDimensionOrder() { 
    438     FormatTools.assertId(currentId, true, 2); 
     357    FormatTools.assertId(getCurrentFile(), true, 2); 
    439358    if (noStitch) return reader.getDimensionOrder(); 
    440     return originalOrder[getSeries()]; 
     359    return core[getSeries()].dimensionOrder; 
    441360  } 
    442361 
    443362  /* @see IFormatReader#isOrderCertain() */ 
    444363  public boolean isOrderCertain() { 
    445     FormatTools.assertId(currentId, true, 2); 
     364    FormatTools.assertId(getCurrentFile(), true, 2); 
    446365    return noStitch ? reader.isOrderCertain() : core[getSeries()].orderCertain; 
    447366  } 
     
    449368  /* @see IFormatReader#isThumbnailSeries() */ 
    450369  public boolean isThumbnailSeries() { 
    451     FormatTools.assertId(currentId, true, 2); 
     370    FormatTools.assertId(getCurrentFile(), true, 2); 
    452371    return noStitch ? reader.isThumbnailSeries() : core[getSeries()].thumbnail; 
    453372  } 
     
    455374  /* @see IFormatReader#isInterleaved() */ 
    456375  public boolean isInterleaved() { 
    457     FormatTools.assertId(currentId, true, 2); 
     376    FormatTools.assertId(getCurrentFile(), true, 2); 
    458377    return noStitch ? reader.isInterleaved() : 
    459       readers[seriesInFile ? 0 : getSeries()][0].isInterleaved(); 
     378      getReader(getSeries(), 0).isInterleaved(); 
    460379  } 
    461380 
    462381  /* @see IFormatReader#isInterleaved(int) */ 
    463382  public boolean isInterleaved(int subC) { 
    464     FormatTools.assertId(currentId, true, 2); 
     383    FormatTools.assertId(getCurrentFile(), true, 2); 
    465384    return noStitch ? reader.isInterleaved(subC) : 
    466       readers[seriesInFile ? 0 : getSeries()][0].isInterleaved(subC); 
     385      getReader(getSeries(), 0).isInterleaved(subC); 
    467386  } 
    468387 
     
    483402    throws FormatException, IOException 
    484403  { 
    485     FormatTools.assertId(currentId, true, 2); 
    486     IFormatReader r = getReader(no); 
    487     int ino = getAdjustedIndex(no); 
    488     if (ino < r.getImageCount()) return r.openBytes(ino, x, y, w, h); 
    489  
    490     // return a blank image to cover for the fact that 
    491     // this file does not contain enough image planes 
    492     int sno = getSeries(); 
    493     if (blankBytes[sno] == null) { 
    494       int bytes = FormatTools.getBytesPerPixel(getPixelType()); 
    495       blankBytes[sno] = new byte[w * h * bytes * getRGBChannelCount()]; 
    496     } 
    497     return blankBytes[sno]; 
     404    int bpp = FormatTools.getBytesPerPixel(getPixelType()); 
     405    int ch = getRGBChannelCount(); 
     406    byte[] buf = new byte[w * h * ch * bpp]; 
     407    return openBytes(no, buf, x, y, w, h); 
    498408  } 
    499409 
     
    502412    throws FormatException, IOException 
    503413  { 
    504     FormatTools.assertId(currentId, true, 2); 
    505  
    506     IFormatReader r = getReader(no); 
    507     int ino = getAdjustedIndex(no); 
     414    FormatTools.assertId(getCurrentFile(), true, 2); 
     415 
     416    int[] pos = computeIndices(no); 
     417    IFormatReader r = getReader(getSeries(), pos[0]); 
     418    int ino = pos[1]; 
     419 
    508420    if (ino < r.getImageCount()) return r.openBytes(ino, buf, x, y, w, h); 
    509421 
     
    518430    throws FormatException, IOException 
    519431  { 
    520     FormatTools.assertId(currentId, true, 2); 
     432    FormatTools.assertId(getCurrentFile(), true, 2); 
    521433 
    522434    IFormatReader r = getReader(no); 
     
    529441  /* @see IFormatReader#openThumbBytes(int) */ 
    530442  public byte[] openThumbBytes(int no) throws FormatException, IOException { 
    531     FormatTools.assertId(currentId, true, 2); 
     443    FormatTools.assertId(getCurrentFile(), true, 2); 
    532444 
    533445    IFormatReader r = getReader(no); 
     
    537449    // return a blank image to cover for the fact that 
    538450    // this file does not contain enough image planes 
    539     int sno = getSeries(); 
    540     if (blankThumbBytes[sno] == null) { 
    541       int bytes = FormatTools.getBytesPerPixel(getPixelType()); 
    542       blankThumbBytes[sno] = new byte[getThumbSizeX() * getThumbSizeY() * 
    543         bytes * getRGBChannelCount()]; 
    544     } 
    545     return blankThumbBytes[sno]; 
     451    return externals[getExternalSeries()].getBlankThumbBytes(); 
     452  } 
     453 
     454  /* @see IFormatReader#close() */ 
     455  public void close() throws IOException { 
     456    close(false); 
    546457  } 
    547458 
    548459  /* @see IFormatReader#close(boolean) */ 
    549460  public void close(boolean fileOnly) throws IOException { 
    550     if (readers == null) reader.close(fileOnly); 
    551     else { 
    552       for (int i=0; i<readers.length; i++) { 
    553         for (int j=0; j<readers[i].length; j++) { 
    554           if (readers[i][j] != null) readers[i][j].close(fileOnly); 
     461    if (reader != null) reader.close(fileOnly); 
     462    if (externals != null) { 
     463      for (ExternalSeries s : externals) { 
     464        for (DimensionSwapper r : s.getReaders()) { 
     465          if (r != null) r.close(fileOnly); 
    555466        } 
    556467      } 
     
    558469    if (!fileOnly) { 
    559470      noStitch = false; 
    560       readers = null; 
    561       blankBytes = null; 
    562       currentId = null; 
    563       fp = null; 
    564       ag = null; 
    565       files = null; 
    566       usedFiles = null; 
    567       blankThumbBytes = null; 
    568       imagesPerFile = null; 
     471      externals = null; 
    569472      sizeZ = sizeC = sizeT = null; 
    570473      lenZ = lenC = lenT = null; 
    571474      core = null; 
    572475      series = 0; 
    573       seriesBlocks = null; 
    574       fileVector = null; 
    575       seriesNames = null; 
    576       seriesInFile = false; 
    577476      store = null; 
    578       originalOrder = null; 
    579477    } 
    580478  } 
     
    582480  /* @see IFormatReader#getSeriesCount() */ 
    583481  public int getSeriesCount() { 
    584     FormatTools.assertId(currentId, true, 2); 
     482    FormatTools.assertId(getCurrentFile(), true, 2); 
    585483    return noStitch ? reader.getSeriesCount() : core.length; 
    586484  } 
     
    588486  /* @see IFormatReader#setSeries(int) */ 
    589487  public void setSeries(int no) { 
    590     FormatTools.assertId(currentId, true, 2); 
     488    FormatTools.assertId(getCurrentFile(), true, 2); 
    591489    int n = reader.getSeriesCount(); 
    592490    if (n > 1) reader.setSeries(no); 
     
    596494  /* @see IFormatReader#getSeries() */ 
    597495  public int getSeries() { 
    598     FormatTools.assertId(currentId, true, 2); 
    599     return seriesInFile || noStitch ? reader.getSeries() : series; 
     496    FormatTools.assertId(getCurrentFile(), true, 2); 
     497    return reader.getSeries() > 0 ? reader.getSeries() : series; 
    600498  } 
    601499 
     
    603501  public void setGroupFiles(boolean group) { 
    604502    reader.setGroupFiles(group); 
    605     if (readers != null) { 
    606       for (int i=0; i<readers.length; i++) { 
    607         for (int j=0; j<readers[i].length; j++) { 
    608           readers[i][j].setGroupFiles(group); 
     503    if (externals != null) { 
     504      for (ExternalSeries s : externals) { 
     505        for (DimensionSwapper r : s.getReaders()) { 
     506          r.setGroupFiles(group); 
    609507        } 
    610508      } 
    611509    } 
    612   } 
    613  
    614   /* @see IFormatReader#isGroupFiles() */ 
    615   public boolean isGroupFiles() { 
    616     return reader.isGroupFiles(); 
    617   } 
    618  
    619   /* @see IFormatReader#fileGroupOption(String) */ 
    620   public int fileGroupOption(String id) throws FormatException, IOException { 
    621     return reader.fileGroupOption(id); 
    622   } 
    623  
    624   /* @see IFormatReader#isMetadataComplete() */ 
    625   public boolean isMetadataComplete() { 
    626     return reader.isMetadataComplete(); 
    627510  } 
    628511 
    629512  /* @see IFormatReader#setNormalized(boolean) */ 
    630513  public void setNormalized(boolean normalize) { 
    631     FormatTools.assertId(currentId, false, 2); 
    632     if (readers == null) reader.setNormalized(normalize); 
     514    FormatTools.assertId(getCurrentFile(), false, 2); 
     515    if (externals == null) reader.setNormalized(normalize); 
    633516    else { 
    634       for (int i=0; i<readers.length; i++) { 
    635         for (int j=0; j<readers[i].length; j++) { 
    636           readers[i][j].setNormalized(normalize); 
     517      for (ExternalSeries s : externals) { 
     518        for (DimensionSwapper r : s.getReaders()) { 
     519          r.setNormalized(normalize); 
    637520        } 
    638521      } 
    639522    } 
    640523  } 
    641  
    642   /* @see IFormatReader#isNormalized() */ 
    643   public boolean isNormalized() { return reader.isNormalized(); } 
    644524 
    645525  /** 
     
    648528   */ 
    649529  public void setMetadataCollected(boolean collect) { 
    650     FormatTools.assertId(currentId, false, 2); 
    651     if (readers == null) reader.setMetadataCollected(collect); 
     530    FormatTools.assertId(getCurrentFile(), false, 2); 
     531    if (externals == null) reader.setMetadataCollected(collect); 
    652532    else { 
    653       for (int i=0; i<readers.length; i++) { 
    654         for (int j=0; j<readers[i].length; j++) { 
    655           readers[i][j].setMetadataCollected(collect); 
     533      for (ExternalSeries s : externals) { 
     534        for (DimensionSwapper r : s.getReaders()) { 
     535          r.setMetadataCollected(collect); 
    656536        } 
    657537      } 
    658538    } 
    659   } 
    660  
    661   /** 
    662    * @deprecated 
    663    * @see IFormatReader#isMetadataCollected() 
    664    */ 
    665   public boolean isMetadataCollected() { 
    666     return reader.isMetadataCollected(); 
    667539  } 
    668540 
    669541  /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */ 
    670542  public void setOriginalMetadataPopulated(boolean populate) { 
    671     FormatTools.assertId(currentId, false, 1); 
    672     if (readers == null) reader.setOriginalMetadataPopulated(populate); 
     543    FormatTools.assertId(getCurrentFile(), false, 1); 
     544    if (externals == null) reader.setOriginalMetadataPopulated(populate); 
    673545    else { 
    674       for (int i=0; i<readers.length; i++) { 
    675         for (int j=0; j<readers[i].length; j++) { 
    676           readers[i][j].setOriginalMetadataPopulated(populate); 
     546      for (ExternalSeries s : externals) { 
     547        for (DimensionSwapper r : s.getReaders()) { 
     548          r.setOriginalMetadataPopulated(populate); 
    677549        } 
    678550      } 
    679551    } 
    680   } 
    681  
    682   /* @see IFormatReader#isOriginalMetadataPopulated() */ 
    683   public boolean isOriginalMetadataPopulated() { 
    684     return reader.isOriginalMetadataPopulated(); 
    685552  } 
    686553 
    687554  /* @see IFormatReader#getUsedFiles() */ 
    688555  public String[] getUsedFiles() { 
    689     FormatTools.assertId(currentId, true, 2); 
     556    FormatTools.assertId(getCurrentFile(), true, 2); 
    690557 
    691558    if (noStitch) return reader.getUsedFiles(); 
     
    695562    // when each constituent file does not itself have multiple used files 
    696563 
    697     if (reader.getUsedFiles().length > 1) { 
    698       // each constituent file has multiple used files; we must build the list 
    699       // this could happen with, e.g., a stitched collection of ICS/IDS pairs 
    700       // we have no datasets structured this way, so this logic is untested 
    701       if (usedFiles == null) { 
    702         String[][][] used = new String[files.length][][]; 
    703         int total = 0; 
    704         for (int i=0; i<files.length; i++) { 
    705           used[i] = new String[files[i].length][]; 
    706           for (int j=0; j<files[i].length; j++) { 
    707             try { 
    708               initReader(i, j); 
    709             } 
    710             catch (FormatException exc) { 
    711               LOGGER.info("", exc); 
    712               return null; 
    713             } 
    714             catch (IOException exc) { 
    715               LOGGER.info("", exc); 
    716               return null; 
    717             } 
    718             used[i][j] = readers[i][j].getUsedFiles(); 
    719             total += used[i][j].length; 
    720           } 
    721         } 
    722         usedFiles = new String[total]; 
    723         for (int i=0, off=0; i<used.length; i++) { 
    724           for (int j=0; j<used[i].length; j++) { 
    725             System.arraycopy(used[i][j], 0, usedFiles, off, used[i][j].length); 
    726             off += used[i][j].length; 
    727           } 
    728         } 
    729       } 
    730       return usedFiles; 
    731     } 
    732     // assume every constituent file has no other used files 
    733     // this logic could fail if the first constituent has no extra used files, 
    734     // but later constituents do; in practice, this scenario seems unlikely 
    735     Vector<String> v = new Vector<String>(); 
    736     for (int i=0; i<files.length; i++) { 
    737       for (int j=0; j<files[i].length; j++) { 
    738         v.add(files[i][j]); 
    739       } 
    740     } 
    741     return v.toArray(new String[0]); 
     564    Vector<String> files = new Vector<String>(); 
     565    for (ExternalSeries s : externals) { 
     566      String[] f = s.getFiles(); 
     567      for (String file : f) { 
     568        if (!files.contains(file)) files.add(file); 
     569      } 
     570    } 
     571    return files.toArray(new String[files.size()]); 
    742572  } 
    743573 
     
    760590  /* @see IFormatReader#getAdvancedUsedFiles(boolean) */ 
    761591  public FileInfo[] getAdvancedUsedFiles(boolean noPixels) { 
     592    if (noStitch) return reader.getAdvancedUsedFiles(noPixels); 
    762593    String[] files = getUsedFiles(noPixels); 
    763594    if (files == null) return null; 
     
    767598      infos[i].filename = files[i]; 
    768599      try { 
    769         infos[i].reader = reader.unwrap().getClass(); 
     600        infos[i].reader = ((DimensionSwapper) reader).unwrap().getClass(); 
    770601      } 
    771602      catch (FormatException e) { 
     
    782613  /* @see IFormatReader#getAdvancedSeriesUsedFiles(boolean) */ 
    783614  public FileInfo[] getAdvancedSeriesUsedFiles(boolean noPixels) { 
     615    if (noStitch) return reader.getAdvancedSeriesUsedFiles(noPixels); 
    784616    String[] files = getSeriesUsedFiles(noPixels); 
    785617    if (files == null) return null; 
     
    789621      infos[i].filename = files[i]; 
    790622      try { 
    791         infos[i].reader = reader.unwrap().getClass(); 
     623        infos[i].reader = ((DimensionSwapper) reader).unwrap().getClass(); 
    792624      } 
    793625      catch (FormatException e) { 
     
    802634  } 
    803635 
    804   /* @see IFormatReader#getCurrentFile() */ 
    805   public String getCurrentFile() { return currentId; } 
    806  
    807636  /* @see IFormatReader#getIndex(int, int, int) */ 
    808637  public int getIndex(int z, int c, int t) { 
    809     FormatTools.assertId(currentId, true, 2); 
     638    FormatTools.assertId(getCurrentFile(), true, 2); 
    810639    return FormatTools.getIndex(this, z, c, t); 
    811640  } 
     
    813642  /* @see IFormatReader#getZCTCoords(int) */ 
    814643  public int[] getZCTCoords(int index) { 
    815     FormatTools.assertId(currentId, true, 2); 
     644    FormatTools.assertId(getCurrentFile(), true, 2); 
    816645    return noStitch ? reader.getZCTCoords(index) : 
    817646      FormatTools.getZCTCoords(core[getSeries()].dimensionOrder, 
     
    819648  } 
    820649 
    821   /* @see IFormatReader#getMetadataValue(String) */ 
    822   public Object getMetadataValue(String field) { 
    823     FormatTools.assertId(currentId, true, 2); 
    824     return reader.getMetadataValue(field); 
    825   } 
    826  
    827   /* @see IFormatReader#getSeriesMetadataValue(String) */ 
    828   public Object getSeriesMetadataValue(String field) { 
    829     FormatTools.assertId(currentId, true, 2); 
    830     return reader.getSeriesMetadataValue(field); 
    831   } 
    832  
    833   /* @see IFormatReader#getGlobalMetadata() */ 
    834   public Hashtable<String, Object> getGlobalMetadata() { 
    835     FormatTools.assertId(currentId, true, 2); 
    836     return reader.getGlobalMetadata(); 
    837   } 
    838  
    839650  /* @see IFormatReader#getSeriesMetadata() */ 
    840651  public Hashtable<String, Object> getSeriesMetadata() { 
    841     FormatTools.assertId(currentId, true, 2); 
    842     return reader.getSeriesMetadata(); 
    843   } 
    844  
    845   /** @deprecated */ 
    846   public Hashtable<String, Object> getMetadata() { 
    847     FormatTools.assertId(currentId, true ,2); 
    848     return reader.getMetadata(); 
     652    FormatTools.assertId(getCurrentFile(), true, 2); 
     653    return noStitch ? reader.getSeriesMetadata() : 
     654      core[getSeries()].seriesMetadata; 
    849655  } 
    850656 
    851657  /* @see IFormatReader#getCoreMetadata() */ 
    852658  public CoreMetadata[] getCoreMetadata() { 
    853     FormatTools.assertId(currentId, true, 2); 
     659    FormatTools.assertId(getCurrentFile(), true, 2); 
    854660    return noStitch ? reader.getCoreMetadata() : core; 
    855   } 
    856  
    857   /* @see IFormatReader#setMetadataFiltered(boolean) */ 
    858   public void setMetadataFiltered(boolean filter) { 
    859     FormatTools.assertId(currentId, false, 2); 
    860     reader.setMetadataFiltered(filter); 
    861   } 
    862  
    863   /* @see IFormatReader#isMetadataFiltered() */ 
    864   public boolean isMetadataFiltered() { 
    865     return reader.isMetadataFiltered(); 
    866661  } 
    867662 
    868663  /* @see IFormatReader#setMetadataStore(MetadataStore) */ 
    869664  public void setMetadataStore(MetadataStore store) { 
    870     FormatTools.assertId(currentId, false, 2); 
     665    FormatTools.assertId(getCurrentFile(), false, 2); 
    871666    reader.setMetadataStore(store); 
     667    this.store = store; 
    872668  } 
    873669 
    874670  /* @see IFormatReader#getMetadataStore() */ 
    875671  public MetadataStore getMetadataStore() { 
    876     FormatTools.assertId(currentId, true, 2); 
     672    FormatTools.assertId(getCurrentFile(), true, 2); 
    877673    return noStitch ? reader.getMetadataStore() : store; 
    878674  } 
     
    880676  /* @see IFormatReader#getMetadataStoreRoot() */ 
    881677  public Object getMetadataStoreRoot() { 
    882     FormatTools.assertId(currentId, true, 2); 
     678    FormatTools.assertId(getCurrentFile(), true, 2); 
    883679    return noStitch ? reader.getMetadataStoreRoot() : store.getRoot(); 
    884680  } 
     
    887683  public IFormatReader[] getUnderlyingReaders() { 
    888684    List<IFormatReader> list = new ArrayList<IFormatReader>(); 
    889     for (int i=0; i<readers.length; i++) { 
    890       for (int j=0; j<readers[i].length; j++) { 
    891         list.add(readers[i][j]); 
     685    for (ExternalSeries s : externals) { 
     686      for (DimensionSwapper r : s.getReaders()) { 
     687        list.add(r); 
    892688      } 
    893689    } 
     
    895691  } 
    896692 
    897   /* @see IFormatReader#isSingleFile(String) */ 
    898   public boolean isSingleFile(String id) throws FormatException, IOException { 
    899     return reader.isSingleFile(id); 
    900   } 
    901  
    902   /* @see IFormatReader#hasCompanionFiles() */ 
    903   public boolean hasCompanionFiles() { 
    904     return reader.hasCompanionFiles(); 
    905   } 
    906  
    907   /* @see IFormatReader#getPossibleDomains(String) */ 
    908   public String[] getPossibleDomains(String id) 
    909     throws FormatException, IOException 
    910   { 
    911     return reader.getPossibleDomains(id); 
    912   } 
    913  
    914   /* @see IFormatReader#getDomains(String) */ 
    915   public String[] getDomains() { 
    916     return reader.getDomains(); 
    917   } 
    918  
    919   // -- IFormatHandler API methods -- 
    920  
    921   /* @see IFormatHandler#isThisType(String) */ 
    922   public boolean isThisType(String name) { 
    923     return reader.isThisType(name); 
    924   } 
    925  
    926   /* @see IFormatHandler#getFormat() */ 
    927   public String getFormat() { 
    928     FormatTools.assertId(currentId, true, 2); 
    929     return reader.getFormat(); 
    930   } 
    931  
    932   /* @see IFormatHandler#getSuffixes() */ 
    933   public String[] getSuffixes() { 
    934     return reader.getSuffixes(); 
    935   } 
    936  
    937   /* @see IFormatHandler#getNativeDataType() */ 
    938   public Class<?> getNativeDataType() { 
    939     FormatTools.assertId(currentId, true, 2); 
    940     return reader.getNativeDataType(); 
    941   } 
    942  
    943   /* @see IFormatHandler#setId(String) */ 
     693  /* @see IFormatReader#setId(String) */ 
    944694  public void setId(String id) throws FormatException, IOException { 
    945     if (!id.equals(currentId)) initFile(id); 
    946   } 
    947  
    948   /* @see IFormatHandler#close() */ 
    949   public void close() throws IOException { close(false); } 
     695    close(); 
     696    initFile(id); 
     697  } 
    950698 
    951699  // -- Internal FormatReader API methods -- 
     
    955703    LOGGER.debug("initFile: {}", id); 
    956704 
    957     close(); 
    958     currentId = id; 
    959  
    960     fp = findPattern(currentId); 
     705    String[] patterns = findPatterns(id); 
     706    externals = new ExternalSeries[patterns.length]; 
     707 
     708    for (int i=0; i<externals.length; i++) { 
     709      externals[i] = new ExternalSeries(new FilePattern(patterns[i])); 
     710    } 
     711 
     712    FilePattern fp = new FilePattern(patterns[0]); 
    961713 
    962714    boolean mustGroup = false; 
     
    975727        reader.setId(fp.getFiles()[0]); 
    976728      } 
    977       else reader.setId(currentId); 
     729      else reader.setId(id); 
    978730      return; 
    979731    } 
     732    reader.close(); 
     733    reader.setGroupFiles(false); 
    980734 
    981735    if (!fp.isValid()) { 
     
    983737    } 
    984738    reader.setId(fp.getFiles()[0]); 
     739 
     740    String msg = " Please rename your files or disable file stitching."; 
     741    if (reader.getSeriesCount() > 1 && 
     742      (externals.length > 1 || fp.getFiles().length > 1)) 
     743    { 
     744      throw new FormatException("Unsupported grouping: File pattern contains " + 
     745        "multiple files and each file contains multiple series." + msg); 
     746    } 
     747 
    985748    if (reader.getUsedFiles().length > 1) { 
    986749      noStitch = true; 
     
    993756 
    994757    // use the dimension order recommended by the axis guesser 
    995     reader.swapDimensions(guesser.getAdjustedOrder()); 
     758    ((DimensionSwapper) reader).swapDimensions(guesser.getAdjustedOrder()); 
    996759 
    997760    // if this is a multi-series dataset, we need some special logic 
    998     int seriesCount = reader.getSeriesCount(); 
    999     seriesInFile = true; 
    1000     if (guesser.getAxisCountS() > 0) { 
    1001       int[] axes = guesser.getAxisTypes(); 
    1002  
    1003       seriesInFile = false; 
    1004  
    1005       String[] blockPrefixes = fp.getPrefixes(); 
    1006       Vector<String> sBlock = new Vector<String>(); 
    1007  
    1008       for (int i=0; i<axes.length; i++) { 
    1009         if (axes[i] == AxisGuesser.S_AXIS) sBlock.add(blockPrefixes[i]); 
    1010       } 
    1011  
    1012       seriesBlocks = sBlock.toArray(new String[0]); 
    1013       fileVector = new Vector<String[]>(); 
    1014       seriesNames = new Vector<String>(); 
    1015  
    1016       String file = fp.getFiles()[0]; 
    1017       Location dir = new Location(file).getAbsoluteFile().getParentFile(); 
    1018       String dpath = dir.getAbsolutePath(); 
    1019       String[] fs = dir.list(); 
    1020  
    1021       String ext = ""; 
    1022       if (file.indexOf(".") != -1) { 
    1023         ext = file.substring(file.lastIndexOf(".") + 1); 
    1024       } 
    1025  
    1026       Vector<String> tmpFiles = new Vector<String>(); 
    1027       for (int i=0; i<fs.length; i++) { 
    1028         if (fs[i].endsWith(ext)) tmpFiles.add(fs[i]); 
    1029       } 
    1030  
    1031       setFiles(tmpFiles.toArray(new String[0]), seriesBlocks[0], 
    1032         fp.getFirst()[0], fp.getLast()[0], fp.getStep()[0], dpath, 0); 
    1033  
    1034       seriesCount = fileVector.size(); 
    1035       files = new String[seriesCount][]; 
    1036  
    1037       for (int i=0; i<seriesCount; i++) { 
    1038         files[i] = fileVector.get(i); 
    1039       } 
     761    int seriesCount = externals.length; 
     762    if (externals.length == 1) { 
     763      seriesCount = reader.getSeriesCount(); 
    1040764    } 
    1041765 
    1042766    // verify that file pattern is valid and matches existing files 
    1043     String msg = " Please rename your files or disable file stitching."; 
    1044767    if (!fp.isValid()) { 
    1045768      throw new FormatException("Invalid " + 
    1046769        (patternIds ? "file pattern" : "filename") + 
    1047         " (" + currentId + "): " + fp.getErrorMessage() + msg); 
    1048     } 
    1049     if (files == null) { 
    1050       files = new String[1][]; 
    1051       files[0] = fp.getFiles(); 
    1052     } 
     770        " (" + id + "): " + fp.getErrorMessage() + msg); 
     771    } 
     772    String[] files = fp.getFiles(); 
    1053773 
    1054774    if (files == null) { 
     
    1056776        fp.getPattern() + "). " + msg); 
    1057777    } 
     778 
    1058779    for (int i=0; i<files.length; i++) { 
    1059       for (int j=0; j<files[i].length; j++) { 
    1060         String file = files[i][j]; 
    1061  
    1062         // HACK: skip file existence check for fake files 
    1063         if (file.toLowerCase().endsWith(".fake")) continue; 
    1064  
    1065         if (!new Location(file).exists()) { 
    1066           throw new FormatException("File #" + i + 
    1067             " (" + file + ") does not exist."); 
    1068         } 
     780      String file = files[i]; 
     781 
     782      // HACK: skip file existence check for fake files 
     783      if (file.toLowerCase().endsWith(".fake")) continue; 
     784 
     785      if (!new Location(file).exists()) { 
     786        throw new FormatException("File #" + i + 
     787          " (" + file + ") does not exist."); 
    1069788      } 
    1070789    } 
     
    1072791    // determine reader type for these files; assume all are the same type 
    1073792    Class<? extends IFormatReader> readerClass = 
    1074       reader.unwrap(files[0][0]).getClass(); 
    1075  
    1076     // construct list of readers for all files 
    1077     readers = new DimensionSwapper[files.length][]; 
    1078     for (int i=0; i<readers.length; i++) { 
    1079       readers[i] = new DimensionSwapper[files[i].length]; 
    1080       for (int j=0; j<readers[i].length; j++) { 
    1081         readers[i][j] = i == 0 && j == 0 ? reader : 
    1082           (DimensionSwapper) reader.duplicate(readerClass); 
    1083       } 
    1084     } 
    1085  
    1086     ag = new AxisGuesser[seriesCount]; 
    1087     blankBytes = new byte[seriesCount][]; 
    1088     blankThumbBytes = new byte[seriesCount][]; 
    1089     imagesPerFile = new int[seriesCount]; 
     793      ((DimensionSwapper) reader).unwrap(files[0]).getClass(); 
     794 
    1090795    sizeZ = new int[seriesCount]; 
    1091796    sizeC = new int[seriesCount]; 
     
    1096801    lenT = new int[seriesCount][]; 
    1097802 
    1098     originalOrder = new String[seriesCount]; 
    1099  
    1100803    // analyze first file; assume each file has the same parameters 
    1101804    core = new CoreMetadata[seriesCount]; 
    1102     int oldSeries = reader.getSeries(); 
    1103     IFormatReader rr = reader; 
     805    int oldSeries = getSeries(); 
    1104806    for (int i=0; i<seriesCount; i++) { 
    1105       if (seriesInFile) rr.setSeries(i); 
    1106       else { 
    1107         initReader(i, 0); 
    1108         rr = readers[i][0]; 
    1109       } 
     807      IFormatReader rr = getReader(i, 0); 
    1110808 
    1111809      core[i] = new CoreMetadata(); 
     
    1117815      // NB: core.sizeT populated in computeAxisLengths below 
    1118816      core[i].pixelType = rr.getPixelType(); 
    1119       imagesPerFile[i] = rr.getImageCount(); 
    1120       core[i].imageCount = 
    1121         imagesPerFile[i] * files[seriesInFile ? 0 : i].length; 
     817 
     818      ExternalSeries external = externals[getExternalSeries(i)]; 
     819      core[i].imageCount = rr.getImageCount() * external.getFiles().length; 
    1122820      core[i].thumbSizeX = rr.getThumbSizeX(); 
    1123821      core[i].thumbSizeY = rr.getThumbSizeY(); 
     
    1125823      // NB: core.cTypes[i] populated in computeAxisLengths below 
    1126824      core[i].dimensionOrder = rr.getDimensionOrder(); 
    1127       originalOrder[i] = rr.getDimensionOrder(); 
    1128825      // NB: core.orderCertain[i] populated below 
    1129826      core[i].rgb = rr.isRGB(); 
     
    1138835      certain[i] = rr.isOrderCertain(); 
    1139836    } 
    1140     reader.setSeries(oldSeries); 
    1141  
    1142     // guess at dimensions corresponding to file numbering 
    1143     for (int i=0; i<seriesCount; i++) { 
    1144       ag[i] = new AxisGuesser(fp, core[i].dimensionOrder, 
    1145         sizeZ[i], sizeT[i], sizeC[i], certain[i]); 
    1146     } 
    1147837 
    1148838    // order may need to be adjusted 
    1149839    for (int i=0; i<seriesCount; i++) { 
    1150840      setSeries(i); 
    1151       core[i].dimensionOrder = ag[i].getAdjustedOrder(); 
    1152       core[i].orderCertain = ag[i].isCertain(); 
     841      AxisGuesser ag = externals[getExternalSeries()].getAxisGuesser(); 
     842      core[i].dimensionOrder = ag.getAdjustedOrder(); 
     843      core[i].orderCertain = ag.isCertain(); 
    1153844      computeAxisLengths(); 
    1154845    } 
     
    1158849    store = reader.getMetadataStore(); 
    1159850    // don't overwrite pixel info if files aren't actually grouped 
    1160     if (!seriesInFile || files.length > 1 || files[0].length > 1) { 
     851    if (!noStitch && externals.length > 1) { 
    1161852      MetadataTools.populatePixels(store, this); 
    1162     } 
    1163     for (int i=0; i<core.length; i++) { 
    1164       if (seriesNames != null && !seriesInFile) { 
    1165         store.setImageName(seriesNames.get(i), i); 
     853      for (int i=0; i<getSeriesCount(); i++) { 
     854        store.setImageName(externals[i].getFilePattern().getPattern(), i); 
    1166855      } 
    1167856    } 
     
    1169858 
    1170859  // -- Helper methods -- 
     860 
     861  private int getExternalSeries() { 
     862    return getExternalSeries(getSeries()); 
     863  } 
     864 
     865  private int getExternalSeries(int currentSeries) { 
     866    if (reader.getSeriesCount() > 1) return 0; 
     867    return currentSeries; 
     868  } 
    1171869 
    1172870  /** Computes axis length arrays, and total axis lengths. */ 
    1173871  protected void computeAxisLengths() throws FormatException { 
    1174     int sno = seriesInFile ? 0 : getSeries(); 
    1175  
    1176     FilePattern p = new FilePattern(FilePattern.findPattern(files[sno][0], 
    1177       new Location(files[sno][0]).getAbsoluteFile().getParentFile().getPath(), 
    1178       files[sno])); 
     872    int sno = getSeries(); 
     873    ExternalSeries s = externals[getExternalSeries()]; 
     874    FilePattern p = s.getFilePattern(); 
    1179875 
    1180876    int[] count = p.getCount(); 
    1181877 
    1182     try { 
    1183       initReader(sno, 0); 
    1184     } 
    1185     catch (IOException e) { 
    1186       throw new FormatException(e); 
    1187     } 
    1188  
    1189     ag[getSeries()] = new AxisGuesser(p, readers[sno][0].getDimensionOrder(), 
    1190       readers[sno][0].getSizeZ(), readers[sno][0].getSizeT(), 
    1191       readers[sno][0].getSizeC(), readers[sno][0].isOrderCertain()); 
    1192     sno = getSeries(); 
    1193     int[] axes = ag[sno].getAxisTypes(); 
    1194     int numZ = ag[sno].getAxisCountZ(); 
    1195     int numC = ag[sno].getAxisCountC(); 
    1196     int numT = ag[sno].getAxisCountT(); 
     878    initReader(sno, 0); 
     879 
     880    AxisGuesser ag = s.getAxisGuesser(); 
     881    int[] axes = ag.getAxisTypes(); 
     882    int numZ = ag.getAxisCountZ(); 
     883    int numC = ag.getAxisCountC(); 
     884    int numT = ag.getAxisCountT(); 
    1197885 
    1198886    core[sno].sizeZ = sizeZ[sno]; 
     
    1266954   */ 
    1267955  protected int[] computeIndices(int no) throws FormatException, IOException { 
     956    if (noStitch) return new int[] {0, no}; 
    1268957    int sno = getSeries(); 
    1269  
    1270     int[] axes = ag[sno].getAxisTypes(); 
    1271     int[] count = fp.getCount(); 
     958    ExternalSeries s = externals[getExternalSeries()]; 
     959 
     960    int[] axes = s.getAxisGuesser().getAxisTypes(); 
     961    int[] count = s.getFilePattern().getCount(); 
    1272962 
    1273963    // get Z, C and T positions 
     
    1283973    int[] tmpT = new int[posT.length]; 
    1284974    System.arraycopy(posT, 0, tmpT, 0, tmpT.length); 
    1285  
    1286     for (int i=0; i<3; i++) { 
    1287       char originalAxis = core[sno].dimensionOrder.charAt(i + 2); 
    1288       char newAxis = originalOrder[sno].charAt(i + 2); 
    1289  
    1290       if (newAxis != originalAxis) { 
    1291         int src = -1; 
    1292         if (originalAxis == 'Z') src = tmpZ[tmpZ.length - 1]; 
    1293         else if (originalAxis == 'C') src = tmpC[tmpC.length - 1]; 
    1294         else if (originalAxis == 'T') src = tmpT[tmpT.length - 1]; 
    1295  
    1296         if (newAxis == 'Z') posZ[posZ.length - 1] = src; 
    1297         else if (newAxis == 'C') posC[posC.length - 1] = src; 
    1298         else if (newAxis == 'T') posT[posT.length - 1] = src; 
    1299       } 
    1300     } 
    1301975 
    1302976    // convert Z, C and T position lists into file index and image index 
     
    1314988 
    1315989    int fno = FormatTools.positionToRaster(count, pos); 
    1316  
    1317     if (seriesInFile) sno = 0; 
    1318  
    1319     // configure the reader, in case we haven't done this one yet 
    1320     initReader(sno, fno); 
     990    DimensionSwapper r = getReader(sno, fno); 
    1321991 
    1322992    int ino; 
    1323     if (posZ[0] < readers[sno][fno].getSizeZ() && 
    1324       posC[0] < readers[sno][fno].getSizeC() && 
    1325       posT[0] < readers[sno][fno].getSizeT()) 
     993    if (posZ[0] < r.getSizeZ() && posC[0] < r.getSizeC() && 
     994      posT[0] < r.getSizeT()) 
    1326995    { 
    1327       if (readers[sno][fno].isRGB() && 
    1328         (posC[0] * readers[sno][fno].getRGBChannelCount() >= lenC[sno][0])) 
     996      if (r.isRGB() && (posC[0] * r.getRGBChannelCount() >= lenC[sno][0])) { 
     997        posC[0] /= lenC[sno][0]; 
     998      } 
     999      ino = FormatTools.getIndex(r, posZ[0], posC[0], posT[0]); 
     1000    } 
     1001    else ino = Integer.MAX_VALUE; // coordinates out of range 
     1002 
     1003    return new int[] {fno, ino}; 
     1004  } 
     1005 
     1006  protected void initReader(int sno, int fno) { 
     1007    int external = getExternalSeries(sno); 
     1008    DimensionSwapper r = externals[external].getReaders()[fno]; 
     1009    try { 
     1010      if (r.getCurrentFile() == null) { 
     1011        r.setGroupFiles(false); 
     1012      } 
     1013      r.setId(externals[external].getFiles()[fno]); 
     1014      r.setSeries(reader.getSeriesCount() > 1 ? sno : 0); 
     1015      String newOrder = ((DimensionSwapper) reader).getInputOrder(); 
     1016      if ((externals[external].getFiles().length > 1 || !r.isOrderCertain()) && 
     1017        (r.getRGBChannelCount() == 1 || 
     1018        newOrder.indexOf("C") == r.getDimensionOrder().indexOf("C"))) 
    13291019      { 
    1330         posC[0] /= lenC[sno][0]; 
    1331       } 
    1332       ino = FormatTools.getIndex(readers[sno][fno], posZ[0], posC[0], posT[0]); 
    1333     } 
    1334     else ino = Integer.MAX_VALUE; // coordinates out of range 
    1335  
    1336     return new int[] {fno, ino}; 
    1337   } 
    1338  
    1339   /** 
    1340    * Gets a list of readers to include in relation to the given C position. 
    1341    * @return Array with indices corresponding to the list of readers, and 
    1342    *   values indicating the internal channel index to use for that reader. 
    1343    */ 
    1344   protected int[] getIncludeList(int theC) throws FormatException, IOException { 
    1345     int[] include = new int[readers.length]; 
    1346     Arrays.fill(include, -1); 
    1347     for (int t=0; t<sizeT[getSeries()]; t++) { 
    1348       for (int z=0; z<sizeZ[getSeries()]; z++) { 
    1349         int no = getIndex(z, theC, t); 
    1350         int[] q = computeIndices(no); 
    1351         int fno = q[0], ino = q[1]; 
    1352         include[fno] = ino; 
    1353       } 
    1354     } 
    1355     return include; 
    1356   } 
    1357  
    1358   protected void initReader(int sno, int fno) 
    1359     throws FormatException, IOException 
    1360   { 
    1361     if (files[sno][fno].equals(readers[sno][fno].getCurrentFile())) { 
    1362       return; 
    1363     } 
    1364     readers[sno][fno].setId(files[sno][fno]); 
    1365     readers[sno][fno].setSeries(seriesInFile ? getSeries() : 0); 
    1366     readers[sno][fno].swapDimensions(reader.getInputOrder()); 
    1367   } 
    1368  
    1369   private FilePattern getPattern(String[] f, String dir, String block) { 
    1370     Vector<String> v = new Vector<String>(); 
    1371     for (int i=0; i<f.length; i++) { 
    1372       if (f[i].indexOf(File.separator) != -1) { 
    1373         f[i] = f[i].substring(f[i].lastIndexOf(File.separator) + 1); 
    1374       } 
    1375       if (dir.endsWith(File.separator)) f[i] = dir + f[i]; 
    1376       else f[i] = dir + File.separator + f[i]; 
    1377       if (f[i].indexOf(block) != -1 && new Location(f[i]).exists()) { 
    1378         v.add(f[i].substring(f[i].lastIndexOf(File.separator) + 1)); 
    1379       } 
    1380     } 
    1381     f = v.toArray(new String[0]); 
    1382     return new FilePattern(FilePattern.findPattern(f[0], dir, f)); 
    1383   } 
    1384  
    1385   private void setFiles(String[] list, String prefix, BigInteger first, 
    1386     BigInteger last, BigInteger step, String dir, int blockNum) 
    1387   { 
    1388     long f = first.longValue(); 
    1389     long l = last.longValue(); 
    1390     long s = step.longValue(); 
    1391     for (long i=f; i<=l; i+=s) { 
    1392       FilePattern newPattern = getPattern(list, dir, prefix + i); 
    1393       if (blockNum == seriesBlocks.length - 1) { 
    1394         fileVector.add(newPattern.getFiles()); 
    1395         String name = newPattern.getPattern(); 
    1396         if (name.indexOf(File.separator) != -1) { 
    1397           name = name.substring(name.lastIndexOf(File.separator) + 1); 
    1398         } 
    1399         seriesNames.add(name); 
    1400       } 
    1401       else { 
    1402         String next = seriesBlocks[blockNum + 1]; 
    1403         String[] blocks = newPattern.getPrefixes(); 
    1404         BigInteger fi = null; 
    1405         BigInteger la = null; 
    1406         BigInteger st = null; 
    1407         for (int q=0; q<blocks.length; q++) { 
    1408           if (blocks[q].indexOf(next) != -1) { 
    1409             fi = newPattern.getFirst()[q]; 
    1410             la = newPattern.getLast()[q]; 
    1411             st = newPattern.getStep()[q]; 
    1412             break; 
    1413           } 
    1414         } 
    1415  
    1416         setFiles(newPattern.getFiles(), next, fi, la, st, dir, blockNum + 1); 
    1417       } 
    1418     } 
     1020        r.swapDimensions(newOrder); 
     1021      } 
     1022      r.setOutputOrder(newOrder); 
     1023    } 
     1024    catch (FormatException e) { 
     1025      LOGGER.debug("", e); 
     1026    } 
     1027    catch (IOException e) { 
     1028      LOGGER.debug("", e); 
     1029    } 
     1030  } 
     1031 
     1032  // -- Helper classes -- 
     1033 
     1034  class ExternalSeries { 
     1035    private DimensionSwapper[] readers; 
     1036    private String[] files; 
     1037    private FilePattern pattern; 
     1038    private byte[] blankThumbBytes; 
     1039    private String originalOrder; 
     1040    private AxisGuesser ag; 
     1041    private int imagesPerFile; 
     1042 
     1043    public ExternalSeries(FilePattern pattern) 
     1044      throws FormatException, IOException 
     1045    { 
     1046      this.pattern = pattern; 
     1047      files = this.pattern.getFiles(); 
     1048 
     1049      readers = new DimensionSwapper[files.length]; 
     1050      for (int i=0; i<readers.length; i++) { 
     1051        readers[i] = new DimensionSwapper(); 
     1052        readers[i].setGroupFiles(false); 
     1053      } 
     1054      readers[0].setId(files[0]); 
     1055 
     1056      ag = new AxisGuesser(this.pattern, readers[0].getDimensionOrder(), 
     1057        readers[0].getSizeZ(), readers[0].getSizeT(), 
     1058        readers[0].getSizeC(), readers[0].isOrderCertain()); 
     1059 
     1060      blankThumbBytes = new byte[FormatTools.getPlaneSize(readers[0], 
     1061        readers[0].getThumbSizeX(), readers[0].getThumbSizeY())]; 
     1062 
     1063      originalOrder = readers[0].getDimensionOrder(); 
     1064      imagesPerFile = readers[0].getImageCount(); 
     1065    } 
     1066 
     1067    public DimensionSwapper[] getReaders() { 
     1068      return readers; 
     1069    } 
     1070 
     1071    public FilePattern getFilePattern() { 
     1072      return pattern; 
     1073    } 
     1074 
     1075    public String getOriginalOrder() { 
     1076      return originalOrder; 
     1077    } 
     1078 
     1079    public AxisGuesser getAxisGuesser() { 
     1080      return ag; 
     1081    } 
     1082 
     1083    public byte[] getBlankThumbBytes() { 
     1084      return blankThumbBytes; 
     1085    } 
     1086 
     1087    public String[] getFiles() { 
     1088      return files; 
     1089    } 
     1090 
     1091    public int getImagesPerFile() { 
     1092      return imagesPerFile; 
     1093    } 
     1094 
    14191095  } 
    14201096 
  • trunk/components/bio-formats/src/loci/formats/in/DicomReader.java

    r6881 r7021  
    204204  } 
    205205 
    206   /* @see loci.formats.IFormatReader#fileGroupOption(String) */ 
    207206  public int fileGroupOption(String id) throws FormatException, IOException { 
    208     return MUST_GROUP; 
     207    return CAN_GROUP; 
    209208  } 
    210209 
  • trunk/components/loci-plugins/src/loci/plugins/in/ImportProcess.java

    r6881 r7021  
    485485      // overwrite base filename with file pattern 
    486486      String id = options.getId(); 
    487       if (id == null) id = getCurrentFile(); 
    488       FilePattern fp = fileStitcher.findPattern(id); 
    489       if (fp.isValid()) id = fp.getPattern(); 
    490       else id = getCurrentFile(); 
    491487      options.setId(id); 
    492488      fileStitcher.setUsingPatternIds(true); 
  • trunk/components/loci-plugins/src/loci/plugins/util/BFVirtualStack.java

    r6881 r7021  
    7575  // -- Static utility methods -- 
    7676 
    77   protected static int getWidth(IFormatReader r, String path) 
     77  protected static int getWidth(IFormatReader r, String path, int series) 
    7878    throws FormatException, IOException 
    7979  { 
    8080    r.setId(path); 
     81    r.setSeries(series); 
    8182    return r.getSizeX(); 
    8283  } 
    8384 
    84   protected static int getHeight(IFormatReader r, String path) 
     85  protected static int getHeight(IFormatReader r, String path, int series) 
    8586    throws FormatException, IOException 
    8687  { 
    8788    r.setId(path); 
     89    r.setSeries(series); 
    8890    return r.getSizeY(); 
    8991  } 
     
    9597    throws FormatException, IOException, CacheException 
    9698  { 
    97     super(getWidth(r, path), getHeight(r, path), null, path); 
     99    super(getWidth(r, path, r.getSeries()), getHeight(r, path, r.getSeries()), 
     100      null, path); 
    98101    reader = new ImageProcessorReader(r); 
    99102    id = path; 
Note: See TracChangeset for help on using the changeset viewer.