GuidesExporting data

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:

ToolGood forNotes
HDFViewBrowsing the tree, attributes, and values with no code at allFree GUI from The HDF Group; shows every attribute and dataset exactly as described below
h5dump / h5lsQuick command-line inspectionShips with the standard HDF5 command-line tools; h5dump -A file.h5dcnvs prints every attribute in the tree
Python (h5py, xarray, pandas)Scripted analysis, notebooksSee below
MATLAB (built-in h5info/h5read/h5readatt)Scripted analysis, plottingNo toolbox required; see below
R (rhdf5)Scripted analysisBioconductor 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:

AttributeMeaning
formatAlways "datacnvs.interchange.hdf5", which identifies this as a DataCNVS export, not just any HDF5 file
format_versionContainer format version (integer)
generated_atWhen the export was generated, ISO-8601 UTC
source_tenant_slugThe workspace the data was exported from
title, readmeA 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:

AttributeStyleMeaning
unitsCF conventionPhysical units, resolved from the quantity kind (or units_override if the field overrides them)
long_nameCF conventionHuman label: the field's alias if it has one, otherwise the variable's name
standard_nameCF convention slotRepurposed to hold the DataCNVS quantity-kind key; it is not validated against the real CF vocabulary
roleDataCNVSWhy this field exists on the observation, e.g. dependent, contextual, index
dtypeDataCNVSThe value's DataCNVS data type; see the type reference below
variable_key, quantity_kind_keyDataCNVSLookup keys into catalog/variables/ and catalog/quantity_kinds/
alias, units_overrideDataCNVSPresent 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 dtypeStored asNotes
stringUTF-8 text
booleanboolean
integer64-bit integer
number / numeric(p,s)64-bit float
dateISO-8601 date text, e.g. "2026-06-01"
datetime / timestamp64-bit integer milliseconds since the Unix epoch (UTC)Also carries a time_units = "epoch_ms_utc" attribute as a reminder
json, list, struct, map, dictionaryUTF-8 text holding the JSON-encoded valueAlso 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.

python
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):

python
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.

matlab
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/h5readatt as 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 in string(...) if you need consistent typing across fields.
  • A json/list/struct/map/dictionary value reads back as JSON text; decode it with jsondecode(...).
  • h5info(file, "/observations/obs_000001") gives you the same attribute/dataset listing h5disp prints, 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