7.6 KiB
SRAS Scan Binary Format — Version 4
Each .sras file contains one complete scan: all GR rotation angles and all
Y rows. Files are named {prefix}.sras.
File Layout
[Global Header — 43 bytes]
[Angle Table — n_angles × 4 bytes (float32 per angle)]
[Row Table — n_rows × 4 bytes (float32 per row)]
[Preamble Blocks — n_channels × (uint16 length + UTF-8 WFMOutpre string)]
[Background Block — uint32 n_bg_samples + n_bg_samples × int8 bytes]
[Waveform Data — n_angles × n_rows × n_channels × n_frames × samples_per_frame × bps bytes]
All multi-byte integers and floats use big-endian byte order
(> in Python's struct module).
Global Header (42 bytes)
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | 4s |
magic |
Always SRAS (0x53 0x52 0x41 0x53) |
| 4 | 1 | uint8 |
version |
Format version — 4 |
| 5 | 2 | uint16 |
n_angles |
Number of GR rotation angles |
| 7 | 2 | uint16 |
n_rows |
Number of Y rows per angle |
| 9 | 4 | float32 |
x_start_mm |
X scan start position in mm |
| 13 | 4 | float32 |
x_delta_mm |
X scan width in mm |
| 17 | 4 | float32 |
velocity_mm_s |
Stage scan velocity in mm/s |
| 21 | 4 | float32 |
laser_freq_hz |
Laser repetition rate in Hz |
| 25 | 4 | uint32 |
n_frames |
A-scans per row (= FastFrame count per channel) |
| 29 | 4 | uint32 |
samples_per_frame |
Time samples per waveform |
| 33 | 8 | float64 |
sample_rate_hz |
Oscilloscope sample rate in Hz (e.g. 6.25e9) |
| 41 | 1 | uint8 |
bytes_per_sample |
Bytes per ADC sample: 1 = int8, 2 = int16 |
| 42 | 1 | uint8 |
n_channels |
Number of channels recorded (currently 3) |
Total header size: 43 bytes — verified:
struct.calcsize(">4sBHHffffIIdBB") == 43.
Angle Table
Immediately after the header: n_angles big-endian float32 values, one per GR angle (degrees, 0–180).
angle[0], angle[1], …, angle[n_angles - 1]
Row Table
Immediately after the angle table: n_rows big-endian float32 values, one per Y row (mm).
y_mm[0], y_mm[1], …, y_mm[n_rows - 1]
Preamble Blocks
Immediately after the row table: n_channels length-prefixed UTF-8 strings,
one per channel in SCAN_CHANNELS order (CH1, CH3, CH4). Each block is:
uint16 length — byte length of the following UTF-8 string
bytes preamble — WFMOutpre response string from the oscilloscope
The preamble captures per-channel scaling constants (YMULT, YOFF, YZERO) needed to convert raw ADC values to volts.
Background Block
Immediately after the preamble blocks: a single CH1 waveform captured with the Helios (generation) laser enabled and the Genesis (detection) laser disabled. This provides a noise/background reference for subtraction during post-processing.
uint32 n_bg_samples — number of samples in the background waveform
int8[] bg_data — raw ADC samples (same encoding as waveform data)
n_bg_samples equals samples_per_frame under normal acquisition settings.
Waveform Data
Immediately after the background block. Data is stored in angle-major, row-minor order. Within each row, channels are interleaved in ascending channel-index order, with each channel's FastFrame data written in frame order.
for angle in 0 … n_angles-1:
for row in 0 … n_rows-1:
for channel in [CH1, CH3, CH4]: # 3 channels, fixed order
for frame in 0 … n_frames-1:
samples[0 … samples_per_frame-1] # bps bytes each
Each sample is a raw signed ADC value. With bytes_per_sample = 1 this is
int8 (−128 … +127). With bytes_per_sample = 2 this is big-endian
int16.
Total data size:
n_angles × n_rows × 3 × n_frames × samples_per_frame × bytes_per_sample
Incomplete files: If a scan is aborted the file is closed immediately and the data block will be shorter than the expected size. Readers should check
file_size >= header + angle_table + row_table + databefore reshaping.
Spatial Mapping
The k-th waveform (frame) in a row corresponds to the k-th laser pulse that hit the sample. The physical X position of that pulse is:
x_k = x_start_mm + k * (velocity_mm_s / laser_freq_hz)
Python Read Example
import struct, numpy as np
from pathlib import Path
HDR_FMT = ">4sBHHffffIIdBB"
HDR_SIZE = struct.calcsize(HDR_FMT) # 43 bytes
def read_sras(path):
with open(path, "rb") as f:
hdr = struct.unpack(HDR_FMT, f.read(HDR_SIZE))
magic, ver, n_angles, n_rows, xs, xd, vel, freq, nf, spf, sr, bps, n_ch = hdr
assert magic == b"SRAS" and ver == 4, "Not a v4 SRAS file"
angles = np.frombuffer(f.read(n_angles * 4), dtype=">f4")
y_positions = np.frombuffer(f.read(n_rows * 4), dtype=">f4")
# Preamble blocks (one per channel)
preambles = []
for _ in range(n_ch):
(plen,) = struct.unpack(">H", f.read(2))
preambles.append(f.read(plen).decode("utf-8"))
# Background waveform block (v4+)
(n_bg,) = struct.unpack(">I", f.read(4))
background = np.frombuffer(f.read(n_bg), dtype=np.int8)
dtype = np.int8 if bps == 1 else ">i2"
data = np.frombuffer(f.read(), dtype=dtype).reshape(
n_angles, n_rows, n_ch, nf, spf
)
return {
"angles_deg": angles,
"y_positions_mm": y_positions,
"x_start_mm": xs,
"x_delta_mm": xd,
"velocity_mm_s": vel,
"laser_freq_hz": freq,
"sample_rate_hz": sr,
"n_channels": n_ch, # 3: CH1, CH3, CH4 (see Acquisition Settings)
"preambles": preambles, # WFMOutpre strings, same order as n_channels
"background": background,# shape: (n_bg_samples,) — CH1 noise reference
# shape: (n_angles, n_rows, n_channels, n_frames, samples_per_frame)
"data": data,
}
Acquisition Settings (fixed by sc3_aui_app.py)
| Parameter | Value |
|---|---|
| Oscilloscope trigger | CH2, rising edge, 1.24 V |
| Trigger offset | 0 % (trigger at left edge) |
| Sample rate | 6.25 GS/s (160 ps/sample) |
| Channels recorded | CH1, CH3, CH4 |
| Stage X velocity | 100 mm/s |
| Stage X acceleration | 1500 mm/s² |
| Stage X trigger out | Logic-high at max velocity |
| Acquisition mode | FastFrame, Normal trigger |
Version History
| Version | Change |
|---|---|
| 1 | One file per row; header included angle_idx, row_idx, angle_deg, y_mm. |
| 2 | One file per scan; global header with n_angles/n_rows; separate angle and row tables; three channels (CH1, CH3, CH4) per row. |
| 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. |