761 lines
24 KiB
HTML
761 lines
24 KiB
HTML
<!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>v2–v4:</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_angles−1</td>
|
||
<td>Scan angle (rotation position)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>[r]</code></td>
|
||
<td>0 … n_rows−1</td>
|
||
<td>Row (Y position); row 0 is the first acquired</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>[c]</code></td>
|
||
<td>0 … n_channels−1</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_frames−1</td>
|
||
<td>Frame (X position) within the row</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>[s]</code></td>
|
||
<td>0 … spf−1</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>v2–v4:</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>1–7</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_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)
|
||
</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 "unsupported version" 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>"Pre-process and Save as v5"</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 < 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> |