15 KiB
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
- Overview
- Type notation
- Version history
- File structure
- Derived quantities
- ADC calibration
- Waveform data layout detail
- Size reference
- 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→i8bytes_per_sample == 2→i16big-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_hdrdirectly; 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:
- Copy the source file (any version) verbatim.
- Set
version = 5at byte offset 4. - Set
n_frames_hdrat 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, anddc3_mvfor 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.