Skip to content

visualdynamics.io.native

native

Native .vdyn file format: HDF5.

A single object is one group named for its kind at the root. A whole test is an objects/ container of numbered groups — number for order, name and kind as attributes, so a user's name is free to contain anything h5py would read as structure — plus the test's own name and active geometry as root attributes. Ragged connectivity is stored as a flat array plus offsets.

Functions:

Name Description
save_sine_specification

One subgroup per tone, the file's own shape: breakpoints with

save_sine_levels

One subgroup per tone, each the ordinary data layout — the set

save

Save a visualdynamics object to a .vdyn (HDF5) file.

save_into

Write one object into an open, empty HDF5 file — the whole of

save_test

Save a whole test — every named object — to one .vdyn file.

save_test_into

save_test into an open, empty HDF5 file (see save_into).

load

Load a .vdyn file: the object it contains, or a whole test.

load_from

load from an open HDF5 file (see save_into); path names

Classes

Functions:

save_sine_specification

save_sine_specification(spec, group) -> None

One subgroup per tone, the file's own shape: breakpoints with the per-segment sweep law, bands where they exist.

Source code in src/visualdynamics/io/native.py
def save_sine_specification(spec, group) -> None:
    """One subgroup per tone, the file's own shape: breakpoints with
    the per-segment sweep law, bands where they exist."""
    _write_strings(group, 'response_dof', spec.response_dof)
    group.attrs['ordinate_dim'] = spec.ordinate_dim
    group.attrs['ordinate_unit'] = spec.ordinate_unit or ''
    group.attrs['comment'] = spec.comment
    _write_strings(group, 'tone_order', [tone.name for tone in spec.tones])
    tones = group.create_group('tones')
    for k, tone in enumerate(spec.tones):
        # index-named subgroups: tone names are user text and h5py
        # group names cannot hold a '/', so the order array holds the
        # names and the groups hold the numbers
        sub = tones.create_group(str(k))
        sub.attrs['name'] = tone.name
        sub.attrs['start_time'] = tone.start_time
        sub.create_dataset('frequency', data=tone.frequency)
        sub.create_dataset('amplitude', data=tone.amplitude)
        sub.create_dataset('phase', data=tone.phase)
        sub.create_dataset('segment_type', data=tone.segment_type)
        sub.create_dataset('segment_rate', data=tone.segment_rate)
        for name, values in tone.limits.items():
            sub.create_dataset(name, data=values)

save_sine_levels

save_sine_levels(levels, group) -> None

One subgroup per tone, each the ordinary data layout — the set is grouping, not a new format.

Source code in src/visualdynamics/io/native.py
def save_sine_levels(levels, group) -> None:
    """One subgroup per tone, each the ordinary data layout — the set
    is grouping, not a new format."""
    _write_strings(group, 'tone_order',
                   [level.tone for level in levels.levels])
    tones = group.create_group('tones')
    for k, level in enumerate(levels.levels):
        save_data(level, tones.create_group(str(k)))

save

save(obj: Any, path: str | PathLike) -> None

Save a visualdynamics object to a .vdyn (HDF5) file.

Source code in src/visualdynamics/io/native.py
def save(obj: Any, path: str | os.PathLike) -> None:
    """Save a visualdynamics object to a .vdyn (HDF5) file."""
    import h5py

    with h5py.File(_visualdynamics_path(path), 'w') as f:
        save_into(obj, f)

save_into

save_into(obj: Any, f: File) -> None

Write one object into an open, empty HDF5 file — the whole of save but the opening, so another container (io.matlab) can run the same writer into memory and carry the tree away.

Source code in src/visualdynamics/io/native.py
def save_into(obj: Any, f: h5py.File) -> None:
    """Write one object into an open, empty HDF5 file — the whole of
    `save` but the opening, so another container (`io.matlab`) can run
    the same writer into memory and carry the tree away."""
    group_name, saver = _saver_for(obj)
    f.attrs['visualdynamics_schema'] = SCHEMA_VERSION
    saver(obj, f.create_group(group_name))

save_test

save_test(path: str | PathLike, name: str, objects: Mapping[str, Any], active_geometry: str | None = None, project_type: str | None = None, links: Sequence[Mapping[str, Any]] | None = None, provenance: Mapping[str, Any] | None = None) -> None

Save a whole test — every named object — to one .vdyn file.

Objects go in numbered groups with the name as an attribute, so a name is free to contain anything h5py would read as structure. links is the explicit association groups, lists of object names.

