Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
ctddump
A Rust CLI tool for converting oceanographic CTD (Conductivity, Temperature, Depth) data from NetCDF to Parquet (data) or YAML (metadata).
Links:
- 📦 crates.io
- 📖 Documentation
- 🌊 Live example report site: sample output of the
summary_site.shpipeline phase
Two data sources are supported:
| Source | Description |
|---|---|
| NRT | Near Real Time: Arctic Sea (nrt_ar), Baltic Sea (nrt_bo), Mediterranean Sea (nrt_mo), Global (nrt_gl) |
| CORA | Copernicus Ocean Reanalysis: current format (cora), legacy format (cora_legacy) |
Installation
Download a prebuilt binary (no Rust needed)
Grab an archive for your platform from the latest release. HDF5 and netCDF are built into the binary, so nothing else has to be installed:
VERSION=v0.26.0
TARGET=x86_64-unknown-linux-gnu # or aarch64-unknown-linux-gnu,
# aarch64-apple-darwin, x86_64-apple-darwin
&&
The archive also carries the helper scripts, so the pipeline works as soon as
ctddump is on your PATH.
Install from crates.io
Needs a Rust toolchain and the HDF5 development headers:
# Ubuntu / Debian
# macOS (Homebrew)
The binary lands in ~/.cargo/bin. See the
installation guide if the
build cannot find libnetcdf.
Build from source
Needs the same system dependencies as above.
The binary is placed at target/release/ctddump.
Commands
Every command and subcommand supports -h / --help.
convert: single NetCDF → Parquet
ctddump convert <subcommand> [OPTIONS] <src_file> <target_file>
# Use a saved config preset
# Override individual fields
batch: directory tree NetCDF → Parquet or YAML (multi-threaded)
ctddump batch convert <subcommand> [OPTIONS] <src_dir>
ctddump batch header <subcommand> [OPTIONS] <src_dir>
Recursively finds all matching .nc files and processes them in parallel. Output files keep the source stem with a new extension. If --output is omitted, each output is written alongside its source.
# Convert all NRT Arctic files to Parquet, flat into /output
# Limit to 4 threads
# Override the filename pattern
# Extract YAML metadata for all NRT files
Default filename patterns:
| Subcommand | Pattern |
|---|---|
nrt_ar |
AR_PR_CT_*.nc |
nrt_bo |
BO_PR_CT_*.nc |
nrt_mo |
MO_PR_CT_*.nc |
nrt_gl |
GL_PR_CT_*.nc |
cora, cora_legacy |
*.nc |
batch header nrt, batch header cora |
*.nc |
header: single NetCDF → YAML metadata
ctddump header <subcommand> <src_file> <target_file>
concat: merge files from a directory tree into a single file
ctddump concat convert [OPTIONS] <src_dir> <output_file>
ctddump concat header [OPTIONS] <src_dir> <output_file>
concat convert merges Parquet files and re-assigns profile_no and observation_no by default (pass --no-renumber to skip). Renumbering sorts rows by platform_code, profile_timestamp, longitude, latitude, pres; pass --no-pres-sort to sort without pres, keeping each profile's observations in their original source order instead of reordering them by pressure. Rows with missing (null/NaN) pres are dropped before merging by default (this keeps observation_no contiguous over the remaining rows); pass --keep-na-pres to retain them. Renumbering processes platform ranges in parallel across all CPU cores by default (via temporary files in the output folder); pass --threads N to cap the worker count, or --threads 1 for the sequential, lowest-memory path. Peak memory rises with the thread count, but the result is identical either way.
concat header merges YAML header files: each file contributes its top-level keys to the combined output. An error is raised if any two files share the same key.
# Merge all Parquet files with profile renumbering
# Merge without renumbering
# Merge, but keep each profile's observations in their original order (don't sort by pres)
# Merge, keeping rows with missing pres (they are dropped by default)
# Cap renumbering at 8 threads (defaults to all cores)
# Sequential, lowest-memory merge
# Merge only a subset
# Merge YAML headers
report: summarise a Parquet or YAML file
ctddump report parquet [--level global|platform|profile] [--format tsv|text|json] <src.parquet> [dest]
ctddump report yaml [--format tsv|text|json] <src.yaml> [dest]
Writes a text summary to dest, or to stdout when omitted. Default format is tsv.
report parquet aggregates a data file at one of three --levels (default platform): global (one row), platform (one row per platform_code), or profile (one row per profile). Each row reports profile / observation counts, per-profile "good" QC counts (time_qc/position_qc == "1"), missing-value counts, min / max / mean for temp, psal, and pres, and the geographic bounding box (longitude/latitude min/max; global and platform levels only).
report yaml summarises a merged header YAML: one row per source file with presence flags for the core columns (TEMP, PSAL, PRES, DEPH, TIME, position) and an extra_params list of the extra measurement parameters detected: biogeochemical/biological and other non-core variables (e.g. DOXY;FLU2;TUR3).
# Per-platform summary of a merged Parquet file
# Whole-file summary, human-readable, to stdout
# YAML header summary as JSON
filter: filter a Parquet file's profiles by a bounding box
ctddump filter [--mode include|exclude] --min-lon <W> --max-lon <E> --min-lat <S> --max-lat <N> <src.parquet> <dest.parquet>
filter keeps or drops whole profiles by a geographic bounding box (inclusive on all edges; an inverted box is rejected). --mode defaults to include (keep profiles inside the box); exclude keeps everything outside it. A profile with a NaN position counts as outside the box. The file is streamed one row group at a time, so peak memory stays bounded regardless of size.
# Keep only profiles inside a bounding box
# Drop profiles inside that box, keep the rest
dropna: drop profiles with no usable data in a core parameter
ctddump dropna <src.parquet> <dest.parquet>
dropna keeps a profile only if each of temp, psal, and pres has at least one non-NA observation, and drops the whole profile if any one of them is entirely NA (null or NaN). Partial NAs are fine: a kept profile keeps all its rows. It runs in two streaming passes, so peak memory stays bounded regardless of file size and the output is independent of chunking.
# Drop profiles with an all-NA temp, psal, or pres
dropqc: drop profiles flagged bad by profile-level QC
ctddump dropqc <src.parquet> <dest.parquet>
dropqc keeps a profile only if both time_qc and position_qc are either "1" (OK) or missing (absent QC or the NA byte -128, both stored as ""). Any other flag ("0", "2"…"9", or a non-numeric code) drops the whole profile. Missing QC is kept on purpose: several source files ship no profile-level QC at all. Since these flags are constant within a profile, it is a plain per-row predicate; the file is streamed one row group at a time, so peak memory stays bounded regardless of size.
# Drop profiles flagged bad in time_qc or position_qc
markdup: mark duplicate profiles
ctddump markdup [OPTIONS] <src.parquet> <dest.parquet> <dups.tsv>
markdup adds a Boolean is_dup column marking profiles that share a duplicate key with at least one other profile. The key is built from profile_timestamp (date only by default), longitude, and latitude (rounded to 3 decimals by default), across platforms. It also writes a TSV listing the duplicated profiles (dup_group, platform_code, profile_no, profile_time, profile_timestamp, longitude, latitude, n_obs). The timestamp format (--time-format), rounding precision (--decimals), and rounding mode (--round-mode = round/floor/ceil/trunc) are configurable.
dedup: remove duplicate profiles
ctddump dedup [OPTIONS] <src.parquet> <dest.parquet>
dedup re-derives the same key as markdup (pass the same options) and keeps one profile per duplicate group: the one with the most observation rows, ties broken by first appearance. Profiles with no key (NaN position / null timestamp) are always kept; an is_dup column, if present, is reset to false.
compare: compare two Parquet files
ctddump compare [OPTIONS] <a.parquet> <b.parquet> [dest]
compare reports how far each of two files covers the other's platforms and profiles, writing one row per direction (the second file is the reference first) to dest or stdout. Profiles are matched on the same key as markdup, except that platform_code is part of the key by default; pass --no-platform-key to match on time and position alone. Coverage is reported both ways because it is not symmetric: a file contained in a larger one is fully covered while covering only part of it. For the profiles found in both files the report also says whether they carry the same number of observations (same_nobs / diff_nobs), which separates the same profiles cleaned differently from genuinely different profiles. The key column names (--platform-col, --time-col, --lon-col, --lat-col), the time format, the decimals, and the rounding mode are configurable, and the time column may be a datetime or a float of days since 1950. Output is --format tsv (default), text, or json.
Configuration
All convert and batch convert subcommands support a --config TOML file plus individual flag overrides. Priority order:
built-in default < --config file < individual CLI flags
NRT flags
| Flag | Field | Default |
|---|---|---|
--deph-source / --no-deph-source |
has_deph_source |
true for BO/GL, false for AR/MO |
--profile-coords / --no-profile-coords |
has_profile_coords |
true for BO, false otherwise |
--pattern <GLOB> |
pattern |
see table above |
CORA flags
| Flag | Field | cora default |
cora_legacy default |
|---|---|---|---|
--time-var <VAR> |
time_var |
TIME |
JULD |
--qc-type <int|char> |
qc_type |
int |
char |
--time-qc / --no-time-qc |
has_time_qc |
true |
false |
--deph-source / --no-deph-source |
has_deph_source |
true |
false |
--pattern <GLOB> |
pattern |
*.nc |
*.nc |
TOML config file format
# NRT
= true
= false
= "AR_PR_CT_*.nc" # optional
# CORA
= "TIME"
= "int" # "int" or "char"
= true
= true
= "*.nc" # optional
Output schema
All converters produce a uniform, observation-level flat table:
| Column | Type | Notes |
|---|---|---|
platform_code |
String |
|
profile_no |
u32 |
|
profile_time |
f64 |
days since 1950-01-01 |
profile_timestamp |
Datetime(ms) |
Unix milliseconds |
observation_no |
u32 |
|
longitude / latitude |
f32 (NRT) / f64 (CORA) |
|
profile_longitude / profile_latitude |
f32 (NRT) / f64 (CORA) |
from PRECISE_* or expanded DEPLOY_*; NaN when unavailable |
time_qc / position_qc |
String |
"" if absent |
filename |
String |
source file stem |
temp, psal, pres, deph |
f32 |
NaN where missing |
temp_qc, psal_qc, pres_qc, deph_qc |
String |
single-char flag; "" if missing |
pres_conv, deph_conv |
i8 |
1 = derived by conversion |
Development
# Run all tests
# Run a single test
# Download test fixture files (requires gh CLI, authenticated)
Note: HDF5-DIAG messages may appear in test output on systems with HDF5 ≤ 1.10. They are harmless: the data is read correctly and all tests pass.
Releases are versioned, changelogged, and published following RELEASING.md.
License
Licensed under the MIT License.