diff --git a/.gitignore b/.gitignore index 670a936..2694b86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ __pycache__/ .venv/ +.DS_Store +.pytest_cache/ +*.png +*.sras +baseline*.txt +after*.txt diff --git a/SRAS_FORMAT.html b/SRAS_FORMAT.html deleted file mode 100644 index 204a4e5..0000000 --- a/SRAS_FORMAT.html +++ /dev/null @@ -1,761 +0,0 @@ - - - - - SRAS File Format Specification - - - - - - - - - - - - -

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. -
  3. Type notation
  4. -
  5. Version history
  6. -
  7. File structure - -
  8. -
  9. Derived quantities
  10. -
  11. ADC calibration
  12. -
  13. Waveform data layout detail
  14. -
  15. Size reference
  16. -
  17. Compatibility notes
  18. -
-
-

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:

- - - - - - - - - - - - - - - - - - - - - - - - - -
IndexHardware channelSignal
0CH1RF acoustic packet (AC-coupled)
1CH3Bias A — DC mean used for masking
2CH4Bias B — DC mean used for masking
-
-

Type notation

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SymbolC typeSizeNotes
u8uint8_t1 byteunsigned
u16uint16_t2 bytesbig-endian
u32uint32_t4 bytesbig-endian
i8int8_t1 bytesigned, used for ADC samples when bytes_per_sample == 1
i16int16_t2 bytesbig-endian signed, used when bytes_per_sample == 2
f32float4 bytesbig-endian IEEE 754 single
f64double8 bytesbig-endian IEEE 754 double
char[N]—N bytesraw bytes, no null terminator unless noted
utf8[N]—N bytesUTF-8 string, length-prefixed (see preamble section)
-
-

Version history

- - - - - - - - - - - - - - - - - - - - - - - - - -
VersionAdded
2Baseline: fixed header, angle table, row table, raw waveform data. No scope calibration (fallback constants used by readers).
3Per-channel Tektronix WFMOutpre preamble strings carrying YMULT / YOFF / YZERO calibration.
4Background 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)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OffsetSizeTypeFieldDescription
04char[4]magicSRAS (ASCII, no null terminator). Reject file if this does not match.
41u8versionFormat version. This document describes version 5.
52u16n_anglesNumber of scan angles (rotation positions). ≥ 1.
72u16n_rowsNumber of scan rows (Y positions). ≥ 1.
94f32x_start_mmX position of the first frame in the first row, in millimetres.
134f32x_delta_mmPre-computed pixel pitch in mm (velocity_mm_s / laser_freq_hz). Provided for convenience; readers should prefer the derived value.
174f32velocity_mm_sScanner stage velocity, mm s⁻¹. Used together with laser_freq_hz to compute pixel pitch.
214f32laser_freq_hzLaser repetition rate, Hz.
254u32n_frames_hdrv2–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.
294u32samples_per_frameADC samples per waveform (spf).
338f64sample_rate_hzOscilloscope sample rate, Hz (e.g. 5 × 10⁹ for 5 GS/s).
411u8bytes_per_sampleADC word size: 1 → i8, 2 → i16 (big-endian).
421u8n_channelsNumber 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):

- - - - - - - - - - - - - - - - - - - - - - - - - -
KeyStored unitConversion to mV
YMULTV count⁻¹multiply by 1000
YOFFADC countsused directly
YZEROVmultiply 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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DimensionRangeMeaning
[a]0 … n_angles−1Scan angle (rotation position)
[r]0 … n_rows−1Row (Y position); row 0 is the first acquired
[c]0 … n_channels−1Channel (0=CH1 RF, 1=CH3 Bias A, 2=CH4 Bias B)
[f]0 … n_frames−1Frame (X position) within the row
[s]0 … spf−1Sample 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)SizeTypeFieldDescription
04char[4]prec_magicPREC (ASCII). Absent or wrong magic → ignore section.
41u8prec_versionPREC format version. Currently 1.
51u8flagsBitmask (see below).
62u16n_storedNumber of angle entries that follow. 0 ≤ n_stored ≤ n_angles.
-
Flags byte
- - - - - - - - - - - - - - - - - - - - -
BitMaskMeaning when set
00x01bg_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_anglesn_rowsn_framesspfWaveform dataPREC section
1500500400300 MB12 MB
45005004001.2 GB48 MB
1200020004004.8 GB48 MB
42000200040019.2 GB192 MB
82000200040038.4 GB384 MB
162000200040076.8 GB768 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. -
  3. Set version = 5 at byte offset 4.
  4. -
  5. Set n_frames_hdr at byte offset 25 to the actual acquired frame count.
  6. -
  7. Truncate the copy to data_offset + waveform_bytes (removes any pre-existing stale PREC tail).
  8. -
  9. Compute peak_freq_mhz, dc4_mv, and dc3_mv for every angle using chunked FFT.
  10. -
  11. Append the PREC section.
  12. -
-

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.