Exporting data: the .h5dcnvs format
When you export a dataset, referred to as an observation, from DataCNVS, you get a file you can actually open anywhere, not a proprietary blob that only makes sense inside our app. This page covers what's in the file, how to open it, and worked examples in Python and MATLAB.
On this page
What you get #
Exporting produces a single file with the extension .h5dcnvs. Underneath, it's a completely standard HDF5 file; any HDF5-aware tool can open it, even without knowing anything about DataCNVS. What makes it a .h5dcnvs file is a specific, self-describing tree layout inside: every value is labelled with its role, its physical units, and the variable and quantity kind it represents, so the file explains itself whether you open it in a generic HDF5 browser, or by writing five lines of Python.
An export contains one observation, with:
- its scalar (single-value) fields, e.g. a furnace setpoint or sample ID,
- its series (array-valued) fields, e.g. a series of temperatures or stress measurements,
- the sample it belongs to, if any,
- and a catalog of the variables and quantity kinds those fields reference.
Only DataCNVS-canonical data is exported: the same processed, unit-resolved values you see in the app's canvases. Raw, as-uploaded data is never included.
Exporting from the app #
Open an observation's page and use the Download button in the top right corner. Export runs as a background job, so the page shows a progress state while the file is being built; once it finishes, the download will start automatically. HDF5 is currently the only export format offered, and export is per-observation: there's one file per export, for the observation you're viewing.
Opening the file #
Because .h5dcnvs is plain HDF5, you have a wide choice of tools:
| Tool | Good for | Notes |
|---|---|---|
| HDFView | Browsing the tree, attributes, and values with no code at all | Free GUI from The HDF Group; shows every attribute and dataset exactly as described below |
h5dump / h5ls | Quick command-line inspection | Ships with the standard HDF5 command-line tools; h5dump -A file.h5dcnvs prints every attribute in the tree |
| Python (h5py, xarray, pandas) | Scripted analysis, notebooks | See below |
| MATLAB (built-in h5info/h5read/h5readatt) | Scripted analysis, plotting | No toolbox required; see below |
| R (rhdf5) | Scripted analysis | Bioconductor package; same attribute/dataset model applies |
If whatever else you use has a generic "open HDF5/NetCDF file" option and lets you browse groups and attributes, it will work; DataCNVS doesn't require a DataCNVS-specific plugin anywhere. Point it at any values/value dataset and read the units/long_name attributes alongside it.
File structure #
The core rule #
Attributes hold every piece of description; datasets hold only actual values.
Anything that's metadata about a measurement, including its units, its label, which variable it is, and which observation it belongs to, is an HDF5 attribute. The only two kinds of HDF5 dataset in the whole file are a series' values array and a scalar's single value. Once you know this, the tree is predictable to browse: attributes tell you what something is, datasets give you the numbers.
Layout at a glance #
/ (root group, file-level attributes, see below)
├─ observations_summary (a JSON mirror of the whole tree, for convenience)
├─ catalog/
│ ├─ quantity_kinds/
│ │ └─ temperature/ (one group per quantity kind, named by its key)
│ └─ variables/
│ └─ temperature/ (one group per variable, named by its key)
└─ observations/
└─ obs_000001/
├─ sample/ (present only if the observation has a sample)
├─ scalars/
│ └─ contextual_furnace_setpoint/
│ └─ value (Dataset)
├─ series/
│ ├─ index_time/
│ │ └─ values (Dataset)
│ └─ dependent_temperature/
│ └─ values (Dataset)
└─ series_groups/ (present only when series share a common index)
└─ group_000001/Group and field names are stable, deterministic, and human-readable. The same observation exported twice produces the same tree, and a field's HDF5 path (e.g. observations/obs_000001/series/dependent_temperature) doubles as its plain-English location.
Root attributes #
A handful of attributes on the file's root group describe the export as a whole:
| Attribute | Meaning |
|---|---|
format | Always "datacnvs.interchange.hdf5", which identifies this as a DataCNVS export, not just any HDF5 file |
format_version | Container format version (integer) |
generated_at | When the export was generated, ISO-8601 UTC |
source_tenant_slug | The workspace the data was exported from |
title, readme | A human-readable title and a short paragraph explaining the tree; read this first if you're exploring a file for the first time |
Catalog: variables and quantity kinds #
catalog/quantity_kinds/<key>/ and catalog/variables/<key>/ hold the shared metadata that scalar and series fields refer back to, so it's only written once even if several fields use the same variable. A quantity kind group looks like:
catalog/quantity_kinds/temperature/
├─ @key = "temperature"
├─ @label = "Temperature"
├─ @canonical_units = "K"
├─ @units = "K" (duplicate, CF-style, see below)
├─ @symbol = "T"
└─ @common_units = ["degC", "degF", "K"]A variable group is similar, and additionally carries @quantity_kind_key, which is the string you look up in catalog/quantity_kinds/ to resolve its units and definition.
The observation #
The observations/obs_000001/ group is the observation you exported, with obs_type, title, technique, notes, ingestion_status, and created_at as attributes, plus a sample/ sub-group if a sample is attached.
Inside, every scalar and series field gets its own group under scalars/ or series/, named by a readable slug (e.g. contextual_furnace_setpoint, dependent_temperature, index_time). Every field group carries the same attribute vocabulary, in two overlapping styles, side by side:
| Attribute | Style | Meaning |
|---|---|---|
units | CF convention | Physical units, resolved from the quantity kind (or units_override if the field overrides them) |
long_name | CF convention | Human label: the field's alias if it has one, otherwise the variable's name |
standard_name | CF convention slot | Repurposed to hold the DataCNVS quantity-kind key; it is not validated against the real CF vocabulary |
role | DataCNVS | Why this field exists on the observation, e.g. dependent, contextual, index |
dtype | DataCNVS | The value's DataCNVS data type; see the type reference below |
variable_key, quantity_kind_key | DataCNVS | Lookup keys into catalog/variables/ and catalog/quantity_kinds/ |
alias, units_override | DataCNVS | Present only when the field overrides the variable's default label/units |
The CF-style names (units, long_name, standard_name) exist because they're widely recognized by generic scientific HDF5/NetCDF tools, so you get readable units and labels without any DataCNVS-specific plugin. The DataCNVS-style names are there for unambiguous, code-friendly lookups. Both describe the same field; use whichever fits your tool.
Series fields additionally carry series_kind ("series" or "index"), length, and, if the series shares a common index with others (e.g. a time axis shared by several dependent curves), series_group_ref, pointing at a series_groups/ entry (see next section).
Series groups #
When two or more series in the observation share a common index (most often: one time or position axis with several dependent measurements against it), they're linked via observations/obs_000001/series_groups/group_NNNNNN/. This group doesn't duplicate any data; it just lists the member paths and (as a browsing convenience only) provides HDF5 soft links into the real values datasets, so a tree browser visually shows the relationship. Code should always resolve the relationship from the series_group_ref attribute, not from link traversal.
A series with no series_group_ref attribute simply has no shared index, so treat its values as row-position ordered (point 0, point 1, point 2, …).
observations_summary #
The root-level observations_summary dataset holds the same information as the whole tree, restated as one JSON document containing the observation, its fields, and the catalog entries with their HDF5 paths. It's a convenience for scripts that would rather parse one JSON blob than walk the group tree, and it's guaranteed to be a strict mirror of what the attributes already say (never a source of extra information).
Data type reference #
Every field's dtype attribute tells you how its value/values dataset is physically stored:
| DataCNVS dtype | Stored as | Notes |
|---|---|---|
string | UTF-8 text | |
boolean | boolean | |
integer | 64-bit integer | |
number / numeric(p,s) | 64-bit float | |
date | ISO-8601 date text, e.g. "2026-06-01" | |
datetime / timestamp | 64-bit integer milliseconds since the Unix epoch (UTC) | Also carries a time_units = "epoch_ms_utc" attribute as a reminder |
json, list, struct, map, dictionary | UTF-8 text holding the JSON-encoded value | Also carries encoding = "json"; parse the string as JSON to get the structured value back |
Large numeric series are gzip-compressed and chunked; small values and text are stored plainly. This is transparent to every reader below: h5py, xarray, and MATLAB all decompress automatically.
Working with exports in Python #
The most direct route is h5py; it needs no DataCNVS-specific code, just the attribute names documented above.
import datetime as dt
import json
import h5py
with h5py.File("furnace_run_14.h5dcnvs", "r") as f:
print(f.attrs["title"])
print(f.attrs["readme"])
obs = f["observations/obs_000001"]
print(obs.attrs["title"], "-", obs.attrs["technique"])
# A scalar value, with its units.
setpoint = obs["scalars/contextual_furnace_setpoint"]
print(setpoint["value"][()], setpoint.attrs["units"])
# A series and its units/label.
temperature = obs["series/dependent_temperature"]
values = temperature["values"][:]
print(f"{len(values)} points, units={temperature.attrs['units']}")
# A datetime index series decodes from epoch milliseconds.
index_time = obs["series/index_time"]
epoch_ms = index_time["values"][:]
timestamps = [
dt.datetime(1970, 1, 1, tzinfo=dt.timezone.utc) + dt.timedelta(milliseconds=int(t))
for t in epoch_ms
]
# Look up a field's quantity kind via the shared catalog.
qk_key = temperature.attrs["quantity_kind_key"]
qk = f[f"catalog/quantity_kinds/{qk_key}"]
print(qk.attrs["label"], qk.attrs["canonical_units"])
# A json/list/struct/map field decodes with json.loads.
# struct_field = obs["scalars/contextual_some_json_field"]
# value = json.loads(struct_field["value"][()])
# The whole tree, restated as JSON, if that's easier to work with.
summary = json.loads(f["observations_summary"][()])If you'd rather work with labelled arrays than raw HDF5 paths, build an xarray.Dataset from the pieces above (the file doesn't attach dimension scales itself, so you assemble the labels once, on read):
import h5py
import xarray as xr
with h5py.File("furnace_run_14.h5dcnvs", "r") as f:
obs = f["observations/obs_000001"]
time = obs["series/index_time/values"][:]
temp_grp = obs["series/dependent_temperature"]
temp = temp_grp["values"][:]
units = temp_grp.attrs["units"]
ds = xr.Dataset(
{"temperature": ("time", temp, {"units": units})},
coords={"time": ("time", time, {"units": "epoch_ms_utc"})},
)
ds.temperature.plot()For tabular work, a series' values array converts straight into a pandas.Series or DataFrame column: pandas.Series(temperature["values"][:]).
Working with exports in MATLAB #
MATLAB's built-in HDF5 functions (h5info, h5disp, h5read, h5readatt) need no toolbox and work directly against .h5dcnvs files.
file = "furnace_run_14.h5dcnvs";
% Browse the whole tree, including every attribute.
h5disp(file);
% Read the observation's title.
obsTitle = h5readatt(file, "/observations/obs_000001", "title");
% Read a series and its units.
temperature = h5read(file, "/observations/obs_000001/series/dependent_temperature/values");
units = h5readatt(file, "/observations/obs_000001/series/dependent_temperature", "units");
% The paired time index decodes from epoch milliseconds (UTC).
epochMs = h5read(file, "/observations/obs_000001/series/index_time/values");
t = datetime(epochMs, "ConvertFrom", "epochtime", ...
"TicksPerSecond", 1000, "Epoch", "1970-01-01", "TimeZone", "UTC");
% A scalar value, with its units.
setpoint = h5read(file, "/observations/obs_000001/scalars/contextual_furnace_setpoint/value");
setpointUnits = h5readatt(file, "/observations/obs_000001/scalars/contextual_furnace_setpoint", "units");
plot(t, temperature);
xlabel("Time");
ylabel(sprintf("Temperature (%s)", units));
title(sprintf("%s — setpoint %.1f %s", obsTitle, setpoint, setpointUnits));Notes for MATLAB users:
- Variable-length text fields (labels, JSON-encoded values, ISO date strings) come back from
h5read/h5readattas a MATLAB string or a cell array of character vectors, depending on whether the underlying dataset/attribute is scalar or array-valued; wrap single values instring(...)if you need consistent typing across fields. - A
json/list/struct/map/dictionaryvalue reads back as JSON text; decode it withjsondecode(...). h5info(file, "/observations/obs_000001")gives you the same attribute/dataset listingh5dispprints, but as a struct you can walk programmatically instead of reading it off the console.
Limits & retention #
- Export is per observation: one file, one observation.
- Only canonical (processed) data is exported, never raw, as-uploaded data.
- Download links expire 7 days after the export completes; the underlying observation data is untouched, so you can simply export again.
Want a different format? #
HDF5 is the only export format today. If your workflow needs something else, a flat CSV, a different interchange format, or a specific tool's native format, let us know.
Talk to the DataCNVS team