Conventions and terms

The file names, stage names and measurement terms used throughout this documentation, defined once.

In short: the rules that make the port auditable — visible MATLAB indices, netCDF semantics, fidelity profiles, and which MATLAB findings are fixed (in the v3.5 engine or the opt-in enhancements) versus preserved as written.

See also Findings for what reproducing those preserved bugs revealed about the original · Glossary.

Every name below comes from UIOOPS: the file naming is combine_sizedist.m's, the stage boundaries the original scripts', the size bins sizeDist.m's. A campaign's files substitute into the placeholders.

Placeholders

<stem>a raw file's name without its extension. SPEC probes write base<YYMMDDhhmmss>, e.g. a file that opened at 06:44:03 on 6 September 2016 is base160906064403. Other manufacturers' formats have their own conventions; nothing in the pipeline requires this one.
<channel>H or V — the probe's horizontal or vertical array. Both are decoded; which is usable is a per-campaign question about the instrument.
<probe>the probe name as the project/probe table spells it: 2DS, HVPS, CIP, PIP, 2DC, 2DP, F2DC.

The pipeline's files

StageProducesContents
Decode (read-binary)<stem>.<channel>.cdfone record per image record: the eight timestamp fields and the packed 8 × 1700 image block
<stem>.tas.csvone row per housekeeping packet: time, true airspeed, and six counters. The one-second grid every later stage uses is built from these times
<stem>.NV.csv, <stem>.MK.csvthe vertical-count and mask sidecars, written verbatim as the original writes them (including its missing line terminators in .MK.csv)
Frame and describe (imgproc)proc.<stem>.<channel>.ncone row per particle: sizes under six definitions, habit, reject code, area, perimeter, hollow diagnostics, and the record/position bookkeeping that lets any particle be reconstructed
Size distributions (sizedist)sd.<stem>.<channel>.ncone column per second: counts and concentrations per size bin, sample volume, mean area ratio
sd.<stem>.<channel>.nc.callwhich call produced it — the output itself cannot be asked (see User guide, step 3)
Combine (combine)<stem₁>.combined.<channel>.<probe>.cdfa flight's sd files on one time axis, named after the flight's first raw file — the product a paper is written from

UIOOPS ships no canonical combine output name: each group's script writes its own, and one field campaign's scripts append _2 for a second processing pass, so the products validated here are …combined.H.2DS_2.cdf. arrayprobe.cli combine takes the output path explicitly. The batch driver writes …combined.H.2DS.cdf, and does not append _2.

Opt-in variants never overwrite the faithful output; they take a suffix before the extension, e.g. proc.<stem>.<channel>.diodecorrected.nc. Engine 3.5 writes its proc and sd files, and its SPEC (2D-S/HVPS) decode, NETCDF4, as v3.5's own netcdf.create(..., 'NETCDF4') calls do; its DMT, PMS and SEA decodes and its combined product stay NETCDF3_CLASSIC.

Stage names

The design documents number the original's stage boundaries:

S2raw binary → decoded image records (read_binary_*)
S3image records → per-particle file (imgProc_sm)
S4per-particle file → one-second size distributions (sizeDist)
S4ca flight's size distributions → one combined product (combine_sizedist)

Measurement terms

TermDefinition
Size binsizeDist's ladder of 128 bins, 10 µm wide, the first centred at 5 µm.
Used binsbins 6–128. The first five are at or below the diode pitch and are not used scientifically, though they hold 99.4-100.0 % of all particles. Headline agreement figures are quoted over the used bins.
Both-present denominatorevery (second, bin) cell where both files have a value, including 0 = 0. Dominated by agreement about emptiness.
Non-zero denominatoronly cells where at least one side is non-zero. The demanding figure. Both are always reported; see Validation.
Accepted particleone whose reject code is in 0 H h u R and whose centre is in the sample volume. sizeDist then applies its own area-ratio and inter-arrival cuts, so "accepted" at the per-particle stage is always more than what reaches a distribution.
Fidelity profilea named set of the axes on which two MATLAB lineages provably differ — bounds guards, reject thresholds, the zero-image branch. Never defaulted; see Configuration.
Treewhich sizeDist.m lineage's project/probe table to use: stock (UIOOPS as released) or production (the lineage that produced the reference products validated here).

