Events document¶
An analysis container produces one output: a JSON document listing the events
it found, written to the path the manifest names in outputs.events.path.
This page specifies that document in prose and by schema so that it can be
produced from any language. save_event_blocks in this package is one encoder
for it; the schema is the definition.
The contract is the Pydantic model
EventBlocksDocument;
EventBlocksDocument.model_json_schema() publishes the JSON Schema for a
container written in another language.
Shape¶
{
"format_version": 1,
"blocks": [
{
"origin_ms": 0,
"period_ms": 30000,
"labels": ["Sleep stage W", "Sleep stage W", "Sleep stage N1", "Sleep stage N2"],
"probability": null,
"value": null,
"channels": ["Fpz-Cz"]
},
{
"origin_ms": 4210000,
"period_ms": 0,
"labels": ["EEG arousal"],
"probability": [0.91],
"value": null,
"channels": []
}
],
"device": {"software": "my-analysis", "version": "1.2.0"}
}
| Field | Type | Meaning |
|---|---|---|
format_version |
1 |
The version of this format. It changes only when a field changes meaning. |
blocks |
list of blocks | The events, grouped as described below. An analysis that found nothing writes an empty list, which is a result; a document that is missing is a failure. |
device |
object, optional | Provenance the analysis chooses to record: software and model identity, versions, a hash of its input, anything descriptive. Stored with the result, never interpreted. |
A block is a run of consecutive events of equal duration that share a set of channels:
| Field | Type | Meaning |
|---|---|---|
origin_ms |
integer ≥ 0 | Start of the first slot, in milliseconds from the start of the recording |
period_ms |
integer ≥ 0 | Duration of every slot in the block. 0 marks instantaneous events, and every slot then sits at origin_ms |
labels |
list of strings, at least one | One label per slot, from the vocabulary. Labels may differ from slot to slot |
probability |
list or null |
One entry per slot, each a number in [0, 1] or null; null for the whole block when no slot carries one. Only for an output the configuration declares probabilistic |
value |
list or null |
One entry per slot, each a number or null. Only for an output the configuration declares valued |
channels |
list of strings | The channels every event in the block applies to, by the manifest's name. Empty means the whole recording |
Slot i spans [origin_ms + i × period_ms, origin_ms + (i + 1) × period_ms).
Every slot is one event. A block is a compact encoding of a list of events
and has no meaning of its own. A night of 30-second epochs is one block of
about a thousand slots, and a single arousal is a block of one slot.
What a document must satisfy¶
- Times are integers in milliseconds from the start of the recording, never negative.
- Labels come from the vocabulary, and only from the labels the
configuration declares under
outputs.events. That declaration is what tells the viewer and the reports what this analysis produces. - Channels are named as the user knows them: the manifest's
name, never itssource_name, which is the header label. probabilityandvalueare written only for outputs the configuration declaresprobabilisticorvalued, and each array has exactly as many entries aslabels.- Order carries no information on the way in. The platform decodes every block to its events and sorts them by their own fields, so blocks may be written in whatever order is natural.
- Encoding is UTF-8 JSON. The platform names the output path with a
.json.gzsuffix and expects the file gzip-compressed there. It detects compression by content rather than by suffix, so an uncompressed file is also read. Write the file compressed regardless. - Exit status is
0after the document is written and non-zero on any failure. An analysis that exits0without writing has failed without signalling it.
Encoding events as blocks¶
The simplest correct encoding is one block per event: origin_ms is its
start, period_ms its duration and labels a list of one. That encoding is
complete but larger than necessary. Events on a regular grid, such as a
hypnogram or any per-epoch output, should be written as one block per
contiguous run with the same duration and channel set, with one label per
slot.
Full probabilistic output, where every prediction carries a probability for
every class, is one block per class on the same grid: the block's labels
repeat that class and its probability array carries the per-slot values. The
configuration declares the ability with supports_full_probabilistic_output,
and a user asks for it per run through a custom parameter the configuration
declares: by convention a bool keyed store-all-probabilities, default
false. The platform passes it like any other parameter: the container reads
manifest.parameters["store-all-probabilities"] and, when it is true, writes
a block per class.
The platform's own encoder, events_to_blocks, sorts events by their fields
and merges the ones that continue a block. A container does not need to
reproduce it, because decoding and re-encoding preserves the events either
way.
Vocabulary¶
Labels are the EDF+ annotation vocabulary as enumerated by
EventLabel. Sleep
stages are Sleep stage W, Sleep stage N1, Sleep stage N2, Sleep stage
N3, Sleep stage R, and Sleep stage ? for an epoch that could not be
scored; the respiratory, arousal and movement families follow the same
convention. A label outside the vocabulary fails validation at load: in the
container when the helper is used, on the platform otherwise.
Reading one: a downstream analysis¶
An analysis whose configuration declares an events input receives an upstream
document at inputs.events.path, in this format. load_events_output reads
it, and the flat event lists below, into an EventBlocksDocument;
blocks_to_events expands that to a list of Event.
Flat event lists¶
The platform also reads two flat shapes: a JSON object with a top-level
events list, and a bare JSON list of events, each
{"start_time_ms", "end_time_ms", "label", "probability", "value", "channels"}.
Both are encoded to blocks on upload. New containers should write the block
document.
Producing one in Python¶
from yousleep_common.models.events import Event
from yousleep_common.utils.event_blocks import save_event_blocks
events = [
Event(start_time_ms=0, end_time_ms=30000, label="Sleep stage W", channels=["Fpz-Cz"]),
Event(start_time_ms=30000, end_time_ms=60000, label="Sleep stage N1", channels=["Fpz-Cz"]),
]
save_event_blocks(
manifest.outputs.events.path,
events,
device={"software": "my-analysis", "version": "1.2.0"}, # optional
)
Building Event objects validates labels and times in the container rather
than at upload. save_event_blocks encodes them to blocks and compresses the
file when the path ends in .gz. device, when given, is written as the
document's device block.