Skip to content
Open
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
10 changes: 8 additions & 2 deletions docs/user-guide/cli-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ The `jabs-cli update-pose` command updates an existing JABS project to use updat
**Usage:**

```bash
jabs-cli update-pose <project_dir> <updated_pose_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations] [--skip-feature-gen]
jabs-cli update-pose <project_dir> <updated_pose_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations] [--skip-feature-gen] [--tolerate-orphan-identities]
```

- `<project_dir>`: Path to the JABS project to update in place.
Expand All @@ -198,9 +198,12 @@ jabs-cli update-pose <project_dir> <updated_pose_dir> [--min-iou-thresh <FLOAT>]
- `--annotate-failures`: Add timeline annotations to the project for blocks whose label remap fails.
- `--drop-timeline-annotations`: Discard existing timeline annotations from the source project instead of copying or remapping them.
- `--skip-feature-gen`: Skip automatic feature regeneration after a successful pose update.
- `--tolerate-orphan-identities`: Continue past orphan-identity references in the live project's annotations (warn instead of erroring at preflight). Affected `(identity, behavior)` pairs are skipped during remap; all other labels are remapped normally. By default the command aborts at preflight when orphans are detected.

Before modifying the project, the command validates the updated pose files, runs the pose update and label remap in disposable staging projects, and creates a timestamped backup zip under `<project_dir>/.backup`. By default, existing timeline annotations are also carried forward: video-level annotations are copied as-is, and identity-scoped annotations are remapped by the same interval-matching logic used for label blocks. Use `--drop-timeline-annotations` if you want to discard existing timeline annotations instead. Only after the staged pose update succeeds are annotations, project metadata, and pose files copied back into the project.

**Orphan identity check.** Preflight rejects projects whose annotation files reference identity indices that the corresponding pose file no longer supplies — typically a sign the pose was regenerated with fewer identities, leaving stale label entries that can no longer be matched against pose data. The command lists every offending video and identity, then exits without modifying anything. Clean up the affected annotation files (drop the orphan identity entries from each `.json`) and re-run, or pass `--tolerate-orphan-identities` to warn-and-skip the affected entries instead of aborting.

After a successful live pose update, features are regenerated automatically only when `--skip-feature-gen` is not passed and the existing `jabs/project.json` already contains explicit `window_sizes`. If the project file has no `window_sizes` entry, there is nothing to regenerate and feature generation is skipped. If you want more control over feature regeneration, use `--skip-feature-gen` and run `jabs-init` manually, or generate features from the GUI.

