Skip to content

Types

The TimeF value types live in timenet.types (one module per concept, re-exported from the package). Every schema-carrying type is a plain frozen dataclass so it pickles and round-trips through TimeFReader without runtime class synthesis. Errors live in timenet.errors.


Version

A semantic major.minor.patch version. Frozen and ordered, so versions compare with the usual precedence (Version(1, 2, 0) > Version(1, 1, 9)). Components must be non-negative integers.

from timenet.types import Version

Version(1, 0, 0)
Version.parse("1.0.0")   # equivalent
str(Version(1, 2, 3))    # "1.2.3"

Units

TimeNet uses pint for all physical units. One process-wide registry, ureg, owns every definition and conversion, plus two custom units (beat, bpm) pint does not ship. Reference units through ureg (ureg.hertz, ureg.millivolt, ureg.standard_gravity, ureg.dimensionless), never a second registry, or comparisons and conversions fail.

from timenet.types import ureg

(5.0 * ureg.millivolt).to(ureg.volt).magnitude   # 0.005

DataSource

The origin that produced a modality: a device, an API feed, a model, an institution. A flat frozen dataclass built directly.

from timenet.types import DataSource

DataSource(data_source_type="holter_x", name="Holter Monitor X", provider="Acme")
Field Type Required Description
data_source_type str yes Dataset-unique type tag, referenced by TimeSeriesSpec.data_source.
name str yes Human-readable name.
provider str \| None no Vendor / originator.

TimeSeriesSpec

The contract for a measurement modality: its type tag, display name, and the units of its three axes. One spec is shared across every channel of a modality; the per-channel identifier lives on TimeSeries.channel, not here.

from timenet.types import TimeSeriesSpec, ureg

ecg_lead = TimeSeriesSpec(
    spec_type="ecg_lead",
    name="ECG Lead",
    unit_sampling_rate=ureg.hertz,
    unit_timestamp=ureg.second,
    unit_value=ureg.millivolt,
)
Field Type Required Description
spec_type str yes Dataset-unique modality tag (e.g. "ecg_lead").
name str yes Human-readable modality label.
unit_sampling_rate pint.Unit yes Must be a frequency, else ValueError.
unit_timestamp pint.Unit yes Must be a time, else ValueError.
unit_value pint.Unit yes Any unit (mV, g, bpm, dimensionless, ...).
data_source DataSource \| None no The source that produced this modality.

Connectors that reuse a modality can subclass with field defaults:

from dataclasses import dataclass

@dataclass(frozen=True)
class ECGLead(TimeSeriesSpec):
    spec_type: str = "ecg_lead"
    name: str = "ECG Lead"
    unit_sampling_rate: object = ureg.hertz
    unit_timestamp: object = ureg.second
    unit_value: object = ureg.millivolt

Annotations

An annotation is extra context attached to a Sample: side information a task can read as input, or that can itself become a task's question or answer. It is scoped at one of three levels, and the scopes combine:

  • sample: the whole sample (a static fact, or a trial-level temporal marker),
  • time range: a span in the recording timeline (start_time_send_time_s),
  • signal: one or more specific channels (time_series_ids).

Three shapes, one flat frozen dataclass each. key / value / unit / description / id are instance fields, so connectors author annotations directly (or subclass with field defaults for reuse) and they round-trip without runtime class synthesis.

Class Extra fields Scope
StaticAnnotation value (required) Whole sample, time-independent (age, sex, device, ticker).
PointAnnotation start_time_s, time_series_ids One instant, on specific signals or the whole sample.
IntervalAnnotation start_time_s, end_time_s, time_series_ids A bounded span (end > start), on specific signals or the whole sample.

Shared fields: key: str, value: Any = None, unit: str | pint.Unit | None = None, description: str | None = None, id: str (auto uuid7). unit takes either a unit string ("years") or a pint.Unit (ureg.millivolt, stored as its canonical name); both are validated against the shared registry on construction, and an unrecognized unit string raises ValueError. On the temporal shapes, time_series_ids=None means trial-level (the whole sample); a non-empty tuple restricts the annotation to those channels (each id must match a TimeSeries.time_series_id on the sample).

from timenet.types import StaticAnnotation, PointAnnotation, IntervalAnnotation

# sample scope
StaticAnnotation(key="age", value=64, unit="years")

# time range on the whole sample (trial-level)
IntervalAnnotation(key="artifact", start_time_s=10.0, end_time_s=12.0)

# signal + time range: leads V1 and V2, seconds 5 to 6
IntervalAnnotation(
    key="st_elevation",
    value="ST elevation",
    start_time_s=5.0,
    end_time_s=6.0,
    time_series_ids=("lead_v1", "lead_v2"),
)

# one instant on a single channel
PointAnnotation(key="r_peak", start_time_s=4.2, time_series_ids=("lead_v1",))

A connector that emits the same key repeatedly can subclass with field defaults:

from dataclasses import dataclass

@dataclass(frozen=True, kw_only=True)
class Age(StaticAnnotation):
    key: str = "age"
    unit: str | None = "years"

annotation_type_of(ann) returns the AnnotationType (STATIC / POINT / INTERVAL); ANNOTATION_BASES maps each back to its class. AnnotationDescriptor is the type-level projection (key, annotation_type, value_type, unit, description) hoisted into the schema and manifest at write time.


Tasks

A task is one labeled training target referencing one or more samples. The class is the type tag (usable as a search filter, e.g. search(task=ReasoningTask)); the instance carries the payload. Tasks are mutable so add_task can populate sample_ids after construction.

