# 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 + data` before 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 ```python 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. |