Skip to content

Tasks

A Task describes one modeling problem. It holds ordered input Records and one representation of the expected output.

from timenet.types import ClassificationTask, Label, LabelSet

CONDITION = LabelSet(name="condition", labels=(Label(name="healthy"), Label(name="faulty")))

task = ClassificationTask(
    inputs=(record,),
    targets=("faulty",),
    label_set=CONDITION,
)
dataset.add_task(task=task)

The concrete class identifies the task type. ClassificationTask and AnswerTask are different task types, even when both contain string targets.

Common fields

Every Task supports these fields:

Field Purpose
id Read-only int64 Task identity. add_task() sets it.
inputs Ordered input Records.
targets Ordered inline values or stored objects.
prompt Optional instruction or question.
input_modalities Optional override for the input kinds. None enables inference.
split Optional partition: train, validation, or test. None when the dataset does not partition its Tasks.
scope Optional input region.
input_annotations Attached Annotation occurrences supplied as context.
target_annotations Attached Annotation occurrences used as the answer.
rationale Optional explanation associated with the answer.
from_tasks Parent Tasks from which this Task derives.
annotations Annotations that describe the Task itself.
metadata JSON-compatible Task details.

targets can contain strings, numbers, Booleans, Records, Signals, or Spans. None means that target_annotations supplies the answer. An empty tuple is an explicit empty answer.

A Task cannot set both targets and target_annotations.

Input modalities

None by default, in this case the value is inferred. Inference uses each input Signal's spec.modality, restricted to Signals named in scope, if any. It also adds TEXT for a non-empty prompt or text input annotations. It ignores targets, target annotations, rationale, and metadata. It does not load Signal values.

To override inference, pass a non-empty set:

from timenet.types import AnswerTask, InputModality

task = AnswerTask(
    inputs=(record,),
    prompt="Describe this image.",
    input_modalities=frozenset({InputModality.IMAGE, InputModality.TEXT}),
)

The override remains subject to validation. For example, a Task with a prompt must declare TEXT. The writer stores the resolved set in DuckDB, so SQL filters need no inference or Signal reads. Read-back Tasks carry that stored set as an explicit declaration.

Splits

A dataset that ships a partition marks each Task with its split. Records carry no split of their own: one Record may serve Tasks of several splits.

from timenet.types import ClassificationTask, Label, LabelSet, Split

ACTIVITY = LabelSet(
    name="activity",
    labels=(Label(name="walking"), Label(name="sitting")),
)

dataset.add_task(
    task=ClassificationTask(
        inputs=(record,),
        targets=("walking",),
        label_set=ACTIVITY,
        split=Split.TRAIN,
    )
)

TimeFDataset.get_train(), get_validation(), and get_test() return the Tasks of one split, and get_all() returns every Task. Asking a split of a dataset that assigns none raises, since that is a mistake rather than an empty selection. iter_tasks(split=...) on a dataset or a TimeFReader filters the same way; the reader applies the filter in DuckDB, so a Task outside the split is never built. to_features_and_targets(split=...) builds the (X, y) pair of one split.

To train on train and validation together while keeping test separate, select their union:

training_tasks = dataset.get_splits(Split.TRAIN, Split.VALIDATION)
test_tasks = dataset.get_test()

# Or consume the selection lazily from a TimeFDataset:
for task in dataset.iter_tasks(split=(Split.TRAIN, Split.VALIDATION)):
    process(task)

Selection preserves dataset order and each Task's original split. Repeated split names do not duplicate Tasks, and Tasks with split=None are excluded. get_splits() materializes its result in one pass and raises if the dataset has no split assignments. The dataset iterator remains lazy and simply yields no matches in that case. Both selectors require at least one valid split; pass split=None, omit the argument, or use get_all() to include every Task. The dataset iterator's split argument accepts an enum, a string, or an iterable of either. Modality filters can be combined with split selection.

Register Tasks

Register all referenced objects before you register the Task:

dataset.add_record(record=record)
dataset.add_task(task=task)

add_task() validates Record, Signal, Annotation, Span, and parent-Task references. It does not change the input Records. Use dataset.tasks_for(record) to find the Tasks that take a Record as input.

Use add_tasks() for an atomic batch. A parent in from_tasks can appear in the same batch.

For a large task corpus, use set_task_stream(). The writer validates each streamed Task before it writes the Task. A stream does not keep the complete task set in memory. TimeNet therefore cannot validate from_tasks relationships across streamed Tasks.

Records that a Task references

A Task references these Records:

  • Its inputs.
  • Its candidate Records.
  • Its Record targets.
  • Each Record that holds a Signal that the Task references, for example a Signal target.

Only the inputs are Records that the Task takes as input. tasks_for(record) returns the Task only for its inputs, and read_tasks(inputs=) selects the Task only through its inputs.

Scope an input

scope limits the region that the model gets. A scope holds the Signal objects that it covers:

from timenet.types import ClassificationTask, TimeInterval

