/* * Copyright 2011 Christian Thiemann <christian@spato.net> * Developed at Northwestern University <http://rocs.northwestern.edu> * * This file is part of the SPaTo Visual Explorer (SPaTo). * * SPaTo 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 3 of the License, or * (at your option) any later version. * * SPaTo 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 SPaTo. If not, see <http://www.gnu.org/licenses/>. */ package net.spato.sve.app; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Array; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.Arrays; import processing.core.PApplet; import processing.xml.XMLElement; import de.cthiemann.tGUI.TConsole; public class BinaryThing { public static final Class supportedTypes[] = { Integer.TYPE, Float.TYPE, SparseMatrix.class }; protected Class type = null; protected int size[] = null; protected Object blob = null; public BinaryThing() {} public BinaryThing(Object blob) { setBlob(blob); } public Class getType() { return type; } public int[] getSize() { return size; } public int getSize(int i) { return ((size != null) && (i >= 0) && (i < size.length)) ? size[i] : 0; } public boolean is(Class type, int size[]) { return (blob != null) && (this.type == type) && Arrays.equals(this.size, size); } public boolean isInt2(int NN) { return is(Integer.TYPE, new int[] { NN, NN }); } public boolean isFloat1(int NN) { return is(Float.TYPE, new int[] { 1, NN }); } public boolean isFloat2(int NN) { return is(Float.TYPE, new int[] { NN, NN }); } public boolean isSparse(int NN) { return (type == SparseMatrix.class) && (getSparseMatrix().N == NN); } public Object getBlob() { return blob; } public int[][] getIntArray() { return (blob != null) && (type == Integer.TYPE) && (size.length == 2) ? (int[][])blob : null; } public float[][] getFloatArray() { return (blob != null) && (type == Float.TYPE) && (size.length == 2) ? (float[][])blob : null; } public SparseMatrix getSparseMatrix() { return (blob instanceof SparseMatrix) ? (SparseMatrix)blob : null; } public void setBlob(Object blob) { this.type = null; this.size = null; this.blob = blob; if (blob == null) return; type = blob.getClass(); size = new int[0]; while (type.isArray()) { type = type.getComponentType(); size = PApplet.append(size, (blob == null) ? 0 : Array.getLength(blob)); blob = (Array.getLength(blob) > 0) ? Array.get(blob, 0) : null; // we've saved the original blob to this.blob already // FIXME: bad things would happen if the sub-arrays are not all of the same length, but I am too lazy to check here... } } public static BinaryThing parseFromXML(XMLElement xml) throws Exception { return parseFromXML(xml, null); } public static BinaryThing parseFromXML(XMLElement xml, TConsole.Message msg) throws Exception { if (xml == null) return null; BinaryThing res = new BinaryThing(); res.type = null; res.size = null; res.blob = null; XMLElement tmp[] = null; // will hold the slice/values/source children XMLElement xmlp = xml; // the first non-snapshot ancestor while (xmlp.getName().equals("snapshot")) if ((xmlp = xmlp.getParent()) == null) throw new Exception("choked on orphaned snapshot"); // determine type if (xmlp.getName().equals("slices")) { res.type = Integer.TYPE; tmp = xml.getChildren("slice"); } else if (xmlp.getName().equals("data")) { res.type = Float.TYPE; tmp = xml.getChildren("values"); } else if (xmlp.getName().equals("links")) { res.type = SparseMatrix.class; tmp = xml.getChildren("source"); } else throw new Exception("unsupported XML element \u2018" + xmlp.getName() + "\u2019"); if (((tmp == null) || (tmp.length == 0)) && (res.type != SparseMatrix.class)) throw new Exception("no values found"); // determine size and create blob int N = tmp.length, NN = 0; if (res.type == SparseMatrix.class) { N = xml.getInt("size"); for (int i = 0; i < tmp.length; i++) { N = PApplet.max(N, tmp[i].getInt("index")); XMLElement tmp2[] = tmp[i].getChildren("target"); for (int j = 0; j < tmp2.length; j++) N = PApplet.max(N, tmp2[j].getInt("index", 0)); if (msg != null) msg.updateProgress(i, 2*N); } res.blob = new SparseMatrix(N); } else { if (tmp[0].getContent() == null) throw new Exception("no values in first " + tmp[0].getName() + " element"); NN = PApplet.splitTokens(PApplet.trim(tmp[0].getContent())).length; if (!((N == NN) || (N == 1))) throw new Exception("wrong number of " + tmp[0].getName() + " elements"); res.size = new int[] { N, NN }; res.blob = Array.newInstance(res.type, res.size); } // parse values boolean filled[] = new boolean[N]; // track which indices have been filled already String indexAttr = (res.type == SparseMatrix.class) ? "index" : "root"; for (int i = 0; i < N; i++) { String inElemStr = " in " + tmp[i].getName() + " element " + (i+1); int ii = (N == 1) ? 0 : tmp[i].getInt(indexAttr, 0) - 1; if ((ii < 0) || (ii >= N)) throw new Exception("invalid " + indexAttr + " " + tmp[i].getString(indexAttr) + inElemStr); if (filled[ii]) throw new Exception("duplicate " + indexAttr + " value" + inElemStr); if (res.type == SparseMatrix.class) { SparseMatrix matrix = (SparseMatrix)res.blob; XMLElement tmp2[] = tmp[i].getChildren("target"); matrix.index[ii] = new int[tmp2.length]; matrix.value[ii] = new float[tmp2.length]; boolean filled2[] = new boolean[N]; for (int j = 0; j < tmp2.length; j++) { String inElemStr2 = inElemStr + ", " + tmp2[j].getName() + " " + (j+1); int jj = matrix.index[ii][j] = tmp2[j].getInt("index", 0) - 1; if ((jj < 0) || (jj >= N)) throw new Exception("invalid index " + tmp2[j].getString("index") + inElemStr2); if (filled2[jj]) throw new Exception("duplicate index value" + inElemStr2); if (Float.isNaN(matrix.value[ii][j] = tmp2[j].getFloat("weight", Float.NaN))) throw new Exception("invalid weight value " + tmp[j].getString("weight") + inElemStr2); } if (msg != null) msg.updateProgress(i + N, 2*N); } else { if (tmp[i].getContent() == null) throw new Exception("no values" + inElemStr); Object row = parseLine(tmp[i].getContent(), ' ', res.type); if (Array.getLength(row) != NN) throw new Exception("wrong number of values" + inElemStr); Array.set(res.blob, ii, row); if (msg != null) msg.updateProgress(i, N); } } // post-process if necessary if (xml.getName().equals("slices")) { int pred[][] = (int[][])res.blob; for (int r = 0; r < NN; r++) for (int i = 0; i < NN; i++) pred[r][i]--; // indices are 1-based in XML, but 0-based in binary } return res; } public static BinaryThing parseFromText(String lines[], char sep) throws Exception { return parseFromText(lines, sep, Float.TYPE, null); } public static BinaryThing parseFromText(String lines[], char sep, TConsole.Message msg) throws Exception { return parseFromText(lines, sep, Float.TYPE, msg); } public static BinaryThing parseFromText(String lines[], char sep, Class type) throws Exception { return parseFromText(lines, sep, type, null); } public static BinaryThing parseFromText(String lines[], char sep, Class type, TConsole.Message msg) throws Exception { if (type == SparseMatrix.class) return parseSparseFromText(lines, sep, msg); if ((lines == null) || (lines.length == 0) || (lines[0] == null) || (Array.getLength(parseLine(lines[0], sep, type)) == 0)) throw new Exception("no data to parse"); BinaryThing res = new BinaryThing(); res.type = type; res.size = new int[] { lines.length, Array.getLength(parseLine(lines[0], sep, type)) }; res.blob = Array.newInstance(res.type, res.size); int i = -1; try { for (i = 0; i < res.size[0]; i++) { Object row = parseLine(lines[i], sep, res.type); if (Array.getLength(row) != res.size[1]) throw new Exception("wrong number of elements"); Array.set(res.blob, i, row); if (msg != null) msg.updateProgress(i, res.size[0]); } } catch (Exception e) { throw new Exception("line " + (i+1) + ": " + e.getMessage()); } return res; } private static Object parseLine(String line, char sep, Class type) throws Exception { String pieces[] = (sep == ' ') ? PApplet.splitTokens(PApplet.trim(line), PApplet.WHITESPACE) : PApplet.split(line, sep); Object result = Array.newInstance(type, new int[] { pieces.length }); int i = -1; try { for (i = 0; i < pieces.length; i++) if (type == Integer.TYPE) Array.set(result, i, Integer.valueOf(pieces[i]).intValue()); else Array.set(result, i, Float.valueOf(pieces[i]).floatValue()); } catch (Exception e) { throw new Exception("not a number: " + pieces[i]); } return result; } private static BinaryThing parseSparseFromText(String lines[], char sep, TConsole.Message msg) throws Exception { if ((lines == null) || (lines.length % 2 != 0)) throw new Exception("no data or odd number of lines"); SparseMatrix matrix = new SparseMatrix(lines.length/2); int i = -1; try { for (i = 0; i < lines.length/2; i++) { matrix.index[i] = (int[])parseLine(lines[2*i+0], sep, Integer.TYPE); for (int j = 0; j < matrix.index[i].length; j++) if (--matrix.index[i][j] < 0) // indices are 1-based in text files throw new Exception("invalid index: " + (matrix.index[i][j]+1)); matrix.value[i] = (float[])parseLine(lines[2*i+1], sep, Float.TYPE); if (matrix.index[i].length != matrix.value[i].length) throw new Exception("index/value length mismatch"); } } catch (Exception e) { throw new Exception("row " + (i+1) + ": " + e.getMessage()); } return new BinaryThing(matrix); } public static BinaryThing loadFromStream(DataInputStream in) throws Exception { return loadFromStream(in, null); } public static BinaryThing loadFromStream(DataInputStream in, TConsole.Message msg) throws Exception { BinaryThing res = new BinaryThing(); res.blob = null; res.type = null; res.size = null; // read type int tmp = in.readInt(); if ((tmp < 0) || (tmp >= supportedTypes.length)) throw new Exception("unknown data type"); res.type = supportedTypes[tmp]; // read array size if (res.type == SparseMatrix.class) res.blob = new SparseMatrix(in.readInt()); else { res.size = new int[0]; while ((tmp = in.readInt()) != -1) res.size = PApplet.append(res.size, tmp); } // read data if (res.type == SparseMatrix.class) { SparseMatrix matrix = res.getSparseMatrix(); for (int i = 0; i < matrix.N; i++) { int M = in.readInt(); matrix.index[i] = (int[])loadFromStream(in, new int[M]); matrix.value[i] = (float[])loadFromStream(in, new float[M]); if (msg != null) msg.updateProgress(i, matrix.N); } } else { res.blob = Array.newInstance(res.type, res.size); for (int i = 0; i < res.size[0]; i++) { Array.set(res.blob, i, loadFromStream(in, Array.get(res.blob, i))); if (msg != null) msg.updateProgress(i, res.size[0]); } } return res; } private static Object loadFromStream(DataInputStream in, Object o) throws Exception { // read single value if o is not an array if (!o.getClass().isArray()) { if (o.getClass().getComponentType() == Integer.TYPE) return in.readInt(); if (o.getClass().getComponentType() == Float.TYPE) return in.readFloat(); return null; } // fill 1-D array with values from stream Class type = o.getClass().getComponentType(); if (!type.isArray()) { byte bytes[] = new byte[4*Array.getLength(o)]; ByteBuffer bbuf = ByteBuffer.wrap(bytes); int off = 0, n; while (off < bytes.length) if ((n = in.read(bytes, off, bytes.length - off)) == -1) throw new IOException("unexpected end-of-file"); else off += n; if (type == Integer.TYPE) bbuf.asIntBuffer().get((int[])o); else if (type == Float.TYPE) bbuf.asFloatBuffer().get((float[])o); return o; } // recursively fill multi-dim array for (int i = 0; i < Array.getLength(o); i++) Array.set(o, i, loadFromStream(in, Array.get(o, i))); return o; } public void saveToStream(DataOutputStream out) throws Exception { saveToStream(out, null); } public void saveToStream(DataOutputStream out, TConsole.Message msg) throws Exception { if ((type == null) || (blob == null)) return; // write type int typeID = -1; for (int i = 0; i < supportedTypes.length; i++) if (type == supportedTypes[i]) typeID = i; if (typeID == -1) throw new Exception("unsupported data type"); out.writeInt(typeID); // write size if (blob instanceof SparseMatrix) out.writeInt(getSparseMatrix().N); else { for (int i = 0; i < size.length; i++) out.writeInt(size[i]); out.writeInt(-1); // array size terminator } // write data if (blob instanceof SparseMatrix) { SparseMatrix matrix = getSparseMatrix(); for (int i = 0; i < matrix.N; i++) { if (matrix.index[i].length != matrix.value[i].length) throw new Exception("index/value length mismatch at row " + (i+1)); out.writeInt(matrix.index[i].length); saveToStream(out, matrix.index[i]); saveToStream(out, matrix.value[i]); if (msg != null) msg.updateProgress(i, matrix.N); } } else { for (int i = 0; i < size[0]; i++) { saveToStream(out, Array.get(blob, i)); if (msg != null) msg.updateProgress(i, size[0]); } } } private void saveToStream(DataOutputStream out, Object o) throws Exception { Class type = null; if (!(type = o.getClass()).isArray()) { // write single value if (type == Integer.TYPE) out.writeInt(((Integer)o).intValue()); else if (type == Float.TYPE) out.writeFloat(((Float)o).floatValue()); } else if (!(type = o.getClass().getComponentType()).isArray()) { // write 1-D array byte bytes[] = new byte[4*Array.getLength(o)]; ByteBuffer bbuf = ByteBuffer.wrap(bytes); Buffer buf = (type == Integer.TYPE) ? bbuf.asIntBuffer() : (type == Float.TYPE) ? bbuf.asFloatBuffer() : null; if (type == Integer.TYPE) ((IntBuffer)buf).put((int[])o); else if (type == Float.TYPE) ((FloatBuffer)buf).put((float[])o); out.write(bytes, 0, bytes.length); } else { // recursively write multi-dim array for (int i = 0; i < Array.getLength(o); i++) saveToStream(out, Array.get(o, i)); } } /* If size.length == 2, this will replace the blob with a new array of size { size[1] size[0] }, * such that newblob[i][j] == oldblob[j][i]. If size.length != 2, nothing happens. FIXME: throw exception? */ public void transpose() { if ((type == null) || (blob == null)) return; if (blob instanceof SparseMatrix) { BinaryThing bt = new BinaryThing(((SparseMatrix)blob).getFullMatrix()); bt.transpose(); blob = new SparseMatrix(bt.getFloatArray()); } else { if ((size == null) || (size.length != 2)) return; Object oldblob = blob; size = new int[] { size[1], size[0] }; blob = Array.newInstance(type, size); for (int i = 0; i < size[0]; i++) for (int j = 0; j < size[1]; j++) Array.set(Array.get(blob, i), j, Array.get(Array.get(oldblob, j), i)); } } /* Reshapes the current data to fit the new size if prod(newsize) == prod(size). It does so by * reshaping into a linear array and then into the new format. Linearization/delinearization is * row-first; i.e. the last dimension will be filled up first. */ public void reshape(int newsize[]) { if ((type == null) || (size == null) || (newsize == null) || (size.length*newsize.length == 0)) return; int prodsize = 1; for (int i = 0; i < size.length; i++) prodsize *= size[i]; int prodnewsize = 1; for (int i = 0; i < newsize.length; i++) prodnewsize *= newsize[i]; if (prodsize != prodnewsize) return; // reshape into linear blob Object linear = Array.newInstance(type, new int[] { prodsize }); linearize(blob, linear); // create new blob and delinearize blob = Array.newInstance(type, size = newsize); delinearize(linear, blob); } private void linearize(Object src, Object dst) { linearize(src, dst, 0, 0, Array.getLength(dst)); } private void linearize(Object src, Object dst, int dim, int offset, int prodsize) { prodsize /= size[dim]; for (int i = 0; i < Array.getLength(src); i++) if (dim == size.length-1) Array.set(dst, offset + i, Array.get(src, i)); else linearize(Array.get(src, i), dst, dim+1, offset + i*prodsize, prodsize); } private void delinearize(Object src, Object dst) { delinearize(src, dst, 0, 0, Array.getLength(src)); } private void delinearize(Object src, Object dst, int dim, int offset, int prodsize) { prodsize /= size[dim]; for (int i = 0; i < Array.getLength(dst); i++) if (dim == size.length-1) Array.set(dst, i, Array.get(src, offset + i)); else delinearize(src, Array.get(dst, i), dim+1, offset + i*prodsize, prodsize); } public String toString() { if ((type == null) || (blob == null)) return "null"; if (blob instanceof SparseMatrix) return "SparseMatrix(" + getSparseMatrix().N + ")"; String res = type.getName(); if (size != null) for (int i = 0; i < size.length; i++) res += "[" + size[i] + "]"; return res; } }