Class task_type Payload
ClassificationTask classification target, target_schema
LabelingTask labeling target, target_schema, time_series_ids, windows_s
CaptioningTask captioning target
QATask question_and_answer question, target
ForecastingTask forecasting context_sample_ids, target_sample_id
ReasoningTask reasoning question, rationale, target

Every task also carries id (auto uuid7), sample_ids, from_tasks, and a from_task_ids property. TaskType is the enum of type tags; TASKS is derived by walking the task hierarchy, so it can never drift. Unlike specs and annotations, task payloads are fixed in code and resolved on read against TASKS, not reconstructed from the manifest.

The five label-style tasks share a scalar target (a class label, region label, answer, or caption) through a TargetTask base, so generic training code reads task.target regardless of type. Forecasting is the exception: its target is a series, not a scalar, so it carries no target and instead points at the sample holding the ground-truth future values via target_sample_id.

Per-type payloads

  • ClassificationTask: one discrete label for the whole sample; target_schema names the vocabulary the target is drawn from (None for free-form).
    dataset.add_task(sample, ClassificationTask(target="afib", target_schema="AAMI"))
    
  • LabelingTask: a label localized to specific signals and/or time windows: time_series_ids picks the channels (None = all), windows_s the spans (None = full duration).
    dataset.add_task(sample, LabelingTask(
        target="walking",
        time_series_ids=(accel_x.time_series_id, accel_y.time_series_id),
        windows_s=((120.0, 480.0),),
    ))
    
  • CaptioningTask: free-form text describing the sample (no question).
    dataset.add_task(sample, CaptioningTask(target="A 10-second sinus rhythm with one PVC."))
    
  • QATask: a question and its single-label answer.
    dataset.add_task(sample, QATask(question="What happens between 12s and 18s?", target="ST elevation in V2."))
    
  • ForecastingTask: predict a sample's future values from context samples. Its target is that future series, referenced by target_sample_id (not a scalar target).
    dataset.add_task(future, ForecastingTask(context_sample_ids=("rec_001::history",), target_sample_id="rec_001::future"))
    
  • ReasoningTask: a question, the reasoning trace, then the answer. The target is the evaluation target; the rationale (chain of thought) is the training signal and is optional.
    dataset.add_task(sample, ReasoningTask(
        question="Does this ECG show atrial fibrillation?",
        rationale="R-R intervals are irregularly irregular and no P waves precede the QRS complexes.",
        target="Yes.",
    ))
    

Composition (from_tasks)

A task can derive from earlier tasks (or from the annotations that motivated them) via from_tasks. The derived task records the chain it was built from, which is how a handful of base labels multiply into many higher-level training samples:

base = dataset.add_task(sample, ClassificationTask(target="afib"))
dataset.add_task(sample, ReasoningTask(
    question="Is this recording normal?",
    rationale="The rhythm is classified atrial fibrillation, which is abnormal.",
    target="No.",
    from_tasks=(base,),
))

Annotations vs tasks

An annotation is context; a task is a learning target. The same annotation can play either role:

  • As task input, the annotation is fed to the model as grounding. An IntervalAnnotation marking ST elevation in lead V1 over seconds 5 to 6 supplies the detail a QATask or ReasoningTask question builds on.
  • As the task itself, the annotation's content becomes what the model must produce: a question about what happens in lead V1 over that window, answered from the same st_elevation annotation.

Because annotations carry signal and time-range scope, one recording yields many targets: a whole-sample classification, per-lead labelings, windowed QA, and reasoning that composes them via from_tasks.


DatasetMetadata

A dataset's descriptive identity (authored in the card).

Field Type Required Description
dataset_id str yes org/name pair (one slash); matches the card / connector module path.
dataset_version Version yes The upstream source's semantic version.
name str yes Display name.
description str yes One-sentence description.
license License yes SPDX-style license id.
domains tuple[Domain, ...] no Application/clinical domains.
tags tuple[str, ...] no Free-form labels.
source_url str \| None no Canonical source URL.
yaml_schema_version int no The card's field-schema version (default 1).

DatasetSchema

A dataset's type declaration, derived from its data (never hand-authored), then serialized into the manifest. Holds flat descriptors for specs / data sources / annotations and the real built-in Task subclasses.

DatasetSchema(
    time_series_specs: tuple[TimeSeriesSpec, ...] = (),
    data_sources:      tuple[DataSource, ...] = (),
    annotations:       tuple[AnnotationDescriptor, ...] = (),
    tasks:             tuple[type[Task], ...] = (),
)

Enums

  • View: which slice of a source a sample is: FULL, SINGLE_CHANNEL, SUBSET, WINDOW.
  • Domain: HEALTH, CARDIOLOGY, SLEEP, ACTIVITY, ECONOMICS, FINANCE, GENERAL.
  • License: SPDX-style identifiers (MIT, Apache-2.0, CC-BY-4.0, CC0-1.0, ...).

All are StrEnum, so members compare equal to their string values.


Errors

timenet.errors defines the exception hierarchy. TimeNetError is the base; validation and manifest errors also derive from ValueError so existing handlers keep working.

Exception Base(s) Raised when
TimeNetError Exception base for all TimeNet errors
RegistryError TimeNetError a registry can't be loaded/reached/served
DatasetNotFoundError TimeNetError an unknown dataset id/version
TimeFValidationError TimeNetError, ValueError a dataset/array violates a TimeF invariant
TimeFFormatError TimeNetError a corrupt or unsupported on-disk artifact
InvalidManifestError TimeFFormatError, ValueError a malformed manifest.json

See the API reference for timenet.types for the full symbol listing.