annie.dataset.events ==================== .. py:module:: annie.dataset.events .. autoapi-nested-parse:: Event-annotation domain: interval events on tracks, and their export. The Event-annotation task lets a reviewer mark **events** — labelled ``[start, end]`` frame intervals — on named **tracks** (a track *is* its category) while watching one video. Unlike the CSV-driven tasks, nothing is loaded from a source file: events are authored from scratch and persisted to the review database (see :class:`annie.dataset.storage.EventRecord`). This module holds the pure pieces around that store: * frame ↔ second conversion, since the database is the source of truth in **frames** (fps-independent) while humans and exports think in seconds; * interval clamping, so a typed or dragged boundary can never leave the video; * an event's **effective** colour and attributes: an event linked to an event category (:class:`annie.dataset.storage.EventType`) inherits the category's colour and default attributes, and its own values win; * the export shapes. **JSON** is nested — one object per video, tracks holding their events with attributes inline — for downstream code. **CSV** and **XLSX** are flat — one row per event, attributes flattened into ``attr_`` columns — for a spreadsheet (XLSX opens in Excel with typed numbers, a frozen header and filters). That split is deliberate: nested attributes do not live cleanly in a spreadsheet cell. Pure domain: no NiceGUI, no torch. The task UI and the store consume it. Functions --------- .. autoapisummary:: annie.dataset.events.frames_to_seconds annie.dataset.events.seconds_to_frame annie.dataset.events.clamp_event annie.dataset.events.effective_color annie.dataset.events.effective_attributes annie.dataset.events.build_export_tree annie.dataset.events.events_to_json_text annie.dataset.events.export_events_json annie.dataset.events.event_table annie.dataset.events.events_to_csv_text annie.dataset.events.export_events_csv annie.dataset.events.events_to_xlsx_bytes annie.dataset.events.export_events_xlsx Module Contents --------------- .. py:function:: frames_to_seconds(frame: int, fps: float) -> float Convert a frame index to seconds at ``fps``. :param frame: The frame index. :param fps: Frames per second (``<= 0`` is treated as ``0`` seconds, since no timebase is known — the caller falls back to frame numbers). :returns: The time in seconds, or ``0.0`` when ``fps`` is non-positive. .. py:function:: seconds_to_frame(sec: float, fps: float) -> int Convert seconds to the nearest frame index at ``fps``. Rounds to the nearest frame; on constant-fps media this is exact, and on a variable-fps file it may be off by one frame (the documented playback limitation). :param sec: The time in seconds. :param fps: Frames per second (``<= 0`` yields frame ``0``). :returns: The nearest non-negative frame index. .. py:function:: clamp_event(start: int, end: int, num_frames: int) -> tuple[int, int] Order and bound an event's frames to a valid interval within the video. Swaps a reversed pair so ``start <= end``, then clamps both to ``[0, last]`` where ``last = num_frames - 1``. A video with no known frame count (``num_frames <= 0``) is clamped only at the lower bound, so an event can still be authored before metadata loads. :param start: The proposed start frame. :param end: The proposed end frame. :param num_frames: The video's total frame count (``<= 0`` if unknown). :returns: The ordered, bounded ``(start, end)`` pair. .. py:function:: effective_color(event: annie.dataset.storage.EventRecord, types: collections.abc.Mapping[str, annie.dataset.storage.EventType] | None = None, lane_color: str | None = None) -> str | None Return the colour an event is drawn and exported with. Its own colour wins, then its event category's, then the participant lane's. :param event: The event. :param types: Event categories by name (``None`` when there are none). :param lane_color: The participant lane's colour, the last fallback. :returns: A hex colour, or ``None`` when nothing sets one. .. py:function:: effective_attributes(event: annie.dataset.storage.EventRecord, types: collections.abc.Mapping[str, annie.dataset.storage.EventType] | None = None) -> dict[str, str] Return an event's attributes with its event category's defaults filled in. :param event: The event. :param types: Event categories by name (``None`` when there are none). :returns: ``{**category defaults, **event's own}``, so the event's own values win. .. py:function:: build_export_tree(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], meta_by_video: collections.abc.Mapping[str, collections.abc.Mapping[str, str]] | None = None, types: collections.abc.Mapping[str, annie.dataset.storage.EventType] | None = None) -> dict[str, dict[str, object]] Group events into the nested ``video_id -> {fps, tracks}`` export structure. Shared by the JSON export and, indirectly, the CSV export's row order. Videos and their tracks appear in first-seen order; events keep the order they arrive in (the store hands them back sorted by track then start frame). :param events: The events to export. :param fps_by_video: Frames per second per ``video_id``, for the derived seconds. A video missing here is exported with ``fps = 0`` and zero seconds (frames still stand). :param meta_by_video: Optional per-video metadata (e.g. the fields a nested layout captured from the path), exported as a ``"meta"`` object when a video has any. :param types: Event categories by name, so linked events export their effective colour and attributes. :returns: A mapping of ``video_id`` to ``{"fps": float, "tracks": {...}}`` (plus ``"meta"``). .. py:function:: events_to_json_text(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], meta_by_video: collections.abc.Mapping[str, collections.abc.Mapping[str, str]] | None = None, types: collections.abc.Mapping[str, annie.dataset.storage.EventType] | None = None) -> str Render events as nested JSON text (one object per video). Suits downstream code: attributes stay nested, seconds are derived per video's fps. A per-video export is this same shape with a single top-level key; a session export has many. :param events: The events to render. :param fps_by_video: Frames per second per ``video_id``. :param meta_by_video: Optional per-video metadata exported as ``"meta"``. :param types: Event categories by name (for effective colour and attributes). :returns: The JSON document as a string. .. py:function:: export_events_json(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], path: str | pathlib.Path) -> pathlib.Path Write events to a nested JSON file (see :func:`events_to_json_text`). :param events: The events to write. :param fps_by_video: Frames per second per ``video_id``. :param path: Destination ``.json`` path (parent directories are created). :returns: The path written. .. py:function:: event_table(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], meta_by_video: collections.abc.Mapping[str, collections.abc.Mapping[str, str]] | None = None, types: collections.abc.Mapping[str, annie.dataset.storage.EventType] | None = None) -> tuple[list[str], list[dict[str, object]]] Flatten events into spreadsheet rows (one per event), shared by CSV and XLSX. Fixed columns come first, then one ``meta_`` column per metadata key seen, then one ``attr_`` column per attribute key seen across *all* events (empty where an event lacks that key), so colleagues can open and sort the file in Excel. Keys are sorted for a stable header. Colour and attributes are the effective ones, so the file is complete without the database. :param events: The events to flatten. :param fps_by_video: Frames per second per ``video_id`` (for the derived seconds). :param meta_by_video: Optional per-video metadata. :param types: Event categories by name (for effective colour and attributes). :returns: The column names and the rows (frames as ints, seconds as floats, the rest text). .. py:function:: events_to_csv_text(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], meta_by_video: collections.abc.Mapping[str, collections.abc.Mapping[str, str]] | None = None, types: collections.abc.Mapping[str, annie.dataset.storage.EventType] | None = None) -> str Render events as flat CSV text (one row per event; columns as :func:`event_table`). :param events: The events to render. :param fps_by_video: Frames per second per ``video_id`` (for the derived seconds). :param meta_by_video: Optional per-video metadata, as ``meta_`` columns. :param types: Event categories by name (for effective colour and attributes). :returns: The CSV document as a string. .. py:function:: export_events_csv(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], path: str | pathlib.Path) -> pathlib.Path Write events to a flat CSV file (see :func:`events_to_csv_text`). :param events: The events to write. :param fps_by_video: Frames per second per ``video_id`` (for the derived seconds). :param path: Destination ``.csv`` path (parent directories are created). :returns: The path written. .. py:function:: events_to_xlsx_bytes(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], meta_by_video: collections.abc.Mapping[str, collections.abc.Mapping[str, str]] | None = None, types: collections.abc.Mapping[str, annie.dataset.storage.EventType] | None = None) -> bytes Render events as an Excel workbook (same columns as the CSV, see :func:`event_table`). One ``Events`` sheet: a bold, frozen header with filters, frames and seconds stored as numbers (so Excel sorts and sums them), and columns sized to their content. :param events: The events to render. :param fps_by_video: Frames per second per ``video_id`` (for the derived seconds). :param meta_by_video: Optional per-video metadata, as ``meta_`` columns. :param types: Event categories by name (for effective colour and attributes). :returns: The ``.xlsx`` file's bytes. .. py:function:: export_events_xlsx(events: collections.abc.Iterable[annie.dataset.storage.EventRecord], fps_by_video: collections.abc.Mapping[str, float], path: str | pathlib.Path) -> pathlib.Path Write events to an Excel file (see :func:`events_to_xlsx_bytes`). :param events: The events to write. :param fps_by_video: Frames per second per ``video_id`` (for the derived seconds). :param path: Destination ``.xlsx`` path (parent directories are created). :returns: The path written.