From 1aad9450a94244af641d2c15a99639bc14f9e7a4 Mon Sep 17 00:00:00 2001 From: Glen Beane <356266+gbeane@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:54:34 -0400 Subject: [PATCH 1/3] move jabs-project-merge to jabs-cli merge subcommand --- CLAUDE.md | 1 + docs/development/development.md | 6 +- docs/user-guide/cli-tools.md | 30 +++++ pyproject.toml | 2 - ruff.toml | 4 + .../resources/docs/user_guide/cli-tools.md | 30 +++++ src/jabs/scripts/cli/cli.py | 2 + src/jabs/scripts/cli/merge_projects.py | 77 ++++++++++++ src/jabs/scripts/merge_projects.py | 87 -------------- tests/scripts/test_merge_projects_cli.py | 113 ++++++++++++++++++ uv.lock | 14 --- 11 files changed, 261 insertions(+), 105 deletions(-) create mode 100644 src/jabs/scripts/cli/merge_projects.py delete mode 100644 src/jabs/scripts/merge_projects.py create mode 100644 tests/scripts/test_merge_projects_cli.py diff --git a/CLAUDE.md b/CLAUDE.md index 46e62f08..6c0b5a2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,6 +62,7 @@ uv run jabs-stats /path/to/project # View project statistics # jabs-cli subcommands (preferred for new utilities) uv run jabs-cli export-training # Export training data from a project +uv run jabs-cli merge # Merge one JABS project into another uv run jabs-cli rename-behavior # Rename a behavior across a project uv run jabs-cli prune # Remove videos from a project ``` diff --git a/docs/development/development.md b/docs/development/development.md index 8984a782..337d5473 100644 --- a/docs/development/development.md +++ b/docs/development/development.md @@ -960,6 +960,7 @@ jabs-cli --help **Current commands:** - `jabs-cli convert-parquet` - Convert a parquet pose file to JABS HDF5 pose format - `jabs-cli export-training` - Export training data from a project +- `jabs-cli merge` - Merge one JABS project into another - `jabs-cli rename-behavior` - Rename a behavior across a project - `jabs-cli prune` - Remove videos from a project based on criteria @@ -1001,9 +1002,10 @@ See existing commands in `cli.py` for complete examples. Some standalone scripts may be deprecated in favor of expanding `jabs-cli`. Candidates (non-exhaustive) for consolidation include: - `jabs-init` → `jabs-cli init-project` -- `jabs-project-merge` → `jabs-cli merge` -This consolidation would provide a more cohesive user experience and easier maintenance. However, backward compatibility will be maintained during any transition period. +The former `jabs-project-merge` script has already been consolidated as `jabs-cli merge`; the standalone entry point was removed. + +This consolidation would provide a more cohesive user experience and easier maintenance. **When to create a standalone script:** diff --git a/docs/user-guide/cli-tools.md b/docs/user-guide/cli-tools.md index 6957a640..a51fa088 100644 --- a/docs/user-guide/cli-tools.md +++ b/docs/user-guide/cli-tools.md @@ -130,6 +130,7 @@ Commands: convert-to-nwb Convert a JABS pose HDF5 file to NWB format. cross-validation Run leave-one-group-out cross-validation for a JABS project. export-training Export training data for a specified behavior and JABS project directory. + merge Merge one JABS project into another. prune Prune unused videos from a JABS project directory. rename-behavior Rename a behavior in a JABS project. sample-frames Sample PNG frames from a JABS project filtered by a behavior label. @@ -250,6 +251,35 @@ jabs-cli update-labels /path/to/target_project /path/to/source_project --min-iou If instead you want to replace the target's pose while keeping its labels, see [`jabs-cli update-pose`](#jabs-cli-update-pose). +## jabs-cli merge + +The `jabs-cli merge` command merges a source JABS project into a destination JABS project. Videos, pose files, behaviors, and labels from the source project are imported into the destination project, which is modified in place. The source project is left unchanged. + +Note: this command was previously the standalone `jabs-project-merge` script, which has been removed. + +**Usage:** + +```bash +jabs-cli merge --merge-strategy +``` + +- ``: Path to the destination JABS project. This project is modified by importing videos and labels from the source project. +- ``: Path to the source JABS project. Not modified. +- `--merge-strategy `: Required. How to resolve conflicting labels, i.e. frames labeled differently in both projects for the same video, identity, and behavior: + - `behavior-wins`: Keep the label with the behavior annotation. + - `not-behavior-wins`: Keep the label without the behavior annotation. + - `destination-wins`: Keep the label from the destination project. + +Videos present only in the source project are copied into the destination project along with their pose files, and behaviors defined only in the source project are added to the destination project's `project.json`. Labels for videos already in the destination project are merged annotation by annotation using the selected strategy. + +Both paths must already be valid JABS project directories, and they must be different directories. + +**Example:** + +```bash +jabs-cli merge /path/to/destination_project /path/to/source_project --merge-strategy destination-wins +``` + ## jabs-cli sample-frames The `jabs-cli sample-frames` command extracts individual video frames as PNG images diff --git a/pyproject.toml b/pyproject.toml index b6fe3f00..f3da20e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "xgboost>=2.0.0,<3.0.0", "pyarrow>=23.0.1,<24.0.0", "rich>=14.0.0,<15.0.0", - "rich-argparse>=1.7.1,<2.0.0", "intervaltree>=3.1.0,<4.0.0", "qt-material-icons>=0.2.0,<0.3.0", "jsonschema>=4.25.1,<5.0.0", @@ -87,7 +86,6 @@ jabs = "jabs.scripts.gui_entrypoint:main" "jabs-classify" = "jabs.scripts.classify:main" "jabs-init" = "jabs.scripts.initialize_project:main" "jabs-features" = "jabs.scripts.generate_features:main" -"jabs-project-merge" = "jabs.scripts.merge_projects:main" "jabs-cli" = "jabs.scripts.cli:main" [tool.uv.workspace] diff --git a/ruff.toml b/ruff.toml index b468269d..de38bc81 100644 --- a/ruff.toml +++ b/ruff.toml @@ -46,3 +46,7 @@ convention = "google" "src/jabs/scripts/cli/sample_frames.py" = [ "D301", # Click uses \b in docstrings to control help formatting ] +"src/jabs/scripts/cli/merge_projects.py" = [ + "D301", # Click uses \b in docstrings to control help formatting + "D412", # Click Examples sections look better with a blank line before \b blocks +] diff --git a/src/jabs/resources/docs/user_guide/cli-tools.md b/src/jabs/resources/docs/user_guide/cli-tools.md index 6957a640..a51fa088 100644 --- a/src/jabs/resources/docs/user_guide/cli-tools.md +++ b/src/jabs/resources/docs/user_guide/cli-tools.md @@ -130,6 +130,7 @@ Commands: convert-to-nwb Convert a JABS pose HDF5 file to NWB format. cross-validation Run leave-one-group-out cross-validation for a JABS project. export-training Export training data for a specified behavior and JABS project directory. + merge Merge one JABS project into another. prune Prune unused videos from a JABS project directory. rename-behavior Rename a behavior in a JABS project. sample-frames Sample PNG frames from a JABS project filtered by a behavior label. @@ -250,6 +251,35 @@ jabs-cli update-labels /path/to/target_project /path/to/source_project --min-iou If instead you want to replace the target's pose while keeping its labels, see [`jabs-cli update-pose`](#jabs-cli-update-pose). +## jabs-cli merge + +The `jabs-cli merge` command merges a source JABS project into a destination JABS project. Videos, pose files, behaviors, and labels from the source project are imported into the destination project, which is modified in place. The source project is left unchanged. + +Note: this command was previously the standalone `jabs-project-merge` script, which has been removed. + +**Usage:** + +```bash +jabs-cli merge --merge-strategy +``` + +- ``: Path to the destination JABS project. This project is modified by importing videos and labels from the source project. +- ``: Path to the source JABS project. Not modified. +- `--merge-strategy `: Required. How to resolve conflicting labels, i.e. frames labeled differently in both projects for the same video, identity, and behavior: + - `behavior-wins`: Keep the label with the behavior annotation. + - `not-behavior-wins`: Keep the label without the behavior annotation. + - `destination-wins`: Keep the label from the destination project. + +Videos present only in the source project are copied into the destination project along with their pose files, and behaviors defined only in the source project are added to the destination project's `project.json`. Labels for videos already in the destination project are merged annotation by annotation using the selected strategy. + +Both paths must already be valid JABS project directories, and they must be different directories. + +**Example:** + +```bash +jabs-cli merge /path/to/destination_project /path/to/source_project --merge-strategy destination-wins +``` + ## jabs-cli sample-frames The `jabs-cli sample-frames` command extracts individual video frames as PNG images diff --git a/src/jabs/scripts/cli/cli.py b/src/jabs/scripts/cli/cli.py index 69a10b44..eecc6616 100644 --- a/src/jabs/scripts/cli/cli.py +++ b/src/jabs/scripts/cli/cli.py @@ -26,6 +26,7 @@ from .convert_parquet import convert_parquet_command from .convert_to_nwb import run_conversion from .cross_validation import run_cross_validation +from .merge_projects import merge_projects_command from .postprocessing import apply_postprocessing_command from .sample_frames import sample_frames_command from .sample_pose_intervals import sample_pose_intervals_command @@ -53,6 +54,7 @@ def cli(ctx: click.Context, verbose): cli.add_command(apply_postprocessing_command) cli.add_command(compute_features_command) cli.add_command(convert_parquet_command) +cli.add_command(merge_projects_command) cli.add_command(update_pose_command) cli.add_command(update_labels_command) cli.add_command(sample_pose_intervals_command) diff --git a/src/jabs/scripts/cli/merge_projects.py b/src/jabs/scripts/cli/merge_projects.py new file mode 100644 index 00000000..4831784b --- /dev/null +++ b/src/jabs/scripts/cli/merge_projects.py @@ -0,0 +1,77 @@ +"""Merge one JABS project into another. + +The source project's videos, pose files, behaviors, and labels are imported into +the destination project, which is modified in place. The source project is left +unchanged. + +Where both projects label the same video, identity, and behavior, conflicting +labels are resolved by the selected ``--merge-strategy``: + +\b + * behavior-wins: Keep the label with the behavior annotation. + * not-behavior-wins: Keep the label without the behavior annotation. + * destination-wins: Keep the label from the destination project. + +Example: + +\b + jabs-cli merge /path/to/destination_project /path/to/source_project --merge-strategy destination-wins +""" + +from __future__ import annotations + +from pathlib import Path + +import click + +from jabs.project import Project +from jabs.project.project_merge import MergeStrategy, merge_projects + +MERGE_STRATEGY_MAP: dict[str, MergeStrategy] = { + "behavior-wins": MergeStrategy.BEHAVIOR_WINS, + "not-behavior-wins": MergeStrategy.NOT_BEHAVIOR_WINS, + "destination-wins": MergeStrategy.DESTINATION_WINS, +} + + +@click.command( + name="merge", + context_settings={"max_content_width": 120}, + help=__doc__, +) +@click.argument( + "destination_project", + type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path), +) +@click.argument( + "source_project", + type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path), +) +@click.option( + "--merge-strategy", + "merge_strategy", + type=click.Choice(list(MERGE_STRATEGY_MAP), case_sensitive=False), + required=True, + help="Strategy to use when merging conflicting labels.", +) +def merge_projects_command( + destination_project: Path, + source_project: Path, + merge_strategy: str, +) -> None: + """Merge a source JABS project into a destination JABS project.""" + if destination_project.resolve() == source_project.resolve(): + raise click.ClickException("Destination and source projects cannot be the same.") + + if not Project.is_valid_project_directory(destination_project): + raise click.ClickException( + f"Destination project {destination_project} is not a valid JABS project." + ) + + if not Project.is_valid_project_directory(source_project): + raise click.ClickException(f"Source project {source_project} is not a valid JABS project.") + + destination = Project(destination_project) + source = Project(source_project) + + merge_projects(destination, source, MERGE_STRATEGY_MAP[merge_strategy.lower()]) diff --git a/src/jabs/scripts/merge_projects.py b/src/jabs/scripts/merge_projects.py deleted file mode 100644 index 638c1bd0..00000000 --- a/src/jabs/scripts/merge_projects.py +++ /dev/null @@ -1,87 +0,0 @@ -import argparse -import sys -from pathlib import Path - -from rich.console import Console -from rich.markdown import Markdown -from rich_argparse import RichHelpFormatter - -from jabs.project import Project -from jabs.project.project_merge import MergeStrategy, merge_projects - -MERGE_STRATEGY_MAP: dict[str, tuple[MergeStrategy, str]] = { - "behavior-wins": (MergeStrategy.BEHAVIOR_WINS, "Keep the label with the behavior annotation."), - "not-behavior-wins": ( - MergeStrategy.NOT_BEHAVIOR_WINS, - "Keep the label without the behavior annotation.", - ), - "destination-wins": ( - MergeStrategy.DESTINATION_WINS, - "Keep the label from the destination project.", - ), -} - -max_key_len = max(len(key) for key in MERGE_STRATEGY_MAP) -MERGE_STRATEGY_DETAILS = "About Merge Strategy Options:\n" + "\n".join( - f" * {(key + ':').ljust(max_key_len + 1)} {desc}" - for key, (_, desc) in MERGE_STRATEGY_MAP.items() -) - - -def rich_error_exit(message: str) -> None: - """Print a formatted error message and exit the program.""" - console = Console() - console.print(f"\n [bold red]ERROR:[/bold red] {message}\n") - sys.exit(1) - - -def main(): - """project merge script entry point.""" - - def dir_path(path_str: str) -> Path: - path = Path(path_str) - if not path.is_dir(): - raise argparse.ArgumentTypeError(f"{path} is not a valid directory") - return path - - """Command for merging two JABS projects.""" - parser = argparse.ArgumentParser( - description="Merge two JABS projects.", - epilog=Markdown(MERGE_STRATEGY_DETAILS, style="argparse.text"), # type: ignore - formatter_class=RichHelpFormatter, - ) - parser.add_argument( - "destination_project", - type=dir_path, - help="Path to the destination project. This project will be modified by importing videos and labels from the" - " source project.", - ) - parser.add_argument("source_project", type=dir_path, help="Path to the source project.") - parser.add_argument( - "--merge-strategy", - choices=MERGE_STRATEGY_MAP.keys(), - required=True, - default="destination-wins", - help="Strategy to use when merging conflicting labels. (default: destination-wins)", - ) - args = parser.parse_args() - - if args.destination_project == args.source_project: - rich_error_exit("Destination and source projects cannot be the same.") - - if not (args.destination_project / "jabs").exists(): - rich_error_exit( - f"Destination project {args.destination_project} is not a valid JABS project." - ) - - if not (args.source_project / "jabs").exists(): - rich_error_exit(f"Source project {args.source_project} is not a valid JABS project.") - - destination = Project(args.destination_project) - source = Project(args.source_project) - - merge_projects(destination, source, MERGE_STRATEGY_MAP[args.merge_strategy][0]) - - -if __name__ == "__main__": - main() diff --git a/tests/scripts/test_merge_projects_cli.py b/tests/scripts/test_merge_projects_cli.py new file mode 100644 index 00000000..5f50696e --- /dev/null +++ b/tests/scripts/test_merge_projects_cli.py @@ -0,0 +1,113 @@ +"""Tests for the ``jabs-cli merge`` subcommand.""" + +from __future__ import annotations + +from pathlib import Path +from unittest import mock + +from click.testing import CliRunner + +from jabs.project.project_merge import MergeStrategy +from jabs.scripts.cli.cli import cli + + +def _make_project_dir(path: Path) -> Path: + """Create a directory that passes ``Project.is_valid_project_directory``.""" + (path / "jabs").mkdir(parents=True) + (path / "jabs" / "project.json").write_text("{}") + return path + + +def test_merge_strategy_is_required(tmp_path: Path) -> None: + """Omitting --merge-strategy is an error.""" + dest = _make_project_dir(tmp_path / "dest") + source = _make_project_dir(tmp_path / "source") + + result = CliRunner().invoke(cli, ["merge", str(dest), str(source)]) + + assert result.exit_code != 0 + assert "--merge-strategy" in result.output + + +def test_same_project_rejected(tmp_path: Path) -> None: + """Merging a project into itself is rejected before any project is opened.""" + project = _make_project_dir(tmp_path / "project") + + with mock.patch("jabs.scripts.cli.merge_projects.Project") as mock_project: + result = CliRunner().invoke( + cli, + ["merge", str(project), str(project), "--merge-strategy", "destination-wins"], + ) + + assert result.exit_code != 0 + assert "cannot be the same" in result.output + mock_project.assert_not_called() + + +def test_invalid_destination_project_rejected(tmp_path: Path) -> None: + """A destination directory that is not a JABS project is rejected.""" + dest = tmp_path / "dest" + dest.mkdir() + source = _make_project_dir(tmp_path / "source") + + result = CliRunner().invoke( + cli, + ["merge", str(dest), str(source), "--merge-strategy", "destination-wins"], + ) + + assert result.exit_code != 0 + assert "Destination project" in result.output + assert "not a valid JABS project" in result.output + + +def test_invalid_source_project_rejected(tmp_path: Path) -> None: + """A source directory that is not a JABS project is rejected.""" + dest = _make_project_dir(tmp_path / "dest") + source = tmp_path / "source" + source.mkdir() + + result = CliRunner().invoke( + cli, + ["merge", str(dest), str(source), "--merge-strategy", "destination-wins"], + ) + + assert result.exit_code != 0 + assert "Source project" in result.output + assert "not a valid JABS project" in result.output + + +def test_missing_project_directory_rejected(tmp_path: Path) -> None: + """A nonexistent project path fails click's path validation.""" + dest = _make_project_dir(tmp_path / "dest") + missing = tmp_path / "does_not_exist" + + result = CliRunner().invoke( + cli, + ["merge", str(dest), str(missing), "--merge-strategy", "destination-wins"], + ) + + assert result.exit_code != 0 + assert "does_not_exist" in result.output + + +def test_merge_invoked_with_selected_strategy(tmp_path: Path) -> None: + """Valid inputs open both projects and forward the selected merge strategy.""" + dest = _make_project_dir(tmp_path / "dest") + source = _make_project_dir(tmp_path / "source") + + with ( + mock.patch("jabs.scripts.cli.merge_projects.Project") as mock_project, + mock.patch("jabs.scripts.cli.merge_projects.merge_projects") as mock_merge, + ): + result = CliRunner().invoke( + cli, + ["merge", str(dest), str(source), "--merge-strategy", "behavior-wins"], + ) + + assert result.exit_code == 0, result.output + assert mock_project.call_args_list == [mock.call(dest), mock.call(source)] + mock_merge.assert_called_once_with( + mock_project.return_value, + mock_project.return_value, + MergeStrategy.BEHAVIOR_WINS, + ) diff --git a/uv.lock b/uv.lock index ba5668fb..c1a885f0 100644 --- a/uv.lock +++ b/uv.lock @@ -1801,7 +1801,6 @@ dependencies = [ { name = "pyside6" }, { name = "qt-material-icons" }, { name = "rich" }, - { name = "rich-argparse" }, { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "shapely" }, @@ -1865,7 +1864,6 @@ requires-dist = [ { name = "pyyaml", marker = "extra == 'yaml'", specifier = ">=6.0.0" }, { name = "qt-material-icons", specifier = ">=0.2.0,<0.3.0" }, { name = "rich", specifier = ">=14.0.0,<15.0.0" }, - { name = "rich-argparse", specifier = ">=1.7.1,<2.0.0" }, { name = "scikit-learn", specifier = ">=1.5.0,<2.0.0" }, { name = "shapely", specifier = ">=2.0.1,<3.0.0" }, { name = "tabulate", specifier = ">=0.9.0,<1.0.0" }, @@ -4211,18 +4209,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" }, ] -[[package]] -name = "rich-argparse" -version = "1.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/f7/1c65e0245d4c7009a87ac92908294a66e7e7635eccf76a68550f40c6df80/rich_argparse-1.7.2.tar.gz", hash = "sha256:64fd2e948fc96e8a1a06e0e72c111c2ce7f3af74126d75c0f5f63926e7289cd1", size = 38500, upload-time = "2025-11-01T10:35:44.232Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/80/97b6f357ac458d9ad9872cc3183ca09ef7439ac89e030ea43053ba1294b6/rich_argparse-1.7.2-py3-none-any.whl", hash = "sha256:0559b1f47a19bbeb82bf15f95a057f99bcbbc98385532f57937f9fc57acc501a", size = 25476, upload-time = "2025-11-01T10:35:42.681Z" }, -] - [[package]] name = "rpds-py" version = "0.30.0" From 2e7fa9e893e2b023ba4c08fd3d6502f694a165ef Mon Sep 17 00:00:00 2001 From: Glen Beane <356266+gbeane@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:55:00 -0400 Subject: [PATCH 2/3] make jabs-features a deprecated shim for jabs-cli compute-features --- CLAUDE.md | 2 +- docs/development/development.md | 5 +- docs/user-guide/cli-tools.md | 72 ++++++--- .../resources/docs/user_guide/cli-tools.md | 72 ++++++--- src/jabs/scripts/generate_features.py | 108 +++++++------ src/jabs/ui/dialogs/user_guide_dialog.py | 2 +- tests/scripts/test_generate_features.py | 147 ++++++++++++++++++ 7 files changed, 307 insertions(+), 101 deletions(-) create mode 100644 tests/scripts/test_generate_features.py diff --git a/CLAUDE.md b/CLAUDE.md index 6c0b5a2b..514b552f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,11 +56,11 @@ uv run jabs-cli --help # Unified CLI utilities # Standalone CLI scripts uv run jabs-init /path/to/project # Initialize a JABS project -uv run jabs-features /path/to/project # Generate features (batch, no GUI) uv run jabs-classify --classifier model.pkl /path/to/pose.h5 # Batch classification uv run jabs-stats /path/to/project # View project statistics # jabs-cli subcommands (preferred for new utilities) +uv run jabs-cli compute-features # Generate features for a pose file (batch, no GUI) uv run jabs-cli export-training # Export training data from a project uv run jabs-cli merge # Merge one JABS project into another uv run jabs-cli rename-behavior # Rename a behavior across a project diff --git a/docs/development/development.md b/docs/development/development.md index 337d5473..3dea9673 100644 --- a/docs/development/development.md +++ b/docs/development/development.md @@ -938,8 +938,8 @@ jabs # Initialize a JABS project jabs-init /path/to/project -# Generate features for an HDF5 file without requring a JABS project (for batch processing) -jabs-features /path/to/project +# Generate features for a pose file without requiring a JABS project (for batch processing) +jabs-cli compute-features --pose-file /path/to/pose_file.h5 --feature-dir /path/to/features # Run batch classification jabs-classify --classifier my_classifier.pkl /path/to/pose_file.h5 @@ -958,6 +958,7 @@ jabs-cli --help `jabs-cli` is a unified command-line interface that consolidates smaller JABS utilities under a single entry point. Instead of having many separate standalone scripts, `jabs-cli` uses Click's group/command pattern to organize related functionality. **Current commands:** +- `jabs-cli compute-features` - Compute and cache features for a single pose file - `jabs-cli convert-parquet` - Convert a parquet pose file to JABS HDF5 pose format - `jabs-cli export-training` - Export training data from a project - `jabs-cli merge` - Merge one JABS project into another diff --git a/docs/user-guide/cli-tools.md b/docs/user-guide/cli-tools.md index a51fa088..b0b84e2b 100644 --- a/docs/user-guide/cli-tools.md +++ b/docs/user-guide/cli-tools.md @@ -80,32 +80,19 @@ optionally override the classifier specified in the training file: ## jabs-features -JABS includes a script called `jabs-features`, which can be used to generate a feature file for a single video from the command line. +**Deprecated.** `jabs-features` has been replaced by [`jabs-cli compute-features`](#jabs-cli-compute-features). The command still works, but it prints a deprecation warning, translates its arguments, and runs `jabs-cli compute-features` for you. Update existing pipelines to call the new command directly. -```text -usage: jabs-features [-h] --pose-file POSE_FILE --pose-version POSE_VERSION - --feature-dir FEATURE_DIR [--use-cm-distances] - [--window-size WINDOW_SIZE] [--fps FPS] - [--use-pose-hash] - -options: - -h, --help show this help message and exit - --pose-file POSE_FILE - pose file to compute features for - --pose-version POSE_VERSION - pose version to calculate features - --feature-dir FEATURE_DIR - directory to write output features - --use-cm-distances use cm distance units instead of pixel - --window-size WINDOW_SIZE - window size for features (default none) - --fps FPS frames per second to use for feature calculation - --use-pose-hash Include the pose file hash as a subdirectory level in the feature cache - path (e.g. /