oceanarray API reference
Load and process moored oceanographic time series data from raw instrument format to array-integrated products.
Public entry point
process
Top-level process() function, STAGES registry, and resolve_stage()
dispatcher. These are the primary public API for driving the pipeline from
Python code.
Pipeline stage registry and public process() entry point.
The STAGES tuple is the single source of truth for pipeline ordering,
scope, and dispatch. It is used by process(), the CLI, the re-run rule
(plan §7), and the parametrised re-run tests (test plan §7d).
Example usage:
import oceanarray
oceanarray.process("dsG3_1_2026", proc_dir="/data/proc") # all five stages
oceanarray.process("dsG3_1_2026", stage=1, proc_dir="/data/proc",
raw_dir="/data/raw") # stage 1 only
oceanarray.process("dsG3_1_2026", stage="grid", proc_dir="/data/proc") # grid only
- oceanarray.processors.STAGES: tuple[Stage, ...] = (Stage(name='stage1', number=1, scope='instrument', run=<function _run_stage1>), Stage(name='stage2', number=2, scope='instrument', run=<function _run_stage2>), Stage(name='stage3', number=3, scope='instrument', run=<function _run_stage3>), Stage(name='stack', number=None, scope='mooring', run=<function _run_stack>), Stage(name='grid', number=None, scope='mooring', run=<function _run_grid>))
Pipeline stages in execution order. Single source of truth for
process(), the CLI, the re-run rule (plan §7), and the parametrised re-run tests (test plan §7d).
- class oceanarray.processors.Stage(name: str, number: int | None, scope: Literal['instrument', 'mooring'], run: Callable[[...], bool])[source]
One pipeline stage.
- Parameters:
name (str) – Canonical stage name used on the CLI and in log messages. For the numbered stages this matches the output filename suffix (
*_stage3.nc);stackandgridwritestack.ncandgrid.nc.number (int or None) – Position for the numbered stages;
Noneforstackandgrid. This is what makesstage=1resolvable andstage=4aValueErrorrather than a silent wrong dispatch.scope (Scope) – Whether the stage runs once per instrument (
"instrument") or once per mooring ("mooring"). Drives which staleness sources the re-run rule consults (plan §7).run (Callable) – Normalised entry point:
run(mooring, proc_dir, *, force, **kw) -> bool.
- name: str
- number: int | None
- run: Callable[[...], bool]
- scope: Literal['instrument', 'mooring']
- oceanarray.processors.process(mooring: str, stage: int | str | list[int | str] | None = None, *, proc_dir: PathLike, raw_dir: PathLike | None = None, force: bool = False, **kw: Any) bool[source]
Run one or more pipeline stages, or every stage in order, for mooring.
- Parameters:
mooring (str) – Mooring name (the subdirectory under proc_dir).
stage (int, str, list of int/str, or None) – Which stage(s) to run —
1,2,3,"stage1","stack","grid", etc. Pass a list to run a specific subset; the subset is always executed inSTAGESorder regardless of the order of elements in the list.None(the default) runs all five stages inSTAGESorder.proc_dir (path-like) – Processing root directory (parent containing per-mooring subdirectories).
raw_dir (path-like, optional) – Raw-data root. Required only when stage includes stage 1.
force (bool, default False) – Re-run even when the output is newer than its inputs.
**kw – Extra keyword arguments forwarded to the stage’s
runcallable (e.g.serials,dt_seconds,p_start,p_end,dp).
- Returns:
Trueif every requested stage succeeded,Falseif any failed.- Return type:
bool
- oceanarray.processors.resolve_stage(stage: int | str) Stage[source]
Return the
Stagenamed or numbered by stage.- Parameters:
stage (int or str) –
1,2, or3; or a canonical name —"stage1","stage2","stage3","stack","grid". Names are matched case-insensitively. Integer4is deliberately an error:stackandgridhave no number so there is no valid integer shorthand for them.- Returns:
- Return type:
- Raises:
ValueError – If stage matches no entry. The message lists every valid value.
Instrument processing
stage 1 — standardisation
Convert raw instrument files (SeaBird, RBR, Nortek, RDI) to CF-NetCDF. Faithful to raw data; no QC.
Refactored stage1 processing for mooring data with improved readability.
- class oceanarray.processors.stage1.MooringProcessor(*, raw_dir: str, proc_dir: str)[source]
Handles stage1 processing of mooring data.
- COORDS_TO_REMOVE = {'sbe-ascii': ['depth', 'latitude', 'longitude'], 'sbe-cnv': ['depth', 'latitude', 'longitude']}
- SUPPORTED_FILE_TYPES = frozenset({'adcp-matlab', 'nortek-ascii', 'nortek-csv', 'nortek-csv-oa', 'nortek-raw', 'rbr-dat', 'rbr-hex', 'rbr-matlab', 'rbr-matlab-legacy', 'rbr-rsk', 'rdi-raw', 'sbe-ascii', 'sbe-cnv', 'sbe-hex'})
- VARS_TO_REMOVE = {'sbe-ascii': ['potential_temperature', 'julian_days_offset', 'density'], 'sbe-cnv': ['potential_temperature', 'julian_days_offset', 'density']}
- process_mooring(mooring_name: str, output_path: str | None = None, serials: List[str] | None = None, force: bool = False) bool[source]
Process a single mooring’s data.
- Parameters:
mooring_name – Name of the mooring to process
output_path – Optional custom output path. If None, uses default structure.
serials – Optional list of serial numbers to process; if None, process all.
force – Re-process even if output already exists.
- Returns:
True if processing completed successfully, False otherwise
- Return type:
bool
stage 2 — trimming and clock correction
Trim to the deployment window; apply linear clock-offset/drift correction.
Stage 2 processing for mooring data: apply clock corrections and trim to deployment.
Processing order per instrument
Load Stage 1
_stage1.ncfile.Resolve clock offset and drift from YAML.
If either is non-zero, save
time_orig(original instrument time) before correcting.Apply a linear correction that ramps from
clock_offsetat deployment toclock_drift_secondsat recovery. Both default to 0, so: - Onlyclock_offsetset: uniform constant shift (same correction throughout). - Onlyclock_drift_secondsset: ramps from 0 at deployment to drift at recovery. - Both set: ramps fromclock_offsetat deployment toclock_drift_secondsat recovery.Trim record to
deployment_time…recovery_timefrom the mooring YAML.Write
_stage2.nc.time_origis only present when a correction was applied.
Clock correction YAML keys (per instrument)
clock_offsetfloat, secondsTotal correction to apply at the start of the deployment (instrument clock error at deployment time). Positive = instrument was slow (behind UTC).
clock_drift_secondsfloat, seconds [Option A]Total correction to apply at the end of the deployment (instrument clock error at recovery time). Positive = instrument was slow (behind UTC) at recovery. The correction ramps linearly from
clock_offsetat deployment to this value at recovery.computer_clock_at_recovery/instrument_clock_at_recoveryISO-8601 str [Option B]Two timestamps read off at recovery. drift = computer − instrument = total correction at recovery. Equivalent to setting
clock_drift_seconds. Option B takes priority over Option A if both are present.
Sign convention
All clock values are the amounts added to instrument time to obtain corrected time.
Positive value → instrument was slow (behind real time); times shifted later.
Negative value → instrument was fast (ahead of real time); times shifted earlier.
- class oceanarray.processors.stage2.Stage2Processor(*, proc_dir: str)[source]
Handles Stage 2 processing: clock correction and temporal trimming.
- process_mooring(mooring_name: str, output_path: str | None = None, serials: List[str] | None = None, force: bool = False) bool[source]
Process Stage 2 for a single mooring.
- Parameters:
mooring_name – Name of the mooring to process
output_path – Optional custom output path
serials – Optional list of serial numbers to process; if None, process all.
force – Re-process even if Stage 2 output already exists.
- Returns:
True if processing completed successfully
- Return type:
bool
- oceanarray.processors.stage2.detect_deployment_window(ds: Dataset) tuple[datetime64 | None, datetime64 | None, str][source]
Estimate the deployed in-water window from a stage1 pressure record.
Important
Returns ``(None, None, …)`` when no pressure data are available. The caller must check for
Nonebefore using the result. Suggested times are not written to the output file when pressure is absent — instruments without pressure receive nosuggested_*attrs.Important
All non-None timestamps are in the raw instrument clock.
The input ds is a stage 1 dataset whose
"time"coordinate carries the uncorrected instrument clock. Add the YAMLclock_offsetto convert to UTC for copy-pasting into the YAML.Pressure-based algorithm (middle-50 % pmin + 10 dbar):
Middle-50 % reference window: skip the first and last 25 % of the record by time. This excludes any bench / surface period at either end regardless of its length, while avoiding a
pmax-based threshold that would be biased by knockdown events (which push instruments deeper than the nominal deployment depth).pmin_deployed= 1st-percentile of pressure within that middle window (robust to brief sensor-zero artefacts that would drag the absolute minimum to ~0 dbar and make the threshold negative).threshold=pmin_deployed − 10dbar.Opening search window: the first 25 % of the record by time (mirrors the middle window, long enough to cover multi-day bench periods).
Closing search window: the last 25 % of the record by time.
Start: last sample at or below the threshold in the opening window → the next sample is the suggested deployment start.
End: first sample at or below the threshold in the closing window → the preceding sample is the suggested recovery end.
This approach avoids two known failure modes of the original “skip first/last 12 h” middle window:
Bench data in the middle window: if an instrument recorded for days on deck before deployment, the 12 h skip was too short and
pminwould equal the bench pressure (≈ 0 dbar), drivingthresholdnegative so that nothing ever satisfied it.``pmax``-based threshold biased by knockdown: using
0.5 × pmaxas a conservative-window threshold is biased because knockdowns push instruments deeper than their nominal position, inflatingpmaxabove the nominal deployment pressure.0.5 × pmaxtherefore sits deeper than intended, delaying the start of the conservative window. The middle-50 % approach uses the typical deployed pressure (median region of the time series), not the occasional extreme.
Returns
(None, None, "no_pressure")when the dataset has no"pressure"variable, fewer than 10 records, all-NaN pressure, the middle window is empty, or the algorithm cannot produce a valid window.- Parameters:
ds (xr.Dataset) – Stage 1 dataset. Must contain a
"time"coordinate in the raw instrument clock. The"pressure"variable (dbar) is required.- Returns:
(sug_start, sug_end, source)where source is"pressure_pmin10dbar"on success or"no_pressure"when no valid pressure data are available.- Return type:
tuple[Optional[np.datetime64], Optional[np.datetime64], str]
stage 3 — QC, rotation, and derived variables
Apply QC flags, rotate ADCP velocities to ENU, apply magnetic declination correction, and compute derived quantities (salinity, density, speed/direction).
Stage 3: pressure interpolation + QARTOD QC (gross-range and spike tests).
Processing order per instrument
Load
_stage2.nc.Pressure interpolation (targets only — instruments lacking pressure or whose pressure is flagged bad in the YAML): a. Near-neighbour if any source has |Δhab| ≤
HAB_THRESHOLD. b. Weighted bracketing from the closest source above and below. c. Extrapolation (with WARNING) when target is outside all source habs. Interpolated pressure getspressure_qc = 8(interpolated_value).QARTOD gross-range test on temperature, conductivity, pressure, and velocity components. Flags 4 (bad) or 3 (suspect) based on thresholds in
parameters.QC_GROSS_RANGE(overrideable per mooring / per instrument in YAML via aqc_rangeskey).QARTOD spike test on the same variables. Flags from
parameters.QC_SPIKE(overrideable viaqc_spikein YAML).Merge all QC flags using priority order: 9 > 4 > 3 > 8 > 2 > 1.
Write
_stage3.ncfor all instruments (not only pressure targets).
Flag combination priority
Missing (9) > Bad (4) > Suspect (3) > Interpolated (8) > Prob-good (2) > Good (1)
This means that if interpolated pressure also fails the range test it is flagged 4 (bad), not 8 (interpolated).
YAML configuration keys
- Top-level (mooring-wide):
qc_ranges: mapping of variable → {fail_span, suspect_span}qc_spike: mapping of variable → {suspect_threshold, fail_threshold}- Per-instrument (in a clamp entry):
qc_rangessame structure; overrides the mooring-level setting forthe variables listed (others fall back to mooring/global defaults)
qc_spike: same structurepressure_qc: int — mark this instrument’s own pressure as bad (≥3) sostage3 replaces it with an interpolated value.
pressure
Pressure interpolation helpers (HAB computation, gap-filling).
Pressure interpolation helpers for stage 3.
- oceanarray.processors.pressure.compute_adcp_bin_pressure(ds: Dataset, lat: float, log_fn: Any = None) Dataset[source]
Compute pressure at each ADCP bin from transducer pressure and along-beam range.
For each time step, adds a pressure offset per bin based on the bin’s distance from the transducer. The offset is
gsw.p_from_z(-range_m, lat), which approximates the hydrostatic pressure contribution of range_m metres of seawater at the given latitude.Note on ``range`` units: the RDI WorkHorse firmware already converts slant range to vertical distance before writing to the raw output (using the nominal beam angle; see RDI ADCP Coordinate Transformation manual §4.2, Equation 8). Dolfyn reads these vertical distances directly, so
rangeis already in metres of vertical depth — no beam-angle correction is needed.Approximation (fixable post-OdB):
gsw.p_from_z(-range_m, lat)computes the pressure of a water column of depth range_m measured from the sea surface, not the true pressure increment at the ADCP’s actual depth. Error at 300 m range from a 500 m transducer is ~2–3 dbar — within the seabed-QC margin (~20 dbar) for current moorings.The sign follows instrument orientation:
Downward-looking (
orientation == "down"): bins are deeper than the transducer → pressure increases with bin index.Upward-looking (
orientation == "up"): bins are shallower than the transducer → pressure decreases with bin index.
If orientation cannot be determined from dataset attributes a WARNING is emitted and
"down"is assumed.- Parameters:
ds (xr.Dataset) – Stage 3 ADCP dataset. Must contain
pressure(time)in dbar (pressure at the transducer head) andrange(N_BINS)in metres (bin centre distance from the transducer face). Orientation is read from theorientation_yamlglobal attribute (preferred) ororientation_instrument(fallback).lat (float) – Mooring latitude in decimal degrees (positive North), used by
gsw.p_from_zfor the gravitational/centrifugal correction.log_fn (callable, optional) – Logging callback (e.g.
logger.info).
- Returns:
Input dataset with
bin_pressure(time, N_BINS)added in dbar. The variable’scommentattribute records the formula and orientation used. Returns ds unchanged ifpressureorrangeare absent.- Return type:
xr.Dataset
- oceanarray.processors.pressure.interp_pressure(source_time: ndarray, source_pressure: ndarray, target_time: ndarray) ndarray[source]
Linearly interpolate source pressure onto target time axis.
Nearest-point extrapolation at the edges (no NaN fill outside range).
- oceanarray.processors.pressure.interp_pressure_for_hab(hab_t: float, sorted_sources: List[Dict[str, Any]], target_time: ndarray, serial: str, log_fn: Any | None = None) tuple[ndarray, str][source]
Interpolate pressure for a single nominal HAB; return (p_array, method_str).
- Parameters:
hab_t (float) – Target height above bottom in metres.
sorted_sources (list of dict) – Pressure source instrument dicts sorted by hab, each with
'ds','hab','pressure_var','instrument', and'serial'keys.target_time (np.ndarray) – Target datetime64 time axis.
serial (str) – Serial number of the target instrument (used in log messages only).
log_fn (callable, optional) – Logging callback; falls back to print when None.
- Returns:
Interpolated pressure array and a human-readable method string.
- Return type:
tuple[np.ndarray, str]
- oceanarray.processors.pressure.interpolate_pressure(ds: Dataset, target_info: Dict[str, Any], sources: List[Dict[str, Any]], target_time: ndarray, pressure_bad_flag: bool, log_fn: Any | None = None) tuple[Dataset, str][source]
Interpolate pressure from sources onto target; return (ds, method_str).
If target_info contains
hab_segments(a sorted list of(breakpoint_datetime64, hab_float)pairs), the record is split into time segments and each segment is interpolated with its own HAB, allowing for instruments that physically moved during the deployment.HAB convention for segmented deployments
target_info["hab"]is the initial HAB — the height above bottom at which the instrument was deployed. Segment 0 runs from the start of the record up to (but not including) the first breakpoint, using this value. Each entry inhab_segmentsgives the new HAB at and after the breakpoint timestamp (i.e. records withT >= breakpointuse the new HAB; the breakpoint itself belongs to the new segment). This matches the YAML convention wherehab:records the as-deployed position andhab_segments:records subsequent slides, withfrom:marking the first timestamp that belongs to the post-slide position.- param ds:
Stage 2 dataset for the target instrument.
- type ds:
xr.Dataset
- param target_info:
Instrument metadata dict with
'hab','hab_segments','serial', and'qc_flags'keys.- type target_info:
dict
- param sources:
Pressure source instrument dicts (each must have
'ds'loaded).- type sources:
list of dict
- param target_time:
Target datetime64 time axis (must match
ds["time"].values).- type target_time:
np.ndarray
- param pressure_bad_flag:
When True, the original pressure is preserved as
pressure_origandpressure_orig_qcbefore being replaced.- type pressure_bad_flag:
bool
- param log_fn:
Logging callback.
- type log_fn:
callable, optional
- returns:
Updated dataset and a human-readable method string.
- rtype:
tuple[xr.Dataset, str]
qc
QARTOD quality-control tests.
QARTOD QC tests and CTD derivations for stage 3.
- oceanarray.processors.qc.apply_enu_velocity_qc(ds: Dataset, gr_cfg: Dict[str, Any]) Dataset[source]
Apply QARTOD gross-range QC to ENU velocity vars and propagate w flags.
Must be called after apply_beam_to_enu (east/north/up_velocity must exist). No spike test is applied to velocity (burst-mode Aquadopps generate false positives at every burst boundary).
Propagates up_velocity_qc (flag 3 or 4) to east_velocity_qc and north_velocity_qc: if vertical velocity is implausibly large the whole 3-D velocity measurement is suspect/bad. Full bidirectional unification (large east/north flags → up_velocity_qc) is done by unify_velocity_qc, which must be called after apply_tilt_qc.
- oceanarray.processors.qc.apply_qc_tests(ds: Dataset, gross_range: Dict[str, Any], spike: Dict[str, Any], flat_line: Dict[str, Any] | None = None) Dataset[source]
Apply QARTOD gross-range, spike, and flat-line tests, writing
*_qcvariables.Three QARTOD tests are applied in sequence; the worst flag across all tests wins for each sample:
Gross-range: flags values outside physically plausible bounds as SUSPECT (3) or BAD (4) — e.g. temperature below -2.5 °C or above 40 °C.
Spike: flags isolated outliers that deviate from the surrounding record by more than a threshold — e.g. a brief salinity glitch from biofouling or a pressure transient.
Flat-line (stuck-sensor): flags runs of consecutive samples where the value does not change by more than
tolerance— e.g. a pressure sensor frozen at 0 dbar after a failure. Threshold is expressed in sample counts (suspect_n,fail_n) and converted to seconds using the median sample interval. Applied by default to pressure only (seeparameters.QC_FLAT_LINE).
Pre-existing
*_qcvalues (e.g.pressure_qc = 8set by the pressure-interpolation step in the stack) are preserved: the worst flag across the incoming value and the new test flags is written to the output.Provenance: the thresholds actually applied are stored as attributes on each
{var}_qcvariable so the treatment can be reconstructed from the NetCDF file alone, without re-reading the YAML:qc_gross_range_fail_min/qc_gross_range_fail_maxqc_gross_range_suspect_min/qc_gross_range_suspect_maxqc_spike_suspect_threshold/qc_spike_fail_thresholdqc_flat_line_suspect_count/qc_flat_line_fail_count
- oceanarray.processors.qc.apply_tilt_qc(ds: Dataset, tilt_cfg: Dict[str, Any]) tuple[Dataset, int, int][source]
Flag velocity variables when pitch or roll exceeds QC thresholds.
Primary path — pitch_qc / roll_qc already exist (created by the gross-range QC step when pitch and/or roll appear in
qc_rangesin the YAML):The two flag arrays are merged element-wise (worst flag wins) and the result is propagated to every velocity variable. Any time step where pitch OR roll is flagged suspect (3) or bad (4) will flag the velocities with the same severity.
- Fallback path — neither pitch_qc nor roll_qc exist:
tilt is computed as max(|pitch|, |roll|) and compared against tilt_cfg thresholds (
suspect_threshold/fail_threshold).
In both paths
tilt_suspect_thresholdandtilt_fail_thresholdare written to global attrs so the report can draw reference lines on the tilt time-series panel.Returns (ds, n_suspect, n_bad). No-ops when both pitch and roll are absent.
- oceanarray.processors.qc.compute_salinity_data(ds: Dataset, log_fn: Any = None) Dataset[source]
Compute Practical Salinity (SP) data values only — no QC flags yet.
Call this BEFORE
apply_qc_testsso that salinity participates in the gross-range QC pass and gets its threshold attrs written tosalinity_qc. Callmerge_salinity_parent_qcafterward to fold in T/C/P parent flags.
- oceanarray.processors.qc.derive_oxygen_saturation(ds: Dataset) Dataset[source]
Compute O2 % saturation and AOU from dissolved_oxygen, temperature, salinity, pressure.
Requires dissolved_oxygen (µmol/L), temperature (°C), salinity (PSU), and pressure (dbar) all present in ds. Returns ds unchanged when any variable is missing or when gsw is unavailable.
Unit conversion uses in-situ seawater density from
gsw.rho(SA, CT, p)(kg m⁻³) — NOT freshwater density (~1000 kg m⁻³). Seawater density at typical mooring conditions is ~1025–1028 kg m⁻³, giving a ~2.5 % correction relative to freshwater. This correction is oceanographically significant and must not be skipped.lon/lat for
gsw.SA_from_SPare taken from global attrs (default 0.0 if absent; the SA error from a wrong position is typically < 0.05 g kg⁻¹ at open-ocean sites).Derived variables stored
- oxygen_saturation_pct%
100 × O2_measured(µmol kg⁻¹) / O2sol(µmol kg⁻¹) where O2sol is from
gsw.O2sol_SP_pt(SP, pt).- apparent_oxygen_utilizationµmol kg⁻¹
O2sol − O2_measured (positive = oxygen-depleted water).
- oceanarray.processors.qc.ensure_conductivity_units(ds: Dataset, log_fn: Any = None) Dataset[source]
Convert conductivity from S/m → mS/cm if needed.
QC thresholds in parameters.QC_GROSS_RANGE are in mS/cm. Some readers (notably sbe-ascii) write S/m; this normalises before QC is applied so thresholds are always compared against values in the same unit.
- oceanarray.processors.qc.load_qc_config(mooring_cfg: Dict[str, Any], entry: Dict[str, Any]) tuple[Dict, Dict, Dict, Dict][source]
Return QC threshold dicts for one instrument.
Returns a 4-tuple
(gross_range, spike, tilt, flat_line)built from package defaults with mooring-level and then instrument-level YAML overrides applied (later values win).gross_rangeandspikemap variable names (e.g."temperature","pressure") to threshold dicts understood byapply_qc_tests.tiltholdssuspect_threshold/fail_thresholdin degrees, used to flag Aquadopp velocity data when the instrument is tilted beyond acceptable limits.flat_linemaps variable names to{suspect_n, fail_n, tolerance}dicts for the stuck-sensor test.YAML override keys:
qc_ranges(mooring or instrument level) — gross-range and tilt spansqc_spike— spike thresholdstilt_qc— tilt thresholds (takes precedence overqc_ranges.tilt)qc_flat_line— stuck-sensor thresholds
tiltis stripped from the gross-range dict before returning so it is not passed to the QARTOD gross-range test runner.
- oceanarray.processors.qc.merge_salinity_parent_qc(ds: Dataset) Dataset[source]
Merge parent (T/C/P) QC flags into salinity_qc after QC tests have run.
apply_qc_testssets salinity_qc from the gross-range test and stores the threshold attrs needed by the report histogram. Here we additionally fold in the worst flag from temperature_qc, conductivity_qc, and pressure_qc so that a bad/suspect input propagates to salinity.
- oceanarray.processors.qc.set_qc_attrs(ds: Dataset, var: str, extra: Dict[str, Any] | None = None) Dataset[source]
Attach the OceanSITES flag attributes to
{var}_qcin place.The single owner of QC-flag metadata: call this wherever a
_qcvariable is created or updated so the CF flag attributes cannot be forgotten. The flag table is sourced fromoceanarray.parameters(one source of truth) and the full legal set is declared, not only the values present. The status-flagstandard_name("{parent} status_flag") is attached only when the parent variable has astandard_name— it is skipped, never fabricated, when the parent has none (standard_nameis an optional CF attribute).- Parameters:
ds (xarray.Dataset) – Dataset containing both
varand its{var}_qccompanion.var (str) – Name of the data variable whose
_qccompanion is annotated.extra (dict of str to Any, optional) – Additional attributes to attach (e.g. QC threshold provenance).
- Returns:
ds, modified in place.
- Return type:
xarray.Dataset
- oceanarray.processors.qc.unify_velocity_qc(ds: Dataset) Dataset[source]
Unify ENU velocity QC flags so all three components share the worst flag.
After gross-range, up-velocity propagation, and tilt QC, each component may carry different flags. A large east/north velocity flags only that component; tilt flags all three; up-velocity flags east and north but not itself from the east/north gross-range test. This function computes the element-wise worst flag across east_velocity_qc, north_velocity_qc, and up_velocity_qc, then writes that combined flag back to all three. The result: masking on any single component produces an identical, consistent velocity mask.
coordinate
Coordinate system transformations (BEAM → XYZ → ENU, magnetic declination).
BEAM→ENU coordinate transforms and ADCP-specific QC for stage 3.
- oceanarray.processors.coordinate.apply_adcp_seabed_qc(ds: Dataset, water_depth_m: float, lat: float, fail_margin_m: float = 20.0, log_fn: Any = None) Dataset[source]
Flag ADCP bins that are at or below the seabed.
Bins whose
bin_pressureexceeds the estimated seabed pressure are flagged suspect (3); bins more than fail_margin_m metres below the seabed are flagged bad (4). The flag is stored as the standalone variableseabed_qc(time, N_BINS).Flag scale (OceanSITES / QARTOD convention): 1 = good, 3 = suspect, 4 = bad, 9 = missing.
Standalone flag:
seabed_qcis not merged intoeast_velocity_qc,north_velocity_qc, orup_velocity_qc. Downstream code that needs clean velocity data must explicitly include all relevant flags, for example:clean = (ds.east_velocity_qc == 1) & (ds.seabed_qc == 1)
The seabed pressure threshold is computed via
gsw.p_from_z(-water_depth_m, lat), consistent withcompute_adcp_bin_pressure.- Parameters:
ds (xr.Dataset) – Stage 3 ADCP dataset containing
bin_pressure(time, N_BINS)in dbar.water_depth_m (float) – Water depth at the mooring site in metres (from YAML
waterdepthkey). If ≤ 0 the function is a no-op.lat (float) – Mooring latitude in decimal degrees, used by
gsw.p_from_z.fail_margin_m (float) – Margin below the seabed (in metres) that separates suspect (3) from bad (4). Default 20 m. Bins between 0 and fail_margin_m below the seabed receive flag 3; bins more than fail_margin_m below receive flag 4.
log_fn (callable, optional) – Logging callback.
- Returns:
Input dataset with
seabed_qc(time, N_BINS)added. Returns ds unchanged ifwater_depth_m <= 0orbin_pressureis absent.- Return type:
xr.Dataset
- oceanarray.processors.coordinate.apply_adcp_surface_qc(ds: Dataset, lat: float, suspect_margin_m: float = 20.0, log_fn: Any = None) Dataset[source]
Flag ADCP bins that are at or above the sea surface.
Bins whose
bin_pressureis ≤ 0 dbar are flagged bad (4); bins within suspect_margin_m metres of the surface (0 < bin_pressure < p_suspect) are flagged suspect (3). The result is stored as the standalone variablesurface_qc(time, N_BINS).This catches upward-looking ADCP bins that extend above the water surface when the instrument range exceeds the distance to the surface. Symmetric counterpart to
apply_adcp_seabed_qc.Flag scale (OceanSITES / QARTOD convention): 1 = good, 3 = suspect, 4 = bad.
Standalone flag:
surface_qcis not merged into the velocity QC variables. Downstream consumers must combine flags explicitly:clean = (ds.east_velocity_qc == 1) & (ds.surface_qc == 1) & (ds.seabed_qc == 1)
- Parameters:
ds (xr.Dataset) – Stage 3 ADCP dataset containing
bin_pressure(time, N_BINS)in dbar.lat (float) – Mooring latitude in decimal degrees, used by
gsw.p_from_z.suspect_margin_m (float) – Depth (m) below the surface that defines the suspect zone. Bins with 0 < bin_pressure < p_from_z(-suspect_margin_m) receive flag 3; bins with bin_pressure ≤ 0 receive flag 4. Default 20 m.
log_fn (callable, optional) – Logging callback.
- Returns:
Input dataset with
surface_qc(time, N_BINS)added. Returns ds unchanged ifbin_pressureis absent.- Return type:
xr.Dataset
- oceanarray.processors.coordinate.apply_adcp_velocity_qc(ds: Dataset, gr_cfg: Dict[str, Any], prcnt_gd_bad: float, prcnt_gd_suspect: float, error_vel_threshold: float, log_fn: Any = None) Dataset[source]
Apply QC to ADCP 2D velocity variables (time × N_BINS).
Each QC criterion produces its own standalone variable; flags are not merged across variables. Downstream users combine them to mask data, e.g.:
good = ( (ds.east_velocity_qc == 1) & (ds.percent_good_qc == 1) & (ds.error_velocity_qc == 1) & (ds.seabed_qc == 1) & (ds.surface_qc == 1) )
QC variables produced
east/north/up_velocity_qcGross-range flag on the velocity component value itself (same fail_span / suspect_span thresholds as point instruments). Flag 1 means the velocity value is within the accepted range; it says nothing about acoustic quality.
percent_good_qc(time, N_BINS)RDI ADCPs write four percent-good columns per ensemble per bin:
Column 3 (4-beam solutions) is the relevant quality indicator. Averaging all four columns is wrong: when data is perfect, col 3 ≈ 100 % and cols 0–2 ≈ 0 %, giving a mean of ~25 %, which falls below any reasonable suspect threshold and flags everything. This function therefore uses column 3 alone (falling back to the column mean for non-4-beam ADCPs that store fewer than 4 columns).
Flags: col3 < prcnt_gd_bad → bad (4); col3 < prcnt_gd_suspect → suspect (3); otherwise good (1).
error_velocity_qc(time, N_BINS)For a 4-beam ADCP the error velocity is the difference between two independent estimates of vertical velocity from opposite beam pairs. It is zero for a perfect measurement; large values indicate beam decorrelation (e.g. fish, bubbles, mooring motion). |error_velocity| > error_vel_threshold → bad (4).
- param ds:
Stage 2 dataset with 2-D ADCP velocity variables.
- type ds:
xr.Dataset
- param gr_cfg:
Gross-range config (same format as
load_qc_configreturns).- type gr_cfg:
dict
- param prcnt_gd_bad:
4-beam percent good below this → flag 4 (bad). Percent.
- type prcnt_gd_bad:
float
- param prcnt_gd_suspect:
4-beam percent good below this (but above prcnt_gd_bad) → flag 3 (suspect). Percent.
- type prcnt_gd_suspect:
float
- param error_vel_threshold:
|error_velocity| above this → flag 4 (bad). m s⁻¹.
- type error_vel_threshold:
float
- param log_fn:
Logging callback.
- type log_fn:
callable, optional
- returns:
Input dataset with the following standalone QC variables added (where their parent data variables are present):
east_velocity_qc(time, N_BINS)— gross-range flag on east velocitynorth_velocity_qc(time, N_BINS)— gross-range flag on north velocityup_velocity_qc(time, N_BINS)— gross-range flag on up velocitypercent_good_qc(time, N_BINS)— 4-beam percent-good flagerror_velocity_qc(time, N_BINS)— error velocity magnitude flag
All flags use the OceanSITES scale: 1 = good, 3 = suspect, 4 = bad, 9 = missing. Variables are standalone; no merging across criteria is performed.
- rtype:
xr.Dataset
- oceanarray.processors.coordinate.apply_beam_to_enu(ds: Dataset, entry: Dict[str, Any], lat: float, lon: float, latlon_source: str = 'unknown', log_fn: Any = None) Dataset[source]
Transform BEAM or XYZ Nortek velocities to ENU geographic coordinates.
Adds east_velocity, north_velocity, up_velocity, current_speed, current_direction. Updates coordinate_system attr to ‘ENU’. No-ops for instruments already in ENU or with unknown coordinate system. Requires normalized variable names (heading, pitch, roll) — re-run stage1 if these are absent.
- oceanarray.processors.coordinate.apply_declination_to_enu(ds: Dataset, lat: float, lon: float, latlon_source: str = 'unknown', log_fn: Any = None) Dataset[source]
Apply magnetic declination rotation to velocities already in ENU frame.
When a Nortek instrument is configured to output ENU coordinates internally, the heading reference used is magnetic north. This function rotates east_velocity and north_velocity by the declination angle so that north aligns with true (geographic) north.
- Rotation (D = declination, positive = east):
u_true = u_mag * cos(D) + v_mag * sin(D) v_true = -u_mag * sin(D) + v_mag * cos(D)
No-ops if east_velocity or north_velocity are absent, or if magnetic declination has already been applied (
magnetic_declinationattr present).
- oceanarray.processors.coordinate.xyz_to_enu(vx: ndarray, vy: ndarray, vz: ndarray, heading_deg: ndarray, pitch_deg: ndarray, roll_deg: ndarray, declination_deg: float = 0.0) tuple[ndarray, ndarray, ndarray][source]
Vectorised XYZ → ENU rotation per Nortek Support reference script.
- R = H @ P where (with hdg = heading - 90 + declination):
H = [[cos(hdg), sin(hdg), 0], [-sin(hdg), cos(hdg), 0], [0, 0, 1]] P = [[cos(p), -sin(p)*sin(r), -cos(r)*sin(p)],
[0, cos(r), -sin(r)], [sin(p), sin(r)*cos(p), cos(p)*cos(r)]]
The -90 offset accounts for the Nortek Aquadopp instrument frame where heading=90° aligns X→East, Y→North (standard geography at zero tilt). Magnetic declination is added to convert magnetic heading to true north.
Returns (east, north, up) arrays of the same shape as the inputs.
caldip
Cal-dip cast processing (stub; full implementation in progress).
CalDip calibration-dip drift corrections for stage 3 (not yet implemented).
Mooring processing
stack
Interpolate multiple instruments onto a common time grid and stack into a
single mooring dataset (oceanarray process MOORING --stage stack).
MooringStacker: interpolate all instruments on a mooring onto a common time grid.
- class oceanarray.processors.stack.MooringStacker(*, proc_dir: str)[source]
Step 1: stack all instruments onto a common time axis →
_stack.nc.- stack(mooring_name: str, dt_seconds: int = 60, force: bool = False) bool[source]
Stack all processed instruments for mooring_name onto a common time grid.
Reads
_stage3.nc(falling back to_stage2.nc) for every instrument listed in the mooring YAML, resamples each to dt_seconds resolution, and writes{mooring}_stack.ncunder the mooring proc directory.Returns True on success, False if no instruments could be loaded or an unrecoverable error occurs.
grid
Interpolate stacked mooring data onto a regular pressure grid
(oceanarray process MOORING --stage grid).
MooringGridder and TimeGriddingProcessor: grid mooring data onto regular time/depth axes.
- class oceanarray.processors.grid.MooringGridder(*, proc_dir: str)[source]
Step 2: vertically interpolate stacked instruments onto a pressure grid →
_grid.nc.- grid(mooring_name: str, p_start: float = 200.0, p_end: float = 1000.0, dp: float = 20.0, force: bool = False) bool[source]
Interpolate the stacked mooring dataset onto a regular pressure grid.
Reads
{mooring}_stack.nc, interpolates all variables onto a pressure axis from p_start to p_end in steps of dp dbar, and writes{mooring}_grid.nc.Returns True on success, False on error.
- class oceanarray.processors.grid.TimeGriddingProcessor(*, proc_dir: str)[source]
Handles Step 1 processing: time gridding and optional filtering of mooring instruments.
- process_mooring(mooring_name: str, output_path: str | None = None, file_suffix: str = '_stage2', vars_to_keep: List[str] = None, filter_type: str | None = None, filter_params: Dict[str, Any] | None = None) bool[source]
Process Step 1 for a single mooring: time gridding and optional filtering.
- Parameters:
mooring_name – Name of the mooring to process
output_path – Optional custom output path
file_suffix – Suffix for input files (‘_stage2’ or ‘_raw’)
vars_to_keep – List of variables to include in combined dataset
filter_type – Type of time filtering to apply (‘lowpass’, ‘detide’, ‘bandpass’)
filter_params – Parameters for filtering
- Returns:
True if processing completed successfully
- Return type:
bool
- oceanarray.processors.grid.process_multiple_moorings_time_gridding(mooring_list: List[str], proc_dir: str, file_suffix: str = '_stage2', filter_type: str | None = None, filter_params: Dict[str, Any] | None = None) Dict[str, bool][source]
Process Step 1 for multiple moorings.
- Parameters:
mooring_list – List of mooring names to process
proc_dir – Cruise-level processed output directory (appends /{mooring}/)
file_suffix – Suffix for input files (‘_stage2’ or ‘_raw’)
filter_type – Optional time filtering to apply (‘lowpass’, ‘detide’, ‘bandpass’)
filter_params – Optional parameters for filtering
- Returns:
Dict mapping mooring names to success status
- oceanarray.processors.grid.time_gridding_mooring(mooring_name: str, proc_dir: str, output_path: str | None = None, file_suffix: str = '_stage2', filter_type: str | None = None, filter_params: Dict[str, Any] | None = None) bool[source]
Process Step 1 for a single mooring (convenience function).
- Parameters:
mooring_name – Name of the mooring to process
proc_dir – Cruise-level processed output directory (appends /{mooring}/)
output_path – Optional output path override
file_suffix – Suffix for input files (‘_stage2’ or ‘_raw’)
filter_type – Optional time filtering to apply (‘lowpass’, ‘detide’, ‘bandpass’)
filter_params – Optional parameters for filtering
- Returns:
True if processing completed successfully
- Return type:
bool
mooring helpers
Shared helpers for position parsing, HAB computation, and instrument metadata.
Internal helper functions for mooring-level stack and grid operations.
Configuration and validation
parameters
Global processing parameters (QC thresholds, grid defaults, file paths, variable registry).
Package-level defaults for oceanarray.
Matplotlib appearance (font sizes, figure size, DPI, grid style) belongs in
config/report.mplstyle — that is the right place for anything that maps to a
matplotlib rcParam. There is a single style file: it is applied both by the
report encoder and by plotters that set their own style context (via
MPLSTYLE below), so the two can never diverge.
This file holds the things the mplstyle cannot express: instrument abbreviations, colorbar percentile clipping, downsample interval, and figure sizes that differ from the per-plot mplstyle default.
Import and reassign any value here to override it globally, e.g.:
import oceanarray.parameters as params
params.DOWNSAMPLE_SECONDS = 60
params.DEFAULT_COLORMAP = "viridis"
These values are read at call time, so assignment before calling a function is sufficient.
- oceanarray.config.parameters.CMAPS_BY_VARIABLE: dict[str, str] = {'absolute_salinity': 'YlGnBu_r', 'conservative_temperature': 'RdBu_r', 'dissolved_oxygen': 'RdYlGn', 'dissolved_oxygen_ml_l': 'RdYlGn', 'east_velocity': 'RdBu_r', 'n2': 'plasma', 'north_velocity': 'RdBu_r', 'oxygen_saturation_pct': 'RdYlGn', 'potential_density': 'BuPu', 'salinity': 'YlGnBu_r', 'speed': 'plasma', 'temperature': 'RdBu_r', 'turbidity': 'YlOrBr', 'u': 'RdBu_r', 'up_velocity': 'RdBu_r', 'v': 'RdBu_r', 'w': 'RdBu_r'}
Colormap lookup derived from
VARIABLES— single source of truth so the two cannot drift. Excludes entries wherecmapisNone.
- oceanarray.config.parameters.GRID_PANEL_ROW_IN: float = 2.5
Height (inches) of one gridded-section / time-series panel row at full width. Matches the per-row height of the two-row “velocity at depth” figure (5” / 2), so stacked grid panels and single-row section figures share one scale and do not render over-tall.
- oceanarray.config.parameters.LINE_CMAPS_BY_VARIABLE: dict[str, str] = {'east_velocity': 'Blues_r', 'north_velocity': 'Blues_r', 'pressure': 'Blues_r', 'salinity': 'YlGnBu_r', 'temperature': 'RdBu_r', 'up_velocity': 'Blues_r'}
Colormaps for colouring lines (one per instrument, deep-first) — distinct from the pcolormesh field maps in
CMAPS_BY_VARIABLE, because a field map that is fine for a filled panel can be wrong for overlaid lines (e.g. a diverging map’s pale midpoint washes lines out). Sampled deep→shallow with washed-out colours skipped by luminance (seeoceanarray.plotters.helpers.ordered_line_colors()). Directions are chosen so the deepest instrument gets the darkest/most-saturated colour: temperature blue(cold/deep)→red(warm/shallow); pressure dark→lighter blue (bathymetry convention, deep = dark); salinity starts at the blue end.
- oceanarray.config.parameters.VARIABLES: dict[str, dict] = {'absolute_salinity': {'cmap': 'YlGnBu_r', 'label': 'Absolute salinity', 'label_units': 'g kg⁻¹', 'standard_name': 'sea_water_absolute_salinity', 'units': 'g kg-1'}, 'apparent_oxygen_utilization': {'cmap': None, 'label': 'Apparent oxygen utilization', 'label_units': 'µmol kg⁻¹', 'standard_name': 'apparent_oxygen_utilization', 'units': 'umol kg-1'}, 'conductivity': {'cmap': None, 'label': 'Conductivity', 'label_units': 'mS cm⁻¹', 'standard_name': 'sea_water_electrical_conductivity', 'units': 'mS cm-1', 'valid_max': 80.0, 'valid_min': 0.0}, 'conservative_temperature': {'cmap': 'RdBu_r', 'label': 'Conservative temperature', 'label_units': '°C', 'standard_name': 'sea_water_conservative_temperature', 'units': 'degree_Celsius', 'valid_max': 42.0, 'valid_min': -5.0}, 'depth': {'cmap': None, 'label': 'Depth', 'label_units': 'm', 'standard_name': 'depth', 'units': 'm', 'valid_max': 12000.0, 'valid_min': 0.0}, 'dissolved_oxygen': {'cmap': 'RdYlGn', 'label': 'Dissolved oxygen', 'label_units': 'µmol L⁻¹', 'standard_name': 'mole_concentration_of_dissolved_molecular_oxygen_in_sea_water', 'units': 'umol L-1'}, 'dissolved_oxygen_ml_l': {'cmap': 'RdYlGn', 'label': 'Dissolved oxygen', 'label_units': 'mL L⁻¹', 'standard_name': None, 'units': 'mL L-1'}, 'east_velocity': {'cmap': 'RdBu_r', 'label': 'Eastward velocity', 'label_units': 'm s⁻¹', 'standard_name': 'eastward_sea_water_velocity', 'units': 'm s-1'}, 'n2': {'cmap': 'plasma', 'label': 'N²', 'label_units': 's⁻²', 'standard_name': 'square_of_brunt_vaisala_frequency_in_sea_water', 'units': 's-2'}, 'north_velocity': {'cmap': 'RdBu_r', 'label': 'Northward velocity', 'label_units': 'm s⁻¹', 'standard_name': 'northward_sea_water_velocity', 'units': 'm s-1'}, 'oxygen_saturation_pct': {'cmap': 'RdYlGn', 'label': 'O₂ percent saturation', 'label_units': '%', 'standard_name': None, 'units': '%'}, 'potential_density': {'cmap': 'BuPu', 'label': 'σ₀', 'label_units': 'kg m⁻³', 'standard_name': 'sea_water_sigma_theta', 'units': 'kg m-3'}, 'pressure': {'cmap': None, 'label': 'Pressure', 'label_units': 'dbar', 'standard_name': 'sea_water_pressure', 'units': 'dbar', 'valid_max': 11000.0, 'valid_min': 0.0}, 'salinity': {'cmap': 'YlGnBu_r', 'label': 'Salinity', 'label_units': 'PSU', 'standard_name': 'sea_water_practical_salinity', 'units': '1'}, 'speed': {'cmap': 'plasma', 'label': 'Speed', 'label_units': 'm s⁻¹', 'standard_name': 'sea_water_speed', 'units': 'm s-1'}, 'temperature': {'cmap': 'RdBu_r', 'label': 'Temperature', 'label_units': '°C', 'standard_name': 'sea_water_temperature', 'units': 'degree_Celsius', 'valid_max': 42.0, 'valid_min': -5.0}, 'turbidity': {'cmap': 'YlOrBr', 'label': 'Turbidity', 'label_units': 'NTU', 'standard_name': 'sea_water_turbidity', 'units': 'NTU'}, 'u': {'cmap': 'RdBu_r', 'label': 'Eastward velocity', 'label_units': 'm s⁻¹', 'standard_name': 'eastward_sea_water_velocity', 'units': 'm s-1'}, 'up_velocity': {'cmap': 'RdBu_r', 'label': 'Upward velocity', 'label_units': 'm s⁻¹', 'standard_name': 'upward_sea_water_velocity', 'units': 'm s-1'}, 'v': {'cmap': 'RdBu_r', 'label': 'Northward velocity', 'label_units': 'm s⁻¹', 'standard_name': 'northward_sea_water_velocity', 'units': 'm s-1'}, 'w': {'cmap': 'RdBu_r', 'label': 'Vertical velocity', 'label_units': 'm s⁻¹', 'standard_name': 'upward_sea_water_velocity', 'units': 'm s-1'}}
Display metadata for each physical variable.
- Each entry has:
label— human-readable name for plot titles and legends.label_units— Unicode units string for plot axis labels (e.g."°C").Use
vlabel()to get the combined"Label (units)"string. Empty string for dimensionless quantities.units— udunits-2 / CF-compliant ASCII units for NetCDF attributes(e.g.
"degree_Celsius"). May differ fromlabel_unitsonly in encoding (Unicode → ASCII).standard_name— CF standard name.Nonewhen no standard name exists(e.g. turbidity in NTU, which has no udunits-2 unit).
cmap— default matplotlib colormap name, orNonefor variableswithout a natural diverging / sequential convention.
valid_min— CFvalid_minattribute written to NetCDF output (physicallyvalid_maxvalid range). Present only whereOS1_vocab_attrs.yamlprovides an authoritative value; absent for other variables.
- oceanarray.config.parameters.VAR_COLORS: dict[str, str] = {'absolute_salinity': '#E69F00', 'conductivity': '#44AA99', 'conservative_temperature': '#56B4E9', 'depth': '#000000', 'dissolved_oxygen': '#332288', 'dissolved_oxygen_ml_l': '#332288', 'east_velocity': '#D55E00', 'n2': '#000000', 'north_velocity': '#0072B2', 'oxygen_saturation_pct': '#332288', 'potential_density': '#009E73', 'pressure': '#000000', 'salinity': '#E69F00', 'speed': '#D55E00', 'temperature': '#56B4E9', 'turbidity': '#661100', 'u': '#D55E00', 'up_velocity': '#CC79A7', 'v': '#0072B2', 'w': '#CC79A7'}
One colourblind-safe colour per variable, for single-instrument panels where each variable is drawn as one line (not the multi-instrument stack, which uses
LINE_CMAPS_BY_VARIABLE). Physics use the Okabe-Ito palette (Wong, Nature Methods 8:441, 2011); biogeochemistry uses the Paul Tol palette. Look up withvar_color()(single fallback, no scattered literals). Mirrors ctdcast’sVAR_COLORS.
- oceanarray.config.parameters.VAR_COLOR_DEFAULT: str = '#000000'
Colour for a variable with no
VAR_COLORSentry — one canonical fallback so callers don’t scatter their own literal defaults (which drift).
- oceanarray.config.parameters.var_color(var: str) str[source]
Return the line/marker colour for var from
VAR_COLORS.Falls back to
VAR_COLOR_DEFAULTfor an unregistered variable, so every caller shares one default rather than hard-coding its own.- Parameters:
var (str) – Variable name (key in
VARIABLES/VAR_COLORS).- Returns:
A hex colour string.
- Return type:
str
- oceanarray.config.parameters.vlabel(var: str, prefix: str = '') str[source]
Return a matplotlib axis label for var from the
VARIABLESregistry.Format is
"{prefix}Label (units)"whenlabel_unitsis non-empty, or"{prefix}Label (1)"for a dimensionless quantity —1is the CF / UDUNITS unit string for dimensionless (e.g. practical salinity, whose NetCDFunitsattribute is"1"), so the label matches the stored metadata rather than leaving the reader to wonder whether a unit was forgotten. Theprefixis prepended to the label component only, not the units, sovlabel("temperature", prefix="Δ")produces"ΔTemperature (°C)".- Parameters:
var (str) – Variable name (key in
VARIABLES).prefix (str, optional) – Text prepended to the label component — e.g.
"Gridded "or"Δ".
- Returns:
Ready-to-use axis label. Falls back to
"{prefix}{var}"when var is not in the registry so callers always get something useful.- Return type:
str
- oceanarray.config.parameters.vunit(var: str) str[source]
Return the axis-label units string for var from
VARIABLES.This is the
label_unitscomponent alone (e.g."°C","dbar"), suitable for a units-only colorbar title. Returns an empty string for a dimensionless quantity or an unknown variable.- Parameters:
var (str) – Variable name (key in
VARIABLES).- Returns:
The unit string, or
""when none is registered.- Return type:
str
validation
Validate mooring YAML configuration files and check instrument type names.
Validation utilities for oceanarray mooring YAML configuration files.
The instrument field in each clamp/instruments entry is used as a
subdirectory name when reading raw files and writing processed output:
<raw-dir>/<mooring_name>/<instrument>/<filename>
<proc-dir>/<mooring_name>/<instrument>/<output>.nc
Valid instrument names and their typical file types
- Do NOT use hardware/model names as the instrument value:
sbe37→ usemicrocatnortek→ useaquadopp
Clock correction YAML fields (per-instrument, applied in Stage 2)
Stage 2 applies corrections in this order:
Constant offset — applied uniformly across the entire record:
clock_offset: 15 # seconds; positive = instrument was slow (behind)Linear drift — grows linearly from 0 at deployment to the full drift at recovery. Two equivalent ways to specify it:
Option A — direct:
clock_drift_seconds: 8 # positive = instrument was slow (behind at recovery)Option B — two timestamps read off at recovery (preferred; no sign errors):
clock_computer_at_recovery: '2026-07-11T10:23:30' clock_instrument_at_recovery: '2026-07-11T10:23:22' # drift = computer − instrument = +8 s (instrument was 8 s behind)If both Option A and Option B are present, Option B takes priority.
Trimming — data outside deployment_time … recovery_time is discarded after all clock corrections, so corrections are applied to the full raw record.
Sign convention — both values are amounts added to instrument time:
Situation
clock_offset
clock_drift_seconds
Instrument clock was slow (behind real time)
positive (+)
positive (+)
Instrument clock was fast (ahead of real time)
negative (-)
negative (-)
The original, uncorrected time is always saved as time_orig in the output
NetCDF alongside the corrected time coordinate. The history attribute
records what was applied and when.
- class oceanarray.config.validation.ValidationIssue(level: str, message: str)[source]
A single validation finding with a severity level and human-readable message.
- level: str
Alias for field number 0
- message: str
Alias for field number 1
- oceanarray.config.validation.print_validation_report(yaml_path: str) bool[source]
Print a human-readable validation report. Returns True if no errors.
- oceanarray.config.validation.validate_mooring_yaml(yaml_path: str) List[ValidationIssue][source]
Validate a mooring YAML configuration file.
Checks: - Required top-level keys are present - Each instrument entry uses a valid
instrumentname (not a model alias) - Each instrument entry with afile_typeuses a recognised value - Instruments withfilenamealso havefile_type- Instruments withoutfilenameare flagged as warnings (not yet staged)Returns a list of
ValidationIssuenamed-tuples. An empty list means the file passed all checks.
Analysis
science
QC routines: flag_salinity_outliers, flag_temporal_spikes,
flag_vertical_inconsistencies, run_qc.
QC and dataset processing functions for oceanographic instrument data.
Contains salinity/conductivity quality-control routines and a dataset processing helper. Hydrographic, spectral, time-series, and vector utilities have been split into dedicated submodules:
oceanarray.analysis.hydrographic— salinity calculation, isopycnal tracking, cold-regime detection, dataset differencing.oceanarray.analysis.spectral— Gonella rotary spectra, Welch PSD, continuous wavelet transforms.oceanarray.analysis.temporal— lag correlation, split value, downsampling, Tukey filtering.oceanarray.analysis.vector— XYZ→ENU rotation, progressive vector.
Backward-compatible re-exports at the bottom of this file preserve existing
from oceanarray.analysis._science import … usage.
- oceanarray.analysis.science.flag_salinity_outliers(ds: Dataset, n_std: float = 4) DataArray[source]
Flag PSAL values more than n_std standard deviations from the mean.
Computed separately for each depth level.
- Parameters:
ds (xarray.Dataset) – Dataset containing “PSAL” variable with dimensions including “DEPTH”.
n_std (float, optional) – Number of standard deviations from the mean to define an outlier (default is 4).
- Returns:
Boolean array with True where salinity is flagged as an outlier.
- Return type:
xarray.DataArray (bool)
- oceanarray.analysis.science.flag_temporal_spikes(ds: Dataset, var: str = 'CNDC', threshold: float = 5) DataArray[source]
Flag large absolute differences in time for each depth.
threshold: maximum allowed difference in units of the variable.
- oceanarray.analysis.science.flag_vertical_inconsistencies(ds: Dataset, var: str = 'CNDC', threshold: float = 2) DataArray[source]
Flag points that are very different from vertical neighbors.
threshold: max allowed difference between vertically adjacent sensors.
- oceanarray.analysis.science.process_dataset(ds: Dataset, latlim: tuple[float, float] = (26.0, 27.0), lonlim: tuple[float, float] = (-77.0, -76.5), pgrid: ndarray = None) tuple[Dataset, Dataset][source]
Filter and process a hydrographic dataset for use in training.
This function selects a region of interest, extracts and downsamples profiles of temperature and salinity onto both standard and sparse pressure grids. It also computes potential density anomaly for both resolutions.
- Parameters:
ds (xr.Dataset) – Input dataset containing hydrographic data including CT, SA, PRES, and metadata.
latlim (tuple of float, optional) – Latitude limits for filtering, by default (26.0, 27.0).
lonlim (tuple of float, optional) – Longitude limits for filtering, by default (-77.0, -76.5).
pgrid (np.ndarray, optional) – Target pressure levels (dbar) for vertical interpolation. When None, a 20 dbar grid is constructed automatically from 0 to the maximum observed pressure.
- Returns:
ds_standard (xr.Dataset) – Dataset downsampled to standard pressure levels.
ds_sparse (xr.Dataset) – Dataset downsampled to sparse pressure levels.
See also
verticalnn.data_utils.downsample_to_sparseUsed to interpolate to target pressure levels.
verticalnn.config.STANDARD_PRESSURESStandard pressure grid.
verticalnn.config.SPARSE_PRESSURESSparse pressure grid.
hydrographic
Salinity calculation, isopycnal tracking, cold-regime detection, and dataset differencing.
Hydrographic analysis utilities for oceanographic mooring data.
hydrographic.py provides salinity computation, isopycnal tracking, dataset differencing, and cold-regime detection functions extracted from the general science utilities.
Pairs with oceanarray.plotters.hydrography for figure output.
- oceanarray.analysis.hydrographic.calc_ds_difference(ds1: Dataset, ds2: Dataset) Dataset[source]
Compute the variable-by-variable difference between two time-matched datasets.
- oceanarray.analysis.hydrographic.calc_psal(ds: Dataset) Dataset[source]
Compute Practical Salinity from conductivity, temperature, and pressure.
Uses the Gibbs SeaWater (GSW) toolbox:
gsw.SP_from_Capplies the PSS-78 equation to derive Practical Salinity (dimensionless, roughly PSU) from conductivity (mS cm⁻¹), temperature (°C), and pressure (dbar).If
PSALis already present in ds the function is a no-op.- Parameters:
ds (xarray.Dataset) – Dataset containing
CNDC(conductivity, mS cm⁻¹),TEMP(temperature, °C), andPRES(pressure, dbar).- Returns:
Input dataset with
PSALadded (same dimensions asCNDC), or unchanged ifPSALwas already present.- Return type:
xarray.Dataset
- oceanarray.analysis.hydrographic.find_cold_entry_exit(time: ndarray, temp: ndarray, quantile: float = 0.95, dwell_seconds: int = 1800, smooth_window: int = 5) tuple[Timestamp | None, Timestamp | None, float][source]
Identify first sustained entry into ‘cold’ regime and last sustained exit.
- Parameters:
time (array-like of datetime64) – Time coordinate array.
temp (array-like of float) – Temperature values aligned with time.
quantile (float) – Percentile for threshold (e.g. 0.1 ~ 10th percentile).
dwell_seconds (int) – Minimum time in cold regime for it to count (seconds).
smooth_window (int) – Rolling median window length (samples).
- Returns:
t_start, t_end, threshold
- Return type:
tuple of (Timestamp or None, Timestamp or None, float)
- oceanarray.analysis.hydrographic.isopycnal_dataset(ds: Dataset, sigma_var: str = 'sigma0', sigma_grid: ndarray | None = None) Dataset[source]
Build an isopycnal-tracking dataset from a gridded mooring file.
Tracks the pressure (and height above seabed) of density surfaces through time using the gridded sigma0 field. Calls
isopycnal_pressure_series()column-by-column.- Parameters:
ds – Gridded mooring xr.Dataset with
(pressure, time)dimensions and a sigma variable. Awaterdepthglobal attribute (metres) is used for the height-above-seabed conversion; if absent both_heightvariables are all-NaN.sigma_var – Name of the sigma variable in ds (default
"sigma0").sigma_grid – Target sigma0 values to track. If
None, a 0.1 kg m⁻³ grid is computed from the observed data range.
- Returns:
Dimensions
(sigma0_level, time). Variables:isopycnal_pressure— dbar, NaN where absent.isopycnal_height— m above seabed, NaN where absent.
- Return type:
xr.Dataset
- oceanarray.analysis.hydrographic.isopycnal_pressure_series(sigma0_tp: ndarray, pressure: ndarray, sigma_grid: ndarray) ndarray[source]
Find the pressure of each target density surface at every time step.
Uses a “first crossing from shallow” approach: for each target σ₀ value, scans from the shallowest pressure downward and finds the first level where sigma0 transitions from below to at-or-above the target, then linearly interpolates between those two levels. This is robust to non-monotonic sigma0 columns (which occur in gridded fields during knockdowns) and always returns the shallowest crossing — the physically meaningful pycnocline.
Returns NaN only when the target density is absent from the entire column (too light or too dense for any observed level at that time step).
Note
Operates on σ₀ (potential density referenced to 0 dbar). If the mooring was processed with a different reference pressure (e.g. σ₂ at 2000 dbar), pass the corresponding sigma variable. A future option
--sig-ref/density_referencewill generalise this; for now the variable name in the dataset controls which reference is used.- Parameters:
sigma0_tp – Shape
(time, pressure). NaN where missing.pressure – 1-D array of pressure levels in dbar. Need not be sorted — the function sorts each time step’s finite values by pressure before scanning for crossings.
sigma_grid – Target sigma0 values (kg m⁻³) at which to find the pressure.
- Returns:
Shape
(time, len(sigma_grid)). NaN where the target isopycnal is absent from the observed column at that time step.- Return type:
np.ndarray
temporal
Lag correlation, histogram-based split value, T/S downsampling, and Tukey time-series filtering.
Time-series analysis utilities for oceanographic data.
temporal.py provides filtering, lag correlation, histogram-based splitting, and sparse downsampling operations used across instrument processing and reporting.
Pairs with oceanarray.plotters.timeseries for figure output.
- oceanarray.analysis.temporal.downsample_to_sparse(temp_profiles: ndarray, salt_profiles: ndarray, full_pressures: ndarray, sparse_pressures: ndarray) tuple[ndarray, ndarray][source]
Downsample full T/S profiles to sparse pressure levels.
- Parameters:
temp_profiles (np.ndarray) – Full temperature profiles, shape (n_profiles, n_pressures_full).
salt_profiles (np.ndarray) – Full salinity profiles, shape (n_profiles, n_pressures_full).
full_pressures (np.ndarray) – Full pressure levels corresponding to temp_profiles and salt_profiles, shape (n_pressures_full,).
sparse_pressures (np.ndarray) – Target sparse pressure levels to sample, shape (n_pressures_sparse,).
- Returns:
temp_sparse (np.ndarray) – Sparse temperature profiles, shape
(n_profiles, n_pressures_sparse). NaN where the target pressure is outsidefull_pressures.salt_sparse (np.ndarray) – Sparse salinity profiles, same shape. NaN at the same out-of-range levels.
- oceanarray.analysis.temporal.filter_sigma_tukey(data: ndarray, window_samples: int, alpha: float = 0.5) ndarray[source]
Apply a Tukey moving-average filter along axis=1 (time), NaN-aware.
Uses a finite-weight convolution: each output point is the weighted mean of the finite values within the window, so NaN gaps never contaminate adjacent points. Output is set to NaN where fewer than 10 % of the window weights are finite (edges of large data gaps).
- Parameters:
data (np.ndarray) – 2-D array with shape
(n_pressure, n_time). NaN marks missing values.window_samples (int) – Length of the Tukey window in samples. Values < 3 or ≥ n_time return a copy of data unchanged.
alpha (float, optional) – Shape parameter of the Tukey window in
[0, 1](default 0.5).alpha=0is a rectangular window;alpha=1is a Hann window.
- Returns:
Smoothed array with the same shape as data. Rows that are entirely NaN are returned unchanged.
- Return type:
np.ndarray
- oceanarray.analysis.temporal.lag_correlation(x: ndarray, y: ndarray, max_lag: int, min_overlap: int = 10) ndarray[source]
Pearson correlation at integer lags in
[-max_lag, max_lag].- Parameters:
x (np.ndarray) – 1-D arrays of the same length. NaN values are excluded pairwise at each lag.
y (np.ndarray) – 1-D arrays of the same length. NaN values are excluded pairwise at each lag.
max_lag (int) – Maximum lag (in samples) to compute. Output has length
2 * max_lag + 1.min_overlap (int, optional) – Minimum number of finite pairs required to compute a correlation at a given lag. Lags with fewer pairs return NaN (default 10).
- Returns:
Correlation coefficients, shape
(2 * max_lag + 1,). Positive lags mean x leads y; NaN where overlap is insufficient.- Return type:
np.ndarray
- Raises:
ValueError – If x and y do not have the same shape.
- oceanarray.analysis.temporal.split_value(data: ndarray, nbins: int = 30) float[source]
Find the histogram-based threshold between two data modes.
Computes a
nbins-bin histogram, locates the two highest peaks, and returns the left edge of the minimum-count bin between them.- Parameters:
data (np.ndarray) – 1-D array (NaNs are removed before binning).
nbins (int, optional) – Number of histogram bins (default 30). Increase if the two modes are not resolved.
- Returns:
Left edge of the minimum-count bin between the two dominant peaks.
- Return type:
float
- Raises:
ValueError – If fewer than two histogram peaks are detected. This occurs when the data are unimodal or when nbins is too coarse to resolve the two modes.
spectral
Gonella rotary spectra, Welch PSD, and continuous wavelet transforms.
Spectral analysis utilities for oceanographic time series.
Provides rotary spectrum decomposition (Gonella 1972), Welch PSD estimates with and without gap-awareness, and continuous wavelet transforms.
Pairs with oceanarray.plotters.spectrum for figure output.
- oceanarray.analysis.spectral.compute_cwt(x: ndarray, dt_seconds: float, wavelet: str = 'morlet', dj: float = 0.25, significance_level: float = 0.95) dict[source]
Compute a continuous wavelet transform (CWT) on a 1-D time series.
Uses pycwt (Torrence & Compo 1998 method). The input array is gap-filled by linear interpolation before the transform; a boolean gap mask is returned so callers can overlay the filled regions.
If the AR(1) coefficient estimation fails (series too short or strongly trended), a WARNING is logged and the significance test falls back to a white-noise background (alpha=0); the wavelet itself is unaffected.
- Parameters:
x – 1-D array of values. NaNs are treated as gaps and filled by linear interpolation before the transform.
dt_seconds – Sample interval in seconds.
wavelet –
"morlet"(default, complex — gives amplitude and phase, best for oscillatory signals) or"mexican_hat"(real — better for detecting edges/sharp features, useful for wave-skewness studies).dj – Fractional octave spacing between scales. Smaller values give more scales (finer period resolution) at higher compute cost. Default 0.25 (4 scales per octave).
significance_level – Confidence level for the chi-squared significance test against a red-noise background. Default 0.95 (95 %).
- Returns:
dict with keys
- ``power`` (2-D array
(n_scales, n_time), real wavelet power.)- ``periods`` (1-D array of periods in **days*.*)
- ``coi`` (1-D array of raw record-edge COI periods in **days* (pycwt output).*)
- ``effective_coi`` (1-D array of gap-aware COI periods in **days*. At each time*) – step this is the minimum of the record-edge COI and the COI contributed by the nearest gap boundary. Inside gap columns it is 0 (entire period range unreliable). Use this for hatching in plots.
- ``signif`` (1-D array
(n_scales,)— significance threshold for each) – scale; power > signif[:,None] is significant.- ``gap_mask`` (boolean 1-D array
(n_time,)— True where the original) – data was NaN (gap-filled region).- ``dt_days`` (sample interval in days (convenience).)
- oceanarray.analysis.spectral.gonella_rotary_spectrum(u_col: ndarray, v_col: ndarray, fs: float, nperseg: int, noverlap: int) tuple[ndarray, ndarray, ndarray, ndarray][source]
Compute the Gonella (1972) rotary power spectrum from 1-D velocity components.
Both input arrays must be finite (no NaN). Calls
scipy.signal.welchfor the auto-spectra andscipy.signal.csdfor the cross-spectrum, then applies the Gonella rotary decomposition.- Parameters:
u_col (np.ndarray) – 1-D east-velocity time series in m s⁻¹, finite (gap-filled).
v_col (np.ndarray) – 1-D north-velocity time series in m s⁻¹, finite (gap-filled).
fs (float) – Sampling frequency in cycles per day.
nperseg (int) – Number of samples per Welch segment.
noverlap (int) – Number of overlapping samples between adjacent Welch segments.
- Returns:
(freq, s_cw, s_ccw, r)where:freq— 1-D frequency array in cycles per day.s_cw— clockwise power spectral density (≥ 0).s_ccw— counter-clockwise power spectral density (≥ 0).r— rotary coefficient(s_ccw - s_cw) / (s_ccw + s_cw), in[-1, 1]; 0 where both spectra are zero.
- Return type:
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
References
Gonella, J. (1972). A rotary-component method for analysing meteorological and oceanographic vector time series. Deep-Sea Research, 19(12), 833–846.
- oceanarray.analysis.spectral.welch_psd(x: ndarray, dt_days: float, segment_length: int, overlap: float = 0.5, window: str = 'hann') tuple[ndarray, ndarray][source]
Welch PSD estimate on a gap-filled (finite) time series.
- Parameters:
x – 1-D array of evenly-spaced, finite values (no NaNs).
dt_days – Sample interval in days.
segment_length – Number of samples per Welch window (
nperseg).overlap – Fractional overlap between windows (default 0.5 → 50 %).
window – Window function name accepted by
scipy.signal.welch.
- Returns:
(frequencies_cpd, psd)where frequencies are in cycles per day.- Return type:
tuple[np.ndarray, np.ndarray]
- oceanarray.analysis.spectral.welch_psd_gapaware(x: ndarray, dt_days: float, segment_length: int, overlap: float = 0.5, window: str = 'hann') tuple[ndarray | None, ndarray | None, int][source]
Welch PSD over contiguous finite runs only; windows straddling gaps are skipped.
Returns a sample-count-weighted average PSD across all valid windows from all contiguous finite segments. This avoids the low-frequency bias that arises from gap-filling (linear interpolation) before the Welch estimate.
Switching the LF panel to gap-aware is a one-line change: replace
welch_psd(col_filled, ...)withwelch_psd_gapaware(col, ...).- Parameters:
x – 1-D array of evenly-spaced values; NaN marks gaps.
dt_days – Sample interval in days.
segment_length – Number of samples per Welch window (
nperseg).overlap – Fractional overlap between windows (default 0.5 → 50 %).
window – Window function name accepted by
scipy.signal.welch.
- Returns:
(frequencies_cpd, psd, n_windows)where n_windows is the total number of valid Welch windows used. Returns(None, None, 0)when no contiguous run is long enough for a single window.- Return type:
tuple[np.ndarray | None, np.ndarray | None, int]
vector
XYZ→ENU rotation and progressive-vector trajectory computation.
Vector rotation and progressive-vector utilities for oceanographic current analysis.
Provides coordinate-system rotation (XYZ → ENU) and progressive-vector (pseudo-Lagrangian) trajectory computation extracted from instrument-specific processing code so they can be tested and reused independently.
Pairs with oceanarray.plotters.current for figure output.
- oceanarray.analysis.vector.progressive_vector(east_2d: ndarray, north_2d: ndarray, dt_s: ndarray, pressure: ndarray) list[tuple[float, ndarray, ndarray]][source]
Compute pseudo-Lagrangian progressive-vector trajectories by pressure level.
Integrates east and north velocity time series using the Euler forward method to produce cumulative horizontal displacement from the origin. Levels with all-NaN velocity are silently skipped.
- Parameters:
east_2d (np.ndarray) – East velocity array, shape
(n_time, n_pressure)in m s⁻¹. NaN is treated as zero for the integration step in which it occurs.north_2d (np.ndarray) – North velocity array, shape
(n_time, n_pressure)in m s⁻¹. NaN is treated as zero for the integration step in which it occurs.dt_s (np.ndarray) – 1-D array of time-step sizes in seconds, length
n_time - 1.pressure (np.ndarray) – 1-D pressure array of length
n_pressurein dbar.
- Returns:
One entry
(p_val, x_km, y_km)per pressure level that has at least one finite velocity sample.p_valis the pressure in dbar;x_kmandy_kmare 1-D arrays of cumulative east and north displacement in km, lengthn_time.- Return type:
list of tuple[float, np.ndarray, np.ndarray]
- oceanarray.analysis.vector.xyz_to_enu_2d(vx: ndarray, vy: ndarray, vz: ndarray, heading_deg: ndarray, pitch_deg: ndarray, roll_deg: ndarray, declination_deg: float = 0.0) tuple[ndarray, ndarray][source]
Rotate XYZ instrument-frame velocities to ENU using the Nortek heading convention.
The transformation is vectorised and handles arbitrary array shapes as long as all inputs broadcast together. The Nortek heading convention subtracts 90° before constructing the rotation matrix so that a heading of 0° (north) maps to the correct ENU orientation.
- Parameters:
vx (np.ndarray) – Along-beam (X) velocity component, m s⁻¹.
vy (np.ndarray) – Lateral (Y) velocity component, m s⁻¹.
vz (np.ndarray) – Vertical (Z) velocity component, m s⁻¹.
heading_deg (np.ndarray) – Instrument heading in degrees, positive clockwise from north.
pitch_deg (np.ndarray) – Instrument pitch in degrees.
roll_deg (np.ndarray) – Instrument roll in degrees.
declination_deg (float, optional) – Magnetic declination to add to heading before rotation (degrees, positive east). Default is 0.0 (no correction).
- Returns:
(east, north)velocity components in m s⁻¹.- Return type:
tuple[np.ndarray, np.ndarray]
Plotters
primitives
Tier-1 data-agnostic plot primitives (array-in / Figure-out).
Tier-1 data-agnostic plotting primitives.
These functions accept pre-processed arrays (not xarray Datasets) and have no knowledge of oceanographic variable naming conventions. They are the lowest layer in the three-tier plotters architecture:
Tier 1: primitives.py — generic, array-in / Figure-out Tier 2: _current.py etc. — domain wrappers (xr.Dataset-in / Figure-out) Tier 3: report/_plots.py — thin wrappers (path-in / base64-out)
Post-OdB: add plot_vector_heatmap, plot_section, plot_spectrum, plot_polar_histogram, plot_timeseries.
- oceanarray.plotters.primitives.colorbar_norm(data: ndarray | None = None, *, vmin: float | None = None, vmax: float | None = None, n: int = 20, symmetric: bool = False) tuple[ndarray, BoundaryNorm][source]
Return
(bounds, norm)for a discrete pcolormesh colorbar.Computes percentile limits from data when vmin / vmax are not given. Explicit vmin / vmax override the percentile calculation. Pass
symmetric=Trueto force the range symmetric about zero.- Parameters:
data (np.ndarray, optional) – Source array used for percentile-based limit computation. Ignored when both vmin and vmax are given.
vmin (float, optional) – Explicit color limits. Either one or both may be supplied; the other falls back to the percentile of data.
vmax (float, optional) – Explicit color limits. Either one or both may be supplied; the other falls back to the percentile of data.
n (int) – Target number of colorbar levels (default 20).
symmetric (bool) – If
True, expand [vmin, vmax] to [-max, +max] before computing bounds.
- Returns:
bounds (np.ndarray) – Boundary array for
BoundaryNormand colorbar ticks.norm (matplotlib.colors.BoundaryNorm)
- Raises:
ValueError – If neither data nor both vmin and vmax are provided.
- oceanarray.plotters.primitives.date_axis(ax: Any) None[source]
Apply a concise auto-scaled date formatter to ax’s x-axis (offset left).
- oceanarray.plotters.primitives.date_offset_left(ax: Any) None[source]
Move the x-axis date offset label (e.g.
2026-Jul) to the bottom-left.matplotlib’s
ConciseDateFormatterdraws the year/month offset at the bottom-right. Only the offset’s vertical position is updated on each draw, so the left x-position set here persists. Call after setting the date formatter.
- oceanarray.plotters.primitives.figure_title(fig: Any, text: str, **kwargs: Any) Any[source]
Set a figure-level title (
suptitle), centred over all panels.Use for a title that spans a multi-panel figure; per-panel titles use
plot_title()(left-aligned). Keeping both in one module makes the left-vs-centre policy a single switch point.- Parameters:
fig (matplotlib.figure.Figure) – Figure whose suptitle is set.
text (str) – Title text.
**kwargs (Any) – Forwarded to
matplotlib.figure.Figure.suptitle(e.g.y,fontsize).
- Returns:
The created suptitle artist.
- Return type:
matplotlib.text.Text
- oceanarray.plotters.primitives.hodograph_panel(ax: Any, e_v: ndarray, n_v: ndarray, t_frac: ndarray, title: str, units: str) Any[source]
Draw a single velocity hodograph on a pre-squared ax; return its mappable.
Renders a time-coloured
LineCollectiontrajectory (downsampled to <= 2 000 segments for performance) with start/end markers, symmetric-lim..limlimits, and a subtle grid. The panel does NOT draw its own colorbar — the caller owns one shared colorbar (all panels use the same 0->1 plasma time mapping); pass the returnedScalarMappabletounit_colorbar()on a sharedcaxfromsquare_axes_grid().e_v, n_v, and t_frac must already be filtered to the same finite-valid indices (no NaN, same length). ax is expected to be an exact square from
square_axes_grid(); equal aspect is enforced withadjustable='datalim'so the box is never resized (which would strand the shared colorbar).- Parameters:
ax (matplotlib Axes) – Pre-squared target axes to draw on.
e_v (np.ndarray) – East and north velocity (finite values only, same length).
n_v (np.ndarray) – East and north velocity (finite values only, same length).
t_frac (np.ndarray) – Fractional deployment time 0 -> 1, same length as e_v.
title (str) – Axes title.
units (str) – Velocity unit string appended to axis labels, e.g.
"m s^-1".
- Returns:
The 0->1 plasma time mapping, for a shared colorbar.
- Return type:
matplotlib.cm.ScalarMappable
- oceanarray.plotters.primitives.pcolormesh_panel(fig: Any, ax: Any, data: ndarray, time: ndarray, pressure: ndarray, title: str, units: str = '', cmap: str = 'RdYlBu_r', style: str = 'pcolormesh', vmin: float | None = None, vmax: float | None = None, n: int = 20, cb_label: str | None = None, title_loc: str = 'left', date_fmt: bool = True) Any[source]
Draw one (pressure × time) field as a discrete-colorbar panel on ax.
A generic time–depth panel primitive. Computes percentile colour limits from data unless vmin / vmax are supplied. Applies a discrete
BoundaryNormcolorbar, an inverted pressure axis, and (optionally) a concise date axis.- Parameters:
fig (matplotlib Figure and Axes) – Target figure and axes to draw on.
ax (matplotlib Figure and Axes) – Target figure and axes to draw on.
data (numpy.ndarray) – 2-D field shaped
(pressure, time).time (numpy.ndarray) – Time coordinate (length matching
data’s second axis).pressure (numpy.ndarray) – Pressure coordinate (length matching
data’s first axis).title (str) – Panel title.
units (str, optional) – Units string appended to cb_label when cb_label is
None.cmap (str, optional) – Colormap name. Default
"RdYlBu_r".style (str, optional) –
"pcolormesh"(default) or"contourf".vmin (float, optional) – Explicit colour limits. Computed from data percentiles when omitted.
vmax (float, optional) – Explicit colour limits. Computed from data percentiles when omitted.
n (int, optional) – Target number of colorbar levels. Default 20.
cb_label (str, optional) – Colorbar label. Defaults to
"{title} ({units})"or"{title}".title_loc (str, optional) – Horizontal alignment of the axes title. Default
"left"(the report panel-title convention; pass"center"for a centred title).date_fmt (bool, optional) – When
True(default), applydate_axis()to ax. PassFalsefor stacked panels where only the last axis needs the formatter.
- Returns:
The pcolormesh/contourf artist (useful for a shared colorbar).
- Return type:
matplotlib collection
- oceanarray.plotters.primitives.plot_title(ax: Any, text: str, *, loc: str = 'left', **kwargs: Any) Any[source]
Set an axes (panel) title, left-aligned by default.
The single place panel-title alignment is decided for report figures, so the left-vs-centre choice for every panel title can be switched here in one line. Figure-level titles that span several panels use
figure_title()(centred) instead — do not route those through this helper. Colorbar unit labels (cb.ax.set_title) are not panel titles and must not use this helper either.- Parameters:
ax (matplotlib.axes.Axes) – Axes whose title is set.
text (str) – Title text.
loc (str, optional) – Horizontal alignment passed to
set_title(default"left").**kwargs (Any) – Forwarded to
matplotlib.axes.Axes.set_title(e.g.fontsize).
- Returns:
The created title artist.
- Return type:
matplotlib.text.Text
- oceanarray.plotters.primitives.plot_trajectory(x: ndarray, y: ndarray, color_data: ndarray | None = None, cmap: str = 'coolwarm', xlabel: str = 'East displacement (m)', ylabel: str = 'North displacement (m)', colorbar_label: str = '', colorbar_unit: str = '', title: str = '', *, width_in: float = 4.5) Figure[source]
Plot a 2D trajectory, optionally coloured per-segment by a scalar field.
When color_data is provided, segments are drawn as a LineCollection with colours mapped through cmap and a height-matched colorbar (unit as a title on top). When omitted, a plain line is drawn. Start and end are marked with green and red markers. The axes are laid out as an exact square via
square_axes_grid()withsquare_limits(), so the colorbar height always matches the plotted square.- Parameters:
x (ndarray) – Trajectory coordinates (same length).
y (ndarray) – Trajectory coordinates (same length).
color_data (ndarray, optional) – Scalar values to map onto the line (same length as x/y).
cmap (str) – Matplotlib colormap name used when color_data is provided.
xlabel (str) – Axis labels.
ylabel (str) – Axis labels.
colorbar_label (str) – Fallback colorbar label placed on top when colorbar_unit is empty.
colorbar_unit (str) – Unit string placed above the colorbar (units-only on top, saves width).
title (str) – Figure title.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
matplotlib.figure.Figure
- oceanarray.plotters.primitives.pressure_axis(ax: Any) None[source]
Configure ax as a standard pressure Y-axis: inverted, labelled, gridded.
- oceanarray.plotters.primitives.square_axes_grid(fig_w: float, nrows: int, ncols: int, *, colorbar: bool = True, per_panel_colorbar: bool = False, top_pad_in: float = 0.0, bottom_pad_in: float = 0.0, wgap_in: float | None = None, cbar_txt_in: float | None = None, left_in: float | None = None) tuple[Figure, ndarray, Any][source]
Lay out an
nrows × ncolsgrid of square axes deterministically in inches.Every panel is an exact square whose side is computed from the usable width, so an equal-aspect plot fills the panel without
set_aspecthaving to shrink the axes box — and a colorbar placed here shares each panel’s exact pixel height rather than the taller subplot cell. A single shared colorbar axes (spanning the full height of the panel stack) is reserved on the right when colorbar is true.fig._manual_layoutis set so the base64 encoder skipstight_layout(which would re-flow these hand-placed axes).Callers must NOT call
set_aspect('equal', adjustable='box')on the returned axes: the box is already square and authoritative, so use symmetric limits (oradjustable='datalim') instead, or the box would resize and strand the shared colorbar again.- Parameters:
fig_w (float) – Figure width in inches — must equal the display slot so the PNG is not rescaled by the browser.
nrows (int) – Grid shape (both >= 1).
ncols (int) – Grid shape (both >= 1).
colorbar (bool) – Reserve and return a single shared colorbar axes on the right. Default True. Ignored when per_panel_colorbar is set.
per_panel_colorbar (bool) – Give each panel its own height-matched colorbar axes to its right (for figures where every panel encodes a different field, e.g. a T-S dot plot + count heatmap + O₂ panel). The third return value is then an
(nrows, ncols)object array of colorbar axes instead of a single one.top_pad_in (float) – Extra inches reserved above the panel-title strip, e.g. for a figure
suptitle. Default 0.bottom_pad_in (float) – Extra inches reserved below the x-tick strip, e.g. for rotated tick labels or a second x-axis label line. Default 0.
wgap_in (float, optional) – Override the inter-column gap (inches). Default (
None) uses the standard gap, which reserves room for each column’s y-labels; a caller whose panels share a y-axis (right panel hides its y-labels) can pass a smaller value to close the gap.cbar_txt_in (float, optional) – Override the reserved width (inches) for the shared colorbar’s tick labels and axis label. Default (
None) uses the standard reserve; pass a larger value for a long colorbar label so it stays on-canvas.left_in (float, optional) – Override the reserved left-margin width (inches) for the y-tick labels plus rotated y-axis label. Default (
None) uses the standard reserve (_SQ_LABEL_IN), which fits up to ~4-character tick labels; pass a larger value when the y-tick labels are wide (e.g. thousands-of-km trajectory displacements) so the axis label stays on-canvas. Applies to the standard (nonper_panel_colorbar) layout. Useytick_reserve_in()to size it from the data range.
- Returns:
The figure; an
(nrows, ncols)object array of panel axes; and the colorbar axes — a single sharedAxes(or None) normally, or an(nrows, ncols)object array when per_panel_colorbar is set.- Return type:
tuple of (matplotlib.figure.Figure, numpy.ndarray, object)
- oceanarray.plotters.primitives.square_limits(x: ndarray, y: ndarray, *, pad_frac: float = 0.05) tuple[tuple[float, float], tuple[float, float]][source]
Return
(xlim, ylim)framing x, y as an equal-extent square.The larger of the x and y data ranges is applied to both axes (each centred on its own data midpoint), so an equal-aspect plot of the data is square and neither axis is a thin strip. Non-finite values are ignored; a degenerate (zero-extent) input falls back to a unit square. Choose the limits with this helper before placing a square axes so the colorbar sizing stays exact.
- Parameters:
x (numpy.ndarray) – Data coordinates (any shape; flattened, non-finite dropped).
y (numpy.ndarray) – Data coordinates (any shape; flattened, non-finite dropped).
pad_frac (float) – Fractional padding added to the half-extent on all sides. Default 0.05.
- Returns:
((x0, x1), (y0, y1)).- Return type:
tuple of (tuple of float, tuple of float)
- oceanarray.plotters.primitives.unit_colorbar(cax: Any, mappable: Any, *, unit: str = '', ticks: ndarray | None = None, ticklabels: list[str] | None = None) Any[source]
Draw mappable’s colorbar into the pre-placed cax with the unit on top.
The unit is rendered as a title above the bar (
cax.set_title) rather than a rotated side label, which saves horizontal width and reads cleanly — the same convention as the cruise-map depth colorbar. cax is expected to come fromsquare_axes_grid()so its height already matches the plotted square.- Parameters:
cax (matplotlib Axes) – Pre-placed colorbar axes.
mappable (matplotlib ScalarMappable) – The artist (LineCollection, pcolormesh, ScalarMappable, …) to map.
unit (str) – Unit string placed above the bar (e.g.
"m s⁻¹"). Empty renders no title.ticks (numpy.ndarray, optional) – Explicit colorbar tick positions.
ticklabels (list of str, optional) – Explicit tick labels (same length as ticks), e.g.
["start", "end"]for a fractional-time bar.
- Return type:
matplotlib.colorbar.Colorbar
- oceanarray.plotters.primitives.ytick_reserve_in(y: ndarray | float) float[source]
Left-margin inches to fit the widest y-tick label plus rotated y-axis label.
Sizes the
left_inreserve forsquare_axes_grid()from the y-data range instead of a fixed shrink: the base reserve (_SQ_LABEL_IN) already fits up to ~4-character tick labels, and each additional character (a larger magnitude, or a leading minus) adds_SQ_PER_CHAR_IN. Returns the base reserve unchanged for <=4-character labels, so a small-displacement trajectory keeps the full square (no 9% shrink) while a thousands-of-km one reserves just enough.- Parameters:
y (numpy.ndarray or float) – The y values (or the maximum absolute y-limit) about to be plotted.
- Returns:
Left-margin reserve in inches, always >=
_SQ_LABEL_IN.- Return type:
float
helpers
Shared colormap, normalisation, and style helpers used across Tier-2 modules.
Shared helpers for the plotters package.
Provides colormap helpers and rose-diagram rendering used across multiple Tier-2 plotter modules.
Note: _fig_to_base64 stays in reports/_html_helpers.py (called only by Tier-3 wrappers in reports/_plots.py; plotters/ never serialises to base64).
- oceanarray.plotters.helpers.OKABE_ITO: list[str] = ['#000000', '#E69F00', '#56B4E9', '#009E73', '#F0E442', '#0072B2', '#D55E00', '#CC79A7']
Okabe-Ito 8-colour qualitative palette — the accessibility-standard colourblind-safe set (distinguishable under protan/deutan/tritan CVD). Note the yellow (#F0E442) is pale on a white background; the linewidth tiers in
distinct_line_styles()and the linestyle help keep it legible.
- oceanarray.plotters.helpers.distinct_line_styles(n: int) list[tuple[str, str, float]][source]
Return n colourblind-safe
(color, linestyle, linewidth)line styles.Cycles the 8-colour
OKABE_ITOpalette and advances the linestyle every 8 lines (solid → dashed → dash-dot → dotted), so up to 32 lines each get a unique(color, linestyle)pair — enough to tell apart a mooring’s worth of instruments where colour alone (max 8–20 hues) collides. Linewidth increases with the linestyle group so the sparser styles (dash-dot, dotted) stay as visible as the solid ones: solid thinnest, dashed thin, dash-dot and dotted thicker. Beyond 32 lines the linestyle group is clamped (styles repeat) rather than raising.- Parameters:
n (int) – Number of line styles to return (>= 0).
- Returns:
(hex_color, linestyle, linewidth)per line, in order.- Return type:
list of (str, str, float)
- oceanarray.plotters.helpers.grid_despine(ax: plt.Axes, *, axis: str = 'both') None[source]
Turn the grid on and hide the top and right spines (report convention).
The report style keeps
axes.gridoff by default and figures opt in; when they do, the top and right spines are redundant clutter. Call this instead ofax.grid(True)so the two always travel together. Grid appearance (dotted, faint) comes from the active mplstyle, not hard-coded here, so a single style change restyles every grid.- Parameters:
ax (matplotlib.axes.Axes) – Axes to style.
axis ({"both", "x", "y"}, optional) – Which gridlines to draw (default
"both"). Bar/profile plots that want one-directional gridlines pass"x"or"y"and still get the top/right spines hidden.
- oceanarray.plotters.helpers.ordered_line_colors(cmap_name: str, n: int, *, max_luminance: float = 0.72) list[source]
Return n colours from cmap_name, in colormap order, skipping pale ones.
Samples the colormap on a fine grid, keeps only positions whose relative luminance is
<= max_luminance(so no line washes out against white), then returns n colours evenly spaced across the usable positions. For a diverging colormap this drops the pale midpoint, leaving two saturated arcs; for a sequential one it drops the pale end. Callers assign the colours in a fixed order (e.g. deep-first) so the darkest end maps to the intended extreme.- Parameters:
cmap_name (str) – Matplotlib colormap name.
n (int) – Number of colours to return (>= 1).
max_luminance (float) – Rec. 709 relative-luminance ceiling in
[0, 1]; positions lighter than this are excluded. Default 0.72 — low enough that the least-saturated remaining colour on a diverging map (the arc boundary either side of the excluded pale centre) is still legible on white.
- Returns:
n colours (length exactly
max(n, 1)).- Return type:
list of RGBA tuples
- oceanarray.plotters.helpers.tukey_smooth(arr: ndarray, window_n: int) ndarray[source]
Zero-phase Tukey (cosine-tapered) smooth, NaN-gap aware.
Uses a convolution-based approach so NaN gaps do not propagate: output points near gaps are weighted only by the finite neighbours that fall within the window. Output is set to NaN where fewer than 10 % of the window weights are finite (edges of large data gaps).
Requires
scipy.- Parameters:
arr (np.ndarray) – 1-D array, possibly containing NaNs.
window_n (int) – Window length in samples.
- Returns:
Smoothed array, same shape as arr.
- Return type:
np.ndarray
current
ADCP velocity plots: hodographs, current roses, stick plots, depth-time panels.
Tier-2 domain wrappers for current/velocity instrument plots.
These functions accept xarray Datasets and delegate rendering to Tier-1 primitives in _primitives.py. They understand oceanarray variable naming conventions but do not serialise to base64 (that happens in report/_plots.py).
Pairs with oceanarray.analysis.vector for coordinate transformations.
Public draw_* functions (migrated from report/_plots.py): - draw_instrument_rose: rose diagram grid for a single Aquadopp instrument. - draw_rose_grid: grid of current roses for instruments in a stack dataset. - draw_grid_rose: grid of current roses by pressure level for the grid report. - draw_grid_trajectory: pseudo-Lagrangian trajectory by pressure for the grid report. - draw_adcp_velocity: stacked colour panels for the ADCP per-instrument HTML page. - draw_adcp_rose: current rose panels for an ADCP per-instrument report. - draw_adcp_hodograph: two-depth hodograph for an ADCP per-instrument report. - draw_grid_hodograph: two-depth hodograph for the grid report.
- oceanarray.plotters.current.draw_adcp_hodograph(nc_path: str, lp_days: float = 4.0, smooth_hours: float = 24.0, *, width_in: float = 9.0) Figure | None[source]
Two-depth hodograph for an ADCP per-instrument report; return a Figure.
Picks the bins nearest the 25th and 75th percentile of the valid range and renders a 2×2 figure: top row = far bin (75th pctile — typically shallower for upward-looking), bottom row = near bin (25th pctile). Left column = Tukey-smoothed raw; right column = eddy (LP mean removed). Returns None if east/north velocity are absent, 1-D, or too few valid bins.
Accepts both the current naming convention (
east_velocity/north_velocity) and the legacy dolfyn names (u/v) so that files produced before the stage1 renaming was introduced can still be visualised. Files need to be regenerated with the current stage1 to get the canonical names.- Parameters:
nc_path (str) – Path to a stage-3 ADCP NetCDF file.
lp_days (float) – Low-pass filter cutoff in days for eddy extraction.
smooth_hours (float) – Tukey smoothing window in hours for the raw panel.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if velocity data are absent or insufficient.
- Return type:
plt.Figure or None
- oceanarray.plotters.current.draw_adcp_rose(nc_path: str, *, width_in: float = 9.0) Figure | None[source]
Current rose panels for an ADCP: depth-average plus percentile-selected bins.
Selects the depth-average and up to four individual range bins at the 10th, 37th, 63rd, and 90th percentile positions of the valid bin indices (bins with at least 5 % of time steps having finite data). Returns None if east/north velocity are absent or too few samples exist.
- Parameters:
nc_path (str) – Path to a stage-3 ADCP NetCDF file.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if east/north velocity are absent or too few samples exist.
- Return type:
plt.Figure or None
- oceanarray.plotters.current.draw_adcp_velocity(nc_path: str, *, width_in: float = 9.0) Figure | None[source]
Stacked colour panels for the ADCP per-instrument HTML report page; return a Figure.
Reads the stage-3 NetCDF file at nc_path and produces a multi-panel time × range pcolormesh figure.
Coordinate convention: velocities are in geographic ENU (East–North–Up), after magnetic declination correction applied in stage 3. Positive east = rightward facing north; positive north = toward True North.
Direction convention (
current_directionpanel): direction toward which the water flows, clockwise from True North (0° = northward, 90° = eastward). This is the oceanographic convention, opposite to meteorological “direction from”. Computed asatan2(east, north) mod 360.Panels rendered for each variable present in the file:
Diverging panels (east/north/up/error) share symmetric colormap bounds set to ±max(|2nd pctile|, |98th pctile|) of all finite ENU velocity values.
Bins flagged at or below the seabed (
seabed_qc >= 3) are masked to NaN. Y-axis (range coordinate) is inverted for downward-looking instruments (pressure increases into the page); non-inverted for upward-looking.- Parameters:
nc_path (str) – Path to a stage-3 NetCDF file for a single ADCP instrument.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if no velocity data are present or range coordinate is absent.
- Return type:
plt.Figure or None
- oceanarray.plotters.current.draw_grid_hodograph(ds: Dataset, smooth_hours: float = 24.0, *, width_in: float = 9.0) Figure | None[source]
Two-depth hodograph for the grid report; return a Figure.
Takes an already-loaded xr.Dataset (not a path). Picks the pressure levels nearest the 25th and 75th percentile of the valid gridded pressure range and renders a 1×2 figure (shallow / deep, each showing the
smooth_hours-smoothed hodograph). Returns None if east/north velocity are absent or fewer than two valid levels exist.- Parameters:
ds (xr.Dataset) – Gridded mooring dataset with
east_velocityandnorth_velocity.smooth_hours (float) – Tukey smoothing window in hours.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if velocity data are absent or insufficient.
- Return type:
plt.Figure or None
- oceanarray.plotters.current.draw_grid_rose(ds: Dataset, max_roses: int = 4, *, width_in: float = 9.0) Figure | None[source]
Grid of current roses, one per pressure level, for the grid report.
Shows up to max_roses pressure levels (at most 1/5th of valid levels, capped at 4), each labelled with its pressure (dbar). Levels with no finite ENU velocity are skipped. Returns
Nonewhen east/north velocity are absent or all-NaN.- Parameters:
ds (xr.Dataset) – Gridded dataset with dimensions
(time, pressure)containingeast_velocityandnorth_velocityin m s⁻¹.max_roses (int) – Maximum number of rose panels to draw (default 4).
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
plt.Figure or None
- oceanarray.plotters.current.draw_grid_trajectory(ds: Dataset, *, width_in: float = 4.5) Figure | None[source]
Pseudo-Lagrangian current-vector integral by pressure level for the grid report.
For each pressure level, integrates east and north velocity over time using the Euler forward method to produce a cumulative displacement trajectory from the origin (0, 0). Returns
Nonewhen east/north velocity are absent or all-NaN.- Parameters:
ds (xr.Dataset) – Gridded dataset with dimensions
(time, pressure), containingeast_velocityandnorth_velocityin m s⁻¹.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
plt.Figure or None
- oceanarray.plotters.current.draw_instrument_rose(nc_path: Path, *, width_in: float = 9.0) Figure | None[source]
Rose diagram grid for a single Aquadopp instrument; return Figure or None.
Loads the stage-3 NetCDF at nc_path, builds one polar panel per available velocity QC tier (ENU magnetic, ENU good, suspect, fail), and returns the Figure. Returns
Nonewhen no velocity data are found.- Parameters:
nc_path (Path) – Path to a stage-3 NetCDF file for a single Aquadopp instrument.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
matplotlib.figure.Figure or None
- oceanarray.plotters.current.draw_rose_grid(ds: Dataset, serial_list: list, *, width_in: float = 9.0) tuple[Figure, int] | None[source]
Grid of current roses (max 4 per row) for instruments with ENU velocity data.
- Parameters:
ds (xr.Dataset) – Stack dataset with
east_velocityandnorth_velocity.serial_list (list) – Serial numbers corresponding to the instrument axis of the velocity arrays.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure and the number of rose panels rendered, or None if no velocity data.
- Return type:
tuple of (plt.Figure, int) or None
- oceanarray.plotters.current.plot_adcp_trajectories(ds: Dataset, u_var: str = 'east_velocity', v_var: str = 'north_velocity', instr_type_var: str = 'instrument_type', hab_var: str = 'hab', seabed_qc_var: str = 'seabed_qc', percent_good_qc_var: str = 'percent_good_qc', *, width_in: float = 4.5) Figure | None[source]
Lagrangian per-bin trajectories for ADCP data, coloured by HAB.
Each depth bin integrated by Euler-forward from the origin. Bins that are entirely below the seabed (all seabed_qc >= 3) are silently omitted. QC masking is applied before integration so bad pings are treated as zero velocity (do not accumulate spurious displacement).
- Parameters:
ds (xr.Dataset) – Stacked mooring dataset. ADCP bins are identified by
instrument_type == "ADCP".u_var (str) – Eastward and northward velocity variable names (m s⁻¹).
v_var (str) – Eastward and northward velocity variable names (m s⁻¹).
instr_type_var (str) – Coordinate variable names.
hab_var (str) – Coordinate variable names.
seabed_qc_var (str) – QC variable for seabed proximity; bins with all values >= 3 are skipped.
percent_good_qc_var (str) – Ping-quality QC; timesteps flagged >= 3 are zeroed before integration.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
None if no ADCP bins are found.
- Return type:
matplotlib.figure.Figure or None
- oceanarray.plotters.current.plot_aquadopp_speed_profile(ds: Dataset, speed_var: str = 'current_speed', u_var: str = 'east_velocity', v_var: str = 'north_velocity', instr_type_var: str = 'instrument_type', serial_var: str = 'serial', hab_var: str = 'hab', *, width_in: float = 4.5) Figure | None[source]
Horizontal speed boxplots for all Aquadopps, one per instrument at its HAB.
X-axis: current speed. Y-axis: height above bottom (m). All Aquadopps appear on the same axes so the speed distribution can be compared across depths.
If speed_var is not present in ds, speed is computed from u_var and v_var as
sqrt(u² + v²).- Parameters:
ds (xr.Dataset) – Stacked mooring dataset with shape (time, N_LEVELS).
speed_var (str) – Preferred speed variable name; computed from u/v if absent.
u_var (str) – Used to compute speed when speed_var is not present.
v_var (str) – Used to compute speed when speed_var is not present.
instr_type_var (str) – Dimension-coordinate variable names.
serial_var (str) – Dimension-coordinate variable names.
hab_var (str) – Dimension-coordinate variable names.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
None if no Aquadopp instruments are found.
- Return type:
matplotlib.figure.Figure or None
Notes
Adapted from
02_aqdp_ploter.pyby L. Moscatel (lmoscat), Universitat de Barcelona.
- oceanarray.plotters.current.plot_hodograph(ds: Dataset, u_var: str = 'east_velocity', v_var: str = 'north_velocity', lp_days: float = 4.0, smooth_hours: float = 3.0, *, width_in: float = 9.0) Figure[source]
Two-panel hodograph: Tukey-smoothed raw and eddy-only, coloured by time.
Panel 1: smooth_hours-Tukey-smoothed raw east-vs-north velocity. Panel 2: eddy component — raw minus a lp_days-day rolling-mean low-pass, then smooth_hours-Tukey smoothed to suppress instrument noise.
Points are coloured by fractional time through the record (0 = start, 1 = end) using a discrete viridis colorbar so the temporal evolution of rotary motion can be followed by eye. The figure title shows the instrument id from
ds.attrs["id"]when available.If u_var or v_var are absent, a placeholder figure is returned with an explanatory message — the caller always receives a renderable image.
- Parameters:
ds (xr.Dataset) – Per-instrument dataset containing east and north velocity variables.
u_var (str) – Eastward velocity variable name (m s⁻¹).
v_var (str) – Northward velocity variable name (m s⁻¹).
lp_days (float) – Low-pass window length in days for the eddy-component panel.
smooth_hours (float) – Tukey smoothing window in hours applied to both panels.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
matplotlib.figure.Figure
- oceanarray.plotters.current.plot_multi_aquadopp_trajectories(ds: Dataset, u_var: str = 'east_velocity', v_var: str = 'north_velocity', temp_var: str = 'temperature', instr_type_var: str = 'instrument_type', serial_var: str = 'serial', title: str = '', *, width_in: float = 4.5) Figure | None[source]
Multi-instrument Lagrangian trajectories for all Aquadopps, coloured by temperature.
Each trajectory starts at the origin and is built by integrating the east/north velocity over time (Euler forward; NaN velocities set to zero). All trajectories share a single temperature colour scale so instruments can be compared directly. End points are annotated with the instrument serial.
- Parameters:
ds (xr.Dataset) – Stacked mooring dataset with shape (time, N_LEVELS). Must contain instr_type_var, serial_var, u_var, v_var.
u_var (str) – Eastward and northward velocity variables (m s⁻¹).
v_var (str) – Eastward and northward velocity variables (m s⁻¹).
temp_var (str) – Temperature variable for colouring; omitted if not present in ds.
instr_type_var (str) – Dimension-coordinate variable names identifying each instrument.
serial_var (str) – Dimension-coordinate variable names identifying each instrument.
title (str) – Optional figure title; falls back to the dataset
idattribute.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
None if no Aquadopp instruments are found in the dataset.
- Return type:
matplotlib.figure.Figure or None
Notes
Adapted from
02_aqdp_ploter.pyby L. Moscatel (lmoscat), Universitat de Barcelona.
- oceanarray.plotters.current.plot_speed_boxplot(ds: Dataset, speed_var: str = 'current_speed', *, width_in: float = 3.0) object[source]
Boxplot of current speed with printed percentile statistics.
Prints the 5th, 25th, 50th, 75th and 95th percentiles to stdout. NaN values are excluded before plotting.
- Parameters:
ds (xr.Dataset) – Dataset containing the speed variable.
speed_var (str) – Name of the current speed variable.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
matplotlib.figure.Figure
- oceanarray.plotters.current.plot_temperature_trajectory(ds: Dataset, u_var: str = 'east_velocity', v_var: str = 'north_velocity', temp_var: str = 'temperature', title: str = '') object[source]
Lagrangian particle trajectory coloured per-segment by temperature.
Integrates u_var and v_var (east/north velocity) over time using a forward Euler scheme. NaN velocity values are treated as zero so the trajectory is not interrupted.
The displacement is in metres; for long deployments the trajectory will drift far from origin and should be interpreted as a pseudo-Lagrangian tracer, not a real particle path.
Note: the velocity integration step will move to
oceanarray.toolspost-OdB so that it can be reused independently of plotting.- Parameters:
ds (xr.Dataset) – Dataset containing east/north velocity and temperature variables.
u_var (str) – Name of the eastward velocity variable (m s⁻¹).
v_var (str) – Name of the northward velocity variable (m s⁻¹).
temp_var (str) – Name of the temperature variable used for colouring.
title (str) – Optional figure title; defaults to the dataset
idattribute.
- Return type:
matplotlib.figure.Figure
timeseries
Grid and mooring time-series figures: T/S/density pcolormesh, velocity panels, N² sections.
Tier-2 domain wrappers for time-series and gridded-section plots.
This module provides the draw_* functions used by the HTML report pipeline
to render instrument time-series panels and mooring-grid section plots.
Pairs with oceanarray.analysis.temporal for time-series analysis.
Grid section panels (data on a pressure × time grid):
draw_grid_fig()— generic pcolormesh / contourf wrapper for a single variable.draw_grid_hydro()— stacked temperature / salinity (and optionally oxygen) panels.draw_grid_velocity_stacked()— stacked east / north / up velocity panels.draw_grid_sigma()— potential density (sigma) panels.draw_grid_n2()— buoyancy frequency squared N² computed viagsw.draw_grid_timeseries()— velocity time series at the depth of maximum mean speed.
Instrument time-series panels:
draw_analog_timeseries()— full-record time series for analog channel variables.
- Post-OdB: migrate the following from plotter.py and report/_plots.py:
plot_microcat_raw, plot_aquadopp_raw, plot_mooring_timeseries (the three kept plotter.py functions; §11), plot_aquadopp_quick, build_instrument_fig (was _build_fig_from_ds), plot_instrument_windows.
- oceanarray.plotters.timeseries.draw_analog_timeseries(nc_path: Path, analog_vars: List[str], *, width_in: float = 9.0) plt.Figure | None[source]
Full-record time series for analog channel variables, one panel per variable.
Returns
Nonewhen the dataset lacks atimedimension.- Parameters:
nc_path (Path) – Path to the stage-3 or stack NetCDF file.
analog_vars (list of str) – Variable names to plot (caller must ensure the list is non-empty).
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
plt.Figure or None
- oceanarray.plotters.timeseries.draw_grid_fig(da: xr.DataArray, title: str, units: str, cmap: str, style: str = 'pcolormesh', contour_levels: list | None = None, symmetric: bool = False, vmin: float | None = None, vmax: float | None = None, *, width_in: float = 9.0) plt.Figure[source]
Render a grid figure from da (dims time × pressure); return a Figure.
- Parameters:
da (xr.DataArray) – Data array with
timeandpressuredimensions.title (str) – Panel title and colorbar label prefix.
units (str) – Unit string appended to the colorbar label.
cmap (str) – Matplotlib colormap name.
style (str) –
"pcolormesh"(default) or"contourf".contour_levels (list, optional) – If given, overlay black contour lines at these levels.
symmetric (bool) – If True, force a symmetric (diverging) color range.
vmin (float, optional) – Override the automatic percentile-based color limits.
vmax (float, optional) – Override the automatic percentile-based color limits.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
plt.Figure
- oceanarray.plotters.timeseries.draw_grid_hydro(ds: xr.Dataset, var_bounds: dict | None = None, *, width_in: float = 9.0) plt.Figure | None[source]
Stacked temperature / salinity pcolormesh panels for the grid report; return a Figure.
Both panels have pressure (dbar) on the Y-axis (inverted, surface at top). Colorbar bounds are clipped to the
COLORBAR_PLOW–COLORBAR_PHIGHpercentiles of the data to reduce the influence of outliers on color scaling.Temperature (°C, colormap
RdYlBu_r): sea water temperature on the gridded pressure–time grid.Salinity (PSU, colormap
YlGnBu_r): taken from thesalinityvariable if present. If absent but bothconductivity(mS cm⁻¹) andtemperature(°C) are present, Practical Salinity is derived viagsw.SP_from_C(PSS-78). Note: this is Practical Salinity, not Absolute Salinity (g kg⁻¹); the latter would require pressure and longitude viagsw.SA_from_SP.Panels are rendered only for variables that are present in ds or derivable.
- Parameters:
ds (xr.Dataset) – Gridded mooring dataset with dimensions
(time, pressure).var_bounds (dict, optional) – Pre-computed colorbar limits keyed by variable name, e.g.
{"t_lim": (vmin, vmax), "s_lim": (vmin, vmax), "o2_lim": (vmin, vmax)}. When a key is present its limits are used instead of computing from the data. Intended for passing the T-S diagram axis limits so both figures share scales.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if no hydrographic data are present.
- Return type:
plt.Figure or None
- oceanarray.plotters.timeseries.draw_grid_n2(ds: xr.Dataset, lat: float = 0.0, *, width_in: float = 9.0) plt.Figure | None[source]
Compute and plot buoyancy frequency squared N² on the pressure-time grid.
Returns
Nonewhen temperature or salinity are absent.
- oceanarray.plotters.timeseries.draw_grid_sigma(ds: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
Stacked sigma0 pcolormesh panel(s) for the stratification section.
Returns
Nonewhen no sigma variables are present.
- oceanarray.plotters.timeseries.draw_grid_timeseries(ds: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
Velocity time series at the depth of maximum time-mean current speed.
Two stacked panels (shared time axis):
Speed (black, m s⁻¹):
sqrt(east² + north²); always ≥ 0.East and North velocity (same axes, m s⁻¹): east in Okabe-Ito blue (#0072B2), north in Okabe-Ito orange (#E69F00). Both signed components are plotted together so the relationship between along- and cross-stream flow is immediately visible.
The target pressure level is chosen automatically as the level with the highest time-mean current speed and at least 70 % non-NaN coverage. The selected pressure is annotated in the figure title.
- Parameters:
ds (xr.Dataset) – Gridded dataset with dimensions
(time, pressure)containing at minimumeast_velocityandnorth_velocityin m s⁻¹.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if horizontal velocity data are absent or all-NaN.
- Return type:
plt.Figure or None
- oceanarray.plotters.timeseries.draw_grid_velocity_stacked(ds: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
Stacked east / north / up velocity pcolormesh panels for the grid report.
All three panels share the time axis and show pressure (dbar) on the Y-axis (inverted, surface at top). East and north share symmetric diverging bounds; up uses its own symmetric bounds (open-ocean vertical velocities are typically 1–2 cm s⁻¹ vs. tens of cm s⁻¹ horizontal). Returns
Nonewhen no velocity variables are present.- Parameters:
ds (xr.Dataset) – Gridded dataset with dimensions
(time, pressure).width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
plt.Figure or None
diagnostic
Diagnostic plots: boxplots, clock-alignment checks, deployment-boundary windows.
Tier-2 domain wrappers for diagnostic plots (T-S, histograms, spectra, QC).
Knockdown plots
plot_knockdown_pressure()— IQR of measured pressure vs. nominal design depth, equal aspect ratio with 1:1 reference line.plot_knockdown_hab()— IQR of measured pressure vs. nominal HAB, equal aspect ratio with expected-pressure reference line.plot_knockdown_anomaly()— IQR of pressure anomaly (measured minus nominal) per instrument, colour-coded by knockdown severity.plot_knockdown_displacement()— scatter and 2-D heatmap of estimated horizontal displacement vs. measured pressure.
Clock-alignment check
plot_clock_offset_check()— overlaid temperature time series zoomed to deployment start and end.
Deployment window figures (moved from report._plots)
draw_windows()— combined start+end window figure for a single instrument.draw_data_histogram()— histogram of data values with QC thresholds.draw_velocity_iqr_profile()— percentile-profile figure for gridded ADCP velocity data.
- Post-OdB: migrate the following from report/_plots.py:
plot_ts_diagram (was _make_ts_diagram), plot_stack_ts_diagram, plot_grid_ts_diagram, plot_data_histogram (was _make_data_histogram), plot_spectrum (was _make_spectrum_fig_b64).
Tier-1 primitives: plot_vector_heatmap (for T-S, U-V, any pair), plot_spectrum (any 1D time series), plot_polar_histogram (current rose).
- oceanarray.plotters.diagnostic.draw_data_histogram(nc_path: Path, *, width_in: float = 9.0) plt.Figure | None[source]
Histogram of data values for each main variable; return a Figure.
Each panel shows grey bars (all finite data) and blue bars (kept, not bad/missing), with QC range threshold lines overlaid.
- Parameters:
nc_path (Path) – Path to a stage-3 NetCDF file.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if no plottable variables are found.
- Return type:
plt.Figure or None
- oceanarray.plotters.diagnostic.draw_velocity_iqr_profile(ds: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
Percentile-profile figure for gridded ADCP velocity data; return a Figure.
Three side-by-side panels, all with pressure (dbar) on the Y-axis (inverted, surface at top). All velocity units are m s⁻¹.
- Left — current speed (always ≥ 0):
Shaded percentile profile: outer band p2.5–p97.5 (95% range of the distribution); inner band IQR p25–p75; median p50. Wide IQR at a given depth indicates high velocity variability (e.g. an eddy-active layer or a strong tidal signal); narrow IQR with a large median indicates a persistent mean flow.
current_speedis computed fromsqrt(east² + north²)if not already present in the dataset.- Middle — east and north velocity (can be negative):
Median and IQR (p25/p50/p75) for each component. East velocity in Okabe-Ito blue (#0072B2); north velocity in Okabe-Ito orange (#E69F00); both colours are distinguishable for common colour-vision deficiencies. Positive east = rightward facing north (geographic ENU after declination correction); positive north = toward True North. A median near zero with large IQR suggests rotary motion (e.g. tides or near-inertial oscillations); a non-zero median indicates a mean current.
- Right — count (dimensionless):
Number of non-NaN time steps at each pressure level. Use this panel to assess data coverage before interpreting velocity statistics: pressure levels with very few records produce unreliable percentile estimates.
- Parameters:
ds (xr.Dataset) – Gridded dataset with dimensions
(time, pressure)containing at minimum one ofcurrent_speed,east_velocity, ornorth_velocityin m s⁻¹.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if no velocity data are present.
- Return type:
plt.Figure or None
- oceanarray.plotters.diagnostic.draw_windows(nc_path: Path, instr_type: str, hours: int = 6, show_qc: bool = True, vlines: list | None = None, stage1_nc: Path | None = None, panels: list | None = None, *, width_in: float = 9.0) plt.Figure | None[source]
Combined start + end window figure: (nrows × 2) — left = first N h, right = last N h.
- Parameters:
nc_path (Path) – Path to the processed (stage2 or stage3) NetCDF file.
instr_type (str) – Instrument type string (used in the figure title).
hours (int) – Width of each window in hours (default 6).
show_qc (bool) – Overlay QC flag markers on the data.
vlines (list of (time_val, color, label), optional) – Vertical marker lines to draw on both panels. time_val may be a
numpy.datetime64, an ISO-8601 string, or apandas.Timestamp. Lines are only drawn when they fall inside the plotted window. Labels appear as small rotated text at the top of the first row only to avoid excessive clutter.stage1_nc (Path, optional) – Path to the stage1 NC file. When provided, the raw stage1 data is plotted as a light-grey background trace in each window panel so the full pre/post-deployment record is visible (bench → deployment in the left panel; deployment → recovery in the right panel). The x-axis limits are taken from the stage1 window extent so that the recovery transition appears even if the stage2/3 YAML trim cut it off. The y-axis limits are taken from the primary (stage2/3) data only so that bench-pressure outliers (p ≈ 0) do not squish the deployment-depth view.
panels (list, optional) – Subset of
_instrument_panelstuples to draw. When given, only these rows are rendered (used to paginate a tall window figure across several images); otherwise every panel for the instrument is drawn on one figure.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if the dataset is too short to show windows.
- Return type:
plt.Figure or None
- oceanarray.plotters.diagnostic.plot_clock_offset_check(nc_paths: Dict[str, Path], deploy_dt: datetime | None, recover_dt: datetime | None, window_minutes: int = 30, *, width_in: float = 9.0) matplotlib.figure.Figure | None[source]
Overlaid, per-instrument normalised temperature around deploy and recover.
Plots a
±window_minuteswindow centred on deployment and on recovery for every instrument with a temperature variable, so clock alignment between instruments can be assessed visually. If an instrument’s clock is offset the temperature signal appears shifted in time relative to the others.Each instrument’s trace is standardised over the plotted window (subtract the window mean, divide by the window standard deviation) so instruments with different absolute temperatures and amplitudes overlay on a common
stdy-axis and their timing can be compared directly.Two sub-panels are produced side by side:
Left:
deploy_dt ± window_minutesRight:
recover_dt ± window_minutes
When
deploy_dtorrecover_dtisNone, only the available window is produced. A shared legend below both panels lists all instruments. An instrument with zero variance in a window (flat/constant) is skipped for that window (no timing information, and normalisation is undefined).- Parameters:
nc_paths (dict of {serial: Path}) – Paths to stage-2 or stage-3 NetCDF files keyed by serial number. Only instruments with temperature data are included in the plot.
deploy_dt (datetime or None) – Deployment time (UTC).
recover_dt (datetime or None) – Recovery time (UTC).
window_minutes (int) – Duration of each zoom window in minutes.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
None when fewer than two instruments have temperature data, or if any other error prevents plotting.
- Return type:
matplotlib.figure.Figure or None
- oceanarray.plotters.diagnostic.plot_knockdown_anomaly(ds: xr.Dataset, *, width_in: float = 4.5) matplotlib.figure.Figure | None[source]
IQR of pressure anomaly (measured − nominal) per instrument.
Each non-ADCP instrument is shown as a horizontal box-and-whisker at its nominal pressure on the y-axis; the box spans the IQR of the pressure anomaly distribution (actual − nominal, positive = knocked down deeper).
A vertical reference line at x = 0 marks zero knockdown. Box colour indicates the magnitude of the median knockdown:
Interpolated pressure (QC flag 8) is excluded. Rendered at half-width in the stack report.
- Parameters:
ds (xr.Dataset) – Stack dataset; same requirements as
plot_knockdown_pressure().width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
matplotlib.figure.Figure or None
- oceanarray.plotters.diagnostic.plot_knockdown_displacement(ds: xr.Dataset, *, width_in: float = 9.0) matplotlib.figure.Figure | None[source]
Scatter and heatmap of estimated horizontal displacement vs. measured pressure.
For each non-ADCP instrument the horizontal displacement is estimated at every time step as:
x_horiz = sqrt(max(0, hab_nom² − hab_meas²))
where
hab_nomis the nominal height-above-bottom (m) andhab_meas = waterdepth − pressureis the measured HAB.Left panel — scatter of
(x_horiz, measured_pressure)per instrument, coloured by serial number. y-axis is measured pressure (dbar, increasing downward); x-axis is horizontal displacement (m). Square axes (equal aspect, adjustable data limits).Right panel — 2-D count heatmap of the same points across all instruments combined. Discrete colorbar (up to 15 levels).
- Parameters:
ds (xr.Dataset) – Stack dataset; same requirements as
plot_knockdown_pressure().width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
matplotlib.figure.Figure or None
- oceanarray.plotters.diagnostic.plot_knockdown_hab(ds: xr.Dataset, *, width_in: float = 4.5) matplotlib.figure.Figure | None[source]
IQR of measured pressure vs. nominal HAB, equal aspect ratio.
Companion to
plot_knockdown_pressure(). The y-axis is identical (measured pressure, dbar, increasing downward); only the x-axis changes from nominal pressure to nominal height-above-bottom (m).Each non-ADCP instrument is shown as a vertical box-and-whisker positioned at its nominal HAB on the x-axis; the box spans the IQR of the measured pressure distribution. The dashed reference line shows the expected pressure for each HAB (
pressure = waterdepth − hab). Boxes below the line were pulled deeper than their design height by current drag.With 1 dbar ≈ 1 m, equal aspect keeps the reference line near 45° and the departure from it is directly related to horizontal displacement.
- Parameters:
ds (xr.Dataset) – Stack dataset with dimensions
(time, N_LEVELS)containingpressure,hab,serial,instrument_type, and optionallypressure_qc. Thewaterdepthglobal attribute must be present and non-zero.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Return type:
matplotlib.figure.Figure or None
- oceanarray.plotters.diagnostic.plot_knockdown_pressure(ds: xr.Dataset) matplotlib.figure.Figure | None[source]
IQR of measured pressure vs. nominal design depth, equal aspect ratio.
Each non-ADCP instrument is shown as a vertical box-and-whisker positioned at its nominal pressure on the x-axis; the box spans the IQR of the actual measured pressure distribution on the y-axis (increasing downward).
The axes have equal aspect (
ax.set_aspect("equal")) and both run from 0 to the maximum value, so the 1:1 reference line appears at 45°. Instruments on the diagonal are at their design depth; boxes that drop below the diagonal experienced knockdown (measured deeper than nominal).Boxes are coloured blue; interpolated pressure (QC flag 8) is excluded. Rendered at half-width in the stack report.
- Parameters:
ds (xr.Dataset) – Stack dataset with dimensions
(time, N_LEVELS)containingpressure,hab,serial,instrument_type, and optionallypressure_qc. Thewaterdepthglobal attribute must be present and non-zero.- Return type:
matplotlib.figure.Figure or None
hydrography
T-S diagrams and isopycnal overlay figures.
Tier-2 domain wrappers for hydrographic section and isopycnal plots.
- hydrography.py contains:
draw_isopycnal_ts_fig: isopycnal height-above-seabed time series.draw_isopycnal_coverage: three-panel isopycnal diagnostic.draw_overflow_temperature_fig: temperature time series at ~100 m above seabed.
Pairs with oceanarray.analysis.hydrographic for density computations.
- oceanarray.plotters.hydrography.draw_isopycnal_coverage(ds: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
Three-panel isopycnal diagnostic; return a Figure.
Panel 0 — Distribution: horizontal histogram of all gridded σ₀ values (all time steps × all pressure levels), binned at 0.1 kg m⁻³. Shows how the water column is distributed in density space.
Panel 1 — Coverage: for each σ₀ value at 0.1 kg m⁻³ spacing, the percentage of valid time steps during which the target surface lies within the measured column (
min(sigma0_column) ≤ target ≤ max(sigma0_column)). Colour-coded: green ≥ 80 %, amber 50–80 %, red < 50 %. Dashed reference at 80 %.Panel 2 — Depth distribution: for each target surface, the median height above seabed (or pressure when
waterdepthis unavailable), with the IQR (25th–75th percentile) as a thick bar and the 5th–95th percentile as a thin whisker.The shared y-axis (σ₀) is clipped to the 2.5th–99.99th percentile of the distribution — this removes rare light-water outliers from the top while retaining all of the dense water at the bottom. Currently selected
params.SIGMA_GRIDtargets are marked with orange diamonds (panel 1) and dotted guide lines (panel 2).- Parameters:
ds – Gridded mooring xr.Dataset containing a variable whose name starts with
"sigma"and haspressureandtimedimensions.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or
Noneif required sigma data are absent.- Return type:
plt.Figure or None
- oceanarray.plotters.hydrography.draw_isopycnal_ts_fig(ds_iso: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
Isopycnal height-above-seabed time series; return a Figure.
Plots a 1-hour running median of each σ₀ surface’s height above seabed. NaN gaps break the line naturally (pandas rolling preserves NaN boundaries). Colormap: Blues — light blue = lower density (shallower), dark = denser (deeper).
- Parameters:
ds_iso – Output of
isopycnal_dataset()— must containisopycnal_height(sigma0_level, time)and thesigma0_levelcoordinate.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or
Noneif required data are absent.- Return type:
plt.Figure or None
- oceanarray.plotters.hydrography.draw_overflow_temperature_fig(ds: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
Temperature time series at ~100 m above the seabed; return a Figure.
Selects the grid pressure level nearest to
waterdepth - 100dbar and plots a 1-hour running median temperature time series. ReturnsNoneifwaterdepthis missing, temperature is absent, or all values are NaN.- Parameters:
ds – Gridded mooring xr.Dataset. Must have a
waterdepthglobal attribute (metres) and atemperaturevariable withpressureandtimedimensions.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or
Noneif required data are absent.- Return type:
plt.Figure or None
spectrum
Power spectra and rotary spectrum figures.
Tier-2 domain wrappers for spectral diagnostics.
- spectrum.py contains:
wavelet_panel: Tier-1 primitive for rendering a single wavelet scalogram.draw_spectrum: Two-panel Welch PSD of gridded temperature (migrated fromreport/_plots.py).draw_wavelet: Continuous wavelet transform scalogram for gridded temperature (migrated fromreport/_plots.py).draw_grid_rotary_spectrum: Two-panel rotary velocity spectrum (migrated fromreport/_plots.py).
Pairs with oceanarray.analysis.spectral for spectral computations.
- Post-OdB remaining migrations from report/_plots.py:
plot_grid_fig (was _make_grid_fig_b64), plot_grid_n2 (was _make_grid_n2_b64).
- oceanarray.plotters.spectrum.draw_grid_rotary_spectrum(ds: xr.Dataset, lat: float = 0.0, *, width_in: float = 9.0) plt.Figure | None[source]
Two-panel rotary velocity spectrum for the grid report; return a Figure.
Left panel: CW (solid, reds) and CCW (dashed, blues) power spectra on the same axes, one line per selected pressure level. Right panel: rotary coefficient r = (CCW - CW) / (CCW + CW) on a linear [-1, 1] scale. Welch PSD with Hann window, 14-day segments, 50% overlap.
Level selection: min(4, max(1, n_valid_levels // 5)) evenly-spaced levels from those with >= 5% finite data in both east and north velocity.
- Parameters:
ds (xr.Dataset) – Gridded dataset with
east_velocityandnorth_velocityon(time, pressure)dimensions.lat (float) – Mooring latitude (degrees, positive north) used for the inertial period marker.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if insufficient velocity data.
- Return type:
plt.Figure or None
- oceanarray.plotters.spectrum.draw_spectrum(da_temp: xr.DataArray, dt_seconds: float, lat: float = 0.0, hf_segment_days: float = 1.0, hf_x_max_days: float = 3.0, *, width_in: float = 9.0) plt.Figure | None[source]
Two-panel Welch PSD of gridded temperature, one line per depth level.
Left panel: low-frequency overview using 14-day Hann windows. Right panel: high-frequency zoom using
hf_segment_daysHann windows, giving more (shorter) windows and a smoother estimate at tidal and inertial frequencies.- Parameters:
da_temp – Gridded temperature DataArray with dimensions (pressure, time).
dt_seconds – Uniform time step of the grid in seconds.
lat – Mooring latitude in decimal degrees; used to compute the inertial frequency marker. Pass 0 to omit.
hf_segment_days – Window length for the HF panel in days. Default 2 days (~180 windows per year, ~7x more than the LF panel). Reduce to focus on higher frequencies, e.g.
hf_segment_days=1/24for 1-hour windows on sub-hourly data.hf_x_max_days – Upper x-axis limit (longest period shown) for the HF panel in days. When <= 3 the HF x-axis is displayed in hours; otherwise in days.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
Notes
The LF panel uses gap-filled interpolation before calling
welch_psd(current behaviour). The HF panel useswelch_psd_gapaware, which operates only on contiguous finite runs and skips any window that straddles a gap – avoiding the low-pass bias that linear interpolation introduces at high frequencies.To switch the LF panel to gap-aware as well, replace the two lines in the computation loop that read:
col_filled = col.copy() if not good.all(): col_filled = np.interp(...) f, p = welch_psd(col_filled, dt_days, segment_length_lf)
with the single line:
f, p, _ = welch_psd_gapaware(col, dt_days, segment_length_lf)
- oceanarray.plotters.spectrum.draw_wavelet(da_temp: xr.DataArray, dt_seconds: float, wavelet: str = 'morlet', *, width_in: float = 9.0) plt.Figure | None[source]
Continuous wavelet transform scalogram for gridded temperature; return a Figure.
Produces three stacked wavelet + time-series panel pairs. Depth levels are selected from 100-dbar multiples that have at least 75 % data coverage (sparse near-bottom levels are excluded). Levels within 100 dbar of the shallowest valid level are also excluded to avoid gappy near-surface data. From the remaining candidates the deepest, an upper-middle, and a lower-middle level are chosen (biased toward the deeper water column).
Each wavelet panel shows log10(Morlet power) as a filled contour plot; the gap-aware cone of influence (COI) is hatched – this covers both the record edges and the COI wings that spread out from every data gap. The y-axis is trimmed per-panel to the longest period that is reliable at any time step, so gappier levels automatically get a tighter period range. A 95 % significance contour is drawn in black (falls back to white-noise background if AR(1) estimation fails). Below each wavelet panel a short temperature time series for that depth level shares the x-axis.
- Parameters:
da_temp – Gridded temperature DataArray with dimensions
(pressure, time).dt_seconds – Sample interval in seconds.
wavelet –
"morlet"(default, Morlet omega_0=6) or"mexican_hat".width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if the dataset has insufficient temperature data.
- Return type:
plt.Figure or None
- oceanarray.plotters.spectrum.wavelet_panel(ax: matplotlib.axes.Axes, times: np.ndarray, periods: np.ndarray, power: np.ndarray, coi: np.ndarray, signif: np.ndarray | None = None, title: str = '') mcolors.ScalarMappable[source]
Draw one wavelet scalogram panel onto ax.
Renders log10(power) as a filled contour plot with period on a log y-axis (inverted so short periods are at the top), time on x.
The COI region (
period > coi[t]) is hatched with diagonal lines. Callers should passeffective_coifromcompute_cwtrather than the raw pycwt COI — the effective COI already incorporates gap-edge wings so that one hatching call covers record edges, gap columns, and the surrounding unreliable region.The y-axis is trimmed to
coi.max()so entirely-hatched long-period rows are not shown; gappier records automatically get a tighter period range.An optional 95 % significance contour is drawn in black.
- Parameters:
ax – Target axes object.
times – 1-D array of time values (any type accepted by matplotlib, e.g. numpy datetime64 or float index).
periods – 1-D array of wavelet periods in days (length
n_scales).power – 2-D array of real wavelet power, shape
(n_scales, n_time).coi – 1-D array of COI periods in days, length
n_time. Passeffective_coifromcompute_cwtto include gap-edge wings.signif – Optional 1-D significance threshold array, length
n_scales. Wherepower[i, t] > signif[i]the transform is significant at the chosen confidence level. If None, no significance contour is drawn.title – Axes title string (e.g. depth label).
- Returns:
Mappable suitable for passing to
fig.colorbar().- Return type:
matplotlib.cm.ScalarMappable
ts
T-S scatter and density-coloured scatter figures.
T-S diagram and thermohaline structure plot functions.
- oceanarray.plotters.ts.draw_grid_ts_diagram(ds: xr.Dataset, n_bins: int = 60, *, width_in: float = 9.0) tuple[plt.Figure, dict] | None[source]
T-S diagram for gridded mooring data; return a (Figure, bounds_dict) tuple.
Left panel: 2-D count heatmap (log₁₀ samples per T-S bin). Right panel (when
oxygen_saturation_pctis present): median O₂ saturation per T-S bin, computed withscipy.stats.binned_statistic_2d. Bins with fewer than 5 samples are masked white.This lets you see which water masses (T-S combinations) are oxygen-rich vs oxygen-depleted at this mooring — a compact water-mass characterisation.
- Parameters:
ds (xr.Dataset) – Gridded dataset with at least
temperatureandsalinityvariables.n_bins (int) – Number of bins per axis for the 2-D histogram.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
bounds_dictcontains the axis/colorbar limits computed from the data so the hydro pcolormesh panels can share the same scales:"t_lim": (vmin, vmax) for temperature"s_lim": (vmin, vmax) for salinity"o2_lim": (vmin, vmax) for oxygen_saturation_pct, or None
- Return type:
tuple of (plt.Figure, bounds_dict) or None
- oceanarray.plotters.ts.draw_stack_ts_diagram(ds: xr.Dataset, *, width_in: float = 9.0) plt.Figure | None[source]
T-S diagram for a stacked dataset; return a Figure.
Scatter-by-pressure, count heatmap, and (when present) scatter-by-AOU. Panels are arranged in a single row. The AOU panel is included when
apparent_oxygen_utilizationis present in ds (written by stage3 for instruments with dissolved oxygen data).QC masking: bad (flag 4) and missing (flag 9) excluded; interpolated pressure (flag 8) is kept as usable colour data.
- Parameters:
ds (xr.Dataset) – Stacked mooring dataset containing
temperatureandsalinity.width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if temperature or salinity are absent.
- Return type:
plt.Figure or None
- oceanarray.plotters.ts.draw_ts_diagram(nc_path: Path, *, width_in: float = 9.0) plt.Figure | None[source]
T-S diagram from a NetCDF path; return a Figure.
Scatter by pressure, 2-D count heatmap, and (when present) scatter by O2 saturation.
- Parameters:
nc_path (Path) – Path to a stage-3 NetCDF file.
width_in (float, optional) – Figure width in inches – the display-slot width the report builder resolves; standalone callers get the full content width.
- Returns:
Figure, or None if temperature or salinity are absent.
- Return type:
plt.Figure or None
animation
Animated GIF output via matplotlib.animation.
Animated plot functions for the oceanarray plotters package.
This module is intentionally separate from the static (Tier-1/2/3) pipeline.
It relies on matplotlib.animation and requires the pillow package as a
GIF writer. scipy is required for the Tukey smoothing window.
Future interactive equivalents (plotly, bokeh) should live in a companion
interactive.py module so the animation and interactive layers stay
clearly distinct.
Tier classification: Tier-2 domain functions (xr.Dataset-in / file-out).
- oceanarray.plotters.animation.animate_hodograph(ds: Dataset, output_path: str | Path, u_var: str = 'east_velocity', v_var: str = 'north_velocity', lp_days: float = 4.0, smooth_hours: float = 3.0, frame_hours: float = 6.0, fps: int = 20, dpi: int = 100) Path | None[source]
Write an animated GIF of the smoothed hodograph drawing itself through time.
Each frame advances by frame_hours of real deployment time and reveals the smoothed velocity trajectory accumulated up to that moment, so the viewer watches rotary/eddy structure build up through the record.
Signal processing (applied once to the full record before animating):
Raw panel — smooth_hours-Tukey filter of the raw east/north signal.
Eddy panel — lp_days-day rolling-mean removed, then smooth_hours- Tukey filter to suppress instrument noise.
A timestamp header on each frame shows the real date and time.
Requires
pillow(pip install pillow) as the GIF writer andscipyfor the Tukey window.- Parameters:
ds (xr.Dataset) – Per-instrument dataset with east/north velocity variables and a
timedimension.output_path (str or Path) – Destination file path;
.gifextension recommended.u_var (str) – Eastward velocity variable name (m s⁻¹).
v_var (str) – Northward velocity variable name (m s⁻¹).
lp_days (float) – Low-pass window length in days for the eddy-component panel.
smooth_hours (float) – Tukey smoothing window in hours applied to both panels.
frame_hours (float) – Time step between frames in hours (default 6 h → one frame per quarter-day of deployment).
fps (int) – Frames per second in the output GIF.
dpi (int) – Resolution of each frame in dots per inch.
- Returns:
Path – The resolved output path on success.
None – If u_var or v_var are absent from ds, or if the
pillowwriter is unavailable.