// // AxisGuesser.java // /* LOCI Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan, Eric Kjellman and Brian Loranger. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats; import java.io.IOException; import java.math.BigInteger; /** * AxisGuesser guesses which blocks in a file pattern correspond to which * dimensional axes (Z, T or C), potentially recommending an adjustment in * dimension order within the files, depending on the confidence of each guess. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/AxisGuesser.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/AxisGuesser.java">SVN</a></dd></dl> * * @author Curtis Rueden ctrueden at wisc.edu */ public class AxisGuesser { // -- Constants -- /** Axis type for unclassified axes. */ public static final int UNKNOWN_AXIS = 0; /** Axis type for focal planes. */ public static final int Z_AXIS = 1; /** Axis type for time points. */ public static final int T_AXIS = 2; /** Axis type for channels. */ public static final int C_AXIS = 3; /** Prefix endings indicating space dimension. */ protected static final String[] Z = { "fp", "sec", "z", "zs", "focal", "focalplane" }; /** Prefix endings indicating time dimension. */ protected static final String[] T = {"t", "tl", "tp", "time"}; /** Prefix endings indicating channel dimension. */ protected static final String[] C = {"c", "ch", "w", "wavelength"}; protected static final BigInteger TWO = new BigInteger("2"); protected static final BigInteger THREE = new BigInteger("3"); // -- Fields -- /** File pattern identifying dimensional axis blocks. */ protected FilePattern fp; /** Original ordering of internal dimensional axes. */ protected String dimOrder; /** Adjusted ordering of internal dimensional axes. */ protected String newOrder; /** Guessed axis types. */ protected int[] axisTypes; /** Whether the guesser is confident that all axis types are correct. */ protected boolean certain; // -- Constructor -- /** * Guesses dimensional axis assignments corresponding to the given * file pattern, using the specified dimensional information from * within each file as a guide. * * @param fp The file pattern of the files * @param dimOrder The dimension order (e.g., XYZTC) within each file * @param sizeZ The number of Z positions within each file * @param sizeT The number of T positions within each file * @param sizeC The number of C positions within each file * @param isCertain Whether the dimension order given is known to be good, * or merely a guess * * @see FilePattern */ public AxisGuesser(FilePattern fp, String dimOrder, int sizeZ, int sizeT, int sizeC, boolean isCertain) { this.fp = fp; this.dimOrder = dimOrder; newOrder = dimOrder; String[] prefixes = fp.getPrefixes(); String suffix = fp.getSuffix(); BigInteger[] first = fp.getFirst(); BigInteger[] last = fp.getLast(); BigInteger[] step = fp.getStep(); int[] count = fp.getCount(); axisTypes = new int[count.length]; boolean foundZ = false, foundT = false, foundC = false; // -- 1) fill in "known" axes based on known patterns and conventions -- for (int i=0; i<axisTypes.length; i++) { String p = prefixes[i].toLowerCase(); // strip trailing digits and divider characters char[] ch = p.toCharArray(); int l = ch.length - 1; while (l >= 0 && (ch[l] >= '0' && ch[l] <= '9' || ch[l] == ' ' || ch[l] == '-' || ch[l] == '_' || ch[l] == '.')) { l--; } // useful prefix segment consists of trailing alphanumeric characters int f = l; while (f >= 0 && ch[f] >= 'a' && ch[f] <= 'z') f--; p = p.substring(f + 1, l + 1); // check against known Z prefixes for (int j=0; j<Z.length; j++) { if (p.equals(Z[j])) { axisTypes[i] = Z_AXIS; foundZ = true; break; } } if (axisTypes[i] != UNKNOWN_AXIS) continue; // check against known T prefixes for (int j=0; j<T.length; j++) { if (p.equals(T[j])) { axisTypes[i] = T_AXIS; foundT = true; break; } } if (axisTypes[i] != UNKNOWN_AXIS) continue; // check against known C prefixes for (int j=0; j<C.length; j++) { if (p.equals(C[j])) { axisTypes[i] = C_AXIS; foundC = true; break; } } if (axisTypes[i] != UNKNOWN_AXIS) continue; // check special case: <2-3> (Bio-Rad PIC) if (first[i].equals(TWO) && last[i].equals(THREE) && step[i].equals(BigInteger.ONE) && suffix.equalsIgnoreCase(".pic")) { axisTypes[i] = C_AXIS; foundC = true; break; } } // -- 2) check for special cases where dimension order should be swapped -- if (!isCertain) { // only switch if dimension order is uncertain if (foundZ && !foundT && sizeZ > 1 && sizeT == 1 || foundT && !foundZ && sizeT > 1 && sizeZ == 1) { // swap Z and T dimensions int indexZ = newOrder.indexOf('Z'); int indexT = newOrder.indexOf('T'); char[] ch = newOrder.toCharArray(); ch[indexZ] = 'T'; ch[indexT] = 'Z'; newOrder = new String(ch); int sz = sizeT; sizeT = sizeZ; sizeZ = sz; } } // -- 3) fill in remaining axis types -- boolean canBeZ = !foundZ && sizeZ == 1; boolean canBeT = !foundT && sizeT == 1; certain = isCertain; for (int i=0; i<axisTypes.length; i++) { if (axisTypes[i] != UNKNOWN_AXIS) continue; certain = false; if (canBeZ) { axisTypes[i] = Z_AXIS; canBeZ = false; } else if (canBeT) { axisTypes[i] = T_AXIS; canBeT = false; } else axisTypes[i] = C_AXIS; } } // -- AxisGuesser API methods -- /** Gets the file pattern. */ public FilePattern getFilePattern() { return fp; } /** Gets the original dimension order. */ public String getOriginalOrder() { return dimOrder; } /** Gets the adjusted dimension order. */ public String getAdjustedOrder() { return newOrder; } /** Gets whether the guesser is confident that all axes are correct. */ public boolean isCertain() { return certain; } /** * Gets the guessed axis type for each dimensional block. * @return An array containing values from the enumeration: * <ul> * <li>Z_AXIS: focal planes</li> * <li>T_AXIS: time points</li> * <li>C_AXIS: channels</li> * </ul> */ public int[] getAxisTypes() { return axisTypes; } /** * Sets the axis type for each dimensional block. * @param axes An array containing values from the enumeration: * <ul> * <li>Z_AXIS: focal planes</li> * <li>T_AXIS: time points</li> * <li>C_AXIS: channels</li> * </ul> */ public void setAxisTypes(int[] axes) { axisTypes = axes; } /** Gets the number of Z axes in the pattern. */ public int getAxisCountZ() { return getAxisCount(Z_AXIS); } /** Gets the number of T axes in the pattern. */ public int getAxisCountT() { return getAxisCount(T_AXIS); } /** Gets the number of C axes in the pattern. */ public int getAxisCountC() { return getAxisCount(C_AXIS); } /** Gets the number of axes in the pattern of the given type. * @param axisType One of: * <ul> * <li>Z_AXIS: focal planes</li> * <li>T_AXIS: time points</li> * <li>C_AXIS: channels</li> * </ul> */ public int getAxisCount(int axisType) { int num = 0; for (int i=0; i<axisTypes.length; i++) { if (axisTypes[i] == axisType) num++; } return num; } // -- Main method -- /** Method for testing pattern guessing logic. */ public static void main(String[] args) throws FormatException, IOException { Location file = args.length < 1 ? new Location(System.getProperty("user.dir")).listFiles()[0] : new Location(args[0]); LogTools.println("File = " + file.getAbsoluteFile()); String pat = FilePattern.findPattern(file); if (pat == null) LogTools.println("No pattern found."); else { LogTools.println("Pattern = " + pat); FilePattern fp = new FilePattern(pat); if (fp.isValid()) { LogTools.println("Pattern is valid."); String id = fp.getFiles()[0]; if (!new Location(id).exists()) { LogTools.println("File '" + id + "' does not exist."); } else { // read dimensional information from first file LogTools.print("Reading first file "); ImageReader reader = new ImageReader(); reader.setId(id); String dimOrder = reader.getDimensionOrder(); int sizeZ = reader.getSizeZ(); int sizeT = reader.getSizeT(); int sizeC = reader.getSizeC(); boolean certain = reader.isOrderCertain(); reader.close(); LogTools.println("[done]"); LogTools.println("\tdimOrder = " + dimOrder + (certain ? " (certain)" : " (uncertain)")); LogTools.println("\tsizeZ = " + sizeZ); LogTools.println("\tsizeT = " + sizeT); LogTools.println("\tsizeC = " + sizeC); // guess axes AxisGuesser ag = new AxisGuesser(fp, dimOrder, sizeZ, sizeT, sizeC, certain); // output results String[] blocks = fp.getBlocks(); String[] prefixes = fp.getPrefixes(); int[] axes = ag.getAxisTypes(); String newOrder = ag.getAdjustedOrder(); LogTools.println("Axis types:"); for (int i=0; i<blocks.length; i++) { String axis; switch (axes[i]) { case Z_AXIS: axis = "Z"; break; case T_AXIS: axis = "T"; break; case C_AXIS: axis = "C"; break; default: axis = "?"; } LogTools.println("\t" + blocks[i] + "\t" + axis + " (prefix = " + prefixes[i] + ")"); } if (!dimOrder.equals(newOrder)) { LogTools.println("Adjusted dimension order = " + newOrder); } } } else LogTools.println("Pattern is invalid: " + fp.getErrorMessage()); } } } // -- Notes -- // INPUTS: file pattern, dimOrder, sizeZ, sizeT, sizeC, isCertain // // 1) Fill in all "known" dimensional axes based on known patterns and // conventions // * known internal axes (ZCT) have isCertain == true // * known dimensional axes have a known pattern or convention // After that, we are left with only unknown slots, which we must guess. // // 2) First, we decide whether we really "believe" the reader. There is a // special case where we may decide that it got Z and T mixed up: // * if a Z block was found, but not a T block: // if !isOrderCertain, and sizeZ > 1, and sizeT == 1, swap 'em // * else if a T block was found, but not a Z block: // if !isOrderCertain and sizeT > 1, and sizeZ == 1, swap 'em // At this point, we can (have to) trust the internal ordering, and use it // to decide how to fill in the remaining dimensional blocks. // // 3) Set canBeZ to true iff no Z block is assigned and sizeZ == 1. // Set canBeT to true iff no T block is assigned and sizeT == 1. // Go through the blocks in order from left to right: // * If canBeZ, assign Z and set canBeZ to false. // * If canBeT, assign T and set canBeT to false. // * Otherwise, assign C. // // OUTPUTS: list of axis assignments, new dimOrder