Files
sras-viewer/SRAS_FORMAT.html
2026-07-30 18:45:40 -05:00

761 lines
24 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SRAS File Format Specification</title>
<style>
/* From extension vscode.github */
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vscode-dark img[src$=\#gh-light-mode-only],
.vscode-light img[src$=\#gh-dark-mode-only],
.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only],
.vscode-high-contrast-light img[src$=\#gh-dark-mode-only] {
display: none;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
font-size: 14px;
line-height: 1.6;
}
</style>
<style>
.task-list-item {
list-style-type: none;
}
.task-list-item-checkbox {
margin-left: -20px;
vertical-align: middle;
pointer-events: none;
}
</style>
<style>
:root {
--color-note: #0969da;
--color-tip: #1a7f37;
--color-warning: #9a6700;
--color-severe: #bc4c00;
--color-caution: #d1242f;
--color-important: #8250df;
}
</style>
<style>
@media (prefers-color-scheme: dark) {
:root {
--color-note: #2f81f7;
--color-tip: #3fb950;
--color-warning: #d29922;
--color-severe: #db6d28;
--color-caution: #f85149;
--color-important: #a371f7;
}
}
</style>
<style>
.markdown-alert {
padding: 0.5rem 1rem;
margin-bottom: 16px;
color: inherit;
border-left: .25em solid #888;
}
.markdown-alert>:first-child {
margin-top: 0
}
.markdown-alert>:last-child {
margin-bottom: 0
}
.markdown-alert .markdown-alert-title {
display: flex;
font-weight: 500;
align-items: center;
line-height: 1
}
.markdown-alert .markdown-alert-title .octicon {
margin-right: 0.5rem;
display: inline-block;
overflow: visible !important;
vertical-align: text-bottom;
fill: currentColor;
}
.markdown-alert.markdown-alert-note {
border-left-color: var(--color-note);
}
.markdown-alert.markdown-alert-note .markdown-alert-title {
color: var(--color-note);
}
.markdown-alert.markdown-alert-important {
border-left-color: var(--color-important);
}
.markdown-alert.markdown-alert-important .markdown-alert-title {
color: var(--color-important);
}
.markdown-alert.markdown-alert-warning {
border-left-color: var(--color-warning);
}
.markdown-alert.markdown-alert-warning .markdown-alert-title {
color: var(--color-warning);
}
.markdown-alert.markdown-alert-tip {
border-left-color: var(--color-tip);
}
.markdown-alert.markdown-alert-tip .markdown-alert-title {
color: var(--color-tip);
}
.markdown-alert.markdown-alert-caution {
border-left-color: var(--color-caution);
}
.markdown-alert.markdown-alert-caution .markdown-alert-title {
color: var(--color-caution);
}
</style>
</head>
<body class="vscode-body vscode-light">
<h1 id="sras-file-format-specification">SRAS File Format Specification</h1>
<p><strong>Format family:</strong> <code>.sras</code><br>
<strong>Byte order:</strong> Big-endian (network byte order) throughout, unless noted.<br>
<strong>Version history:</strong> v2 (baseline), v3 (scope calibration), v4 (background waveform), v5 (precomputed images + guaranteed frame count).</p>
<hr>
<h2 id="table-of-contents">Table of Contents</h2>
<ol>
<li><a href="#overview">Overview</a></li>
<li><a href="#type-notation">Type notation</a></li>
<li><a href="#version-history">Version history</a></li>
<li><a href="#file-structure">File structure</a>
<ul>
<li><a href="#1-fixed-header-43-bytes-all-versions">Fixed header (all versions)</a></li>
<li><a href="#2-angle-table-all-versions">Angle table (all versions)</a></li>
<li><a href="#3-row-position-table-all-versions">Row position table (all versions)</a></li>
<li><a href="#4-channel-preambles-v3">Channel preambles (v3+)</a></li>
<li><a href="#5-background-waveform-v4">Background waveform (v4+)</a></li>
<li><a href="#6-waveform-data-all-versions">Waveform data (all versions)</a></li>
<li><a href="#7-prec-section-v5">PREC section (v5)</a></li>
</ul>
</li>
<li><a href="#derived-quantities">Derived quantities</a></li>
<li><a href="#adc-calibration">ADC calibration</a></li>
<li><a href="#waveform-data-layout-detail">Waveform data layout detail</a></li>
<li><a href="#size-reference">Size reference</a></li>
<li><a href="#compatibility-notes">Compatibility notes</a></li>
</ol>
<hr>
<h2 id="overview">Overview</h2>
<p>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.</p>
<p>A scan consists of one or more <strong>angles</strong> (rotation positions of the sample), each containing a 2-D raster of <strong>rows</strong> × <strong>frames</strong>. At every pixel, <code>n_channels</code> waveforms of <code>samples_per_frame</code> ADC counts are stored. Channel order is fixed:</p>
<table>
<thead>
<tr>
<th>Index</th>
<th>Hardware channel</th>
<th>Signal</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>CH1</td>
<td>RF acoustic packet (AC-coupled)</td>
</tr>
<tr>
<td>1</td>
<td>CH3</td>
<td>Bias A — DC mean used for masking</td>
</tr>
<tr>
<td>2</td>
<td>CH4</td>
<td>Bias B — DC mean used for masking</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="type-notation">Type notation</h2>
<table>
<thead>
<tr>
<th>Symbol</th>
<th>C type</th>
<th>Size</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>u8</code></td>
<td><code>uint8_t</code></td>
<td>1 byte</td>
<td>unsigned</td>
</tr>
<tr>
<td><code>u16</code></td>
<td><code>uint16_t</code></td>
<td>2 bytes</td>
<td>big-endian</td>
</tr>
<tr>
<td><code>u32</code></td>
<td><code>uint32_t</code></td>
<td>4 bytes</td>
<td>big-endian</td>
</tr>
<tr>
<td><code>i8</code></td>
<td><code>int8_t</code></td>
<td>1 byte</td>
<td>signed, used for ADC samples when <code>bytes_per_sample == 1</code></td>
</tr>
<tr>
<td><code>i16</code></td>
<td><code>int16_t</code></td>
<td>2 bytes</td>
<td>big-endian signed, used when <code>bytes_per_sample == 2</code></td>
</tr>
<tr>
<td><code>f32</code></td>
<td><code>float</code></td>
<td>4 bytes</td>
<td>big-endian IEEE 754 single</td>
</tr>
<tr>
<td><code>f64</code></td>
<td><code>double</code></td>
<td>8 bytes</td>
<td>big-endian IEEE 754 double</td>
</tr>
<tr>
<td><code>char[N]</code></td>
<td></td>
<td>N bytes</td>
<td>raw bytes, no null terminator unless noted</td>
</tr>
<tr>
<td><code>utf8[N]</code></td>
<td></td>
<td>N bytes</td>
<td>UTF-8 string, length-prefixed (see preamble section)</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="version-history">Version history</h2>
<table>
<thead>
<tr>
<th>Version</th>
<th>Added</th>
</tr>
</thead>
<tbody>
<tr>
<td>2</td>
<td>Baseline: fixed header, angle table, row table, raw waveform data. No scope calibration (fallback constants used by readers).</td>
</tr>
<tr>
<td>3</td>
<td>Per-channel Tektronix WFMOutpre preamble strings carrying YMULT / YOFF / YZERO calibration.</td>
</tr>
<tr>
<td>4</td>
<td>Background waveform section: one CH1 reference shot subtracted from each CH1 frame before FFT.</td>
</tr>
<tr>
<td>5</td>
<td><strong>(this document)</strong> Version byte incremented to 5. <code>n_frames_hdr</code> is now the <em>actual</em> acquired frame count (authoritative). PREC section appended after waveform data with precomputed FFT-peak and DC images for instant re-display.</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>v2 note:</strong> Version 1 is not defined; version 2 is the lowest observed in the field.</p>
</blockquote>
<hr>
<h2 id="file-structure">File structure</h2>
<pre><code>┌─────────────────────────────────────────────┐
│ 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
└─────────────────────────────────────────────┘
</code></pre>
<hr>
<h3 id="1-fixed-header-43-bytes-all-versions">1. Fixed header (43 bytes, all versions)</h3>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Size</th>
<th>Type</th>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>4</td>
<td><code>char[4]</code></td>
<td><code>magic</code></td>
<td><code>SRAS</code> (ASCII, no null terminator). Reject file if this does not match.</td>
</tr>
<tr>
<td>4</td>
<td>1</td>
<td><code>u8</code></td>
<td><code>version</code></td>
<td>Format version. This document describes version <strong>5</strong>.</td>
</tr>
<tr>
<td>5</td>
<td>2</td>
<td><code>u16</code></td>
<td><code>n_angles</code></td>
<td>Number of scan angles (rotation positions). ≥ 1.</td>
</tr>
<tr>
<td>7</td>
<td>2</td>
<td><code>u16</code></td>
<td><code>n_rows</code></td>
<td>Number of scan rows (Y positions). ≥ 1.</td>
</tr>
<tr>
<td>9</td>
<td>4</td>
<td><code>f32</code></td>
<td><code>x_start_mm</code></td>
<td>X position of the first frame in the first row, in millimetres.</td>
</tr>
<tr>
<td>13</td>
<td>4</td>
<td><code>f32</code></td>
<td><code>x_delta_mm</code></td>
<td>Pre-computed pixel pitch in mm (<code>velocity_mm_s / laser_freq_hz</code>). Provided for convenience; readers should prefer the derived value.</td>
</tr>
<tr>
<td>17</td>
<td>4</td>
<td><code>f32</code></td>
<td><code>velocity_mm_s</code></td>
<td>Scanner stage velocity, mm s⁻¹. Used together with <code>laser_freq_hz</code> to compute pixel pitch.</td>
</tr>
<tr>
<td>21</td>
<td>4</td>
<td><code>f32</code></td>
<td><code>laser_freq_hz</code></td>
<td>Laser repetition rate, Hz.</td>
</tr>
<tr>
<td>25</td>
<td>4</td>
<td><code>u32</code></td>
<td><code>n_frames_hdr</code></td>
<td><strong>v2v4:</strong> the <em>configured</em> frame count written before acquisition; may exceed actual frames acquired (use file-size arithmetic to obtain the true count). <strong>v5:</strong> the <em>actual</em> acquired frame count — authoritative; readers must not re-derive it from file size.</td>
</tr>
<tr>
<td>29</td>
<td>4</td>
<td><code>u32</code></td>
<td><code>samples_per_frame</code></td>
<td>ADC samples per waveform (<code>spf</code>).</td>
</tr>
<tr>
<td>33</td>
<td>8</td>
<td><code>f64</code></td>
<td><code>sample_rate_hz</code></td>
<td>Oscilloscope sample rate, Hz (e.g. 5 × 10⁹ for 5 GS/s).</td>
</tr>
<tr>
<td>41</td>
<td>1</td>
<td><code>u8</code></td>
<td><code>bytes_per_sample</code></td>
<td>ADC word size: <code>1</code><code>i8</code>, <code>2</code><code>i16</code> (big-endian).</td>
</tr>
<tr>
<td>42</td>
<td>1</td>
<td><code>u8</code></td>
<td><code>n_channels</code></td>
<td>Number of channels per frame. Currently always <code>3</code>.</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="2-angle-table-all-versions">2. Angle table (all versions)</h3>
<p>Immediately follows the fixed header.</p>
<pre><code>n_angles × f32 — scan angle in degrees
</code></pre>
<p>Each entry is a big-endian <code>f32</code> giving the sample rotation angle in degrees at which that angle index was acquired.</p>
<hr>
<h3 id="3-row-position-table-all-versions">3. Row position table (all versions)</h3>
<p>Immediately follows the angle table.</p>
<pre><code>n_rows × f32 — Y position of each row, in millimetres
</code></pre>
<hr>
<h3 id="4-channel-preambles-v3">4. Channel preambles (v3+)</h3>
<p>One entry per channel, in channel-index order (CH1 first).</p>
<pre><code>for each channel:
u16 preamble_length — byte count of the UTF-8 string that follows
utf8[N] preamble — Tektronix WFMOutpre string
</code></pre>
<p>The preamble is the oscilloscope's <code>WFMOutpre</code> response string. Readers extract the following keys (case-insensitive, space-separated value):</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Stored unit</th>
<th>Conversion to mV</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>YMULT</code></td>
<td>V count⁻¹</td>
<td>multiply by 1000</td>
</tr>
<tr>
<td><code>YOFF</code></td>
<td>ADC counts</td>
<td>used directly</td>
</tr>
<tr>
<td><code>YZERO</code></td>
<td>V</td>
<td>multiply by 1000</td>
</tr>
</tbody>
</table>
<p><strong>v2 fallback:</strong> when preambles are absent, readers use:</p>
<ul>
<li><code>YMULT</code> = 1.5625 mV count⁻¹ (50 mV/div, 8 div, 8-bit ADC)</li>
<li><code>YOFF</code> = 87.04 ADC counts (scope position = 2.72 div)</li>
<li><code>YZERO</code> = 0 mV</li>
</ul>
<hr>
<h3 id="5-background-waveform-v4">5. Background waveform (v4+)</h3>
<pre><code>u32 n_bg_samples — number of i8 ADC samples that follow
i8[n_bg] background — one representative CH1 background shot
</code></pre>
<p>The background waveform has the same <code>samples_per_frame</code> length as a normal CH1 waveform. It is subtracted from each CH1 waveform before FFT processing when background subtraction is enabled. When <code>n_bg_samples == 0</code> the section is present but empty.</p>
<hr>
<h3 id="6-waveform-data-all-versions">6. Waveform data (all versions)</h3>
<p>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:</p>
<pre><code>waveform_data[n_angles][n_rows][n_channels][n_frames][samples_per_frame]
</code></pre>
<p>Each element is a signed ADC count of size <code>bytes_per_sample</code>:</p>
<ul>
<li><code>bytes_per_sample == 1</code><code>i8</code></li>
<li><code>bytes_per_sample == 2</code><code>i16</code> big-endian</li>
</ul>
<p><strong>Total byte count:</strong></p>
<pre><code>waveform_bytes = n_angles × n_rows × n_channels × n_frames × samples_per_frame × bytes_per_sample
</code></pre>
<h4 id="index-semantics">Index semantics</h4>
<table>
<thead>
<tr>
<th>Dimension</th>
<th>Range</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[a]</code></td>
<td>0 … n_angles1</td>
<td>Scan angle (rotation position)</td>
</tr>
<tr>
<td><code>[r]</code></td>
<td>0 … n_rows1</td>
<td>Row (Y position); row 0 is the first acquired</td>
</tr>
<tr>
<td><code>[c]</code></td>
<td>0 … n_channels1</td>
<td>Channel (0=CH1 RF, 1=CH3 Bias A, 2=CH4 Bias B)</td>
</tr>
<tr>
<td><code>[f]</code></td>
<td>0 … n_frames1</td>
<td>Frame (X position) within the row</td>
</tr>
<tr>
<td><code>[s]</code></td>
<td>0 … spf1</td>
<td>Sample index within the waveform</td>
</tr>
</tbody>
</table>
<h4 id="frame-count-determination">Frame-count determination</h4>
<ul>
<li><strong>v5:</strong> use <code>n_frames_hdr</code> directly; do not use file-size arithmetic.</li>
<li><strong>v2v4:</strong> <code>n_frames = floor((file_bytes_after_header_sections) / (bytes_per_sample × n_angles × n_rows × n_channels × samples_per_frame))</code>. Any remainder bytes are a partial trailing row and are discarded.</li>
</ul>
<hr>
<h3 id="7-prec-section-v5">7. PREC section (v5)</h3>
<p>The PREC section is appended immediately after the waveform data and is present if and only if <code>version == 5</code> and the file size exceeds <code>waveform_end_offset</code>.</p>
<pre><code>waveform_end_offset = data_offset + waveform_bytes
</code></pre>
<p>where <code>data_offset</code> 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).</p>
<h4 id="prec-header-8-bytes">PREC header (8 bytes)</h4>
<table>
<thead>
<tr>
<th>Offset (relative)</th>
<th>Size</th>
<th>Type</th>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>4</td>
<td><code>char[4]</code></td>
<td><code>prec_magic</code></td>
<td><code>PREC</code> (ASCII). Absent or wrong magic → ignore section.</td>
</tr>
<tr>
<td>4</td>
<td>1</td>
<td><code>u8</code></td>
<td><code>prec_version</code></td>
<td>PREC format version. Currently <code>1</code>.</td>
</tr>
<tr>
<td>5</td>
<td>1</td>
<td><code>u8</code></td>
<td><code>flags</code></td>
<td>Bitmask (see below).</td>
</tr>
<tr>
<td>6</td>
<td>2</td>
<td><code>u16</code></td>
<td><code>n_stored</code></td>
<td>Number of angle entries that follow. 0 ≤ <code>n_stored</code><code>n_angles</code>.</td>
</tr>
</tbody>
</table>
<h5 id="flags-byte">Flags byte</h5>
<table>
<thead>
<tr>
<th>Bit</th>
<th>Mask</th>
<th>Meaning when set</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td><code>0x01</code></td>
<td><code>bg_sub_applied</code> — background waveform was subtracted from CH1 before the FFT when these images were computed.</td>
</tr>
<tr>
<td>17</td>
<td></td>
<td>Reserved, must be zero on write; readers must ignore.</td>
</tr>
</tbody>
</table>
<h4 id="prec-angle-entries">PREC angle entries</h4>
<p>Repeated <code>n_stored</code> times, in arbitrary angle-index order:</p>
<pre><code>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
</code></pre>
<p>All image arrays are <code>f32</code> big-endian, stored in row-major order: element <code>[r][f]</code> is at offset <code>(r × n_frames + f) × 4</code> bytes within the array.</p>
<p><strong><code>peak_freq_mhz</code></strong> is computed without any DC-threshold masking (i.e. the FFT is run on every pixel unconditionally). Readers apply the <code>dc4_mv</code> threshold at display time:</p>
<pre><code>pixel is valid ⟺ dc4_mv[r][f] ≥ threshold_mv
display_value = peak_freq_mhz[r][f] if valid, else 0
</code></pre>
<p><strong><code>dc4_mv</code> / <code>dc3_mv</code></strong> are the mean of all ADC samples in the respective channel waveform, converted to millivolts using the channel calibration:</p>
<pre><code>dc_mv = (adc_mean YOFF) × YMULT + YZERO
</code></pre>
<h4 id="when-readers-must-bypass-the-prec-fast-path">When readers must bypass the PREC fast path</h4>
<p>Readers must fall back to real-time FFT computation (ignoring stored <code>peak_freq_mhz</code>) when any of the following are true:</p>
<ul>
<li>Time-domain gating is active (zeroing samples outside a time window changes peak frequency).</li>
<li>Zero-padding (<code>n_fft ≠ samples_per_frame</code>) is requested (changes bin spacing).</li>
<li>The reader's background-subtraction setting does not match <code>flags.bg_sub_applied</code>.</li>
</ul>
<hr>
<h2 id="derived-quantities">Derived quantities</h2>
<pre><code>pixel_pitch_mm = velocity_mm_s / laser_freq_hz
x_axis_mm[f] = x_start_mm + f × pixel_pitch_mm (f = 0 … n_frames1)
time_axis_ns[s] = s / sample_rate_hz × 1e9 (s = 0 … spf1)
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)
</code></pre>
<hr>
<h2 id="adc-calibration">ADC calibration</h2>
<p>Convert raw ADC counts to millivolts:</p>
<pre><code>voltage_mv = (adc_count YOFF) × YMULT_mv + YZERO_mv
</code></pre>
<p>Invert (mV → ADC count):</p>
<pre><code>adc_count = (voltage_mv YZERO_mv) / YMULT_mv + YOFF
</code></pre>
<p>where <code>YMULT_mv</code> is YMULT in mV count⁻¹ (= scope YMULT in V count⁻¹ × 1000).</p>
<hr>
<h2 id="waveform-data-layout-detail">Waveform data layout detail</h2>
<p>For a scan with <code>n_angles=2</code>, <code>n_rows=3</code>, <code>n_channels=3</code>, <code>n_frames=4</code>, <code>spf=5</code> the layout is:</p>
<pre><code>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
</code></pre>
<p>The flat byte offset of sample <code>s</code> of frame <code>f</code>, channel <code>c</code>, row <code>r</code>, angle <code>a</code> is:</p>
<pre><code>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
</code></pre>
<hr>
<h2 id="size-reference">Size reference</h2>
<p>Approximate sizes for representative scans (<code>bytes_per_sample = 1</code>, <code>n_channels = 3</code>).</p>
<table>
<thead>
<tr>
<th>n_angles</th>
<th>n_rows</th>
<th>n_frames</th>
<th>spf</th>
<th>Waveform data</th>
<th>PREC section</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>500</td>
<td>500</td>
<td>400</td>
<td>300 MB</td>
<td>12 MB</td>
</tr>
<tr>
<td>4</td>
<td>500</td>
<td>500</td>
<td>400</td>
<td>1.2 GB</td>
<td>48 MB</td>
</tr>
<tr>
<td>1</td>
<td>2000</td>
<td>2000</td>
<td>400</td>
<td>4.8 GB</td>
<td>48 MB</td>
</tr>
<tr>
<td>4</td>
<td>2000</td>
<td>2000</td>
<td>400</td>
<td>19.2 GB</td>
<td>192 MB</td>
</tr>
<tr>
<td>8</td>
<td>2000</td>
<td>2000</td>
<td>400</td>
<td>38.4 GB</td>
<td>384 MB</td>
</tr>
<tr>
<td>16</td>
<td>2000</td>
<td>2000</td>
<td>400</td>
<td>76.8 GB</td>
<td>768 MB</td>
</tr>
</tbody>
</table>
<p><strong>PREC section size formula:</strong></p>
<pre><code>prec_bytes = 8 + n_stored × (2 + 3 × n_rows × n_frames × 4)
</code></pre>
<hr>
<h2 id="compatibility-notes">Compatibility notes</h2>
<h3 id="reading-v5-files-with-a-v4-reader">Reading v5 files with a v4 reader</h3>
<p>A v4 reader that only accepts versions <code>{2, 3, 4}</code> will reject a v5 file with an &quot;unsupported version&quot; error. This is intentional: a v4 reader would derive <code>n_frames</code> from the file size, incorrectly including the PREC bytes in the sample count, producing a silently wrong reshape.</p>
<h3 id="producing-v5-files">Producing v5 files</h3>
<p>v5 files are produced by the SRAS viewer's <strong>&quot;Pre-process and Save as v5&quot;</strong> action. The procedure is:</p>
<ol>
<li>Copy the source file (any version) verbatim.</li>
<li>Set <code>version = 5</code> at byte offset 4.</li>
<li>Set <code>n_frames_hdr</code> at byte offset 25 to the actual acquired frame count.</li>
<li>Truncate the copy to <code>data_offset + waveform_bytes</code> (removes any pre-existing stale PREC tail).</li>
<li>Compute <code>peak_freq_mhz</code>, <code>dc4_mv</code>, and <code>dc3_mv</code> for every angle using chunked FFT.</li>
<li>Append the PREC section.</li>
</ol>
<h3 id="partially-written-prec-sections">Partially-written PREC sections</h3>
<p>If <code>n_stored &lt; n_angles</code> (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 <code>angle_idx</code> bounds on each entry and stop parsing on an out-of-range value.</p>
</body>
</html>