**Example:**
Expand All @@ -218,7 +221,7 @@ The `jabs-cli update-labels` command is the inverse of [`update-pose`](#jabs-cli
**Usage:**

```bash
jabs-cli update-labels <project_dir> <source_project_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations]
jabs-cli update-labels <project_dir> <source_project_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations] [--tolerate-orphan-identities]
```

- `<project_dir>`: Path to the target JABS project whose labels will be replaced in place. The target's pose is unchanged. If `<project_dir>` is a directory of videos + pose files with no `jabs/` subdirectory, a minimal JABS project is scaffolded automatically — features are not generated, so you may want to run `jabs-init` separately afterwards.
Expand All @@ -227,11 +230,14 @@ jabs-cli update-labels <project_dir> <source_project_dir> [--min-iou-thresh <FLO
- `--verbose`: Print successful label remap assignments in addition to warnings.
- `--annotate-failures`: Add timeline annotations to the target for blocks whose label remap fails. Annotations use the same `behavior-remap-failed` / `not-behavior-remap-failed` tags as `update-pose`; the description text distinguishes the originating operation.
- `--drop-timeline-annotations`: Discard source timeline annotations instead of copying or remapping them.
- `--tolerate-orphan-identities`: Continue past orphan-identity references in the source's annotations (warn instead of erroring at preflight). Affected `(identity, behavior)` pairs are skipped during remap; all other labels are imported normally. By default the command aborts at preflight when orphans are detected.

Before modifying the project, the command validates both inputs, runs the label remap in disposable staging projects, and creates a timestamped backup zip under `<project_dir>/.backup` covering `jabs/project.json`, annotations, and predictions (pose files are not touched). Labels are processed block by block, matched by median bbox IoU between the source pose and the target's existing pose, and written to the staged destination label track. By default, source timeline annotations are also carried forward and remapped the same way as label blocks; use `--drop-timeline-annotations` to discard them.

Existing target labels for videos that the source does not cover are left untouched (per-video replace). Behaviors named in the source's `project.json` but not present in the target are merged into the target's `project.json` so the imported labels are usable in the GUI; behaviors already configured in the target keep their existing settings.

**Orphan identity check.** Preflight rejects the run if any source annotation file references identity indices that the source's own pose file no longer supplies. This typically means the source's pose was regenerated with fewer identities and the affected label entries are stranded — there is no source-side bounding-box data to anchor the IoU match against. The command lists every offending video and identity, then exits without modifying the target. Clean up the source annotation files (drop the orphan identity entries from each `.json`) and re-run, or pass `--tolerate-orphan-identities` to warn-and-skip the affected entries instead of aborting.

The target's pose is unchanged, so the feature cache stays valid and is **not** regenerated. Predictions are cleared because they are stale relative to the new labels; classifiers, the performance cache, and feature files are all left in place. If you need to retrain after a label import, run training from the GUI or via `jabs-classify`.

If a failure occurs after the live apply begins, the command prints the backup path plus cleanup and manual restore instructions instead of restoring automatically.
Expand Down
10 changes: 8 additions & 2 deletions src/jabs/resources/docs/user_guide/cli-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ The `jabs-cli update-pose` command updates an existing JABS project to use updat
**Usage:**

```bash
jabs-cli update-pose <project_dir> <updated_pose_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations] [--skip-feature-gen]
jabs-cli update-pose <project_dir> <updated_pose_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations] [--skip-feature-gen] [--tolerate-orphan-identities]
```

- `<project_dir>`: Path to the JABS project to update in place.
Expand All @@ -198,9 +198,12 @@ jabs-cli update-pose <project_dir> <updated_pose_dir> [--min-iou-thresh <FLOAT>]
- `--annotate-failures`: Add timeline annotations to the project for blocks whose label remap fails.
- `--drop-timeline-annotations`: Discard existing timeline annotations from the source project instead of copying or remapping them.
- `--skip-feature-gen`: Skip automatic feature regeneration after a successful pose update.
- `--tolerate-orphan-identities`: Continue past orphan-identity references in the live project's annotations (warn instead of erroring at preflight). Affected `(identity, behavior)` pairs are skipped during remap; all other labels are remapped normally. By default the command aborts at preflight when orphans are detected.

Before modifying the project, the command validates the updated pose files, runs the pose update and label remap in disposable staging projects, and creates a timestamped backup zip under `<project_dir>/.backup`. By default, existing timeline annotations are also carried forward: video-level annotations are copied as-is, and identity-scoped annotations are remapped by the same interval-matching logic used for label blocks. Use `--drop-timeline-annotations` if you want to discard existing timeline annotations instead. Only after the staged pose update succeeds are annotations, project metadata, and pose files copied back into the project.

**Orphan identity check.** Preflight rejects projects whose annotation files reference identity indices that the corresponding pose file no longer supplies — typically a sign the pose was regenerated with fewer identities, leaving stale label entries that can no longer be matched against pose data. The command lists every offending video and identity, then exits without modifying anything. Clean up the affected annotation files (drop the orphan identity entries from each `.json`) and re-run, or pass `--tolerate-orphan-identities` to warn-and-skip the affected entries instead of aborting.

After a successful live pose update, features are regenerated automatically only when `--skip-feature-gen` is not passed and the existing `jabs/project.json` already contains explicit `window_sizes`. If the project file has no `window_sizes` entry, there is nothing to regenerate and feature generation is skipped. If you want more control over feature regeneration, use `--skip-feature-gen` and run `jabs-init` manually, or generate features from the GUI.

**Example:**
Expand All @@ -218,7 +221,7 @@ The `jabs-cli update-labels` command is the inverse of [`update-pose`](#jabs-cli
**Usage:**

```bash
jabs-cli update-labels <project_dir> <source_project_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations]
jabs-cli update-labels <project_dir> <source_project_dir> [--min-iou-thresh <FLOAT>] [--verbose] [--annotate-failures] [--drop-timeline-annotations] [--tolerate-orphan-identities]
```

- `<project_dir>`: Path to the target JABS project whose labels will be replaced in place. The target's pose is unchanged. If `<project_dir>` is a directory of videos + pose files with no `jabs/` subdirectory, a minimal JABS project is scaffolded automatically — features are not generated, so you may want to run `jabs-init` separately afterwards.
Expand All @@ -227,11 +230,14 @@ jabs-cli update-labels <project_dir> <source_project_dir> [--min-iou-thresh <FLO
- `--verbose`: Print successful label remap assignments in addition to warnings.
- `--annotate-failures`: Add timeline annotations to the target for blocks whose label remap fails. Annotations use the same `behavior-remap-failed` / `not-behavior-remap-failed` tags as `update-pose`; the description text distinguishes the originating operation.
- `--drop-timeline-annotations`: Discard source timeline annotations instead of copying or remapping them.
- `--tolerate-orphan-identities`: Continue past orphan-identity references in the source's annotations (warn instead of erroring at preflight). Affected `(identity, behavior)` pairs are skipped during remap; all other labels are imported normally. By default the command aborts at preflight when orphans are detected.

Before modifying the project, the command validates both inputs, runs the label remap in disposable staging projects, and creates a timestamped backup zip under `<project_dir>/.backup` covering `jabs/project.json`, annotations, and predictions (pose files are not touched). Labels are processed block by block, matched by median bbox IoU between the source pose and the target's existing pose, and written to the staged destination label track. By default, source timeline annotations are also carried forward and remapped the same way as label blocks; use `--drop-timeline-annotations` to discard them.

Existing target labels for videos that the source does not cover are left untouched (per-video replace). Behaviors named in the source's `project.json` but not present in the target are merged into the target's `project.json` so the imported labels are usable in the GUI; behaviors already configured in the target keep their existing settings.

**Orphan identity check.** Preflight rejects the run if any source annotation file references identity indices that the source's own pose file no longer supplies. This typically means the source's pose was regenerated with fewer identities and the affected label entries are stranded — there is no source-side bounding-box data to anchor the IoU match against. The command lists every offending video and identity, then exits without modifying the target. Clean up the source annotation files (drop the orphan identity entries from each `.json`) and re-run, or pass `--tolerate-orphan-identities` to warn-and-skip the affected entries instead of aborting.

The target's pose is unchanged, so the feature cache stays valid and is **not** regenerated. Predictions are cleared because they are stale relative to the new labels; classifiers, the performance cache, and feature files are all left in place. If you need to retrain after a label import, run training from the GUI or via `jabs-classify`.

If a failure occurs after the live apply begins, the command prints the backup path plus cleanup and manual restore instructions instead of restoring automatically.
Expand Down
45 changes: 44 additions & 1 deletion src/jabs/scripts/cli/update_labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@

from .update_pose import (
_create_backup_archive,
_handle_orphan_identities,
_orphan_identities_in_annotation,
_print_manual_restore,
_project_videos,
_restore_cleanup_paths,
Expand Down Expand Up @@ -160,6 +162,8 @@ def _scaffold_target_project_if_missing(target_dir: Path) -> bool:
def _preflight_label_update_inputs(
target_dir: Path,
source_project_dir: Path,
*,
tolerate_orphan_identities: bool = False,
) -> tuple[list[str], set[str]]:
"""Validate target and source inputs, scaffolding the target if needed.

Expand All @@ -171,6 +175,15 @@ def _preflight_label_update_inputs(
features, or pose changes — so on any subsequent failure the user is left
with a harmless no-op directory that the next run reuses.

Args:
target_dir: Path to the target JABS project (or a videos+pose dir to scaffold).
source_project_dir: Path to the source JABS project providing labels.
tolerate_orphan_identities: When ``True``, orphan identity references in
the source's annotations are warned about and remap continues, with
the affected ``(identity, behavior)`` pairs skipped during remap.
When ``False`` (default), the orphan check raises before any backup
or mutation.

Returns:
Tuple ``(videos, live_annotation_videos)`` where ``videos`` is the sorted
list of source-labeled videos to operate on (each also exists in the
Expand Down Expand Up @@ -205,10 +218,13 @@ def _preflight_label_update_inputs(
f"Source-labeled videos missing from target project: {', '.join(missing_in_target)}"
)

source_annotations_dir = source_project_dir / "jabs" / "annotations"
orphan_issues: list[tuple[str, int, list[int]]] = []

for video in labeled_videos:
source_video_path = source_project_dir / video
source_pose_path = get_pose_path(source_video_path)
_validate_pose_file(
_, source_num_identities = _validate_pose_file(
video,
source_video_path,
source_pose_path,
Expand All @@ -226,6 +242,15 @@ def _preflight_label_update_inputs(
require_bboxes=True,
)

annotation_path = source_annotations_dir / Path(video).with_suffix(".json")
orphans = _orphan_identities_in_annotation(annotation_path, source_num_identities)
if orphans:
orphan_issues.append((video, source_num_identities, orphans))

_handle_orphan_identities(
orphan_issues, source_label="source", tolerate=tolerate_orphan_identities
)

target_annotations_dir = target_dir / "jabs" / "annotations"
live_annotation_videos: set[str] = set()
if target_annotations_dir.exists():
Expand Down Expand Up @@ -369,6 +394,7 @@ def update_project_labels_in_place(
verbose: bool = False,
annotate_failures: bool = False,
drop_timeline_annotations: bool = False,
tolerate_orphan_identities: bool = False,
) -> tuple[int, int, Path, list[str]]:
"""Update a live project in place by replacing its labels from ``source_project_dir``.

Expand All @@ -384,6 +410,10 @@ def update_project_labels_in_place(
annotate_failures: Whether to write timeline annotations for failed block matches.
drop_timeline_annotations: Whether to discard source timeline annotations
instead of copying or remapping them.
tolerate_orphan_identities: When ``True``, warn about orphan identity
references in the source's annotations and skip the affected
``(identity, behavior)`` pairs during remap instead of aborting at
preflight.

Returns:
Tuple ``(total_success, total_skipped, backup_path, newly_added_behaviors)``
Expand All @@ -398,6 +428,7 @@ def update_project_labels_in_place(
labeled_videos, _live_annotation_videos = _preflight_label_update_inputs(
project_dir,
source_project_dir,
tolerate_orphan_identities=tolerate_orphan_identities,
)
backup_path = _create_backup_archive(
project_dir,
Expand Down Expand Up @@ -501,13 +532,24 @@ def update_project_labels_in_place(
is_flag=True,
help="Discard source timeline annotations instead of copying or remapping them.",
)
@click.option(
"--tolerate-orphan-identities",
is_flag=True,
help=(
"Continue past orphan identity references in the source's annotation "
"files (warn instead of erroring at preflight). The affected "
"(identity, behavior) pairs are skipped during remap; all other labels "
"are imported normally. By default, the command aborts at preflight."
),
)
def update_labels_command(
project: Path,
source_project: Path,
min_iou: float,
verbose: bool,
annotate_failures: bool,
drop_timeline_annotations: bool,
tolerate_orphan_identities: bool,
) -> None:
"""Update a JABS project in place by replacing labels imported from another project."""
total_success, total_skipped, backup_path, newly_added_behaviors = (
Expand All @@ -518,6 +560,7 @@ def update_labels_command(
verbose=verbose,
annotate_failures=annotate_failures,
drop_timeline_annotations=drop_timeline_annotations,
tolerate_orphan_identities=tolerate_orphan_identities,
)
)

Expand Down
Loading
Loading