Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/jabs-core/src/jabs/core/abstract/pose_est.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ class PoseEstimation(ABC):
hash (str): Hash of the pose file.
static_objects (dict): Static objects in the pose file.
num_lixit_keypoints (int): Number of lixit keypoints (default 0).
external_identities (list[int] | None): Mapping to external identities.
external_identities (list[str] | None): External identity names, indexed by
JABS identity index. None when the pose file has no external identities.
"""

class KeypointIndex(enum.IntEnum):
Expand Down
13 changes: 13 additions & 0 deletions src/jabs/project/timeline_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ def load(
def serialize(self) -> list[dict]:
"""Convert the internal IntervalTree to a JSON-serializable list of dictionaries.

Each annotation is serialized with the following fields, which are also the
fields :meth:`load` reads back:

- ``start``: first frame of the annotation (inclusive).
- ``end``: last frame of the annotation (inclusive).
- ``tag``: annotation tag.
- ``color``: color used to render the annotation.
- ``description``: optional description, omitted when unset.
- ``identity``: optional internal JABS identity index the annotation
applies to, omitted when the annotation is not tied to an identity.
The derived ``display_identity`` is not serialized: :meth:`load`
recomputes it from this index.

Returns:
list[dict]: A list containing a dictionary representation for each timeline annotation,
suitable for JSON serialization.
Expand Down
119 changes: 66 additions & 53 deletions src/jabs/project/video_labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,62 +160,75 @@ def as_dict(
project_metadata: dict | None = None,
video_metadata: dict | None = None,
) -> dict:
"""return dict representation of video labels
"""Return a dict representation of the video labels.

useful for JSON serialization and saving to disk
This is the on-disk format of a project's ``jabs/annotations/<video>.json``
file, so it is also what :meth:`load` consumes.

example return value:
{
"file": "filename.avi",
"num_frames": 100,
"external_identities: {
"jabs identity", 1234,
},
"metadata": {
"project": {},
"video": {},
}
"labels": {
"jabs identity": {
"behavior": [
{
"start": 25,
"end": 50,
"present": True
}
]
}
},
"unfragmented_labels": {
"jabs identity": {
"behavior": [
{
"start": 25,
"end": 50,
"present": True
}
]
}
},
annotations: [
{
"start": 10,
"end": 20,
"tag": "annotationTag",
"color": "#FF0000",
"description": "Description for the annotation"
},
{
"start": 30,
"end": 40,
"tag": "anotherTag",
"color": "#00FF00",
"description": "Another optional description",
"animal_id": 0 # optional, if the annotation is associated with an identity (internal JABS ID)
}
]
}
Args:
pose: PoseEstimation for this video. Its identity mask fragments the
blocks written to ``labels``, and its external identities are
included in the output when the pose file has them.
project_metadata: Optional project-level metadata to embed.
video_metadata: Optional video-level metadata to embed.

Returns:
Dict representation of the labels, with these keys:

- ``version``: serialization format version (``SERIALIZED_VERSION``).
- ``file``: name of the video these labels belong to.
- ``num_frames``: number of frames in the video.
- ``labels``: label blocks masked by ``pose.identity_mask()``, so a
block is split wherever the identity is missing from the pose file.
These are the blocks that correspond to usable training data.
- ``unfragmented_labels``: the same blocks without that mask, which is
what the user actually labeled. :meth:`load` prefers this key and
falls back to ``labels`` for files written before it existed.
- ``metadata``: the ``project_metadata`` and ``video_metadata``
arguments, each defaulting to an empty dict.
- ``external_identities``: only present when the pose file has external
identities. Maps the JABS identity index, as a string, to the
external identity.
- ``annotations``: only present when the video has timeline
annotations. See :meth:`TimelineAnnotations.serialize` for the fields
of an entry.

Both label dicts are keyed by identity (as a string), then by behavior
name, and hold the block lists produced by
:meth:`TrackLabels.get_blocks`.

Example:
The returned dict in its serialized JSON form, as written to
``jabs/annotations/<video>.json`` (so ``true`` below is the JSON
spelling of Python's ``True``):

{
"version": 1,
"file": "filename.avi",
"num_frames": 100,
"labels": {
"0": {
"behavior": [{"start": 25, "end": 40, "present": true}]
}
},
"unfragmented_labels": {
"0": {
"behavior": [{"start": 25, "end": 50, "present": true}]
}
},
"metadata": {"project": {}, "video": {}},
"external_identities": {"0": "mouse_a"},
"annotations": [
{
"start": 10,
"end": 20,
"tag": "annotationTag",
"color": "#FF0000",
"description": "optional description",
"identity": 0
}
]
}
"""
label_dict: dict[str, Any] = {
"version": SERIALIZED_VERSION,
Expand Down
Loading