Add blueye bundle-model CLI for creating CV model packages - #219
Conversation
Adds the SDK's first console script: a `blueye` umbrella command whose `bundle-model` subcommand turns an exported ONNX model into a BlueyeCV-ready package zip (model.onnx + auto-generated model_meta.json at the zip root). - Introspects the ONNX graph and embedded Ultralytics metadata to infer the decoder format (all 8 detection formats plus OSTrack/MixFormerV2 single-object trackers), class count, labels, and input size; clearly unsupported models (classifiers, fp16 inputs, non-image inputs) are rejected with an explanation. - Interactive rich/questionary UI: model summary and inference panels, spinners, byte-level progress while zipping, prompts with autocompletion for everything that cannot be inferred. Every prompt can be pre-answered by a flag, and --yes/--dry-run enable scripting. - Runtime configuration prompts include a DLA-fitness analysis of the network (op-histogram heuristic) that recommends the Jetson DLA cores for convolution-style models and explains why not for NMS-in-graph/transformer models; the rate prompt defaults to unlimited. - Dependencies live in a new optional extra `blueye.sdk[cli]`; a stdlib-only gate prints per-platform install guidance (uv vs pip, shell quoting, Python-version wheel hints) instead of an ImportError when the extra is missing. - Generated metadata is validated against the BlueyeCV model-meta spec and matches the reference packages byte-for-semantics; smoke-tested by loading a generated bundle with be-cv. - 88 unit tests (heuristics, introspection, meta building/validation, zip writing, prompt seams, end-to-end main()) plus a docs page and mkdocs nav/reference entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The runtime.enabled confirm now defaults to true (and --yes runs write enabled: true), so a bundled model runs on the drone out of the box. --runtime-enabled becomes a --runtime-enabled/--no-runtime-enabled pair for explicit control in scripted runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #219 +/- ##
==========================================
+ Coverage 77.88% 84.34% +6.46%
==========================================
Files 11 36 +25
Lines 1723 3463 +1740
==========================================
+ Hits 1342 2921 +1579
- Misses 381 542 +161
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Screen.Recording.2026-07-08.at.3.01.02.PM.mov |
CI fails on windows-latest + Python 3.14 (on master too, verified with a canary run of master HEAD): pyzmq 26.4.0 has no cp314 Windows wheel, so uv compiles it from source, and the current runner image breaks that build (FindVcvars cannot locate the MSVC scripts). pyzmq 27.x ships a cp312-abi3 Windows wheel that covers 3.12+, so widening the constraint to >=26,<28 and locking 27.1.0 removes the source build entirely. The SDK only uses basic zmq APIs (Context, PUB/SUB, Poller), which are unchanged in 27. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces the SDK’s first end-user CLI entrypoint (blueye) and adds a bundle-model subcommand to validate/introspect ONNX exports and produce a BlueyeCV-ready model package (zip with model.onnx + generated model_meta.json). It also adds docs + mkdocs nav/reference entries and a fairly comprehensive new unit test suite around the CLI modules.
Changes:
- Add a
blueyeconsole script and a newblueye.sdk.clisubpackage implementingbundle-model(dependency gate, prompting, ONNX introspection, heuristics, metadata generation, bundle writer, rich UI). - Add optional dependency extra
blueye.sdk[cli](onnx/rich/questionary) plus dev-group deps and lockfile updates. - Add docs (“Bundling CV models”) + API reference entries, and add unit tests covering the CLI flows.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Adds CLI extra deps and updates lock resolution markers/entries. |
| pyproject.toml | Registers blueye console script and adds [cli] optional dependencies + dev-group repeats. |
| mkdocs.yml | Adds “Bundling CV models” page and blueye.sdk.cli reference entry to nav. |
| docs/reference/blueye/sdk/cli.md | Adds mkdocstrings reference stubs for CLI modules. |
| docs/bundling-cv-models.md | New user documentation for installing/using blueye bundle-model. |
| blueye/sdk/cli/init.py | Exposes main for console script while keeping imports stdlib-safe. |
| blueye/sdk/cli/main.py | Umbrella CLI parser + dependency gate + lazy subcommand import/dispatch. |
| blueye/sdk/cli/deps.py | Stdlib-only optional-dependency detection and install guidance output. |
| blueye/sdk/cli/bundle_model.py | bundle-model argparse definitions + orchestration of introspection/inference/prompts/bundling. |
| blueye/sdk/cli/bundle.py | Stdlib-only streaming zip writer + atomic .part rename behavior. |
| blueye/sdk/cli/introspect.py | ONNX-only module for loading model IO shapes/metadata/external-data refs/op histogram. |
| blueye/sdk/cli/heuristics.py | Pure inference logic for output format detection + DLA fitness heuristic. |
| blueye/sdk/cli/meta.py | Stdlib-only model_meta.json builder + validation rules. |
| blueye/sdk/cli/prompts.py | Prompt seam (questionary vs non-interactive) and abort handling. |
| blueye/sdk/cli/ui.py | Rich UI helpers (panels/progress/meta preview). |
| tests/test_cli_prompts.py | Tests prompting behavior + dependency guidance output. |
| tests/test_cli_meta.py | Tests model_meta construction defaults and validation rules. |
| tests/test_cli_main.py | End-to-end-ish CLI tests (help, dep gate, bundle flow, flags, dry-run, etc.). |
| tests/test_cli_introspect.py | Tests ONNX model info extraction/checking/external-data detection. |
| tests/test_cli_heuristics.py | Tests output-shape heuristics, metadata parsing, DLA fitness decisions. |
| tests/test_cli_bundle.py | Tests zip contents/atomicity/progress reporting/external-data validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Restructures the `blueye` CLI so commands are pluggable rather than hard-coded, and adds a discovery mechanism for user-provided tools. First-party commands: each lives in its own package under blueye/sdk/cli/commands/<name>/ exposing a CommandSpec (name, help, required optional deps, add_parser, run); main.py iterates the registry and gates dependencies per command instead of globally. CliError moves to cli/errors.py (removing the prompts->main cycle hazard); bundle-model's introspect/heuristics/meta/bundle modules move into its command package. The CommandSpec contract is designed to double as the payload for future pip-installable plugins (blueye.cli entry-point group, documented as future work). Third-party tools: single-file Python scripts with PEP 723 inline metadata extended by a [tool.blueye] table (name, description, optional min-sdk-version). Discovery scans a per-user tools directory (BLUEYE_CLI_TOOLS_DIR, defaulting per OS), parses only the metadata (never executes code to list), shows tools in `blueye --help`, and dispatches `blueye <tool> args...` as a subprocess with verbatim args and propagated exit code — via `uv run` when the script declares PEP 723 dependencies and uv is available (isolated deps for free). Tool names are intercepted before argparse because a REMAINDER positional cannot forward leading-dash arguments. Built-ins always win name collisions. TOML parsing tiers tomllib -> tomli (added to the [cli] extra for 3.10) -> a minimal regex fallback so a stdlib-only 3.10 install can still discover tools. New `blueye tools` built-in (runs with zero extras — it is the bootstrap surface): list, validate (per-check report), install (validate + copy as <name>.py, --force to replace), uninstall (by name, incl. hand-copied files), and dir. Verified in a no-extras venv: --help with the tools epilog, tools list, tool dispatch with leading-dash args, and the bundle-model dependency gate (exit 2) all work stdlib-only. Docs: new "Extending the blueye CLI" page (third-party authoring guide with a worked example, per-OS directory table, first-party command recipe, future entry-points note) + mkdocs nav and updated reference page. +59 tests (metadata parser incl. forced-fallback path, directory resolution per platform, discovery/shadowing, subprocess dispatch and uv preference, tools subcommands, per-command gating), 369 total. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All seven review findings, verified and fixed: - _parse_anchors and the --runtime-hz flag/custom-rate prompt let ValueError escape as a traceback for non-numeric input; they now raise CliError with a message naming the offending value. - bundle_size() ran before write_bundle()'s external-data validation, so a missing weight file surfaced as an uncaught FileNotFoundError; the checks moved into a shared _validate_external_files() that both entry points call. - build_meta() defaulted empty labels to ["tracked"] for every kind of package — a 1-class detection model without labels would validate with a nonsense label. Detection packages now keep an empty list so validation fails with the direct message; only SOT packages default. - Overwrite semantics: non-interactive runs (--yes or no TTY) now require an explicit --force to overwrite an existing zip (erroring with the flag named); the interactive confirm defaults to No. - The install guidance on Windows suggested single quotes but printed a double-quoted uv command; the quoting now matches the platform. +7 regression tests (376 total). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@sindrehan an idea on how to also allow for third party tools to run on the blueye cli: Screen.Recording.2026-07-09.at.12.41.28.PM.movWe're using PEP 723 inline metadata do declare the name and description that is shown in the cli. And it uses uv to execute if available to pull in the requested dependencies. # /// script
# requires-python = ">=3.10"
# dependencies = ["pandas"]
#
# [tool.blueye]
# name = "export-logs"
# description = "Export dive logs to CSV"
# min-sdk-version = "2.7.0"
# ///
import sys
import pandas as pd
def main() -> int:
print(f"exporting with args: {sys.argv[1:]}")
# Print a dummy Pandas table
df = pd.DataFrame(
{
"Dive Number": [1, 2, 3],
"Date": ["2024-01-01", "2024-01-02", "2024-01-03"],
"Depth (m)": [30, 25, 20],
"Duration (min)": [45, 50, 40],
}
)
print(df)
return 0
if __name__ == "__main__":
sys.exit(main()) |
…e models` Wraps the drone's CV-model management HTTP API (http://<ip>/api/cv-models, served by the onboard backend) in three layers: - SDK: new blueye/sdk/cv_models.py with drone.cv_models — list, upload (multipart 'file' field), delete, download, set_enabled, set_device, set_hz, warmup, rescan, returning CvModel dataclasses. Non-2xx responses raise HTTPError with the drone's text/plain reason included (raise_for_status would discard it). Plain HTTP, independent of the control connection: works on Drone(auto_connect=False) and takes no control of the drone. - bundle-model: new --push and --drone-ip (default 192.168.1.101) flags; interactive runs offer the upload after the zip is written (default No). An unreachable drone or a server rejection fails gracefully with the bundle kept on disk and the path in the message; exit code 1 so scripts notice. - New `blueye models` built-in (requires rich+questionary, no onnx — the per-command gate at work): list (table incl. runtime device/hz/enabled), enable/disable, set-device, set-hz (choices match the server's accepted values), warmup (600s, engine-build note), delete (confirm or --force), upload, download, rescan — and an interactive management loop when invoked bare on a terminal (pick model -> toggle/device/rate/warmup/delete, refreshed each round). Docs and help note that `enabled` is the autolaunch configuration; the API has no live running status. Docs: the bundling page's deploy section now covers --push, `blueye models`, the drone.cv_models snippet, and the Blunux Web App (Computer Vision tab) as the manual alternative; new blueye.sdk.cv_models reference page + nav entry. +42 tests (SDK client incl. multipart/error-body assertions, models subcommands, interactive loop via the prompter seam, push flow incl. graceful failure), 418 total. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Surfaces the documented drone.logs workflow as a CLI built-in: - `blueye logs list` — table of the drone's binary logs (name, time, max depth, size, dive flag). - `blueye logs download [NAME ...] [--latest N | --all] [-o DIR]` — downloads .bez files; unknown names error listing what is available. - Bare `blueye logs` on a terminal opens an interactive picker: the table plus a checkbox multi-select and a destination prompt. The Prompter seam gains a checkbox method (questionary.checkbox interactively; a CliError naming the flags non-interactively). - Connects to the drone as an observer (connect_as_observer=True), so no control is taken and no SDK changes were needed; an unreachable drone fails with the friendly message and exit 1. The connection is released on exit. - The --drone-ip/--timeout parent parser and the failure-translation helper are lifted into commands/_common.py, now shared with the models command. Verified against the bench drone (list + `--latest 1` downloaded a real 2.5 MiB .bez) and an unreachable address. +14 tests (432 total); docs gain a "From the command line" section in the logs guide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`blueye logs download ... --mcap` converts each downloaded .bez to a Foxglove-ready .mcap next to it; the interactive picker offers the same conversion after selecting logs. The converter (commands/logs/mcap.py) is adapted from examples/foxglove_bez_to_mcap.py: a first LogStream pass anchors the dive start time (last record's wall clock minus its monotonic delta, so logs where the clock was set mid-dive stay continuous), a second pass writes every protobuf message via mcap_protobuf.writer. mcap-protobuf-support joins the [cli] extra (and dev group); the --mcap path gates on it at runtime with the standard install guidance, so the rest of the logs command works without it. Verified against the bench drone: `blueye logs download --latest 1 --mcap` produced a valid 8.5 MB .mcap (217k messages, correct MCAP magic) from a real dive log. +6 tests incl. a real protobuf-built .bez round trip (438 total); the Foxglove doc gains a one-step download-and-convert tip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ndpoint Applies sindrehan's review suggestions: - The CLI is no longer optional: rich, questionary, and the tomli backport move into the core dependencies, so `blueye` (including the models and tools commands) works after a plain `pip install blueye.sdk`. The `[cli]` extra now carries only the heavyweight onnx dependency needed by bundle-model, which keeps gating on it with the existing install guidance (verified in a fresh no-extra venv). - CLI documentation consolidated into a single docs/cli.md with one section per command (bundle-model, models, third-party tools), replacing bundling-cv-models.md and extending-the-cli.md in the nav. The "Future work: pip-installable plugins" section is dropped from the docs (the design note lives in the blueye.sdk.cli.commands docstring); the built-in-command recipe becomes a pointer to that docstring. - The deploy section now documents the raw HTTP upload endpoint (curl -F "file=@pkg.zip" http://192.168.1.101/api/cv-models/upload) alongside the Web App, for uploads without the SDK installed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # pyproject.toml
Follow-up to the review changes merged from jp-pino/bundle-model-cli: - The logs command joins the consolidated docs/cli.md as a "Downloading dive logs" section (incl. --mcap); the logs guide and the Foxglove doc keep short pointers to the CLI page. - The quick start gains a "Try the command line interface" section showcasing `blueye logs download --latest 1` with links to the CLI page. - With rich/questionary in the core dependencies, the logs command declares requires=() (the --mcap path keeps its runtime gate); mcap-protobuf-support stays in the [cli] extra alongside onnx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…example Follow-ups on the logs command: - New `blueye logs convert <file.bez ...> [-o DIR]` converts already-downloaded logs to .mcap without touching the drone (dispatch happens before the observer connection; same runtime mcap gate). - Interactive view redesigned: instead of printing the full table and then a duplicate checkbox list, it is now a single scrollable multi-select table (each choice is a column-aligned row under one header line) with type-to-filter (questionary use_search_filter), sorted descending alphabetically — newest logs first. An explicit instruction string works around questionary 2.1.1 showing <ctrl-a> for both toggle-all and invert (the real invert binding is ctrl-i/tab). - New --dives-only / --since / --until filters shared by list, download, and the interactive view; list output is sorted descending too. - The example converter (examples/foxglove_bez_to_mcap.py) is retired in favor of the first-party command: the Foxglove doc now walks through `blueye logs download --latest 1 --mcap` and `blueye logs convert`, and mcap-protobuf-support leaves the [examples] extra (its only consumer). Verified against the bench drone: filtered+sorted list, download, and a local `blueye logs convert` producing a valid .mcap (1.19M messages). +13 tests (451 total); docs build clean with the embed removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Works around a second questionary 2.1.1 search-filter bug (user-found): with a filter active, ctrl-a (toggle all) and ctrl-i/tab (invert) operated on every choice — selecting files not even in view — because questionary's handlers iterate ic.choices instead of ic.filtered_choices. The prompter now replaces both key bindings on the constructed prompt with versions scoped to the visible rows: toggle-all selects/deselects only what the filter shows, invert flips only the visible rows, and selections hidden by the filter are left untouched. The selection logic lives in pure helpers (_toggle_all_visible, _invert_visible) with unit tests, plus an integration test asserting the rebinding attaches to a real questionary prompt. +5 tests (456 total). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prompt_toolkit raises NoConsoleScreenBufferError when questionary builds a prompt without a console; the test now uses a pipe input and DummyOutput app session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses the Copilot review on #220: names/--latest/--all are now mutually exclusive instead of silently prioritized, and --latest must be at least 1 rather than succeeding with an empty selection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The BlueyeCV package format gains four optional informational top-level fields in model_meta.json, so packages stay self-describing wherever they travel (drone web UI, Blueye App, Blueye Cloud). bundle-model now collects them: --model-version defaults to "1.0.0" (prompted after the name), and description/author/license sit behind an optional "package details" prompt, each answerable with a flag. build_meta emits the fields between name and preprocessing; validate_meta type-checks them. Non-interactive runs omit the optional fields instead of failing on the empty prompt default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CvModels.list() now sorts by directory so the docstring matches the behaviour and callers get a deterministic order. - Content-Disposition filename parsing no longer over-captures when the header carries additional parameters. - ui.py docstring cross-references point at the actual commands.bundle_model module paths. - The interactive device prompt only offers the four devices the drone exposes; cpu/coreml stay reachable via an explicit --runtime-device for local be-cv testing, with a printed note when chosen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add `blueye logs` command for listing and downloading dive logs
Summary
Adds the SDK's first console script: a
blueyeumbrella command whosebundle-modelsubcommand turns an exported ONNX model into a BlueyeCV-ready model package — a zip withmodel.onnxand an auto-generatedmodel_meta.jsonat its root, ready to unzip onto the drone.What it does
model_meta.json: infers the decoderoutput_formatfrom the output tensor shapes (all 8 detection formats: yolov2_grid, yolov5/v8 flat, v8-seg, e2e, e2e-seg, ssd_multi, detr) and detects single-object trackers from the input structure (2 image inputs → ostrack, 3 → mixformerv2, template/search sizes from the dims). Class labels and input size come from embedded Ultralytics metadata when present. Per-format preprocessing defaults match the reference packages (ImageNet mean/std for DETR/SOT, raw pixels for YOLOv2/SSD).runtime.enabled: true(--no-runtime-enabledto opt out).--yesaccepts all inferred defaults,--dry-runprints the JSON without writing.rich,questionary) are core dependencies; only the heavyweightonnxpackage lives in the optionalblueye.sdk[cli]extra, needed bybundle-model. A stdlib-only gate detects the missing extra and prints per-platform install guidance (uv vs pip, shell quoting for zsh/PowerShell, Python-version wheel hints) instead of an ImportError.Structure
New
blueye/sdk/cli/subpackage with testable seams:introspect.py(only module importing onnx),heuristics.py(pure inference logic),meta.py(spec-compliant JSON build + validation),bundle.py(streamed zip, atomic rename),prompts.py(Prompter protocol: questionary vs non-interactive),ui.py,main.py(argparse umbrella + dependency gate). Nothing is imported fromblueye/sdk/__init__.py, so the SDK still imports without the extra; theblueye/PEP 420 namespace layout is untouched.Testing
onnx.helpermodels (incl. external-data and dynamic dims), meta validation per spec rule, zip content/atomicity, prompt seams, andmain()end-to-end. Full suite green apart from the pre-existingconnected_to_dronehardware tests.be-cv, which detecteddog (47.0%)on the test image.Docs
New "Bundling CV models" page + mkdocs nav and
blueye.sdk.clireference entries.🤖 Generated with Claude Code