task = ClassificationTask(
    inputs=(record,),
    scope=TimeInterval.seconds(
        5.0,
        8.0,
        time_series=(vibration,),
    ),
    targets=("faulty",),
    label_set=CONDITION,
)

Use TimePoint or TimeInterval for a timeline Signal. Use StepPoint or StepInterval for an ordinal Signal.

Built-in task types

TimeNet defines eight task classes:

Class Modeling operation Typical target items
ClassificationTask Select one or more labels. Strings.
AnswerTask Answer a question or caption the inputs. Strings.
ScalarPredictionTask Predict typed quantities. Integers or floats.
TemporalLocalizationTask Locate events or regions. Points or intervals.
ForecastingTask Predict future values. Records, Signals, or intervals.
TSEditingTask Transform input time-series data. Records or Signals.
TSGenerationTask Generate time-series data from a prompt. Records or Signals.
TSCorrespondenceTask Match inputs to candidates. Records or Signals.

The target collection stays uniform across these classes. Each subclass adds only the configuration that its operation needs.

The set is closed. TimeF stores each Task's type as a tag, and the reader maps that tag back to one of these classes. Each class is therefore final, and Task refuses subclasses defined outside TimeNet. To reuse a prompt or other settings, write a builder function that returns a built-in Task:

from timenet.dataset import Record
from timenet.types import AnswerTask


def age_question(record: Record, age: int) -> AnswerTask:
    return AnswerTask(
        inputs=(record,),
        prompt="How old is the subject?",
        targets=(str(age),),
    )


dataset.add_task(task=age_question(record, 64))

Classification

from timenet.types import Label, LabelSet

CONDITION = LabelSet(
    name="condition",
    labels=(Label(name="healthy"), Label(name="faulty", description="A bearing fault.")),
)
FINDINGS = LabelSet(
    name="findings",
    labels=(Label(name="AF"), Label(name="PVC"), Label(name="LBBB")),
    multi_label=True,
)

ClassificationTask(inputs=(record,), targets=("healthy",), label_set=CONDITION)
ClassificationTask(inputs=(record,), targets=("PVC", "AF"), label_set=FINDINGS)

A LabelSet defines the allowed labels for a classification target. The multi_label flag controls how many labels a target can contain.

If multi_label=False, the default, a target must contain exactly one label. If multi_label=True, a target contains all labels that apply. Omitted labels do not apply. An empty target means that no label applies. The order of labels does not matter.

When the dataset adds or streams a task, it makes sure that the target follows the rules of its label set. Each label must be in the set. No label can repeat. A target with multi_label=False must contain exactly one label.

Labels come from the targets stored directly on the task or from the text of its target annotations.

Tasks can share the same label set object. Each name identifies one label set within a dataset. If two different sets have the same name, add_task(), the task stream, and the writer raise an error. TimeF stores each set once. The reader gives tasks that use the same set a shared object.

Answer and rationale

AnswerTask(
    inputs=(record,),
    prompt="Does this trace show a bearing fault?",
    rationale="The repeated impacts match a bearing fault.",
    targets=("Yes",),
)

A missing prompt makes the Task an unprompted caption or description.

Scalar prediction

ScalarPredictionTask(
    inputs=(record,),
    targets=(62.0,),
    unit="bpm",
    target_name="mean_heart_rate",
)

The class keeps the quantity name and physical unit separate from the numeric target.

Temporal localization

from timenet.types import (
    LocalizationMode,
    TemporalLocalizationTask,
    TimePoint,
)

TemporalLocalizationTask(
    inputs=(record,),
    prompt="Locate all R-peaks in lead II.",
    mode=LocalizationMode.SPARSE,
    targets=(
        TimePoint.seconds(1.20, time_series=(lead_ii,)),
        TimePoint.seconds(2.05, time_series=(lead_ii,)),
    ),
)

SPARSE says that unmarked time is unspecified. EXHAUSTIVE says that the targets describe the complete region of interest.

Forecasting, editing, and generation

These Tasks can target stored Records or Signals directly:

ForecastingTask(
    inputs=(history,),
    targets=(future,),
)

TSEditingTask(
    inputs=(raw_record,),
    prompt="Remove the baseline wander.",
    targets=(clean_record,),
)

TSGenerationTask(
    prompt="Generate 10 seconds of a 150 bpm ECG.",
    targets=(synthetic_record,),
)

A generation Task can have no input Records.

Correspondence

TSCorrespondenceTask(
    inputs=(query,),
    candidate_records=(record_a, record_b, record_c),
    targets=(record_b,),
)

An empty candidate_records tuple means that the candidate pool is dataset-wide.

Compose Tasks

from_tasks records derivation between Tasks:

label = ClassificationTask(
    inputs=(record,),
    targets=("faulty",),
    label_set=CONDITION,
)
answer = AnswerTask(
    inputs=(record,),
    prompt="Is this machine healthy?",
    targets=("No",),
    from_tasks=(label,),
)
dataset.add_tasks(tasks=(label, answer))

TimeNet rejects missing parents, self-references, and derivation cycles.