Source code in src/visualdynamics/io/native.py
def save_test(path: str | os.PathLike, name: str,
              objects: Mapping[str, Any],
              active_geometry: str | None = None,
              project_type: str | None = None,
              links: Sequence[Mapping[str, Any]] | None = None,
              provenance: Mapping[str, Any] | None = None) -> None:
    """Save a whole test — every named object — to one .vdyn file.

    Objects go in numbered groups with the name as an attribute, so a name
    is free to contain anything h5py would read as structure. `links` is
    the explicit association groups, lists of object names.
    """
    import h5py

    with h5py.File(_visualdynamics_path(path), 'w') as f:
        save_test_into(f, name, objects, active_geometry, project_type,
                       links, provenance)

save_test_into

save_test_into(f: File, name: str, objects: Mapping[str, Any], active_geometry: str | None = None, project_type: str | None = None, links: Sequence[Mapping[str, Any]] | None = None, provenance: Mapping[str, Any] | None = None) -> None

save_test into an open, empty HDF5 file (see save_into).

Source code in src/visualdynamics/io/native.py
def save_test_into(f: h5py.File, name: str, objects: Mapping[str, Any],
                   active_geometry: str | None = None,
                   project_type: str | None = None,
                   links: Sequence[Mapping[str, Any]] | None = None,
                   provenance: Mapping[str, Any] | None = None) -> None:
    """`save_test` into an open, empty HDF5 file (see `save_into`)."""
    f.attrs['visualdynamics_schema'] = SCHEMA_VERSION
    f.attrs['test_name'] = name
    f.attrs['active_geometry'] = active_geometry or ''
    f.attrs['project_type'] = project_type or ''
    import json
    f.attrs['links'] = json.dumps(links or [])
    # how each derived object was computed, for the staleness
    # badges — settings fingerprints, so they survive the file
    f.attrs['provenance'] = json.dumps(provenance or {})
    container = f.create_group('objects')
    for i, (obj_name, obj) in enumerate(objects.items()):
        kind, saver = _saver_for(obj)
        group = container.create_group(f'{i:04d}')
        group.attrs['name'] = obj_name
        group.attrs['kind'] = kind
        saver(obj, group)

load

load(path: str | PathLike, progress: Callable[[int, int], None] | None = None) -> Any

Load a .vdyn file: the object it contains, or a whole test.

progress is called as (objects loaded, objects in the file) — once up front with 0 and once per object — because a project file is minutes of someone's day and the reader is the only thing that knows how far along it is. A single-object file reports nothing: one object is one step, and a bar with one step is a light bulb.

Source code in src/visualdynamics/io/native.py
def load(path: str | os.PathLike,
         progress: Callable[[int, int], None] | None = None) -> Any:
    """Load a .vdyn file: the object it contains, or a whole test.

    `progress` is called as (objects loaded, objects in the file) —
    once up front with 0 and once per object — because a project file
    is minutes of someone's day and the reader is the only thing that
    knows how far along it is. A single-object file reports nothing:
    one object is one step, and a bar with one step is a light bulb.
    """
    import h5py

    with h5py.File(path, 'r') as f:
        return load_from(f, progress, str(path))

load_from

load_from(f: File, progress: Callable[[int, int], None] | None = None, path: str = '') -> Any

load from an open HDF5 file (see save_into); path names the file in the errors.

Source code in src/visualdynamics/io/native.py
def load_from(f: h5py.File, progress: Callable[[int, int], None] | None = None,
              path: str = '') -> Any:
    """`load` from an open HDF5 file (see `save_into`); `path` names
    the file in the errors."""
    # A newer stamp means a newer Visual Dynamics wrote fields this
    # reader has no idea exist, and half-loading someone's project
    # quietly is worse than telling them to update. A missing stamp
    # means the file is not ours at all — every writer stamps.
    if 'visualdynamics_schema' not in f.attrs:
        raise ValueError(f'{path} is not a Visual Dynamics file '
                         '(no schema stamp)')
    written = int(f.attrs['visualdynamics_schema'])
    if written > SCHEMA_VERSION:
        raise ValueError(
            f'{path} was written by a newer Visual Dynamics '
            f'(schema {written}; this build reads up to '
            f'{SCHEMA_VERSION}). Update to open it.')
    if 'objects' in f:
        objects = {}
        keys = sorted(f['objects'])
        if progress is not None:
            progress(0, len(keys))
        for done, key in enumerate(keys, start=1):
            group = f['objects'][key]
            objects[group.attrs['name']] = (
                _LOADERS[group.attrs['kind']](group))
            if progress is not None:
                progress(done, len(keys))
        import json
        return Project(f.attrs['test_name'], objects,
                       f.attrs['active_geometry'] or None,
                       f.attrs['project_type'] or None,
                       json.loads(f.attrs['links']),
                       provenance=json.loads(f.attrs['provenance']))
    for group_name, loader in _LOADERS.items():
        if group_name in f:
            return loader(f[group_name])
    raise ValueError(f"No recognized content in {path}")