package sparseviewer.io; import sparseviewer.mda.MdaDetector; import sparseviewer.mda.MdaPositioner; import sparseviewer.mda.MdaScan; import sparseviewer.mda.XdrInput; import sparseviewer.model.ScanDataset; import sparseviewer.mda.MdaFileData; import ij.IJ; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class MdaReader { public static ScanDataset readFiles(List files) throws IOException { if (files == null || files.isEmpty()) { throw new IOException("No MDA file selected."); } if (files.size() > 1) { throw new IOException("MDA support currently expects a single file."); } File file = files.get(0); MdaFileData mda = readMdaFile(file); if (mda.rank == 1) { return toScanDatasetRank1(mda, file); } if (mda.rank == 2) { return toScanDatasetRank2(mda, file); } throw new IOException("Only MDA rank 1 and rank 2 are supported currently. File rank = " + mda.rank); } private static MdaFileData readMdaFile(File file) throws IOException { XdrInput in = new XdrInput(file); try { MdaFileData data = new MdaFileData(); data.version = in.readFloat(); if (Math.abs(data.version - 1.3f) > 0.02f && Math.abs(data.version - 1.4f) > 0.02f) { throw new IOException("Unsupported MDA version: " + data.version); } data.scanNumber = in.readInt(); data.rank = in.readInt(); if (data.rank < 1 || data.rank > 4) { throw new IOException("Invalid MDA rank: " + data.rank); } data.dimensions = in.readIntArray(data.rank); data.isRegular = in.readInt(); data.pExtra = in.readInt(); long mainScanOffset = in.getFilePointer(); data.mainScan = readScan(in, mainScanOffset); if (data.rank == 2) { readRank2InnerScans(in, data); } IJ.log("MDA loaded: " + file.getName() + ", version=" + data.version + ", rank=" + data.rank + ", scanNumber=" + data.scanNumber); return data; } finally { in.close(); } } private static void readRank2InnerScans(XdrInput in, MdaFileData data) throws IOException { MdaScan outer = data.mainScan; if (outer.lowerScanOffsets == null) { throw new IOException("Rank 2 MDA file has no lower scan offsets."); } int nOuter = Math.min(outer.currPt, outer.lowerScanOffsets.length); for (int i = 0; i < nOuter; i++) { int offset = outer.lowerScanOffsets[i]; if (offset <= 0) { data.innerScans.add(null); continue; } MdaScan inner = readScan(in, offset); data.innerScans.add(inner); } } private static MdaScan readScan(XdrInput in, long offset) throws IOException { in.seek(offset); MdaScan scan = new MdaScan(); scan.rank = in.readInt(); if (scan.rank < 0 || scan.rank > 20) { throw new IOException("Invalid scan rank at offset " + offset + ": " + scan.rank); } scan.npts = in.readInt(); scan.currPt = in.readInt(); if (scan.rank > 1) { scan.lowerScanOffsets = in.readIntArray(scan.npts); } scan.name = in.readStringWithLeadingLength(); scan.time = in.readStringWithLeadingLength(); scan.np = in.readInt(); scan.nd = in.readInt(); scan.nt = in.readInt(); for (int i = 0; i < scan.np; i++) { MdaPositioner p = new MdaPositioner(); p.number = in.readInt(); p.fieldName = posName(p.number); p.name = in.readStringWithLeadingLength(); p.desc = in.readStringWithLeadingLength(); p.stepMode = in.readStringWithLeadingLength(); p.unit = in.readStringWithLeadingLength(); p.readbackName = in.readStringWithLeadingLength(); p.readbackDesc = in.readStringWithLeadingLength(); p.readbackUnit = in.readStringWithLeadingLength(); scan.positioners.add(p); } for (int i = 0; i < scan.nd; i++) { MdaDetector d = new MdaDetector(); d.number = in.readInt(); d.fieldName = detName(d.number); d.name = in.readStringWithLeadingLength(); d.desc = in.readStringWithLeadingLength(); d.unit = in.readStringWithLeadingLength(); scan.detectors.add(d); } // Triggers are not needed for ScanDataset, but must be consumed. for (int i = 0; i < scan.nt; i++) { in.readInt(); // trigger number in.readStringWithLeadingLength(); // trigger name in.readFloat(); // command } readScanData(in, scan); return scan; } private static void readScanData(XdrInput in, MdaScan scan) throws IOException { for (int p = 0; p < scan.np; p++) { double[] values = new double[scan.npts]; for (int i = 0; i < scan.npts; i++) { values[i] = in.readDouble(); } scan.positioners.get(p).data = values; } for (int d = 0; d < scan.nd; d++) { float[] values = new float[scan.npts]; for (int i = 0; i < scan.npts; i++) { values[i] = in.readFloat(); } scan.detectors.get(d).data = values; } } private static ScanDataset toScanDatasetRank1(MdaFileData mda, File file) { MdaScan scan = mda.mainScan; int rowCount = scan.currPt; int channelCount = scan.np + scan.nd; String[] channelNames = new String[channelCount]; double[][] columns = new double[channelCount][rowCount]; int[] fileIndex = new int[rowCount]; int c = 0; for (int p = 0; p < scan.np; p++) { MdaPositioner pos = scan.positioners.get(p); channelNames[c] = makePositionerName("rank1", pos); for (int i = 0; i < rowCount; i++) { columns[c][i] = safeDouble(pos.data, i); } c++; } for (int d = 0; d < scan.nd; d++) { MdaDetector det = scan.detectors.get(d); channelNames[c] = makeDetectorName("rank1", det); for (int i = 0; i < rowCount; i++) { columns[c][i] = safeFloat(det.data, i); } c++; } for (int i = 0; i < rowCount; i++) { fileIndex[i] = 0; } List sourceFiles = new ArrayList(); sourceFiles.add(file); IJ.log("MDA rank 1 converted to ScanDataset: " + rowCount + " rows, " + channelCount + " channels."); return new ScanDataset(channelNames, columns, rowCount, fileIndex, sourceFiles); } private static ScanDataset toScanDatasetRank2(MdaFileData mda, File file) throws IOException { MdaScan outer = mda.mainScan; MdaScan firstInner = findFirstInnerScan(mda); if (firstInner == null) { throw new IOException("Rank 2 MDA file contains no readable inner scan."); } int rowCount = 0; for (int j = 0; j < mda.innerScans.size(); j++) { MdaScan inner = mda.innerScans.get(j); if (inner != null) { rowCount += inner.currPt; } } int channelCount = outer.np + outer.nd + firstInner.np + firstInner.nd; String[] channelNames = new String[channelCount]; double[][] columns = new double[channelCount][rowCount]; int[] fileIndex = new int[rowCount]; int c = 0; for (int p = 0; p < outer.np; p++) { channelNames[c++] = makePositionerName("outer", outer.positioners.get(p)); } for (int d = 0; d < outer.nd; d++) { channelNames[c++] = makeDetectorName("outer", outer.detectors.get(d)); } for (int p = 0; p < firstInner.np; p++) { channelNames[c++] = makePositionerName("inner", firstInner.positioners.get(p)); } for (int d = 0; d < firstInner.nd; d++) { channelNames[c++] = makeDetectorName("inner", firstInner.detectors.get(d)); } int row = 0; for (int j = 0; j < mda.innerScans.size(); j++) { MdaScan inner = mda.innerScans.get(j); if (inner == null) { continue; } int innerRows = inner.currPt; for (int k = 0; k < innerRows; k++) { c = 0; for (int p = 0; p < outer.np; p++) { columns[c++][row] = safeDouble(outer.positioners.get(p).data, j); } for (int d = 0; d < outer.nd; d++) { columns[c++][row] = safeFloat(outer.detectors.get(d).data, j); } for (int p = 0; p < firstInner.np; p++) { if (p < inner.positioners.size()) { columns[c++][row] = safeDouble(inner.positioners.get(p).data, k); } else { columns[c++][row] = Double.NaN; } } for (int d = 0; d < firstInner.nd; d++) { if (d < inner.detectors.size()) { columns[c++][row] = safeFloat(inner.detectors.get(d).data, k); } else { columns[c++][row] = Double.NaN; } } fileIndex[row] = 0; row++; } } List sourceFiles = new ArrayList(); sourceFiles.add(file); IJ.log("MDA rank 2 converted to ScanDataset: " + rowCount + " rows, " + channelCount + " channels."); return new ScanDataset(channelNames, columns, rowCount, fileIndex, sourceFiles); } private static MdaScan findFirstInnerScan(MdaFileData mda) { for (int i = 0; i < mda.innerScans.size(); i++) { MdaScan scan = mda.innerScans.get(i); if (scan != null) { return scan; } } return null; } private static String makePositionerName(String prefix, MdaPositioner p) { String pv = clean(p.name); String desc = clean(p.desc); if (pv.length() > 0 && desc.length() > 0) { return prefix + "/" + p.fieldName + "/" + pv + " {" + desc + "}"; } if (pv.length() > 0) { return prefix + "/" + p.fieldName + "/" + pv; } if (desc.length() > 0) { return prefix + "/" + p.fieldName + "/{" + desc + "}"; } return prefix + "/" + p.fieldName; } private static String makeDetectorName(String prefix, MdaDetector d) { String pv = clean(d.name); String desc = clean(d.desc); if (pv.length() > 0 && desc.length() > 0) { return prefix + "/" + d.fieldName + "/" + pv + " {" + desc + "}"; } if (pv.length() > 0) { return prefix + "/" + d.fieldName + "/" + pv; } if (desc.length() > 0) { return prefix + "/" + d.fieldName + "/{" + desc + "}"; } return prefix + "/" + d.fieldName; } private static String clean(String s) { return s == null ? "" : s.trim(); } private static double safeDouble(double[] data, int index) { if (data == null || index < 0 || index >= data.length) { return Double.NaN; } return data[index]; } private static double safeFloat(float[] data, int index) { if (data == null || index < 0 || index >= data.length) { return Double.NaN; } return data[index]; } private static String posName(int i) { if (i < 4) { return "P" + (i + 1); } return "?"; } private static String detName(int i) { if (i < 100) { int n = i + 1; if (n < 10) { return "D0" + n; } return "D" + n; } return "?"; } }