Files
x11ma/plugins/fiji/sparseviewer/io/MdaReader.java
T
2026-06-24 13:28:45 +02:00

420 lines
12 KiB
Java

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<File> 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<File> sourceFiles = new ArrayList<File>();
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<File> sourceFiles = new ArrayList<File>();
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 "?";
}
}