Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed0eba4bae | |||
| a5770491d5 | |||
| f745efe7e0 | |||
| 158f60d2a7 |
+117
-2
@@ -1,4 +1,4 @@
|
|||||||
# SRAS Scan Binary Format — Version 6
|
# SRAS Scan Binary Format — Version 6 / 7
|
||||||
|
|
||||||
Each `.sras` file contains **one complete scan**: all GR rotation angles and all
|
Each `.sras` file contains **one complete scan**: all GR rotation angles and all
|
||||||
Y rows. Files are named `{prefix}.sras`.
|
Y rows. Files are named `{prefix}.sras`.
|
||||||
@@ -10,6 +10,14 @@ rotated by that specific angle** — not the worst case across all angles — so
|
|||||||
fewer rows than a 45° scan of the same ROI, and the file format reflects that
|
fewer rows than a 45° scan of the same ROI, and the file format reflects that
|
||||||
instead of forcing every angle to the largest bounding box.
|
instead of forcing every angle to the largest bounding box.
|
||||||
|
|
||||||
|
v7 is byte-identical to v6 (same header, angle table, geometry table, row
|
||||||
|
table, preamble blocks, background block, waveform data) plus an optional
|
||||||
|
trailing **Cache Tail** holding precomputed per-angle DC and/or FFT images
|
||||||
|
(see [Cache Tail (v7)](#cache-tail-v7) below) — only the header's `version`
|
||||||
|
field and the presence of that trailing section differ. Scans come off the
|
||||||
|
scope as v6; `sras_viewer.py`'s "Convert" menu batch actions convert a file
|
||||||
|
to v7 **in place** the first time either cache block is computed and stored.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## File Layout
|
## File Layout
|
||||||
@@ -22,6 +30,7 @@ instead of forcing every angle to the largest bounding box.
|
|||||||
[Preamble Blocks — n_channels × (uint16 length + UTF-8 WFMOutpre string)]
|
[Preamble Blocks — n_channels × (uint16 length + UTF-8 WFMOutpre string)]
|
||||||
[Background Block — uint32 n_bg_samples + n_bg_samples × int8 bytes]
|
[Background Block — uint32 n_bg_samples + n_bg_samples × int8 bytes]
|
||||||
[Waveform Data (ragged) — per angle: n_rows[a] × n_channels × n_frames[a] × samples_per_frame × bps bytes]
|
[Waveform Data (ragged) — per angle: n_rows[a] × n_channels × n_frames[a] × samples_per_frame × bps bytes]
|
||||||
|
[Cache Tail (optional) — "CACH" + DC block (optional) + FFT block (optional); v7 only]
|
||||||
```
|
```
|
||||||
|
|
||||||
All multi-byte integers and floats use **big-endian** byte order
|
All multi-byte integers and floats use **big-endian** byte order
|
||||||
@@ -182,6 +191,111 @@ using that angle's `x_start` from the Per-Angle Geometry Table (not
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Cache Tail (v7)
|
||||||
|
|
||||||
|
Present iff `version == 7` and `file_size > cache_offset`, where:
|
||||||
|
|
||||||
|
```
|
||||||
|
cache_offset = data_offset + Σ over angles a of:
|
||||||
|
n_rows[a] × n_channels × n_frames[a] × samples_per_frame × bytes_per_sample
|
||||||
|
```
|
||||||
|
|
||||||
|
i.e. exactly `data_offset + waveform_bytes` — the same "Total data size"
|
||||||
|
formula as Waveform Data above. This offset is derivable from the header and
|
||||||
|
Per-Angle Geometry Table alone and does **not** depend on which cache
|
||||||
|
block(s) are present, so a writer can always seek straight there without
|
||||||
|
reading or touching any waveform byte before it.
|
||||||
|
|
||||||
|
Unlike the (removed) v5 `PREC` section, which stored one omnibus per-angle
|
||||||
|
entry (FFT + both DC channels together) in a dense, uniform-geometry array,
|
||||||
|
the v7 Cache Tail splits DC and FFT into two **independent** sub-blocks —
|
||||||
|
each sized per-angle from the Per-Angle Geometry Table, each independently
|
||||||
|
present, and each independently updatable in any order, any number of
|
||||||
|
times, without disturbing the other. This matches `sras_viewer.py`'s
|
||||||
|
"Convert" menu, which exposes DC and FFT store as two separate batch
|
||||||
|
actions.
|
||||||
|
|
||||||
|
### CACH outer header (6 bytes, `">4sBB"`)
|
||||||
|
|
||||||
|
| Offset | Size | Type | Field | Description |
|
||||||
|
|--------|------|------|-------|-------------|
|
||||||
|
| 0 | 4 | `char[4]` | `cach_magic` | `CACH` (ASCII). Missing/wrong magic → treat file as having no cache. |
|
||||||
|
| 4 | 1 | `u8` | `cach_version` | Cache format version. Currently `1`. Readers must treat the file as uncached if this is not a version they understand (unlike v5's `PREC` section, which read but never validated its version byte). |
|
||||||
|
| 5 | 1 | `u8` | `block_flags` | Bit 0 = DC block (`SDCB`) follows. Bit 1 = FFT block (`SFFT`) follows, immediately after the DC block if both are present. Bits 2–7 reserved, must be zero on write. |
|
||||||
|
|
||||||
|
### DC block `SDCB` (present iff `block_flags & 0x01`)
|
||||||
|
|
||||||
|
7-byte block header, format `">4sBH"`:
|
||||||
|
|
||||||
|
| Offset (rel) | Size | Type | Field | Description |
|
||||||
|
|--------------|------|------|-------|-------------|
|
||||||
|
| 0 | 4 | `char[4]` | `magic` | `SDCB` |
|
||||||
|
| 4 | 1 | `u8` | `reserved` | `0`, reserved for future use |
|
||||||
|
| 5 | 2 | `u16` | `n_stored` | Number of angle entries that follow, `0 ≤ n_stored ≤ n_angles` |
|
||||||
|
|
||||||
|
followed by `n_stored` entries, each:
|
||||||
|
|
||||||
|
```
|
||||||
|
u16 angle_idx — index into the angle table (0-based)
|
||||||
|
f32[n_rows[angle_idx] × n_frames[angle_idx]] dc3_mv — CH3 waveform mean, mV, row-major
|
||||||
|
f32[n_rows[angle_idx] × n_frames[angle_idx]] dc4_mv — CH4 waveform mean, mV, row-major
|
||||||
|
```
|
||||||
|
|
||||||
|
Entries may appear in any order and need not be contiguous from angle 0 —
|
||||||
|
this supports storing (or re-storing) a subset of angles, or an
|
||||||
|
interrupted batch run leaving only some angles cached. Readers bounds-check
|
||||||
|
`angle_idx < n_angles` on each entry and stop parsing on an out-of-range
|
||||||
|
value, same as v5's `PREC` section.
|
||||||
|
|
||||||
|
### FFT block `SFFT` (present iff `block_flags & 0x02`)
|
||||||
|
|
||||||
|
7-byte block header, format `">4sBH"`:
|
||||||
|
|
||||||
|
| Offset (rel) | Size | Type | Field | Description |
|
||||||
|
|--------------|------|------|-------|-------------|
|
||||||
|
| 0 | 4 | `char[4]` | `magic` | `SFFT` |
|
||||||
|
| 4 | 1 | `u8` | `flags` | Bit 0 = `bg_sub_applied` — background waveform was subtracted from CH1 before the FFT when these images were computed. Bits 1–7 reserved. |
|
||||||
|
| 5 | 2 | `u16` | `n_stored` | Number of angle entries that follow |
|
||||||
|
|
||||||
|
followed by `n_stored` entries, each:
|
||||||
|
|
||||||
|
```
|
||||||
|
u16 angle_idx — index into the angle table (0-based)
|
||||||
|
f32[n_rows[angle_idx] × n_frames[angle_idx]] peak_freq_mhz — CH1 FFT peak frequency, MHz, row-major
|
||||||
|
```
|
||||||
|
|
||||||
|
**`peak_freq_mhz`** is computed without any DC-threshold masking (i.e. the
|
||||||
|
FFT is run on every pixel unconditionally, same as v5's `PREC` convention).
|
||||||
|
Readers apply the DC4 threshold at display time:
|
||||||
|
|
||||||
|
```
|
||||||
|
pixel is valid ⟺ dc4_mv[r][f] ≥ threshold_mv
|
||||||
|
display_value = peak_freq_mhz[r][f] if valid, else 0
|
||||||
|
```
|
||||||
|
|
||||||
|
using the DC4 image from the DC block if that angle is also cached there,
|
||||||
|
else computed on demand.
|
||||||
|
|
||||||
|
Readers must fall back to real-time FFT computation (ignoring stored
|
||||||
|
`peak_freq_mhz`) under the same conditions as v5's PREC fast path: time-domain
|
||||||
|
gating is active, zero-padding (`n_fft ≠ samples_per_frame`) is requested, or
|
||||||
|
the reader's background-subtraction setting doesn't match `flags.bg_sub_applied`.
|
||||||
|
|
||||||
|
### In-place write ordering
|
||||||
|
|
||||||
|
A writer updating a file's Cache Tail must write the payload (CACH header +
|
||||||
|
whichever block(s) are present) **before** flipping the header's `version`
|
||||||
|
byte to `7`, and `truncate()` the file to the new payload's end immediately
|
||||||
|
after writing it. If the process is interrupted between the payload write
|
||||||
|
and the version-byte flip, the file is still valid v6 — v6 parsing only
|
||||||
|
bounds-checks each angle's `offset + nbytes ≤ file_size`, it never asserts
|
||||||
|
exactly how many bytes follow the last angle's waveform block — so the
|
||||||
|
interrupted write leaves harmless trailing bytes rather than a corrupt file,
|
||||||
|
and the next successful write overwrites them via the same deterministic
|
||||||
|
`cache_offset`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Acquisition Settings (fixed by sc3_aui_app.py)
|
## Acquisition Settings (fixed by sc3_aui_app.py)
|
||||||
|
|
||||||
| Parameter | Value |
|
| Parameter | Value |
|
||||||
@@ -206,5 +320,6 @@ using that angle's `x_start` from the Per-Angle Geometry Table (not
|
|||||||
| 3 | Added preamble blocks (WFMOutpre strings) after the row table, one length-prefixed UTF-8 block per channel. |
|
| 3 | Added preamble blocks (WFMOutpre strings) after the row table, one length-prefixed UTF-8 block per channel. |
|
||||||
| 4 | Added background waveform block (CH1, Helios ON / Genesis OFF) after the preamble blocks; stored as `uint32` sample count followed by raw `int8` ADC bytes. |
|
| 4 | Added background waveform block (CH1, Helios ON / Genesis OFF) after the preamble blocks; stored as `uint32` sample count followed by raw `int8` ADC bytes. |
|
||||||
| 5 | (skipped) |
|
| 5 | (skipped) |
|
||||||
| 6 | Each angle now scans only the bounding box of the nominal ROI rotated by that angle instead of the AABB-expanded worst case across all angles. Header no longer carries a single global `x_start`/`x_delta`/`n_rows` — replaced with `*_nominal` reference fields plus a new Per-Angle Geometry Table (`x_start`, `x_delta`, `n_frames`, `n_rows` per angle) and a ragged Row Table / Waveform Data block sized per angle. **Not compatible with pre-v6 readers** that assume uniform geometry. `sras_viewer.py` reads v6 natively (per-angle `n_rows`/`n_frames`/`x_start`); the "Pre-process and Save as v5" fast-path export is not offered for v6 files since the flat v5 layout cannot represent per-angle geometry. |
|
| 6 | Each angle now scans only the bounding box of the nominal ROI rotated by that angle instead of the AABB-expanded worst case across all angles. Header no longer carries a single global `x_start`/`x_delta`/`n_rows` — replaced with `*_nominal` reference fields plus a new Per-Angle Geometry Table (`x_start`, `x_delta`, `n_frames`, `n_rows` per angle) and a ragged Row Table / Waveform Data block sized per angle. **Not compatible with pre-v6 readers** that assume uniform geometry. `sras_viewer.py` reads v6 natively (per-angle `n_rows`/`n_frames`/`x_start`). |
|
||||||
|
| 7 | Adds an optional trailing **Cache Tail** (`CACH` section, see [Cache Tail (v7)](#cache-tail-v7)) after the ragged waveform data, holding independently-present, independently-updatable per-angle DC (`SDCB`: dc3_mv + dc4_mv) and FFT (`SFFT`: peak_freq_mhz) blocks, so previously-computed images redisplay instantly instead of being recomputed. Header / angle table / geometry table / row table / preamble blocks / background block / waveform data are byte-identical to v6 — only the version byte and the optional Cache Tail differ. Scans still come off the scope as v6; `sras_viewer.py`'s "Convert" menu ("Batch Compute DC and Store" / "Batch Compute FFT and Store") converts a file to v7 **in place** on first use, or updates an existing v7 file's cache blocks, without rewriting any waveform bytes. Supersedes the removed "Pre-process and Save as v5" workflow, which was never available for v6 sources since the flat v5 `PREC` layout can't represent per-angle geometry. |
|
||||||
|
|
||||||
|
|||||||
+1226
-297
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user