An SRAS file stores the raw RF waveforms captured during a Surface-acoustic-wave Resonance And Spectroscopy (SRAS) scan, along with the scan geometry and scope calibration metadata needed to interpret them.
-
A scan consists of one or more angles (rotation positions of the sample), each containing a 2-D raster of rows × frames. At every pixel, n_channels waveforms of samples_per_frame ADC counts are stored. Channel order is fixed:
-
-
-
-
Index
-
Hardware channel
-
Signal
-
-
-
-
-
0
-
CH1
-
RF acoustic packet (AC-coupled)
-
-
-
1
-
CH3
-
Bias A — DC mean used for masking
-
-
-
2
-
CH4
-
Bias B — DC mean used for masking
-
-
-
-
-
Type notation
-
-
-
-
Symbol
-
C type
-
Size
-
Notes
-
-
-
-
-
u8
-
uint8_t
-
1 byte
-
unsigned
-
-
-
u16
-
uint16_t
-
2 bytes
-
big-endian
-
-
-
u32
-
uint32_t
-
4 bytes
-
big-endian
-
-
-
i8
-
int8_t
-
1 byte
-
signed, used for ADC samples when bytes_per_sample == 1
-
-
-
i16
-
int16_t
-
2 bytes
-
big-endian signed, used when bytes_per_sample == 2
-
-
-
f32
-
float
-
4 bytes
-
big-endian IEEE 754 single
-
-
-
f64
-
double
-
8 bytes
-
big-endian IEEE 754 double
-
-
-
char[N]
-
—
-
N bytes
-
raw bytes, no null terminator unless noted
-
-
-
utf8[N]
-
—
-
N bytes
-
UTF-8 string, length-prefixed (see preamble section)
-
-
-
-
-
Version history
-
-
-
-
Version
-
Added
-
-
-
-
-
2
-
Baseline: fixed header, angle table, row table, raw waveform data. No scope calibration (fallback constants used by readers).
Background waveform section: one CH1 reference shot subtracted from each CH1 frame before FFT.
-
-
-
5
-
(this document) Version byte incremented to 5. n_frames_hdr is now the actual acquired frame count (authoritative). PREC section appended after waveform data with precomputed FFT-peak and DC images for instant re-display.
-
-
-
-
-
v2 note: Version 1 is not defined; version 2 is the lowest observed in the field.
-
-
-
File structure
-
┌─────────────────────────────────────────────┐
-│ 1. Fixed header (43 bytes) │ all versions
-├─────────────────────────────────────────────┤
-│ 2. Angle table (n_angles × 4 bytes)│ all versions
-├─────────────────────────────────────────────┤
-│ 3. Row position table (n_rows × 4 bytes)│ all versions
-├─────────────────────────────────────────────┤
-│ 4. Channel preambles (variable) │ v3+
-├─────────────────────────────────────────────┤
-│ 5. Background waveform (variable) │ v4+
-├─────────────────────────────────────────────┤
-│ 6. Waveform data (variable) │ all versions
-├─────────────────────────────────────────────┤
-│ 7. PREC section (variable) │ v5 only
-└─────────────────────────────────────────────┘
-
-
-
1. Fixed header (43 bytes, all versions)
-
-
-
-
Offset
-
Size
-
Type
-
Field
-
Description
-
-
-
-
-
0
-
4
-
char[4]
-
magic
-
SRAS (ASCII, no null terminator). Reject file if this does not match.
-
-
-
4
-
1
-
u8
-
version
-
Format version. This document describes version 5.
-
-
-
5
-
2
-
u16
-
n_angles
-
Number of scan angles (rotation positions). ≥ 1.
-
-
-
7
-
2
-
u16
-
n_rows
-
Number of scan rows (Y positions). ≥ 1.
-
-
-
9
-
4
-
f32
-
x_start_mm
-
X position of the first frame in the first row, in millimetres.
-
-
-
13
-
4
-
f32
-
x_delta_mm
-
Pre-computed pixel pitch in mm (velocity_mm_s / laser_freq_hz). Provided for convenience; readers should prefer the derived value.
-
-
-
17
-
4
-
f32
-
velocity_mm_s
-
Scanner stage velocity, mm s⁻¹. Used together with laser_freq_hz to compute pixel pitch.
-
-
-
21
-
4
-
f32
-
laser_freq_hz
-
Laser repetition rate, Hz.
-
-
-
25
-
4
-
u32
-
n_frames_hdr
-
v2–v4: the configured frame count written before acquisition; may exceed actual frames acquired (use file-size arithmetic to obtain the true count). v5: the actual acquired frame count — authoritative; readers must not re-derive it from file size.
YOFF = −87.04 ADC counts (scope position = −2.72 div)
-
YZERO = 0 mV
-
-
-
5. Background waveform (v4+)
-
u32 n_bg_samples — number of i8 ADC samples that follow
-i8[n_bg] background — one representative CH1 background shot
-
-
The background waveform has the same samples_per_frame length as a normal CH1 waveform. It is subtracted from each CH1 waveform before FFT processing when background subtraction is enabled. When n_bg_samples == 0 the section is present but empty.
-
-
6. Waveform data (all versions)
-
Begins immediately after the fixed header (v2), preambles (v3), or background waveform (v4+). The waveform data is a flat, contiguous array with the following logical shape, stored in row-major (C) order:
v5: use n_frames_hdr directly; do not use file-size arithmetic.
-
v2–v4:n_frames = floor((file_bytes_after_header_sections) / (bytes_per_sample × n_angles × n_rows × n_channels × samples_per_frame)). Any remainder bytes are a partial trailing row and are discarded.
-
-
-
7. PREC section (v5)
-
The PREC section is appended immediately after the waveform data and is present if and only if version == 5 and the file size exceeds waveform_end_offset.
where data_offset is the file offset of the first waveform byte (the byte immediately after the background waveform, or after the angle/row tables for v2 files).
-
PREC header (8 bytes)
-
-
-
-
Offset (relative)
-
Size
-
Type
-
Field
-
Description
-
-
-
-
-
0
-
4
-
char[4]
-
prec_magic
-
PREC (ASCII). Absent or wrong magic → ignore section.
-
-
-
4
-
1
-
u8
-
prec_version
-
PREC format version. Currently 1.
-
-
-
5
-
1
-
u8
-
flags
-
Bitmask (see below).
-
-
-
6
-
2
-
u16
-
n_stored
-
Number of angle entries that follow. 0 ≤ n_stored ≤ n_angles.
-
-
-
-
Flags byte
-
-
-
-
Bit
-
Mask
-
Meaning when set
-
-
-
-
-
0
-
0x01
-
bg_sub_applied — background waveform was subtracted from CH1 before the FFT when these images were computed.
-
-
-
1–7
-
—
-
Reserved, must be zero on write; readers must ignore.
-
-
-
-
PREC angle entries
-
Repeated n_stored times, in arbitrary angle-index order:
-
for each stored angle:
- u16 angle_idx — index into the angle table (0-based)
- f32[n_rows×n_frames] peak_freq_mhz — CH1 FFT peak frequency, MHz, row-major
- f32[n_rows×n_frames] dc4_mv — CH4 waveform mean, mV, row-major
- f32[n_rows×n_frames] dc3_mv — CH3 waveform mean, mV, row-major
-
-
All image arrays are f32 big-endian, stored in row-major order: element [r][f] is at offset (r × n_frames + f) × 4 bytes within the array.
-
peak_freq_mhz is computed without any DC-threshold masking (i.e. the FFT is run on every pixel unconditionally). Readers apply the dc4_mv threshold at display time:
-
pixel is valid ⟺ dc4_mv[r][f] ≥ threshold_mv
-display_value = peak_freq_mhz[r][f] if valid, else 0
-
-
dc4_mv / dc3_mv are the mean of all ADC samples in the respective channel waveform, converted to millivolts using the channel calibration:
-
dc_mv = (adc_mean − YOFF) × YMULT + YZERO
-
-
When readers must bypass the PREC fast path
-
Readers must fall back to real-time FFT computation (ignoring stored peak_freq_mhz) when any of the following are true:
-
-
Time-domain gating is active (zeroing samples outside a time window changes peak frequency).
-
Zero-padding (n_fft ≠ samples_per_frame) is requested (changes bin spacing).
-
The reader's background-subtraction setting does not match flags.bg_sub_applied.
-
-
-
Derived quantities
-
pixel_pitch_mm = velocity_mm_s / laser_freq_hz
-
-x_axis_mm[f] = x_start_mm + f × pixel_pitch_mm (f = 0 … n_frames−1)
-
-time_axis_ns[s] = s / sample_rate_hz × 1e9 (s = 0 … spf−1)
-
-freq_axis_mhz[k] = k × sample_rate_hz / (n_fft × 1e6) (k = 0 … n_fft/2)
- where n_fft = samples_per_frame unless zero-padding is active
-
-velocity_ms[r][f] = peak_freq_mhz[r][f] × grating_um (grating_um user-supplied)
-
A v4 reader that only accepts versions {2, 3, 4} will reject a v5 file with an "unsupported version" error. This is intentional: a v4 reader would derive n_frames from the file size, incorrectly including the PREC bytes in the sample count, producing a silently wrong reshape.
-
Producing v5 files
-
v5 files are produced by the SRAS viewer's "Pre-process and Save as v5" action. The procedure is:
-
-
Copy the source file (any version) verbatim.
-
Set version = 5 at byte offset 4.
-
Set n_frames_hdr at byte offset 25 to the actual acquired frame count.
-
Truncate the copy to data_offset + waveform_bytes (removes any pre-existing stale PREC tail).
-
Compute peak_freq_mhz, dc4_mv, and dc3_mv for every angle using chunked FFT.
-
Append the PREC section.
-
-
Partially-written PREC sections
-
If n_stored < n_angles (e.g. pre-processing was interrupted), the file is still valid. Readers use stored images for the angles present in the PREC section and fall back to real-time FFT for the remainder. Readers must check angle_idx bounds on each entry and stop parsing on an out-of-range value.
-
-
-
-
-
\ No newline at end of file
diff --git a/SRAS_FORMAT.md b/SRAS_FORMAT.md
deleted file mode 100644
index 4bc0346..0000000
--- a/SRAS_FORMAT.md
+++ /dev/null
@@ -1,379 +0,0 @@
-# SRAS File Format Specification
-
-**Format family:** `.sras`
-**Byte order:** Big-endian (network byte order) throughout, unless noted.
-**Version history:** v2 (baseline), v3 (scope calibration), v4 (background waveform), v5 (precomputed images + guaranteed frame count).
-
----
-
-## Table of Contents
-
-1. [Overview](#overview)
-2. [Type notation](#type-notation)
-3. [Version history](#version-history)
-4. [File structure](#file-structure)
- - [Fixed header (all versions)](#1-fixed-header-43-bytes-all-versions)
- - [Angle table (all versions)](#2-angle-table-all-versions)
- - [Row position table (all versions)](#3-row-position-table-all-versions)
- - [Channel preambles (v3+)](#4-channel-preambles-v3)
- - [Background waveform (v4+)](#5-background-waveform-v4)
- - [Waveform data (all versions)](#6-waveform-data-all-versions)
- - [PREC section (v5)](#7-prec-section-v5)
-5. [Derived quantities](#derived-quantities)
-6. [ADC calibration](#adc-calibration)
-7. [Waveform data layout detail](#waveform-data-layout-detail)
-8. [Size reference](#size-reference)
-9. [Compatibility notes](#compatibility-notes)
-
----
-
-## Overview
-
-An SRAS file stores the raw RF waveforms captured during a Surface-acoustic-wave Resonance And Spectroscopy (SRAS) scan, along with the scan geometry and scope calibration metadata needed to interpret them.
-
-A scan consists of one or more **angles** (rotation positions of the sample), each containing a 2-D raster of **rows** × **frames**. At every pixel, `n_channels` waveforms of `samples_per_frame` ADC counts are stored. Channel order is fixed:
-
-| Index | Hardware channel | Signal |
-|-------|-----------------|--------|
-| 0 | CH1 | RF acoustic packet (AC-coupled) |
-| 1 | CH3 | Bias A — DC mean used for masking |
-| 2 | CH4 | Bias B — DC mean used for masking |
-
----
-
-## Type notation
-
-| Symbol | C type | Size | Notes |
-|--------|--------|------|-------|
-| `u8` | `uint8_t` | 1 byte | unsigned |
-| `u16` | `uint16_t` | 2 bytes | big-endian |
-| `u32` | `uint32_t` | 4 bytes | big-endian |
-| `i8` | `int8_t` | 1 byte | signed, used for ADC samples when `bytes_per_sample == 1` |
-| `i16` | `int16_t` | 2 bytes | big-endian signed, used when `bytes_per_sample == 2` |
-| `f32` | `float` | 4 bytes | big-endian IEEE 754 single |
-| `f64` | `double` | 8 bytes | big-endian IEEE 754 double |
-| `char[N]` | — | N bytes | raw bytes, no null terminator unless noted |
-| `utf8[N]` | — | N bytes | UTF-8 string, length-prefixed (see preamble section) |
-
----
-
-## Version history
-
-| Version | Added |
-|---------|-------|
-| 2 | Baseline: fixed header, angle table, row table, raw waveform data. No scope calibration (fallback constants used by readers). |
-| 3 | Per-channel Tektronix WFMOutpre preamble strings carrying YMULT / YOFF / YZERO calibration. |
-| 4 | Background waveform section: one CH1 reference shot subtracted from each CH1 frame before FFT. |
-| 5 | **(this document)** Version byte incremented to 5. `n_frames_hdr` is now the *actual* acquired frame count (authoritative). PREC section appended after waveform data with precomputed FFT-peak and DC images for instant re-display. |
-
-> **v2 note:** Version 1 is not defined; version 2 is the lowest observed in the field.
-
----
-
-## File structure
-
-```
-┌─────────────────────────────────────────────┐
-│ 1. Fixed header (43 bytes) │ all versions
-├─────────────────────────────────────────────┤
-│ 2. Angle table (n_angles × 4 bytes)│ all versions
-├─────────────────────────────────────────────┤
-│ 3. Row position table (n_rows × 4 bytes)│ all versions
-├─────────────────────────────────────────────┤
-│ 4. Channel preambles (variable) │ v3+
-├─────────────────────────────────────────────┤
-│ 5. Background waveform (variable) │ v4+
-├─────────────────────────────────────────────┤
-│ 6. Waveform data (variable) │ all versions
-├─────────────────────────────────────────────┤
-│ 7. PREC section (variable) │ v5 only
-└─────────────────────────────────────────────┘
-```
-
----
-
-### 1. Fixed header (43 bytes, all versions)
-
-| Offset | Size | Type | Field | Description |
-|--------|------|------|-------|-------------|
-| 0 | 4 | `char[4]` | `magic` | `SRAS` (ASCII, no null terminator). Reject file if this does not match. |
-| 4 | 1 | `u8` | `version` | Format version. This document describes version **5**. |
-| 5 | 2 | `u16` | `n_angles` | Number of scan angles (rotation positions). ≥ 1. |
-| 7 | 2 | `u16` | `n_rows` | Number of scan rows (Y positions). ≥ 1. |
-| 9 | 4 | `f32` | `x_start_mm` | X position of the first frame in the first row, in millimetres. |
-| 13 | 4 | `f32` | `x_delta_mm` | Pre-computed pixel pitch in mm (`velocity_mm_s / laser_freq_hz`). Provided for convenience; readers should prefer the derived value. |
-| 17 | 4 | `f32` | `velocity_mm_s` | Scanner stage velocity, mm s⁻¹. Used together with `laser_freq_hz` to compute pixel pitch. |
-| 21 | 4 | `f32` | `laser_freq_hz` | Laser repetition rate, Hz. |
-| 25 | 4 | `u32` | `n_frames_hdr` | **v2–v4:** the *configured* frame count written before acquisition; may exceed actual frames acquired (use file-size arithmetic to obtain the true count). **v5:** the *actual* acquired frame count — authoritative; readers must not re-derive it from file size. |
-| 29 | 4 | `u32` | `samples_per_frame` | ADC samples per waveform (`spf`). |
-| 33 | 8 | `f64` | `sample_rate_hz` | Oscilloscope sample rate, Hz (e.g. 5 × 10⁹ for 5 GS/s). |
-| 41 | 1 | `u8` | `bytes_per_sample` | ADC word size: `1` → `i8`, `2` → `i16` (big-endian). |
-| 42 | 1 | `u8` | `n_channels` | Number of channels per frame. Currently always `3`. |
-
----
-
-### 2. Angle table (all versions)
-
-Immediately follows the fixed header.
-
-```
-n_angles × f32 — scan angle in degrees
-```
-
-Each entry is a big-endian `f32` giving the sample rotation angle in degrees at which that angle index was acquired.
-
----
-
-### 3. Row position table (all versions)
-
-Immediately follows the angle table.
-
-```
-n_rows × f32 — Y position of each row, in millimetres
-```
-
----
-
-### 4. Channel preambles (v3+)
-
-One entry per channel, in channel-index order (CH1 first).
-
-```
-for each channel:
- u16 preamble_length — byte count of the UTF-8 string that follows
- utf8[N] preamble — Tektronix WFMOutpre string
-```
-
-The preamble is the oscilloscope's `WFMOutpre` response string. Readers extract the following keys (case-insensitive, space-separated value):
-
-| Key | Stored unit | Conversion to mV |
-|-----|-------------|-----------------|
-| `YMULT` | V count⁻¹ | multiply by 1000 |
-| `YOFF` | ADC counts | used directly |
-| `YZERO` | V | multiply by 1000 |
-
-**v2 fallback:** when preambles are absent, readers use:
-- `YMULT` = 1.5625 mV count⁻¹ (50 mV/div, 8 div, 8-bit ADC)
-- `YOFF` = −87.04 ADC counts (scope position = −2.72 div)
-- `YZERO` = 0 mV
-
----
-
-### 5. Background waveform (v4+)
-
-```
-u32 n_bg_samples — number of i8 ADC samples that follow
-i8[n_bg] background — one representative CH1 background shot
-```
-
-The background waveform has the same `samples_per_frame` length as a normal CH1 waveform. It is subtracted from each CH1 waveform before FFT processing when background subtraction is enabled. When `n_bg_samples == 0` the section is present but empty.
-
----
-
-### 6. Waveform data (all versions)
-
-Begins immediately after the fixed header (v2), preambles (v3), or background waveform (v4+). The waveform data is a flat, contiguous array with the following logical shape, stored in row-major (C) order:
-
-```
-waveform_data[n_angles][n_rows][n_channels][n_frames][samples_per_frame]
-```
-
-Each element is a signed ADC count of size `bytes_per_sample`:
-- `bytes_per_sample == 1` → `i8`
-- `bytes_per_sample == 2` → `i16` big-endian
-
-**Total byte count:**
-
-```
-waveform_bytes = n_angles × n_rows × n_channels × n_frames × samples_per_frame × bytes_per_sample
-```
-
-#### Index semantics
-
-| Dimension | Range | Meaning |
-|-----------|-------|---------|
-| `[a]` | 0 … n_angles−1 | Scan angle (rotation position) |
-| `[r]` | 0 … n_rows−1 | Row (Y position); row 0 is the first acquired |
-| `[c]` | 0 … n_channels−1 | Channel (0=CH1 RF, 1=CH3 Bias A, 2=CH4 Bias B) |
-| `[f]` | 0 … n_frames−1 | Frame (X position) within the row |
-| `[s]` | 0 … spf−1 | Sample index within the waveform |
-
-#### Frame-count determination
-
-- **v5:** use `n_frames_hdr` directly; do not use file-size arithmetic.
-- **v2–v4:** `n_frames = floor((file_bytes_after_header_sections) / (bytes_per_sample × n_angles × n_rows × n_channels × samples_per_frame))`. Any remainder bytes are a partial trailing row and are discarded.
-
----
-
-### 7. PREC section (v5)
-
-The PREC section is appended immediately after the waveform data and is present if and only if `version == 5` and the file size exceeds `waveform_end_offset`.
-
-```
-waveform_end_offset = data_offset + waveform_bytes
-```
-
-where `data_offset` is the file offset of the first waveform byte (the byte immediately after the background waveform, or after the angle/row tables for v2 files).
-
-#### PREC header (8 bytes)
-
-| Offset (relative) | Size | Type | Field | Description |
-|-------------------|------|------|-------|-------------|
-| 0 | 4 | `char[4]` | `prec_magic` | `PREC` (ASCII). Absent or wrong magic → ignore section. |
-| 4 | 1 | `u8` | `prec_version` | PREC format version. Currently `1`. |
-| 5 | 1 | `u8` | `flags` | Bitmask (see below). |
-| 6 | 2 | `u16` | `n_stored` | Number of angle entries that follow. 0 ≤ `n_stored` ≤ `n_angles`. |
-
-##### Flags byte
-
-| Bit | Mask | Meaning when set |
-|-----|------|-----------------|
-| 0 | `0x01` | `bg_sub_applied` — background waveform was subtracted from CH1 before the FFT when these images were computed. |
-| 1–7 | — | Reserved, must be zero on write; readers must ignore. |
-
-#### PREC angle entries
-
-Repeated `n_stored` times, in arbitrary angle-index order:
-
-```
-for each stored angle:
- u16 angle_idx — index into the angle table (0-based)
- f32[n_rows×n_frames] peak_freq_mhz — CH1 FFT peak frequency, MHz, row-major
- f32[n_rows×n_frames] dc4_mv — CH4 waveform mean, mV, row-major
- f32[n_rows×n_frames] dc3_mv — CH3 waveform mean, mV, row-major
-```
-
-All image arrays are `f32` big-endian, stored in row-major order: element `[r][f]` is at offset `(r × n_frames + f) × 4` bytes within the array.
-
-**`peak_freq_mhz`** is computed without any DC-threshold masking (i.e. the FFT is run on every pixel unconditionally). Readers apply the `dc4_mv` threshold at display time:
-
-```
-pixel is valid ⟺ dc4_mv[r][f] ≥ threshold_mv
-display_value = peak_freq_mhz[r][f] if valid, else 0
-```
-
-**`dc4_mv` / `dc3_mv`** are the mean of all ADC samples in the respective channel waveform, converted to millivolts using the channel calibration:
-
-```
-dc_mv = (adc_mean − YOFF) × YMULT + YZERO
-```
-
-#### When readers must bypass the PREC fast path
-
-Readers must fall back to real-time FFT computation (ignoring stored `peak_freq_mhz`) when any of the following are true:
-
-- Time-domain gating is active (zeroing samples outside a time window changes peak frequency).
-- Zero-padding (`n_fft ≠ samples_per_frame`) is requested (changes bin spacing).
-- The reader's background-subtraction setting does not match `flags.bg_sub_applied`.
-
----
-
-## Derived quantities
-
-```
-pixel_pitch_mm = velocity_mm_s / laser_freq_hz
-
-x_axis_mm[f] = x_start_mm + f × pixel_pitch_mm (f = 0 … n_frames−1)
-
-time_axis_ns[s] = s / sample_rate_hz × 1e9 (s = 0 … spf−1)
-
-freq_axis_mhz[k] = k × sample_rate_hz / (n_fft × 1e6) (k = 0 … n_fft/2)
- where n_fft = samples_per_frame unless zero-padding is active
-
-velocity_ms[r][f] = peak_freq_mhz[r][f] × grating_um (grating_um user-supplied)
-```
-
----
-
-## ADC calibration
-
-Convert raw ADC counts to millivolts:
-
-```
-voltage_mv = (adc_count − YOFF) × YMULT_mv + YZERO_mv
-```
-
-Invert (mV → ADC count):
-
-```
-adc_count = (voltage_mv − YZERO_mv) / YMULT_mv + YOFF
-```
-
-where `YMULT_mv` is YMULT in mV count⁻¹ (= scope YMULT in V count⁻¹ × 1000).
-
----
-
-## Waveform data layout detail
-
-For a scan with `n_angles=2`, `n_rows=3`, `n_channels=3`, `n_frames=4`, `spf=5` the layout is:
-
-```
-angle 0
- row 0
- CH1: [s0 s1 s2 s3 s4] [s0 s1 s2 s3 s4] [s0 s1 s2 s3 s4] [s0 s1 s2 s3 s4]
- frame 0 frame 1 frame 2 frame 3
- CH3: …(same layout)…
- CH4: …(same layout)…
- row 1
- …
- row 2
- …
-angle 1
- …
-```
-
-The flat byte offset of sample `s` of frame `f`, channel `c`, row `r`, angle `a` is:
-
-```
-offset = data_offset
- + (a × n_rows × n_channels × n_frames × spf
- + r × n_channels × n_frames × spf
- + c × n_frames × spf
- + f × spf
- + s)
- × bytes_per_sample
-```
-
----
-
-## Size reference
-
-Approximate sizes for representative scans (`bytes_per_sample = 1`, `n_channels = 3`).
-
-| n_angles | n_rows | n_frames | spf | Waveform data | PREC section |
-|----------|--------|----------|-----|---------------|-------------|
-| 1 | 500 | 500 | 400 | 300 MB | 12 MB |
-| 4 | 500 | 500 | 400 | 1.2 GB | 48 MB |
-| 1 | 2000 | 2000 | 400 | 4.8 GB | 48 MB |
-| 4 | 2000 | 2000 | 400 | 19.2 GB | 192 MB |
-| 8 | 2000 | 2000 | 400 | 38.4 GB | 384 MB |
-| 16 | 2000 | 2000 | 400 | 76.8 GB | 768 MB |
-
-**PREC section size formula:**
-
-```
-prec_bytes = 8 + n_stored × (2 + 3 × n_rows × n_frames × 4)
-```
-
----
-
-## Compatibility notes
-
-### Reading v5 files with a v4 reader
-
-A v4 reader that only accepts versions `{2, 3, 4}` will reject a v5 file with an "unsupported version" error. This is intentional: a v4 reader would derive `n_frames` from the file size, incorrectly including the PREC bytes in the sample count, producing a silently wrong reshape.
-
-### Producing v5 files
-
-v5 files are produced by the SRAS viewer's **"Pre-process and Save as v5"** action. The procedure is:
-
-1. Copy the source file (any version) verbatim.
-2. Set `version = 5` at byte offset 4.
-3. Set `n_frames_hdr` at byte offset 25 to the actual acquired frame count.
-4. Truncate the copy to `data_offset + waveform_bytes` (removes any pre-existing stale PREC tail).
-5. Compute `peak_freq_mhz`, `dc4_mv`, and `dc3_mv` for every angle using chunked FFT.
-6. Append the PREC section.
-
-### Partially-written PREC sections
-
-If `n_stored < n_angles` (e.g. pre-processing was interrupted), the file is still valid. Readers use stored images for the angles present in the PREC section and fall back to real-time FFT for the remainder. Readers must check `angle_idx` bounds on each entry and stop parsing on an out-of-range value.