// // AmiraParameters.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats.tools; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import loci.common.RandomAccessInputStream; import loci.formats.FormatException; /** * AmiraParameters handles parsing and writing of AmiraMesh headers. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/tools/AmiraParameters.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/tools/AmiraParameters.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Gregory Jefferis jefferis at gmail.com * @author Johannes Schindelin johannes.schindelin at gmx.de */ public class AmiraParameters { public int width, height, depth, firstDataStream; public double x0, y0, z0, x1, y1, z1; public boolean littleEndian, ascii; public int nStreams = 0; public String[] streamNames; public String[] streamTypes; protected RandomAccessInputStream in; protected Map map, streams; protected int column, row; protected char c; public AmiraParameters(String path) throws FormatException { readFile(path); } public AmiraParameters(RandomAccessInputStream inputStream) throws FormatException, IOException { readFile(inputStream); } protected void readFile(String path) throws FormatException { try { readFile(new RandomAccessInputStream(path)); } catch (IOException e) { throw new FormatException("read error: " + path); } } protected void readFile(RandomAccessInputStream inputStream) throws FormatException, IOException { String firstLine = inputStream.readLine(); Matcher amiraMeshDef = Pattern.compile("#\\s+AmiraMesh.*?" + "(BINARY|ASCII)(-LITTLE-ENDIAN)*").matcher(firstLine); if (amiraMeshDef.find()) { if (amiraMeshDef.group(1).equals("BINARY")) { littleEndian = amiraMeshDef.group(2) != null; } else if (amiraMeshDef.group(1).equals("ASCII")) { ascii = true; } else { syntaxError("Can't recognise this Amira file type"); } } else { syntaxError("Doesn't seem to be an Amira file"); } column = 0; row = 1; in = inputStream; readByte(); readTopLevel(); extractCoreMetaData(); } protected void extractCoreMetaData() throws FormatException { // Bounding Box if (map.containsKey("Parameters")) { Map p = (Map) map.get("Parameters"); if (p.containsKey("BoundingBox")) { Double[] bb = (Double[]) p.get("BoundingBox"); x0 = bb[0].doubleValue(); x1 = bb[1].doubleValue(); y0 = bb[2].doubleValue(); y1 = bb[3].doubleValue(); z0 = bb[4].doubleValue(); z1 = bb[5].doubleValue(); } } nStreams = streams.size(); if (nStreams > 0) { streamNames = new String[nStreams]; streamTypes = new String[nStreams]; int i = 0; for (Object key : streams.keySet()) { ArrayList al = (ArrayList) streams.get(key); streamNames[i] = (String) al.get(0); Map streamMap = (Map) al.get(1); Iterator it = streamMap.keySet().iterator(); if (it.hasNext()) { streamTypes[i] = (String) it.next(); } else { syntaxError("Unable to identify data type"); } i++; } } } protected void syntaxError(String message) throws FormatException { throw new FormatException("Syntax Error:" + row + ":" + column + ": " + message); } protected void readTopLevel() throws FormatException, IOException { streams = new LinkedHashMap(); map = new LinkedHashMap(); for (;;) { skipWhiteSpace(); if (c == '#') { skipComment(); continue; } if (c == '@') { readByte(); firstDataStream = readNumber().intValue(); skipComment(); return; } String key = readKey(); skipWhiteSpace(); Object value = null; if (key.equals("define")) { key = "n" + readKey(); skipWhiteSpace(); } if (key.equals("nLattice")) { // FIXME Colormaps have a 1D nLattice // This will probably need further attention Integer[] dimensions = readIntArray(); width = dimensions[0].intValue(); if(dimensions.length > 1) height = dimensions[1].intValue(); if(dimensions.length > 2) depth = dimensions[2].intValue(); value = dimensions; } else if(key.equals("nNodes") || key.equals("nTriangles") || key.equals("nTetrahedra") || key.equals("nEdges")) { throw new FormatException("Don't know yet how to handle " + key); } else if (key.equals("Parameters")) { value = readMap(); } else if (key.equals("Lattice") || key.equals("Vertices") || key.equals("Lines") || key.equals("Markers")) { ArrayList list = new ArrayList(); list.add(key); list.add(readMap()); skipWhiteSpace(); if (c == '=') { readByte(); skipWhiteSpace(); } if (c != '@') syntaxError("Missing @"); // store information in an array readByte(); int index = readNumber().intValue(); if (c == '(') list.add(readQuotedString()); skipComment(); streams.put("@" + index, list); continue; // no need to store this information } else skipComment(); map.put(key, value); } } protected char readByte() throws IOException { c = (char) in.read(); if (c == '\n') { row++; column = 1; } else column++; return c; } protected void skipComment() throws IOException { while (c != '\n') readByte(); } protected void skipWhiteSpace() throws IOException { while (c == ' ' || c == '\t' || c == '\n') readByte(); } protected String readKey() throws IOException { String result = ""; while (c >= '0' || c == '-') { result += c; readByte(); } return result; } protected Number readNumber() throws FormatException, IOException { String string = ""; while ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e') { string += c; readByte(); } try { if (string.indexOf('.') < 0 && string.indexOf('e') < 0) { return Integer.valueOf(string); } return Double.valueOf(string); } catch (NumberFormatException e) { syntaxError(e.getMessage()); return null; // shut up the compiler } } protected Integer[] readIntArray() throws FormatException, IOException { // read integers until end of line ArrayList<Integer> result = new ArrayList<Integer>(); int currentRow = row; // Keep reading until we hit newline or a non-numeric while (currentRow == row && ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+')) { result.add((Integer) readNumber()); skipWhiteSpace(); } Integer[] intResult = new Integer[result.size()]; return result.toArray(intResult); } protected Number[] readNumberArray() throws FormatException, IOException { // read integers until end of line ArrayList<Number> result = new ArrayList<Number>(); boolean intsOnly = true; int currentRow = row; // Keep reading until we hit newline or a non-numeric while (currentRow == row && ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+')) { Number n = readNumber(); if (n instanceof Double) { intsOnly = false; } result.add(n); skipWhiteSpace(); } if (intsOnly) { Integer[] intResult = new Integer[result.size()]; return result.toArray(intResult); } else { // nb this is necessary because we may have a mix // of Integers and Doubles Double[] doubleResult = new Double[result.size()]; for (int i = 0; i < doubleResult.length; i++) { doubleResult[i] = new Double(result.get(i).doubleValue()); } return doubleResult; } } protected Integer[] readIntArray(int count) throws FormatException, IOException { Integer[] result = new Integer[count]; for (int i = 0; i < count; i++) { result[i] = (Integer) readNumber(); skipWhiteSpace(); } return result; } protected Double[] readDoubleArray(int count) throws FormatException, IOException { Double[] result = new Double[count]; for (int i = 0; i < count; i++) { result[i] = new Double(readNumber().doubleValue()); skipWhiteSpace(); } return result; } protected String readQuotedString() throws FormatException, IOException { int quote = c; if (quote == '(') { quote = ')'; } else if (quote != '"' && quote != '\'') { syntaxError("Invalid quote: " + c); } String result = ""; for (;;) { readByte(); if (c == quote) { readByte(); return result; } if (quote == '"' && c == '\\') { readByte(); } result += c; } } protected Map readMap() throws FormatException, IOException { if (c != '{') { syntaxError("Illegal block: " + c); } readByte(); Map subMap = new LinkedHashMap(); for (;;) { skipWhiteSpace(); if (c == '#') { skipComment(); continue; } if (c == '}') { readByte(); return subMap; } String key = readKey(); if (key.equals("")) syntaxError("Invalid key"); skipWhiteSpace(); Object value; if (c == '{') value = readMap(); else { if (key.equals("BoundingBox")) { value = readDoubleArray(6); } else if (key.equals("MinMax")) { value = readDoubleArray(2); } else if (key.startsWith("byte") || key.startsWith("short") || key.startsWith("ushort") || key.startsWith("float")) { value = readKey(); } else if (c == '"' || c == '\'') { value = readQuotedString(); } else { Number[] na = readNumberArray(); if (na.length == 1) value = na[0]; else value = na; } if (c == ',') readByte(); } subMap.put(key, value); } } public Map getMap() { return map; } public Map getStreams() { return streams; } public String toString() { try { return toString(map, "") + toString(streams, ""); } catch (FormatException e) { throw new RuntimeException(e); } } public static String toString(Map map, String indent) throws FormatException { String result = "", separator = indent; Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); result += separator + entry.getKey() + " " + entryToString(entry.getValue(), indent); if (result.endsWith("}")) { separator = "\n" + indent; } else { separator = ",\n" + indent; } } return result + "\n"; } public static String entryToString(Object object, String indent) throws FormatException { if (object instanceof Integer || object instanceof Double) { return object.toString(); } if (object instanceof String) { String string = (String) object, result = "\""; int offset = 0; for (;;) { int nextOffset = string.indexOf('"', offset + 1); if (nextOffset < 0) { break; } if (nextOffset > offset + 1) { result += string.substring(offset, nextOffset); } result += "\\"; offset = nextOffset; } if (offset + 1 < string.length()) { result += string.substring(offset); } return result + "\""; } if (object instanceof Integer[]) { Integer[] array = (Integer[]) object; String result = null; for (int i = 0; i < array.length; i++) { result = (i > 0 ? result + " " : "") + array[i]; } return result; } if (object instanceof Double[]) { Double[] array = (Double[]) object; String result = null; for (int i = 0; i < array.length; i++) { result = (i > 0 ? result + " " : "") + array[i]; } return result; } if (object instanceof Map) { return "{\n" + toString((Map) object, indent + "\t") + indent + "}"; } if (object instanceof ArrayList) { String result = "{\n"; for (Object item : (ArrayList) object) { result += entryToString(item, indent + "\t") + "\n"; } return result + indent + "}"; } throw new FormatException("Illegal value type: " + object.getClass().getName()); } public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println("file: " + args[i]); try { System.out.println(new AmiraParameters(args[i])); } catch (Exception e) { e.printStackTrace(); } } } }