Version anchors

An earlier translation of UIOOPS v3.1.4 reproduced archived products and is not part of this package. Findings about UIOOPS are anchored on v3.5, the version people run. See findings.

Deprecation policy

This package ships two engines — the v3.5-based UIOOPS engine (arrayprobe_uiops, the one uiops.engine() returns) and SODA-2 (arrayprobe_soda2) — and neither is deprecated.

The selector cannot change a number: uiops dispatches and computes nothing (tests/test_uiops_selector.py asserts no arithmetic and no module-level engine import).

Not decided: whether changing a scientific default (a profile's threshold, which tree a stock configuration reads) counts as a breaking change, and any formal support window.

Preserved bugs

Porting UIOOPS line by line surfaced the findings below; each is fixed in the v3.5 engine or the opt-in enhancements (see Findings). Each entry has a regression test named after the line it protects; any deviation is an opt-in flag, default off, tested both ways. The mapping is checked, not transcribed: test_preserved_bugs_have_tests.py verifies every named test still exists. Entries marked (v3.5) are bugs of v3.5 itself, translated as written by the v3.5 engine for the same reason.

See Findings for what these revealed about the original MATLAB.

Friendly views of the preserved contracts

Four raw contracts trip Python callers: iter_record_windows yielding n-1 windows, RejectResult.touching_edge's tri-state {0, "0", "t"}, the terminator-less .MK.csv, and MATLAB's 1-based indices. arrayprobe.friendly re-views each — same data, friendlier shape — off the reproduction path (tests/test_friendly.py fails if any pipeline stage under src/ imports it). See the API reference and the user guide.

Every public index is MATLAB's 1-based convention unless a function says otherwise; arrayprobe.matlab_compat.to0/to1 are the only crossing to Python's 0-based indices. The one exception is readers.spec.iter_record_windows's start/stop, which index the parallel decode's own window range and are 0-based, as its docstring states.

Bit-exactness classes

"Identical" is not one question. Each compared variable has a class that decides how a difference is judged, so a report says what kind of disagreement it found. The assignment lives in arrayprobe.compare and is part of the comparison's output.

ClassWhat it holdsHow a difference is judged
A — bit-exactintegers, ASCII codes, counts, bin edgesany difference fails; no floating-point slack, so a mismatch is a real disagreement
B — closed-form floatfloats from a short expression with no accumulationa few ULP (rtol 1e-12). Larger than that is a different formula, not a different rounding order
C — accumulated floatvalues summed along the record, where error grows with indexreported as drift against index (first divergence, slope, endpoint), since one maximum cannot separate steady accumulation from one bad element. Informational
D — algorithm substitutionthe smallest enclosing circle, computed by a different implementation of the same mathematical objectagreement is a theorem, so the tolerance is real but loose (atol 1e-9)

A class is a claim about the variable, and it is revisable on measurement. massBL and vt sat in A until the first cross-platform run showed them differing in the last bit between glibc, Apple's libm and Microsoft's UCRT — both resolve to the platform's pow(). They were moved to B, where the ~1e-15 relative differences sit four orders of magnitude inside the tolerance — a correction, not a relaxation, since class A names integers and counts. mass, re, Prec_rate and Calcd_area stay in A on single-platform evidence, under the same suspicion.

Reference campaigns

Each fidelity claim is tied to a reference pair: raw files plus the products a MATLAB run made from them.

CampaignProbePair heldUsed for
Field campaign, 20162D-S (H channel)raw + processedsix flights; the whole-file fidelity numbers
Same campaign, 20172D-S (H and V)raw + processednine flights, in a year whose configuration this project did not set
Same campaign, 2016HVPSraw + processedreader and framing checks

A number that names a flight or file is evidence from a reference pair, not a property of the package. The pipeline is campaign- and probe-agnostic: the readers cover SPEC, DMT, PMS and SEA raw formats, and probe types 0, 1, 2 and 3 are ported and oracle-validated.

Adding a campaign or probe adds a row above, not pipeline code: supply the raw files and MATLAB-written products, name the profile and tree used, and validate_combined_product.py, validate_by_condition.py and replay_reject_configs.py apply unchanged. A probe with no MATLAB product is validated against the original code under Octave, and that limit is stated wherever it applies.