package sparseviewer.io; import sparseviewer.model.ScanDataset; import ij.IJ; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Vector; public class TxtTableReader { public static ScanDataset readFiles(List files) throws IOException { if (files == null || files.isEmpty()) { throw new IOException("No files selected."); } String[] referenceHeader = null; List allRows = new ArrayList(); List fileIndices = new ArrayList(); for (int fileIdx = 0; fileIdx < files.size(); fileIdx++) { File file = files.get(fileIdx); ParsedTxt parsed = readSingleFile(file); if (referenceHeader == null) { referenceHeader = parsed.channelNames; } else { if (!sameHeader(referenceHeader, parsed.channelNames)) { throw new IOException( "Header mismatch in file:\n" + file.getAbsolutePath() ); } } for (int i = 0; i < parsed.rows.size(); i++) { allRows.add(parsed.rows.get(i)); fileIndices.add(Integer.valueOf(fileIdx)); } } int rowCount = allRows.size(); int colCount = referenceHeader.length; double[][] columns = new double[colCount][rowCount]; int[] fileIndex = new int[rowCount]; for (int r = 0; r < rowCount; r++) { double[] row = allRows.get(r); for (int c = 0; c < colCount; c++) { columns[c][r] = row[c]; } fileIndex[r] = fileIndices.get(r).intValue(); } IJ.log("Sparse Scan Viewer: loaded " + files.size() + " file(s), " + rowCount + " rows, " + colCount + " channels."); return new ScanDataset(referenceHeader, columns, rowCount, fileIndex, files); } private static ParsedTxt readSingleFile(File file) throws IOException { BufferedReader br = null; try { br = new BufferedReader(new FileReader(file)); String line; String[] channels = null; int expectedRows = -1; List rows = new ArrayList(); while ((line = br.readLine()) != null) { String trimmed = line.trim(); if (trimmed.length() == 0) { continue; } if (trimmed.startsWith("#table[")) { HeaderInfo info = parseHeader(trimmed); channels = info.channelNames; expectedRows = info.expectedRows; continue; } if (trimmed.startsWith("#")) { continue; } if (channels == null) { continue; } String[] parts = trimmed.split(";"); if (parts.length < channels.length) { continue; } double[] row = new double[channels.length]; boolean valid = true; for (int i = 0; i < channels.length; i++) { try { row[i] = Double.parseDouble(parts[i].trim()); } catch (NumberFormatException e) { valid = false; break; } } if (valid) { rows.add(row); } } if (channels == null) { throw new IOException("No #table[...] header found in " + file.getName()); } if (expectedRows >= 0 && expectedRows != rows.size()) { IJ.log("Warning: " + file.getName() + " header says " + expectedRows + " rows, but parsed " + rows.size() + " rows."); } ParsedTxt parsed = new ParsedTxt(); parsed.channelNames = channels; parsed.rows = rows; return parsed; } finally { if (br != null) { try { br.close(); } catch (IOException ignored) { } } } } private static HeaderInfo parseHeader(String line) throws IOException { int open = line.indexOf('['); int close = line.indexOf(']'); if (open < 0 || close < 0 || close <= open) { throw new IOException("Invalid #table header:\n" + line); } int expectedRows = -1; try { expectedRows = Integer.parseInt(line.substring(open + 1, close).trim()); } catch (Exception ignored) { } String rest = line.substring(close + 1).trim(); String[] rawChannels = rest.split(";"); Vector names = new Vector(); for (int i = 0; i < rawChannels.length; i++) { String name = rawChannels[i].trim(); if (name.length() > 0) { names.add(name); } } if (names.isEmpty()) { throw new IOException("No channels found in header:\n" + line); } HeaderInfo info = new HeaderInfo(); info.expectedRows = expectedRows; info.channelNames = names.toArray(new String[names.size()]); return info; } private static boolean sameHeader(String[] a, String[] b) { if (a == null || b == null) { return false; } if (a.length != b.length) { return false; } for (int i = 0; i < a.length; i++) { if (!a[i].equals(b[i])) { return false; } } return true; } private static class HeaderInfo { int expectedRows; String[] channelNames; } private static class ParsedTxt { String[] channelNames; List rows; } }