import java.io.File; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Files; /** * Column/row profile of a 160x120 measurement payload, to tell scene structure from * sensor fixed-pattern noise. * * The signature of column FPN is a column-to-column JITTER that is large compared * with the same figure measured along rows: real scene content is smooth in both * directions, stripes are not. Reported per payload: * * colMean step — mean |colMean[i] - colMean[i-1]| (stripes along y) * rowMean step — mean |rowMean[j] - rowMean[j-1]| (stripes along x) * colHF — the same after removing the local trend (3-tap), i.e. how much * of the jitter is high-frequency grain rather than scene slope * * Run it on the RAW frame and on the NUC block of the same capture: if RAW is * striped and NUC is not, the correction works and the stripes seen in a render * must come from somewhere else; if NUC is still striped, the flat-field is stale. * * Usage: java FrameProfile [rawOffsetHex] * Without an offset it profiles every FRAME/NUC block it can find; an explicit * offset is for files that store the payload outside typed blocks (the official * app's MDT: body + 884-byte BasePara, then the frame). */ public class FrameProfile { static final int B_FRAME = 0x5BB5B55D, B_NUC = 0x5BB5B560; static final int W = 160, H = 120, NPIX = W * H; public static void main(String[] args) throws Exception { File f = new File(args[0]); byte[] b = Files.readAllBytes(f.toPath()); ByteBuffer bb = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN); System.out.println("== " + f.getName() + " (" + b.length + " bytes) =="); // typed blocks int t = b.length - 152; if (b.length >= 152 && bb.getInt(t) == 0x5BB5B57B) { int ddt = bb.getInt(t + 4); int bodySize = bb.getInt(ddt + 4); int p = ddt + 0x88, end = p + bodySize; while (p + 8 <= end) { int m = bb.getInt(p), len = bb.getInt(p + 4); if (len < 0 || p + 8 + len > end) break; if ((m == B_FRAME || m == B_NUC) && len >= 38400) { report(m == B_FRAME ? "RAW frame (typed block)" : "NUC block (typed block)", b, p + 8, f.getName()); } p += 8 + len; } // official layout: 884-byte BasePara right after the DDT header, then the frame if (args.length < 2) { int bp = ddt + 0x88; if (bp + 4 <= b.length) { int structLen = bb.getInt(bp); if (structLen > 0 && structLen < 4096 && bp + 4 + structLen + 38400 <= b.length) { report("RAW frame (after " + structLen + "-byte BasePara)", b, bp + 4 + structLen, f.getName()); } } } } if (args.length >= 2) { report("payload at 0x" + args[1], b, Integer.parseInt(args[1], 16), f.getName()); } } static void report(String label, byte[] b, int off, String name) { if (off + 2 * NPIX > b.length) { System.out.println(" " + label + ": out of range"); return; } int[] v = new int[NPIX]; for (int i = 0; i < NPIX; i++) { v[i] = (b[off + 2 * i] & 0xFF) | ((b[off + 2 * i + 1] & 0xFF) << 8); } double[] col = new double[W], row = new double[H]; long sum = 0; for (int y = 0; y < H; y++) { for (int x = 0; x < W; x++) { int p = v[y * W + x]; col[x] += p; row[y] += p; sum += p; } } for (int x = 0; x < W; x++) col[x] /= H; for (int y = 0; y < H; y++) row[y] /= W; System.out.printf(" %-34s mean=%.0f%n", label, sum / (double) NPIX); int[] srt = v.clone(); java.util.Arrays.sort(srt); System.out.printf(" percentiles p1=%.0f p25=%.0f p50=%.0f p75=%.0f p99=%.0f%n", q(srt,1), q(srt,25), q(srt,50), q(srt,75), q(srt,99)); System.out.printf(" colMean step=%.1f colHF=%.1f (vertical stripes)%n", step(col), hf(col)); System.out.printf(" rowMean step=%.1f rowHF=%.1f (horizontal stripes)%n", step(row), hf(row)); double ratio = hf(col) / Math.max(1e-9, hf(row)); System.out.printf(" HF ratio col/row = %.2f %s%n", ratio, ratio > 2 ? "-> COLUMN FPN dominant" : ratio < 0.5 ? "-> ROW FPN dominant" : "-> balanced (scene-like)"); } static double q(int[] s, int p) { return s[Math.min(s.length - 1, p * s.length / 100)]; } static double step(double[] a) { double s = 0; for (int i = 1; i < a.length; i++) s += Math.abs(a[i] - a[i - 1]); return s / (a.length - 1); } /** Mean absolute deviation from the 3-tap local trend: the grain, minus scene slope. */ static double hf(double[] a) { double s = 0; for (int i = 1; i < a.length - 1; i++) { s += Math.abs(a[i] - (a[i - 1] + a[i + 1]) / 2); } return s / (a.length - 2); } }