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
  2. Type notation
  3. Version history
  4. File structure
  5. Derived quantities
  6. ADC calibration
  7. Waveform data layout detail
  8. Size reference
  9. 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: 1i8, 2i16 (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:


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:

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


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_storedn_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:


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.