Changeset 7097


Ignore:
Timestamp:
10/21/10 08:34:34 (9 years ago)
Author:
melissa
Message:

Backported file stitching changes to 4.2: r7021, r7022, r7070, r7071, r7075, r7076, and r7086.

Location:
branches/4.2
Files:
38 edited
1 copied

Legend:

Unmodified
Added
Removed
  • branches/4.2

  • branches/4.2/components/bio-formats/src/loci/formats

  • branches/4.2/components/bio-formats/src/loci/formats/AxisGuesser.java

    r7007 r7097  
    7979  protected static final String[] S = {"s", "series", "sp"}; 
    8080 
    81   protected static final BigInteger TWO = new BigInteger("2"); 
    82   protected static final BigInteger THREE = new BigInteger("3"); 
     81  protected static final String ONE = "1"; 
     82  protected static final String TWO = "2"; 
     83  protected static final String THREE = "3"; 
    8384 
    8485  // -- Fields -- 
     
    125126    String[] prefixes = fp.getPrefixes(); 
    126127    String suffix = fp.getSuffix(); 
    127     BigInteger[] first = fp.getFirst(); 
    128     BigInteger[] last = fp.getLast(); 
    129     BigInteger[] step = fp.getStep(); 
    130     int[] count = fp.getCount(); 
    131     axisTypes = new int[count.length]; 
     128    String[][] elements = fp.getElements(); 
     129    axisTypes = new int[elements.length]; 
    132130    boolean foundZ = false, foundT = false; 
    133131 
     
    189187      if (axisTypes[i] != UNKNOWN_AXIS) continue; 
    190188 
    191       // check special case: <2-3> (Bio-Rad PIC) 
    192       if (first[i].equals(TWO) && last[i].equals(THREE) && 
    193         step[i].equals(BigInteger.ONE) && suffix.equalsIgnoreCase(".pic")) 
     189      // check special case: <2-3>, <1-3> (Bio-Rad PIC) 
     190      if (suffix.equalsIgnoreCase(".pic") && i == axisTypes.length - 1 && 
     191        ((elements[i].length == 2 && 
     192        (elements[i][0].equals(ONE) || elements[i][0].equals(TWO)) && 
     193        (elements[i][1].equals(TWO) || elements[i][1].equals(THREE))) || 
     194        (elements[i].length == 3 && 
     195        elements[i][0].equals(ONE) && elements[i][1].equals(TWO) && 
     196        elements[i][2].equals(THREE)))) 
    194197      { 
    195198        axisTypes[i] = C_AXIS; 
    196         break; 
     199        continue; 
     200      } 
     201      else if (elements[i].length == 2 || elements[i].length == 3) { 
     202        char first = elements[i][0].toLowerCase().charAt(0); 
     203        char second = elements[i][1].toLowerCase().charAt(0); 
     204        char third = elements[i].length == 2 ? 'b' : 
     205          elements[i][2].toLowerCase().charAt(0); 
     206 
     207        if ((first == 'r' || second == 'r' || third == 'r') && 
     208          (first == 'g' || second == 'g' || third == 'g') && 
     209          (first == 'b' || second == 'b' || third == 'b')) 
     210        { 
     211          axisTypes[i] = C_AXIS; 
     212          continue; 
     213        } 
    197214      } 
    198215    } 
     
    305322    } 
    306323    return num; 
     324  } 
     325 
     326  // -- Static API methods -- 
     327 
     328  /** Returns a best guess of the given label's axis type. */ 
     329  public static int getAxisType(String label) { 
     330    String lowerLabel = label.toLowerCase(); 
     331    for (String p : Z) { 
     332      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return Z_AXIS; 
     333    } 
     334    for (String p : C) { 
     335      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return C_AXIS; 
     336    } 
     337    for (String p : T) { 
     338      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return T_AXIS; 
     339    } 
     340    for (String p : S) { 
     341      if (p.equals(lowerLabel) || lowerLabel.endsWith(p)) return S_AXIS; 
     342    } 
     343    return UNKNOWN_AXIS; 
    307344  } 
    308345 
  • branches/4.2/components/bio-formats/src/loci/formats/FilePattern.java

    r7007 r7097  
    2929import java.util.Arrays; 
    3030import java.util.List; 
    31  
     31import java.util.regex.Matcher; 
     32import java.util.regex.Pattern; 
     33import java.util.regex.PatternSyntaxException; 
     34 
     35import loci.common.DataTools; 
    3236import loci.common.Location; 
    3337 
     
    7781  private int[] endIndex; 
    7882 
    79   /** First number of each numerical block. */ 
    80   private BigInteger[] begin; 
    81  
    82   /** Last number of each numerical block. */ 
    83   private BigInteger[] end; 
    84  
    85   /** Step size of each numerical block. */ 
    86   private BigInteger[] step; 
    87  
    88   /** Total numbers withins each numerical block. */ 
    89   private int[] count; 
    90  
    91   /** Whether each numerical block is fixed width. */ 
    92   private boolean[] fixed; 
    93  
    94   /** The number of leading zeroes for each numerical block. */ 
    95   private int[] zeroes; 
     83  /** List of pattern blocks for this file pattern. */ 
     84  private FilePatternBlock[] blocks; 
    9685 
    9786  /** File listing for this file pattern. */ 
     
    126115    int left = -1; 
    127116    while (true) { 
    128       left = pattern.indexOf("<", left + 1); 
     117      left = pattern.indexOf(FilePatternBlock.BLOCK_START, left + 1); 
    129118      if (left < 0) break; 
    130119      lt.add(new Integer(left)); 
     
    132121    int right = -1; 
    133122    while (true) { 
    134       right = pattern.indexOf(">", right + 1); 
     123      right = pattern.indexOf(FilePatternBlock.BLOCK_END, right + 1); 
    135124      if (right < 0) break; 
    136125      gt.add(new Integer(right)); 
     
    161150 
    162151    // parse numerical blocks 
    163     begin = new BigInteger[num]; 
    164     end = new BigInteger[num]; 
    165     step = new BigInteger[num]; 
    166     count = new int[num]; 
    167     fixed = new boolean[num]; 
    168     zeroes = new int[num]; 
     152    blocks = new FilePatternBlock[num]; 
    169153    for (int i=0; i<num; i++) { 
    170154      String block = pattern.substring(startIndex[i], endIndex[i]); 
    171       int dash = block.indexOf("-"); 
    172       String b, e, s; 
    173       if (dash < 0) { 
    174         // no range; assume entire block is a single number (e.g., <15>) 
    175         b = e = block.substring(1, block.length() - 1); 
    176         s = "1"; 
    177       } 
    178       else { 
    179         int colon = block.indexOf(":"); 
    180         b = block.substring(1, dash); 
    181         if (colon < 0) { 
    182           e = block.substring(dash + 1, block.length() - 1); 
    183           s = "1"; 
    184         } 
    185         else { 
    186           e = block.substring(dash + 1, colon); 
    187           s = block.substring(colon + 1, block.length() - 1); 
    188         } 
    189       } 
    190       try { 
    191         begin[i] = new BigInteger(b); 
    192         end[i] = new BigInteger(e); 
    193         if (begin[i].compareTo(end[i]) > 0) { 
    194           msg = "Begin value cannot be greater than ending value."; 
    195           return; 
    196         } 
    197         step[i] = new BigInteger(s); 
    198         if (step[i].compareTo(BigInteger.ONE) < 0) { 
    199           msg = "Step value must be at least one."; 
    200           return; 
    201         } 
    202         count[i] = end[i].subtract(begin[i]).divide(step[i]).intValue() + 1; 
    203         fixed[i] = b.length() == e.length(); 
    204         int z = 0; 
    205         for (z=0; z<e.length(); z++) { 
    206           if (e.charAt(z) != '0') break; 
    207         } 
    208         zeroes[i] = z; 
    209       } 
    210       catch (NumberFormatException exc) { 
    211         msg = "Invalid numerical range values."; 
    212         return; 
    213       } 
    214     } 
     155      blocks[i] = new FilePatternBlock(block); 
     156    } 
     157 
    215158 
    216159    // build file listing 
     
    219162    files = fileList.toArray(new String[0]); 
    220163 
     164    if (files.length == 0 && new Location(pattern).exists()) { 
     165      files = new String[] {pattern}; 
     166    } 
     167 
    221168    valid = true; 
    222169  } 
     
    233180  public String getErrorMessage() { return msg; } 
    234181 
    235   /** Gets the first number of each numerical block. */ 
    236   public BigInteger[] getFirst() { return begin; } 
    237  
    238   /** Gets the last number of each numerical block. */ 
    239   public BigInteger[] getLast() { return end; } 
    240  
    241   /** Gets the step increment of each numerical block. */ 
    242   public BigInteger[] getStep() { return step; } 
    243  
    244   /** Gets the total count of each numerical block. */ 
    245   public int[] getCount() { return count; } 
    246  
    247182  /** Gets a listing of all files matching the given file pattern. */ 
    248183  public String[] getFiles() { return files; } 
     184 
     185  public String[][] getElements() { 
     186    String[][] elements = new String[blocks.length][]; 
     187    for (int i=0; i<elements.length; i++) { 
     188      elements[i] = blocks[i].getElements(); 
     189    } 
     190    return elements; 
     191  } 
     192 
     193  public int[] getCount() { 
     194    int[] count = new int[blocks.length]; 
     195    for (int i=0; i<count.length; i++) { 
     196      count[i] = blocks[i].getElements().length; 
     197    } 
     198    return count; 
     199  } 
    249200 
    250201  /** Gets the specified numerical block. */ 
     
    349300   */ 
    350301  public static String findPattern(String name, String dir, String[] nameList) { 
     302    return findPattern(name, dir, nameList, null); 
     303  } 
     304 
     305  /** 
     306   * Identifies the group pattern from a given file within that group. 
     307   * @param name The filename to use as a template for the match. 
     308   * @param dir The directory prefix to use for matching files. 
     309   * @param nameList The names through which to search for matching files. 
     310   * @param excludeAxes The list of axis types which should be excluded from the 
     311   *  pattern. 
     312   */ 
     313  public static String findPattern(String name, String dir, String[] nameList, 
     314    int[] excludeAxes) 
     315  { 
     316    if (excludeAxes == null) excludeAxes = new int[0]; 
     317 
    351318    if (dir == null) dir = ""; // current directory 
    352319    else if (!dir.equals("") && !dir.endsWith(File.separator)) { 
     
    390357    for (int i=0; i<q; i++) { 
    391358      int last = i > 0 ? endList[i - 1] : 0; 
    392       sb.append(name.substring(last, indexList[i])); 
     359      String prefix = name.substring(last, indexList[i]); 
     360      int axisType = AxisGuesser.getAxisType(prefix); 
     361      if (DataTools.containsValue(excludeAxes, axisType)) { 
     362        sb.append(name.substring(last, endList[i])); 
     363        continue; 
     364      } 
     365 
     366      sb.append(prefix); 
    393367      String pre = name.substring(0, indexList[i]); 
    394368      String post = name.substring(endList[i]); 
     
    403377      } 
    404378      boolean fix = true; 
    405       for (int j=0; j<list.length; j++) { 
    406         if (list[j].length() != len) { 
     379      for (String s : list) { 
     380        if (s.length() != len) { 
    407381          fix = false; 
    408382          break; 
     
    419393          int jx = indexList[i] + j; 
    420394          char c = name.charAt(jx); 
    421           for (int k=0; k<list.length; k++) { 
    422             if (list[k].charAt(jx) != c) { 
     395          for (String s : list) { 
     396            if (s.charAt(jx) != c) { 
    423397              same[j] = false; 
    424398              break; 
     
    464438    } 
    465439    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; 
     440    return sb.toString(); 
     441  } 
     442 
     443  public static String[] findSeriesPatterns(String base) { 
     444    Location file = new Location(base).getAbsoluteFile(); 
     445    Location parent = file.getParentFile(); 
     446    String[] list = parent.list(true); 
     447    return findSeriesPatterns(base, parent.getAbsolutePath(), list); 
     448  } 
     449 
     450  public static String[] findSeriesPatterns(String base, String dir, 
     451    String[] nameList) 
     452  { 
     453    String baseSuffix = base.substring(base.lastIndexOf(File.separator) + 1); 
     454    int dot = baseSuffix.indexOf("."); 
     455    if (dot < 0) baseSuffix = ""; 
     456    else baseSuffix = baseSuffix.substring(dot + 1); 
     457 
     458    ArrayList<String> patterns = new ArrayList<String>(); 
     459    int[] exclude = new int[] {AxisGuesser.S_AXIS}; 
     460    for (String name : nameList) { 
     461      String pattern = findPattern(name, dir, nameList, exclude); 
     462      int start = pattern.lastIndexOf(File.separator) + 1; 
     463      if (start < 0) start = 0; 
     464      String patternSuffix = pattern.substring(start); 
     465      dot = patternSuffix.indexOf("."); 
     466      if (dot < 0) patternSuffix = ""; 
     467      else patternSuffix = patternSuffix.substring(dot + 1); 
     468 
     469      String checkPattern = findPattern(name, dir, nameList); 
     470      String[] checkFiles = new FilePattern(checkPattern).getFiles(); 
     471 
     472      if (!patterns.contains(pattern) && (!new Location(pattern).exists() || 
     473        base.equals(pattern)) && patternSuffix.equals(baseSuffix) && 
     474        DataTools.indexOf(checkFiles, base) >= 0) 
     475      { 
     476        patterns.add(pattern); 
     477      } 
     478    } 
     479    String[] s = patterns.toArray(new String[patterns.size()]); 
     480    Arrays.sort(s); 
     481    return s; 
    484482  } 
    485483 
     
    559557  /** Recursive method for building filenames for the file listing. */ 
    560558  private void buildFiles(String prefix, int ndx, List<String> fileList) { 
    561     // compute bounds for constant (non-block) pattern fragment 
    562     int num = startIndex.length; 
    563     int n1 = ndx == 0 ? 0 : endIndex[ndx - 1]; 
    564     int n2 = ndx == num ? pattern.length() : startIndex[ndx]; 
    565     String pre = pattern.substring(n1, n2); 
    566  
    567     if (ndx == 0) fileList.add(pre + prefix); 
     559    if (blocks.length == 0) { 
     560      // regex pattern 
     561 
     562      String[] files = null; 
     563      int end = pattern.lastIndexOf(File.separator) + 1; 
     564      String dir = pattern.substring(0, end); 
     565      if (dir.equals("") || !new Location(dir).exists()) { 
     566        files = Location.getIdMap().keySet().toArray(new String[0]); 
     567        if (files.length == 0) { 
     568          dir = "."; 
     569          files = new Location(dir).list(true); 
     570        } 
     571      } 
     572      else { 
     573        files = new Location(dir).list(true); 
     574      } 
     575 
     576      Arrays.sort(files); 
     577 
     578      Pattern regex = Pattern.compile(pattern); 
     579 
     580      for (String f : files) { 
     581        if (regex.matcher(f).matches()) { 
     582          Location path = new Location(dir, f); 
     583          if (path.exists()) fileList.add(path.getAbsolutePath()); 
     584          else fileList.add(f); 
     585        } 
     586      } 
     587    } 
    568588    else { 
    569       // for (int i=begin[ndx]; i<end[ndx]; i+=step[ndx]) 
    570       BigInteger bi = begin[--ndx]; 
    571       while (bi.compareTo(end[ndx]) <= 0) { 
    572         String s = bi.toString(); 
    573         int z = zeroes[ndx]; 
    574         if (fixed[ndx]) z += end[ndx].toString().length() - s.length(); 
    575         for (int j=0; j<z; j++) s = "0" + s; 
    576         buildFiles(s + pre + prefix, ndx, fileList); 
    577         bi = bi.add(step[ndx]); 
     589      // compute bounds for constant (non-block) pattern fragment 
     590      int num = startIndex.length; 
     591      int n1 = ndx == 0 ? 0 : endIndex[ndx - 1]; 
     592      int n2 = ndx == num ? pattern.length() : startIndex[ndx]; 
     593      String pre = pattern.substring(n1, n2); 
     594 
     595      if (ndx == 0) fileList.add(pre + prefix); 
     596      else { 
     597        FilePatternBlock block = blocks[--ndx]; 
     598        String[] blockElements = block.getElements(); 
     599        for (String element : blockElements) { 
     600          buildFiles(element + pre + prefix, ndx, fileList); 
     601        } 
    578602      } 
    579603    } 
  • branches/4.2/components/bio-formats/src/loci/formats/FileStitcher.java

    r7007 r7097  
    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    if (patterns.length == 0) patterns = new String[] {id}; 
     229    else { 
     230      FilePattern test = new FilePattern(patterns[0]); 
     231      if (test.getFiles().length == 0) patterns = new String[] {id}; 
     232    } 
     233    patternIds = true; 
     234    return patterns; 
    284235  } 
    285236 
    286237  // -- 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   } 
    302238 
    303239  /* @see IFormatReader#getImageCount() */ 
    304240  public int getImageCount() { 
    305     FormatTools.assertId(currentId, true, 2); 
     241    FormatTools.assertId(getCurrentFile(), true, 2); 
    306242    return noStitch ? reader.getImageCount() : core[getSeries()].imageCount; 
    307243  } 
     
    309245  /* @see IFormatReader#isRGB() */ 
    310246  public boolean isRGB() { 
    311     FormatTools.assertId(currentId, true, 2); 
     247    FormatTools.assertId(getCurrentFile(), true, 2); 
    312248    return noStitch ? reader.isRGB() : core[getSeries()].rgb; 
    313249  } 
     
    315251  /* @see IFormatReader#getSizeX() */ 
    316252  public int getSizeX() { 
    317     FormatTools.assertId(currentId, true, 2); 
     253    FormatTools.assertId(getCurrentFile(), true, 2); 
    318254    return noStitch ? reader.getSizeX() : core[getSeries()].sizeX; 
    319255  } 
     
    321257  /* @see IFormatReader#getSizeY() */ 
    322258  public int getSizeY() { 
    323     FormatTools.assertId(currentId, true, 2); 
     259    FormatTools.assertId(getCurrentFile(), true, 2); 
    324260    return noStitch ? reader.getSizeY() : core[getSeries()].sizeY; 
    325261  } 
     
    327263  /* @see IFormatReader#getSizeZ() */ 
    328264  public int getSizeZ() { 
    329     FormatTools.assertId(currentId, true, 2); 
     265    FormatTools.assertId(getCurrentFile(), true, 2); 
    330266    return noStitch ? reader.getSizeZ() : core[getSeries()].sizeZ; 
    331267  } 
     
    333269  /* @see IFormatReader#getSizeC() */ 
    334270  public int getSizeC() { 
    335     FormatTools.assertId(currentId, true, 2); 
     271    FormatTools.assertId(getCurrentFile(), true, 2); 
    336272    return noStitch ? reader.getSizeC() : core[getSeries()].sizeC; 
    337273  } 
     
    339275  /* @see IFormatReader#getSizeT() */ 
    340276  public int getSizeT() { 
    341     FormatTools.assertId(currentId, true, 2); 
     277    FormatTools.assertId(getCurrentFile(), true, 2); 
    342278    return noStitch ? reader.getSizeT() : core[getSeries()].sizeT; 
    343279  } 
     
    345281  /* @see IFormatReader#getPixelType() */ 
    346282  public int getPixelType() { 
    347     FormatTools.assertId(currentId, true, 2); 
     283    FormatTools.assertId(getCurrentFile(), true, 2); 
    348284    return noStitch ? reader.getPixelType() : core[getSeries()].pixelType; 
    349285  } 
     
    351287  /* @see IFormatReader#getBitsPerPixel() */ 
    352288  public int getBitsPerPixel() { 
    353     FormatTools.assertId(currentId, true, 2); 
     289    FormatTools.assertId(getCurrentFile(), true, 2); 
    354290    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(); 
    367291  } 
    368292 
    369293  /* @see IFormatReader#isIndexed() */ 
    370294  public boolean isIndexed() { 
    371     FormatTools.assertId(currentId, true, 2); 
     295    FormatTools.assertId(getCurrentFile(), true, 2); 
    372296    return noStitch ? reader.isIndexed() : core[getSeries()].indexed; 
    373297  } 
     
    375299  /* @see IFormatReader#isFalseColor() */ 
    376300  public boolean isFalseColor() { 
    377     FormatTools.assertId(currentId, true, 2); 
     301    FormatTools.assertId(getCurrentFile(), true, 2); 
    378302    return noStitch ? reader.isFalseColor() : core[getSeries()].falseColor; 
    379303  } 
     
    381305  /* @see IFormatReader#get8BitLookupTable() */ 
    382306  public byte[][] get8BitLookupTable() throws FormatException, IOException { 
    383     FormatTools.assertId(currentId, true, 2); 
     307    FormatTools.assertId(getCurrentFile(), true, 2); 
    384308    return noStitch ? reader.get8BitLookupTable() : 
    385       readers[seriesInFile ? 0 : getSeries()][0].get8BitLookupTable(); 
     309      getReader(getSeries(), 0).get8BitLookupTable(); 
    386310  } 
    387311 
    388312  /* @see IFormatReader#get16BitLookupTable() */ 
    389313  public short[][] get16BitLookupTable() throws FormatException, IOException { 
    390     FormatTools.assertId(currentId, true, 2); 
     314    FormatTools.assertId(getCurrentFile(), true, 2); 
    391315    return noStitch ? reader.get16BitLookupTable() : 
    392       readers[seriesInFile ? 0 : getSeries()][0].get16BitLookupTable(); 
     316      getReader(getSeries(), 0).get16BitLookupTable(); 
    393317  } 
    394318 
    395319  /* @see IFormatReader#getChannelDimLengths() */ 
    396320  public int[] getChannelDimLengths() { 
    397     FormatTools.assertId(currentId, true, 2); 
     321    FormatTools.assertId(getCurrentFile(), true, 2); 
    398322    if (noStitch) return reader.getChannelDimLengths(); 
    399323    if (core[getSeries()].cLengths == null) { 
     
    405329  /* @see IFormatReader#getChannelDimTypes() */ 
    406330  public String[] getChannelDimTypes() { 
    407     FormatTools.assertId(currentId, true, 2); 
     331    FormatTools.assertId(getCurrentFile(), true, 2); 
    408332    if (noStitch) return reader.getChannelDimTypes(); 
    409333    if (core[getSeries()].cTypes == null) { 
     
    415339  /* @see IFormatReader#getThumbSizeX() */ 
    416340  public int getThumbSizeX() { 
    417     FormatTools.assertId(currentId, true, 2); 
     341    FormatTools.assertId(getCurrentFile(), true, 2); 
    418342    return noStitch ? reader.getThumbSizeX() : 
    419       readers[seriesInFile ? 0 : getSeries()][0].getThumbSizeX(); 
     343      getReader(getSeries(), 0).getThumbSizeX(); 
    420344  } 
    421345 
    422346  /* @see IFormatReader#getThumbSizeY() */ 
    423347  public int getThumbSizeY() { 
    424     FormatTools.assertId(currentId, true, 2); 
     348    FormatTools.assertId(getCurrentFile(), true, 2); 
    425349    return noStitch ? reader.getThumbSizeY() : 
    426       readers[seriesInFile ? 0 : getSeries()][0].getThumbSizeY(); 
     350      getReader(getSeries(), 0).getThumbSizeY(); 
    427351  } 
    428352 
    429353  /* @see IFormatReader#isLittleEndian() */ 
    430354  public boolean isLittleEndian() { 
    431     FormatTools.assertId(currentId, true, 2); 
     355    FormatTools.assertId(getCurrentFile(), true, 2); 
    432356    return noStitch ? reader.isLittleEndian() : 
    433       readers[seriesInFile ? 0 : getSeries()][0].isLittleEndian(); 
     357      getReader(getSeries(), 0).isLittleEndian(); 
    434358  } 
    435359 
    436360  /* @see IFormatReader#getDimensionOrder() */ 
    437361  public String getDimensionOrder() { 
    438     FormatTools.assertId(currentId, true, 2); 
     362    FormatTools.assertId(getCurrentFile(), true, 2); 
    439363    if (noStitch) return reader.getDimensionOrder(); 
    440     return originalOrder[getSeries()]; 
     364    return core[getSeries()].dimensionOrder; 
    441365  } 
    442366 
    443367  /* @see IFormatReader#isOrderCertain() */ 
    444368  public boolean isOrderCertain() { 
    445     FormatTools.assertId(currentId, true, 2); 
     369    FormatTools.assertId(getCurrentFile(), true, 2); 
    446370    return noStitch ? reader.isOrderCertain() : core[getSeries()].orderCertain; 
    447371  } 
     
    449373  /* @see IFormatReader#isThumbnailSeries() */ 
    450374  public boolean isThumbnailSeries() { 
    451     FormatTools.assertId(currentId, true, 2); 
     375    FormatTools.assertId(getCurrentFile(), true, 2); 
    452376    return noStitch ? reader.isThumbnailSeries() : core[getSeries()].thumbnail; 
    453377  } 
     
    455379  /* @see IFormatReader#isInterleaved() */ 
    456380  public boolean isInterleaved() { 
    457     FormatTools.assertId(currentId, true, 2); 
     381    FormatTools.assertId(getCurrentFile(), true, 2); 
    458382    return noStitch ? reader.isInterleaved() : 
    459       readers[seriesInFile ? 0 : getSeries()][0].isInterleaved(); 
     383      getReader(getSeries(), 0).isInterleaved(); 
    460384  } 
    461385 
    462386  /* @see IFormatReader#isInterleaved(int) */ 
    463387  public boolean isInterleaved(int subC) { 
    464     FormatTools.assertId(currentId, true, 2); 
     388    FormatTools.assertId(getCurrentFile(), true, 2); 
    465389    return noStitch ? reader.isInterleaved(subC) : 
    466       readers[seriesInFile ? 0 : getSeries()][0].isInterleaved(subC); 
     390      getReader(getSeries(), 0).isInterleaved(subC); 
    467391  } 
    468392 
     
    483407    throws FormatException, IOException 
    484408  { 
    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]; 
     409    int bpp = FormatTools.getBytesPerPixel(getPixelType()); 
     410    int ch = getRGBChannelCount(); 
     411    byte[] buf = new byte[w * h * ch * bpp]; 
     412    return openBytes(no, buf, x, y, w, h); 
    498413  } 
    499414 
     
    502417    throws FormatException, IOException 
    503418  { 
    504     FormatTools.assertId(currentId, true, 2); 
    505  
    506     IFormatReader r = getReader(no); 
    507     int ino = getAdjustedIndex(no); 
     419    FormatTools.assertId(getCurrentFile(), true, 2); 
     420 
     421    int[] pos = computeIndices(no); 
     422    IFormatReader r = getReader(getSeries(), pos[0]); 
     423    int ino = pos[1]; 
     424 
    508425    if (ino < r.getImageCount()) return r.openBytes(ino, buf, x, y, w, h); 
    509426 
     
    518435    throws FormatException, IOException 
    519436  { 
    520     FormatTools.assertId(currentId, true, 2); 
     437    FormatTools.assertId(getCurrentFile(), true, 2); 
    521438 
    522439    IFormatReader r = getReader(no); 
     
    529446  /* @see IFormatReader#openThumbBytes(int) */ 
    530447  public byte[] openThumbBytes(int no) throws FormatException, IOException { 
    531     FormatTools.assertId(currentId, true, 2); 
     448    FormatTools.assertId(getCurrentFile(), true, 2); 
    532449 
    533450    IFormatReader r = getReader(no); 
     
    537454    // return a blank image to cover for the fact that 
    538455    // 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]; 
     456    return externals[getExternalSeries()].getBlankThumbBytes(); 
     457  } 
     458 
     459  /* @see IFormatReader#close() */ 
     460  public void close() throws IOException { 
     461    close(false); 
    546462  } 
    547463 
    548464  /* @see IFormatReader#close(boolean) */ 
    549465  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); 
     466    super.close(fileOnly); 
     467    if (externals != null) { 
     468      for (ExternalSeries s : externals) { 
     469        for (DimensionSwapper r : s.getReaders()) { 
     470          if (r != null) r.close(fileOnly); 
    555471        } 
    556472      } 
     
    558474    if (!fileOnly) { 
    559475      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; 
     476      externals = null; 
    569477      sizeZ = sizeC = sizeT = null; 
    570478      lenZ = lenC = lenT = null; 
    571479      core = null; 
    572480      series = 0; 
    573       seriesBlocks = null; 
    574       fileVector = null; 
    575       seriesNames = null; 
    576       seriesInFile = false; 
    577481      store = null; 
    578       originalOrder = null; 
     482      patternIds = false; 
    579483    } 
    580484  } 
     
    582486  /* @see IFormatReader#getSeriesCount() */ 
    583487  public int getSeriesCount() { 
    584     FormatTools.assertId(currentId, true, 2); 
     488    FormatTools.assertId(getCurrentFile(), true, 2); 
    585489    return noStitch ? reader.getSeriesCount() : core.length; 
    586490  } 
     
    588492  /* @see IFormatReader#setSeries(int) */ 
    589493  public void setSeries(int no) { 
    590     FormatTools.assertId(currentId, true, 2); 
     494    FormatTools.assertId(getCurrentFile(), true, 2); 
    591495    int n = reader.getSeriesCount(); 
    592496    if (n > 1) reader.setSeries(no); 
     
    596500  /* @see IFormatReader#getSeries() */ 
    597501  public int getSeries() { 
    598     FormatTools.assertId(currentId, true, 2); 
    599     return seriesInFile || noStitch ? reader.getSeries() : series; 
     502    FormatTools.assertId(getCurrentFile(), true, 2); 
     503    return reader.getSeries() > 0 ? reader.getSeries() : series; 
    600504  } 
    601505 
     
    603507  public void setGroupFiles(boolean group) { 
    604508    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); 
     509    if (externals != null) { 
     510      for (ExternalSeries s : externals) { 
     511        for (DimensionSwapper r : s.getReaders()) { 
     512          r.setGroupFiles(group); 
    609513        } 
    610514      } 
    611515    } 
    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(); 
    627516  } 
    628517 
    629518  /* @see IFormatReader#setNormalized(boolean) */ 
    630519  public void setNormalized(boolean normalize) { 
    631     FormatTools.assertId(currentId, false, 2); 
    632     if (readers == null) reader.setNormalized(normalize); 
     520    FormatTools.assertId(getCurrentFile(), false, 2); 
     521    if (externals == null) reader.setNormalized(normalize); 
    633522    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); 
     523      for (ExternalSeries s : externals) { 
     524        for (DimensionSwapper r : s.getReaders()) { 
     525          r.setNormalized(normalize); 
    637526        } 
    638527      } 
    639528    } 
    640529  } 
    641  
    642   /* @see IFormatReader#isNormalized() */ 
    643   public boolean isNormalized() { return reader.isNormalized(); } 
    644530 
    645531  /** 
     
    648534   */ 
    649535  public void setMetadataCollected(boolean collect) { 
    650     FormatTools.assertId(currentId, false, 2); 
    651     if (readers == null) reader.setMetadataCollected(collect); 
     536    FormatTools.assertId(getCurrentFile(), false, 2); 
     537    if (externals == null) reader.setMetadataCollected(collect); 
    652538    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); 
     539      for (ExternalSeries s : externals) { 
     540        for (DimensionSwapper r : s.getReaders()) { 
     541          r.setMetadataCollected(collect); 
    656542        } 
    657543      } 
    658544    } 
    659   } 
    660  
    661   /** 
    662    * @deprecated 
    663    * @see IFormatReader#isMetadataCollected() 
    664    */ 
    665   public boolean isMetadataCollected() { 
    666     return reader.isMetadataCollected(); 
    667545  } 
    668546 
    669547  /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */ 
    670548  public void setOriginalMetadataPopulated(boolean populate) { 
    671     FormatTools.assertId(currentId, false, 1); 
    672     if (readers == null) reader.setOriginalMetadataPopulated(populate); 
     549    FormatTools.assertId(getCurrentFile(), false, 1); 
     550    if (externals == null) reader.setOriginalMetadataPopulated(populate); 
    673551    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); 
     552      for (ExternalSeries s : externals) { 
     553        for (DimensionSwapper r : s.getReaders()) { 
     554          r.setOriginalMetadataPopulated(populate); 
    677555        } 
    678556      } 
    679557    } 
    680   } 
    681  
    682   /* @see IFormatReader#isOriginalMetadataPopulated() */ 
    683   public boolean isOriginalMetadataPopulated() { 
    684     return reader.isOriginalMetadataPopulated(); 
    685558  } 
    686559 
    687560  /* @see IFormatReader#getUsedFiles() */ 
    688561  public String[] getUsedFiles() { 
    689     FormatTools.assertId(currentId, true, 2); 
     562    FormatTools.assertId(getCurrentFile(), true, 2); 
    690563 
    691564    if (noStitch) return reader.getUsedFiles(); 
     
    695568    // when each constituent file does not itself have multiple used files 
    696569 
    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]); 
     570    Vector<String> files = new Vector<String>(); 
     571    for (ExternalSeries s : externals) { 
     572      String[] f = s.getFiles(); 
     573      for (String file : f) { 
     574        if (!files.contains(file)) files.add(file); 
     575      } 
     576    } 
     577    return files.toArray(new String[files.size()]); 
    742578  } 
    743579 
     
    760596  /* @see IFormatReader#getAdvancedUsedFiles(boolean) */ 
    761597  public FileInfo[] getAdvancedUsedFiles(boolean noPixels) { 
     598    if (noStitch) return reader.getAdvancedUsedFiles(noPixels); 
    762599    String[] files = getUsedFiles(noPixels); 
    763600    if (files == null) return null; 
     
    767604      infos[i].filename = files[i]; 
    768605      try { 
    769         infos[i].reader = reader.unwrap().getClass(); 
     606        infos[i].reader = ((DimensionSwapper) reader).unwrap().getClass(); 
    770607      } 
    771608      catch (FormatException e) { 
     
    782619  /* @see IFormatReader#getAdvancedSeriesUsedFiles(boolean) */ 
    783620  public FileInfo[] getAdvancedSeriesUsedFiles(boolean noPixels) { 
     621    if (noStitch) return reader.getAdvancedSeriesUsedFiles(noPixels); 
    784622    String[] files = getSeriesUsedFiles(noPixels); 
    785623    if (files == null) return null; 
     
    789627      infos[i].filename = files[i]; 
    790628      try { 
    791         infos[i].reader = reader.unwrap().getClass(); 
     629        infos[i].reader = ((DimensionSwapper) reader).unwrap().getClass(); 
    792630      } 
    793631      catch (FormatException e) { 
     
    802640  } 
    803641 
    804   /* @see IFormatReader#getCurrentFile() */ 
    805   public String getCurrentFile() { return currentId; } 
    806  
    807642  /* @see IFormatReader#getIndex(int, int, int) */ 
    808643  public int getIndex(int z, int c, int t) { 
    809     FormatTools.assertId(currentId, true, 2); 
     644    FormatTools.assertId(getCurrentFile(), true, 2); 
    810645    return FormatTools.getIndex(this, z, c, t); 
    811646  } 
     
    813648  /* @see IFormatReader#getZCTCoords(int) */ 
    814649  public int[] getZCTCoords(int index) { 
    815     FormatTools.assertId(currentId, true, 2); 
     650    FormatTools.assertId(getCurrentFile(), true, 2); 
    816651    return noStitch ? reader.getZCTCoords(index) : 
    817652      FormatTools.getZCTCoords(core[getSeries()].dimensionOrder, 
     
    819654  } 
    820655 
    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  
    839656  /* @see IFormatReader#getSeriesMetadata() */ 
    840657  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(); 
     658    FormatTools.assertId(getCurrentFile(), true, 2); 
     659    return noStitch ? reader.getSeriesMetadata() : 
     660      core[getSeries()].seriesMetadata; 
    849661  } 
    850662 
    851663  /* @see IFormatReader#getCoreMetadata() */ 
    852664  public CoreMetadata[] getCoreMetadata() { 
    853     FormatTools.assertId(currentId, true, 2); 
     665    FormatTools.assertId(getCurrentFile(), true, 2); 
    854666    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(); 
    866667  } 
    867668 
    868669  /* @see IFormatReader#setMetadataStore(MetadataStore) */ 
    869670  public void setMetadataStore(MetadataStore store) { 
    870     FormatTools.assertId(currentId, false, 2); 
     671    FormatTools.assertId(getCurrentFile(), false, 2); 
    871672    reader.setMetadataStore(store); 
     673    this.store = store; 
    872674  } 
    873675 
    874676  /* @see IFormatReader#getMetadataStore() */ 
    875677  public MetadataStore getMetadataStore() { 
    876     FormatTools.assertId(currentId, true, 2); 
     678    FormatTools.assertId(getCurrentFile(), true, 2); 
    877679    return noStitch ? reader.getMetadataStore() : store; 
    878680  } 
     
    880682  /* @see IFormatReader#getMetadataStoreRoot() */ 
    881683  public Object getMetadataStoreRoot() { 
    882     FormatTools.assertId(currentId, true, 2); 
     684    FormatTools.assertId(getCurrentFile(), true, 2); 
    883685    return noStitch ? reader.getMetadataStoreRoot() : store.getRoot(); 
    884686  } 
     
    887689  public IFormatReader[] getUnderlyingReaders() { 
    888690    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]); 
     691    for (ExternalSeries s : externals) { 
     692      for (DimensionSwapper r : s.getReaders()) { 
     693        list.add(r); 
    892694      } 
    893695    } 
     
    895697  } 
    896698 
    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) */ 
     699  /* @see IFormatReader#setId(String) */ 
    944700  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); } 
     701    close(); 
     702    initFile(id); 
     703  } 
    950704 
    951705  // -- Internal FormatReader API methods -- 
     
    955709    LOGGER.debug("initFile: {}", id); 
    956710 
    957     close(); 
    958     currentId = id; 
    959  
    960     fp = findPattern(currentId); 
     711    FilePattern fp = new FilePattern(id); 
     712    if (!patternIds) patternIds = fp.isValid() && fp.getFiles().length > 1; 
    961713 
    962714    boolean mustGroup = false; 
     
    972724      // reader subclass is handling file grouping 
    973725      noStitch = true; 
     726      reader.close(); 
     727      reader.setGroupFiles(true); 
     728 
    974729      if (patternIds && fp.isValid()) { 
    975730        reader.setId(fp.getFiles()[0]); 
    976731      } 
    977       else reader.setId(currentId); 
     732      else reader.setId(id); 
    978733      return; 
    979734    } 
     735 
     736    String[] patterns = findPatterns(id); 
     737    if (patterns.length == 0) patterns = new String[] {id}; 
     738    externals = new ExternalSeries[patterns.length]; 
     739 
     740    for (int i=0; i<externals.length; i++) { 
     741      externals[i] = new ExternalSeries(new FilePattern(patterns[i])); 
     742    } 
     743    fp = new FilePattern(patterns[0]); 
     744 
     745    reader.close(); 
     746    reader.setGroupFiles(false); 
    980747 
    981748    if (!fp.isValid()) { 
     
    983750    } 
    984751    reader.setId(fp.getFiles()[0]); 
     752 
     753    String msg = " Please rename your files or disable file stitching."; 
     754    if (reader.getSeriesCount() > 1 && 
     755      (externals.length > 1 || fp.getFiles().length > 1)) 
     756    { 
     757      throw new FormatException("Unsupported grouping: File pattern contains " + 
     758        "multiple files and each file contains multiple series." + msg); 
     759    } 
     760 
    985761    if (reader.getUsedFiles().length > 1) { 
    986762      noStitch = true; 
     
    993769 
    994770    // use the dimension order recommended by the axis guesser 
    995     reader.swapDimensions(guesser.getAdjustedOrder()); 
     771    ((DimensionSwapper) reader).swapDimensions(guesser.getAdjustedOrder()); 
    996772 
    997773    // 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       } 
     774    int seriesCount = externals.length; 
     775    if (externals.length == 1) { 
     776      seriesCount = reader.getSeriesCount(); 
    1040777    } 
    1041778 
    1042779    // verify that file pattern is valid and matches existing files 
    1043     String msg = " Please rename your files or disable file stitching."; 
    1044780    if (!fp.isValid()) { 
    1045781      throw new FormatException("Invalid " + 
    1046782        (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     } 
     783        " (" + id + "): " + fp.getErrorMessage() + msg); 
     784    } 
     785    String[] files = fp.getFiles(); 
    1053786 
    1054787    if (files == null) { 
     
    1056789        fp.getPattern() + "). " + msg); 
    1057790    } 
     791 
    1058792    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         } 
     793      String file = files[i]; 
     794 
     795      // HACK: skip file existence check for fake files 
     796      if (file.toLowerCase().endsWith(".fake")) continue; 
     797 
     798      if (!new Location(file).exists()) { 
     799        throw new FormatException("File #" + i + 
     800          " (" + file + ") does not exist."); 
    1069801      } 
    1070802    } 
     
    1072804    // determine reader type for these files; assume all are the same type 
    1073805    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]; 
     806      ((DimensionSwapper) reader).unwrap(files[0]).getClass(); 
     807 
    1090808    sizeZ = new int[seriesCount]; 
    1091809    sizeC = new int[seriesCount]; 
     
    1096814    lenT = new int[seriesCount][]; 
    1097815 
    1098     originalOrder = new String[seriesCount]; 
    1099  
    1100816    // analyze first file; assume each file has the same parameters 
    1101817    core = new CoreMetadata[seriesCount]; 
    1102     int oldSeries = reader.getSeries(); 
    1103     IFormatReader rr = reader; 
     818    int oldSeries = getSeries(); 
    1104819    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       } 
     820      IFormatReader rr = getReader(i, 0); 
    1110821 
    1111822      core[i] = new CoreMetadata(); 
     
    1117828      // NB: core.sizeT populated in computeAxisLengths below 
    1118829      core[i].pixelType = rr.getPixelType(); 
    1119       imagesPerFile[i] = rr.getImageCount(); 
    1120       core[i].imageCount = 
    1121         imagesPerFile[i] * files[seriesInFile ? 0 : i].length; 
     830 
     831      ExternalSeries external = externals[getExternalSeries(i)]; 
     832      core[i].imageCount = rr.getImageCount() * external.getFiles().length; 
    1122833      core[i].thumbSizeX = rr.getThumbSizeX(); 
    1123834      core[i].thumbSizeY = rr.getThumbSizeY(); 
     
    1125836      // NB: core.cTypes[i] populated in computeAxisLengths below 
    1126837      core[i].dimensionOrder = rr.getDimensionOrder(); 
    1127       originalOrder[i] = rr.getDimensionOrder(); 
    1128838      // NB: core.orderCertain[i] populated below 
    1129839      core[i].rgb = rr.isRGB(); 
     
    1138848      certain[i] = rr.isOrderCertain(); 
    1139849    } 
    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     } 
    1147850 
    1148851    // order may need to be adjusted 
    1149852    for (int i=0; i<seriesCount; i++) { 
    1150853      setSeries(i); 
    1151       core[i].dimensionOrder = ag[i].getAdjustedOrder(); 
    1152       core[i].orderCertain = ag[i].isCertain(); 
     854      AxisGuesser ag = externals[getExternalSeries()].getAxisGuesser(); 
     855      core[i].dimensionOrder = ag.getAdjustedOrder(); 
     856      core[i].orderCertain = ag.isCertain(); 
    1153857      computeAxisLengths(); 
    1154858    } 
     
    1158862    store = reader.getMetadataStore(); 
    1159863    // don't overwrite pixel info if files aren't actually grouped 
    1160     if (!seriesInFile || files.length > 1 || files[0].length > 1) { 
     864    if (!noStitch) { 
    1161865      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); 
     866      for (int i=0; i<getSeriesCount(); i++) { 
     867        int index = getExternalSeries(i); 
     868        store.setImageName(externals[index].getFilePattern().getPattern(), i); 
    1166869      } 
    1167870    } 
     
    1169872 
    1170873  // -- Helper methods -- 
     874 
     875  private int getExternalSeries() { 
     876    return getExternalSeries(getSeries()); 
     877  } 
     878 
     879  private int getExternalSeries(int currentSeries) { 
     880    if (reader.getSeriesCount() > 1) return 0; 
     881    return currentSeries; 
     882  } 
    1171883 
    1172884  /** Computes axis length arrays, and total axis lengths. */ 
    1173885  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])); 
     886    int sno = getSeries(); 
     887    ExternalSeries s = externals[getExternalSeries()]; 
     888    FilePattern p = s.getFilePattern(); 
    1179889 
    1180890    int[] count = p.getCount(); 
    1181891 
    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(); 
     892    initReader(sno, 0); 
     893 
     894    AxisGuesser ag = s.getAxisGuesser(); 
     895    int[] axes = ag.getAxisTypes(); 
     896 
     897    int numZ = ag.getAxisCountZ(); 
     898    int numC = ag.getAxisCountC(); 
     899    int numT = ag.getAxisCountT(); 
     900 
     901    if (axes.length == 0 && s.getFiles().length > 1) { 
     902      axes = new int[] {AxisGuesser.T_AXIS}; 
     903      count = new int[] {s.getFiles().length}; 
     904      numT++; 
     905    } 
    1197906 
    1198907    core[sno].sizeZ = sizeZ[sno]; 
     
    1266975   */ 
    1267976  protected int[] computeIndices(int no) throws FormatException, IOException { 
     977    if (noStitch) return new int[] {0, no}; 
    1268978    int sno = getSeries(); 
    1269  
    1270     int[] axes = ag[sno].getAxisTypes(); 
    1271     int[] count = fp.getCount(); 
     979    ExternalSeries s = externals[getExternalSeries()]; 
     980 
     981    int[] axes = s.getAxisGuesser().getAxisTypes(); 
     982    int[] count = s.getFilePattern().getCount(); 
    1272983 
    1273984    // get Z, C and T positions 
     
    1283994    int[] tmpT = new int[posT.length]; 
    1284995    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     } 
    1301996 
    1302997    // convert Z, C and T position lists into file index and image index 
     
    13141009 
    13151010    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); 
     1011    DimensionSwapper r = getReader(sno, fno); 
    13211012 
    13221013    int ino; 
    1323     if (posZ[0] < readers[sno][fno].getSizeZ() && 
    1324       posC[0] < readers[sno][fno].getSizeC() && 
    1325       posT[0] < readers[sno][fno].getSizeT()) 
     1014    if (posZ[0] < r.getSizeZ() && posC[0] < r.getSizeC() && 
     1015      posT[0] < r.getSizeT()) 
    13261016    { 
    1327       if (readers[sno][fno].isRGB() && 
    1328         (posC[0] * readers[sno][fno].getRGBChannelCount() >= lenC[sno][0])) 
     1017      if (r.isRGB() && (posC[0] * r.getRGBChannelCount() >= lenC[sno][0])) { 
     1018        posC[0] /= lenC[sno][0]; 
     1019      } 
     1020      ino = FormatTools.getIndex(r, posZ[0], posC[0], posT[0]); 
     1021    } 
     1022    else ino = Integer.MAX_VALUE; // coordinates out of range 
     1023 
     1024    return new int[] {fno, ino}; 
     1025  } 
     1026 
     1027  protected void initReader(int sno, int fno) { 
     1028    int external = getExternalSeries(sno); 
     1029    DimensionSwapper r = externals[external].getReaders()[fno]; 
     1030    try { 
     1031      if (r.getCurrentFile() == null) { 
     1032        r.setGroupFiles(false); 
     1033      } 
     1034      r.setId(externals[external].getFiles()[fno]); 
     1035      r.setSeries(reader.getSeriesCount() > 1 ? sno : 0); 
     1036      String newOrder = ((DimensionSwapper) reader).getInputOrder(); 
     1037      if ((externals[external].getFiles().length > 1 || !r.isOrderCertain()) && 
     1038        (r.getRGBChannelCount() == 1 || 
     1039        newOrder.indexOf("C") == r.getDimensionOrder().indexOf("C"))) 
    13291040      { 
    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     } 
     1041        r.swapDimensions(newOrder); 
     1042      } 
     1043      r.setOutputOrder(newOrder); 
     1044    } 
     1045    catch (FormatException e) { 
     1046      LOGGER.debug("", e); 
     1047    } 
     1048    catch (IOException e) { 
     1049      LOGGER.debug("", e); 
     1050    } 
     1051  } 
     1052 
     1053  // -- Helper classes -- 
     1054 
     1055  class ExternalSeries { 
     1056    private DimensionSwapper[] readers; 
     1057    private String[] files; 
     1058    private FilePattern pattern; 
     1059    private byte[] blankThumbBytes; 
     1060    private String originalOrder; 
     1061    private AxisGuesser ag; 
     1062    private int imagesPerFile; 
     1063 
     1064    public ExternalSeries(FilePattern pattern) 
     1065      throws FormatException, IOException 
     1066    { 
     1067      this.pattern = pattern; 
     1068      files = this.pattern.getFiles(); 
     1069 
     1070      readers = new DimensionSwapper[files.length]; 
     1071      for (int i=0; i<readers.length; i++) { 
     1072        readers[i] = new DimensionSwapper(); 
     1073        readers[i].setGroupFiles(false); 
     1074      } 
     1075      readers[0].setId(files[0]); 
     1076 
     1077      ag = new AxisGuesser(this.pattern, readers[0].getDimensionOrder(), 
     1078        readers[0].getSizeZ(), readers[0].getSizeT(), 
     1079        readers[0].getSizeC(), readers[0].isOrderCertain()); 
     1080 
     1081      blankThumbBytes = new byte[FormatTools.getPlaneSize(readers[0], 
     1082        readers[0].getThumbSizeX(), readers[0].getThumbSizeY())]; 
     1083 
     1084      originalOrder = readers[0].getDimensionOrder(); 
     1085      imagesPerFile = readers[0].getImageCount(); 
     1086    } 
     1087 
     1088    public DimensionSwapper[] getReaders() { 
     1089      return readers; 
     1090    } 
     1091 
     1092    public FilePattern getFilePattern() { 
     1093      return pattern; 
     1094    } 
     1095 
     1096    public String getOriginalOrder() { 
     1097      return originalOrder; 
     1098    } 
     1099 
     1100    public AxisGuesser getAxisGuesser() { 
     1101      return ag; 
     1102    } 
     1103 
     1104    public byte[] getBlankThumbBytes() { 
     1105      return blankThumbBytes; 
     1106    } 
     1107 
     1108    public String[] getFiles() { 
     1109      return files; 
     1110    } 
     1111 
     1112    public int getImagesPerFile() { 
     1113      return imagesPerFile; 
     1114    } 
     1115 
    14191116  } 
    14201117 
  • branches/4.2/components/bio-formats/src/loci/formats/FormatTools.java

  • branches/4.2/components/bio-formats/src/loci/formats/FormatWriter.java

  • branches/4.2/components/bio-formats/src/loci/formats/MetadataTools.java

  • branches/4.2/components/bio-formats/src/loci/formats/in

  • branches/4.2/components/bio-formats/src/loci/formats/in/BioRadReader.java

    r7063 r7097  
    278278    in = new RandomAccessInputStream(id); 
    279279    in.order(true); 
    280  
    281     /* debug */ System.out.println("initializing .pic file: " + id); 
    282280 
    283281    offset = new Vector<Double>(); 
  • branches/4.2/components/bio-formats/src/loci/formats/in/DicomReader.java

    r7007 r7097  
    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 
  • branches/4.2/components/bio-formats/src/loci/formats/in/FV1000Reader.java

  • branches/4.2/components/bio-formats/src/loci/formats/in/FakeReader.java

  • branches/4.2/components/bio-formats/src/loci/formats/in/L2DReader.java

    r7007 r7097  
    9292    } 
    9393 
     94    if (parent.getParent() == null) return false; 
    9495    parent = parent.getParentFile(); 
    9596    if (parent == null) return false; 
  • branches/4.2/components/bio-formats/src/loci/formats/in/LIFReader.java

  • branches/4.2/components/bio-formats/src/loci/formats/in/MIASReader.java

    r7007 r7097  
    528528        firstTiff.getName(), firstTiff.getParentFile().getAbsolutePath()); 
    529529      String[] blocks = fp.getPrefixes(); 
    530       BigInteger[] firstNumber = fp.getFirst(); 
    531       BigInteger[] lastNumber = fp.getLast(); 
    532       BigInteger[] step = fp.getStep(); 
    533530 
    534531      order[j] = "XY"; 
     532 
     533      int[] count = fp.getCount(); 
    535534 
    536535      for (int block=blocks.length - 1; block>=0; block--) { 
     
    539538          blocks[block].substring(blocks[block].lastIndexOf("_") + 1); 
    540539 
    541         BigInteger tmp = lastNumber[block].subtract(firstNumber[block]); 
    542         tmp = tmp.add(BigInteger.ONE).divide(step[block]); 
    543         int count = tmp.intValue(); 
    544  
    545540        if (blocks[block].equals("z")) { 
    546           zCount[j] = count; 
     541          zCount[j] = count[block]; 
    547542          order[j] += "Z"; 
    548543        } 
    549544        else if (blocks[block].equals("t")) { 
    550           tCount[j] = count; 
     545          tCount[j] = count[block]; 
    551546          order[j] += "T"; 
    552547        } 
    553548        else if (blocks[block].equals("mode")) { 
    554           cCount[j] = count; 
     549          cCount[j] = count[block]; 
    555550          order[j] += "C"; 
    556551        } 
    557         else if (blocks[block].equals("im")) tileRows = count; 
    558         else if (blocks[block].equals("")) tileCols = count; 
     552        else if (blocks[block].equals("im")) tileRows = count[block]; 
     553        else if (blocks[block].equals("")) tileCols = count[block]; 
    559554        else if (blocks[block].replaceAll("\\d", "").length() == 0) { 
    560           if (block == 3) tileRows = count; 
    561           else if (block == 2) tileCols = count; 
     555          if (block == 3) tileRows = count[block]; 
     556          else if (block == 2) tileCols = count[block]; 
    562557          else if (block == 0) { 
    563             zCount[j] = count; 
     558            zCount[j] = count[block]; 
    564559            order[j] += "Z"; 
    565560          } 
    566561          else if (block == 1) { 
    567             tCount[j] = count; 
     562            tCount[j] = count[block]; 
    568563            order[j] += "T"; 
    569564          } 
  • branches/4.2/components/bio-formats/src/loci/formats/in/TCSReader.java

    r7007 r7097  
    277277      AxisGuesser guesser = 
    278278        new AxisGuesser(fp, "XYTZC", 1, ifds.size(), 1, true); 
    279       BigInteger[] first = fp.getFirst(); 
    280       BigInteger[] last = fp.getLast(); 
    281       BigInteger[] step = fp.getStep(); 
    282279 
    283280      int[] axisTypes = guesser.getAxisTypes(); 
     281      int[] count = fp.getCount(); 
    284282 
    285283      for (int i=axisTypes.length-1; i>=0; i--) { 
    286         int size = last[i].subtract(first[i]).divide(step[i]).intValue() + 1; 
    287284        if (axisTypes[i] == AxisGuesser.Z_AXIS) { 
    288285          if (getDimensionOrder().indexOf("Z") == -1) { 
    289286            core[0].dimensionOrder += "Z"; 
    290287          } 
    291           core[0].sizeZ *= size; 
     288          core[0].sizeZ *= count[i]; 
    292289        } 
    293290        else if (axisTypes[i] == AxisGuesser.C_AXIS) { 
     
    295292            core[0].dimensionOrder += "C"; 
    296293          } 
    297           core[0].sizeC *= size; 
     294          core[0].sizeC *= count[i]; 
    298295        } 
    299296      } 
  • branches/4.2/components/bio-formats/src/loci/formats/in/TillVisionReader.java

  • branches/4.2/components/bio-formats/src/loci/formats/meta

  • branches/4.2/components/bio-formats/src/loci/formats/ome

  • branches/4.2/components/bio-formats/src/loci/formats/services/LuraWaveServiceImpl.java

  • branches/4.2/components/bio-formats/src/loci/formats/services/OMEXMLServiceImpl.java

  • branches/4.2/components/bio-formats/src/loci/formats/tiff

  • branches/4.2/components/bio-formats/test/loci/formats/utests

  • branches/4.2/components/bio-formats/test/loci/formats/utests/WrapperTest.java

    r7007 r7097  
    2929import java.io.IOException; 
    3030 
     31import loci.common.Location; 
    3132import loci.formats.ChannelFiller; 
    3233import loci.formats.ChannelMerger; 
     
    5657  @DataProvider(name = "wrappers") 
    5758  public Object[][] createWrappers() { 
     59    Location.mapId(TEST_FILE, TEST_FILE); 
    5860    Object[][] wrappers = new Object[][] { 
    5961      {new ChannelFiller()}, 
  • branches/4.2/components/bio-formats/utils/bfopen.m

  • branches/4.2/components/common/src/loci/common/xml/XMLTools.java

  • branches/4.2/components/loci-plugins

  • branches/4.2/components/loci-plugins/src/loci/plugins/in/Calibrator.java

  • branches/4.2/components/loci-plugins/src/loci/plugins/in/ImportProcess.java

    r7000 r7097  
    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); 
    493489    } 
    494490    r.setId(options.getId()); 
     491 
     492    if (options.isGroupFiles()) { 
     493      options.setId(fileStitcher.getFilePattern().getPattern()); 
     494    } 
    495495 
    496496    // NB: This test will fail if the LUT is null before calling openBytes. 
  • branches/4.2/components/loci-plugins/src/loci/plugins/out

  • branches/4.2/components/loci-plugins/src/loci/plugins/util/BFVirtualStack.java

    r7000 r7097  
    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  { 
    80     r.setId(path); 
     80    r.setSeries(series); 
    8181    return r.getSizeX(); 
    8282  } 
    8383 
    84   protected static int getHeight(IFormatReader r, String path) 
     84  protected static int getHeight(IFormatReader r, String path, int series) 
    8585    throws FormatException, IOException 
    8686  { 
    87     r.setId(path); 
     87    r.setSeries(series); 
    8888    return r.getSizeY(); 
    8989  } 
     
    9595    throws FormatException, IOException, CacheException 
    9696  { 
    97     super(getWidth(r, path), getHeight(r, path), null, path); 
     97    super(getWidth(r, path, r.getSeries()), getHeight(r, path, r.getSeries()), 
     98      null, path); 
    9899    reader = new ImageProcessorReader(r); 
    99100    id = path; 
  • branches/4.2/components/loci-plugins/test/loci/plugins/in/ImporterTest.java

  • branches/4.2/components/native/bf-cpp/build.properties

  • branches/4.2/components/native/bf-cpp/build.xml

  • branches/4.2/components/native/jar2lib

  • branches/4.2/components/ome-xml

  • branches/4.2/components/test-suite/src/loci/tests/testng/FormatReaderTest.java

    r7007 r7097  
    942942  private boolean initFile() { 
    943943    if (skip) throw new SkipException(SKIP_MESSAGE); 
     944 
     945    // initialize configuration tree 
     946    if (config != null) config.setId(id); 
     947 
    944948    if (reader == null) { 
    945       reader = new BufferedImageReader(new FileStitcher()); 
     949      if (config.noStitching()) { 
     950        reader = new BufferedImageReader(); 
     951      } 
     952      else { 
     953        reader = new BufferedImageReader(new FileStitcher()); 
     954      } 
    946955      reader.setNormalized(true); 
    947956      reader.setMetadataFiltered(true); 
     
    985994        LOGGER.error("Used files list does not include base file"); 
    986995      } 
    987  
    988       // initialize configuration tree 
    989       if (config != null) config.setId(id); 
    990996    } 
    991997    catch (Throwable t) { 
Note: See TracChangeset for help on using the changeset viewer.