Skip to content

Merging branch 'dev' into main for release v(Nelson's Sparrow) - #48

Merged
SanjaySoniLV merged 39 commits into
mainfrom
dev
May 15, 2026
Merged

Merging branch 'dev' into main for release v(Nelson's Sparrow)#48
SanjaySoniLV merged 39 commits into
mainfrom
dev

Conversation

@SanjaySoniLV

Copy link
Copy Markdown
Owner

I'm merging my work on branch 'dev' into main. Here are the key changes:

Performance

  1. Batch SpeciesNet classifier runs all detections per image as one ONNX batch instead of one-at-a-time.
  2. Batch SAM-HQ segmentation with updated weights — same batched approach for mask generation.
  3. Bird crop cap is applied before SAM-HQ so over-cap detections don't waste segmentation work.
  4. Fast detector model migrated to MegaDetector v1000-cedar (YOLOv9 gelan-c @ 640). The "Accurate" mode still uses mdv5a.

Resilience & crash handling

  1. GPU/CPU failover for ONNX: new ResilientOnnxSession + ProviderCoordinator automatically falls back to CPU when a GPU provider misbehaves, and can recreate the session on transient failure.
  2. Retry errored images: the Analyze Folders dialog has a new "Re-attempt errored images" toggle that drops Error rows and re-runs them, reusing pre-error similarity values where possible.
  3. OS shutdowns no longer reported as crashes: a new shutdown_watch module hooks WM_ENDSESSION (Windows), NSWorkspaceWillPowerOffNotification (macOS), and SIGTERM/SIGHUP (Linux) so PC reboots, logoffs, and OS upgrades stop generating false "did not shut down cleanly" dialogs and phantom crash reports. Crash reports now carry an exit_reason field.

Species detection

  1. Species detection got a richer taxonomy mapping and assorted UI polish (commit 2501522).
  2. New separate "Identify bird species & family" toggle (can disable classifier for non-NA photographers; detection / quality / culling still run).

Build & distribution

  1. Build attestation: official CI builds now ship a cryptographically signed build_attestation.json. The telemetry layer sends HMAC headers (X-Kestrel-Build-Meta, X-Kestrel-Build-Sig) for the official auth tier; unofficial builds fall back to the legacy X-Kestrel-Key.
  2. macOS quality model: re-saved Keras quality model for XLA / Apple-Silicon compatibility (utils/resave_quality_model.py).

Code quality

  1. Leveled logging API (DEBUG/INFO/WARN/ERROR) via KESTREL_LOG_LEVEL; the ~140 free-form print/log calls all route through level-specific helpers, and per-image / per-detection trace lines moved to DEBUG so real warnings stay visible.
  2. CSS refactor: the 4,388-line visualizer.css is now 18 feature-scoped files under analyzer/css/. No visual changes.
  3. Dead code removed across 10 files; DEVELOPMENT.md and README.md rewritten to match current architecture (no more legacy PyQt/visualizer split).

Testing & CI

  1. Test suite grew from ~110 to 400+. New categories: full ML model integration tests (bird classifier, megadetector, quality, SAM-HQ, SpeciesNet, RAW decode, EXIF), unit tests for ratings/reject-move/settings/speciesnet filtering, legacy DB and settings migration compat tests with captured fixtures from 5 release tags, a pywebview JS↔Python bridge UI test (analyzer/tests/ui/test_pywebview_api.py), and four security regression suites.
  2. Dev CI workflows now run full pytest pre-build plus --validate against the frozen PyInstaller binary on both Windows and macOS.

CLI (new in this PR)

  1. All "Advanced Analysis Settings" exposed as CLI flags. Useful for headless / scripted analysis runs.
  2. Dependency refresh: rawpy 0.27.0, pyinstaller 6.20.0, pywebview 6.2.1, requests 2.34.2, PyQt6 6.11.0.

Merging this into dev will trigger automatic build CI and enable the ability to run dev CI in the future; these CI builds should have a robust suite of tests now to make sure things are working.

SanjaySoniLV and others added 30 commits April 25, 2026 09:45
- Added a new `ProviderCoordinator` class to manage GPU/CPU state transitions and handle inference failures.
- Introduced `ResilienceConfig` for configuring resilience behavior based on user settings.
- Created `ResilientOnnxSession` to wrap `onnxruntime.InferenceSession`, allowing for session recreation on failure.
- Updated `AnalysisPipeline` to utilize the new resilience features, including retrying errored images and preserving computed values.
- Enhanced `QueueManager` to support retrying errored images during analysis.
- Modified settings utility to include GPU resilience options.
- Updated visualizer to indicate errored images and provide a UI option for re-attempting analysis on them.
- Added CSS styles for errored images in the visualizer.
…ports

Previously every startup where the prior session didn't explicitly mark
itself clean was treated as a crash, so PC reboots / logoffs / power loss
all surfaced an alarming "did not shut down cleanly" dialog and generated
a phantom crash report.

Adds an `app_session_exit_reason` state machine (clean / os_shutdown /
crash / unknown) and a new `shutdown_watch` module with platform-specific
listeners (SIGTERM/SIGHUP on Linux, NSWorkspaceWillPowerOffNotification on
macOS, hidden-window WM_QUERYENDSESSION/WM_ENDSESSION pump + console-ctrl
fallback on Windows). The frontend now shows no dialog for `os_shutdown`,
the original alarming wording for `crash`, and a softer prompt for
`unknown` (SIGKILL, power loss, pre-upgrade installs). Crash reports
include `exit_reason` so server-side analytics can drop the noise.
…ling-Xido1

Distinguish OS shutdowns from real crashes to suppress false reports
…tering

- Implemented unit tests for the ratings module, covering profile thresholds, quality to rating mappings, and image display ratings.
- Added tests for the reject move functionality, ensuring proper handling of file movements and undo operations.
- Created tests for settings utility functions, validating coercion and sanitization of various input types.
- Developed tests for speciesnet filtering functions, including box IoU calculations and detection filtering based on confidence scores.
Generated authentic settings.json fixtures by importing each tag's own
analyzer/settings_utils.py and calling save_persisted_settings() with a
seeded payload, then reading the resulting file back from disk.

Of the 17 release tags in this repo, only 5 contain settings_utils.py
(introduced at Lincolns-Sparrow, 2026-03-15). After dedup by unique
key-set:

  - settings_v_Lincolns-Sparrow.json (oldest, no sanitizer)
  - settings_v_Kentucky-Warbler.json (strict sanitizer, drops unknown keys)
  - settings_v_Gambels-Quail.json (sanitizer + forward-compat passthrough;
    same key-set as Lincolns-Sparrow, kept for value-level coercion coverage)
  - settings_v_Gambels-Quail_minimal.json (counters-only edge variant)
  - settings_v_Gambels-Quail_with_future_keys.json (variant used by
    TestForwardCompatibility)

KEY_EVOLUTION.md documents tag-to-key-set mapping, value-level coercion
differences, monotonic-counter coverage, and forward-compat passthrough
per tag.

All 19 parametrized tests in tests/compat/test_settings_migration.py now
pass (previously skipped); all 44 tests/unit/test_settings.py tests still
pass.

The companion legacy_databases task is blocked: every ONNX/PyTorch model
in the repo is a Git LFS pointer and the LFS server in this environment
returns HTTP 502 on every fetch, so the pipeline cannot run at any tag.
legacy_databases/SCHEMA_NOTES.md documents the blocker, what was tried,
and exactly what to do to pick it up in a working LFS environment.

https://claude.ai/code/session_014JRoQ3ztrLkm1F7ZoywPqu
…ure-generation

test fixtures: legacy database schemas captured from release tags
…s-hAHdT

Add legacy settings fixtures and schema evolution documentation
…to perform full validation in the CI environment.
…-image traces

Routes the ~140 free-form print/log calls through level-specific helpers in
settings_utils, gated by the KESTREL_LOG_LEVEL env var (default INFO). Per-image
and per-detection traces (decode-queue progress, SpeciesNet/SAM-HQ diagnostics,
read_raw_full debug-meta, the [DEBUG] console.log lines in visualizer.js and
culling.html) all drop to DEBUG so the runtime log file and crash reports
surface real warnings and errors instead of trace noise. [bracket] area tags
are preserved in messages for grep-filtering. Also fixes a latent import bug
where kestrel_analyzer's relative-import fallbacks failed in the cli.py
invocation path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…lyzer/css/

Pure mechanical move — no semantic style changes. The 4,388-line stylesheet now
lives as one file per feature (base, layout, toolbar, grid, folder-tree, welcome,
timeline, legal-banner, tutorial) plus a dialogs/ subfolder (scene, settings,
analyze, live-analysis, feedback-consent, donate, tutorial-chooser, base).

visualizer.html now declares 18 <link> tags in cascade-preserving source order
(base.css first so :root vars are visible to the rest). PyInstaller specs swap
the single ('visualizer.css', '.') entry for ('css', 'css') so future additions
to the folder auto-bundle.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…vc-runtime from cross-platform requirements

Version audit ahead of Nelson's Sparrow release:

  rawpy        0.26.1   -> 0.27.0   (newer CR3/CR2 demosaic improvements)
  pyinstaller  6.18.0   -> 6.20.0   (build tooling)
  pywebview    6.1      -> 6.2.1    (minor; JS bridge stability fixes)
  requests     2.33.0   -> 2.34.2   (security / patch)
  PyQt6        6.10.2   -> 6.11.0   (minor)

msvc-runtime is Windows-only by definition; it never belonged in
requirements.txt (generic / Linux). Kept in requirements-windows.txt.

Held back (would benefit from a dedicated validation pass after release):
  onnxruntime  1.23.2  -> 1.26.0   (cross-minor; EP behavior changes)
  numpy        2.1.3   -> 2.4.4    (CLAUDE.md still references TF<2.2 caveat)
  opencv       4.11.0.86 -> 4.13.0.92  (cv2.imwrite history; see CLAUDE.md)
  pandas       2.2.2   -> 3.0.3    (major-version bump)
Brings the cli.py surface to parity with the "Advanced Analysis Settings"
section of the in-app Analyze Folders dialog (visualizer.html:651). Until
now only --gpu, --detection-threshold, --parallel-prefetch (and an advanced
--detector-name) were exposed.

New flags:
  --wildlife-model-mode {fast,accurate}
      GUI-aligned detector selector. Maps fast -> mdv1000-cedar,
      accurate -> mdv5a. --detector-name still works and takes precedence.
  --max-bird-crops INT          (1-20)
  --exposure-quality {lenient,balanced,aggressive}
  --scene-time-threshold FLOAT  (0-60s)
  --thumbnail-max-width INT     (400-2400 px)
  --thumbnail-jpeg-compression FLOAT  (0.50-1.00)
  --wildlife / --no-wildlife
  --species-detection / --no-species-detection
  --retry-errored / --no-retry-errored

Pipeline plumbing:
  AnalysisPipeline.process_folder() now accepts three optional override
  parameters (exposure_quality, thumbnail_max_width,
  thumbnail_jpeg_compression). When None, behavior is unchanged - they
  fall back to settings.json. When passed, they override the persisted
  values without mutating settings.json. The other knobs were already
  process_folder parameters; they're now wired up to argparse.

Tests:
  analyzer/tests/unit/test_cli_args.py - 35 unit tests covering argparse
  defaults, per-flag parsing, enum rejection, --detector-name vs
  --wildlife-model-mode precedence, full-command-line wiring through
  main(), clamping at the CLI layer, and pipeline acceptance of the new
  override kwargs.
claude and others added 9 commits May 15, 2026 05:11
The batch classifier preprocess+predict block was inlined twice (commits
0c1bfc8 / 4efb5de "Add batch processing for speciesnet classifier" appear
to have been applied on top of each other). Both blocks ran
classifier.preprocess_many + predict_many over the same animal_dets, and
the second block's classifier_preds_by_idx = {} reset discarded the first
block's work.

Net effect: every image ran the SpeciesNet classifier through the ONNX
batch twice, doubling per-image classifier inference time and erasing
roughly half of the headline "batch processing" speedup. Removes the
second duplicate block; keep only the first.
…sure correction

The auto-bright branch called raw.postprocess() unconditionally and then
immediately overwrote the result with a second exp_shift postprocess() when
exp_correction != 0.0. The first call's RGB array was discarded but its
demosaic cost (~1-2 seconds per 45MP RAW) was wasted on every culling
preview where the user had any exposure slider movement.

Branch on exp_correction first so postprocess() runs exactly once.
When the OS closes our window during reboot/logoff, shutdown_watch
correctly writes app_session_exit_reason='os_shutdown'. But webview.start()
then returns normally, the main() finally block runs
_mark_session_clean_exit(), and that unconditionally clobbered the reason
to 'clean'. Result: telemetry would still report 'clean' on every
OS-driven shutdown, defeating the new exit-reason machinery added in
fd84732.

Skip the overwrite when the existing reason is already 'os_shutdown'
(set by shutdown_watch) or 'crash' (set by the top-level crash handler).
_load() returned out of the candidate loop after touching the first file
that existed, regardless of whether meta/sig were valid. So an official
build where the first candidate path held a corrupt or empty attestation
would silently fall through to legacy X-Kestrel-Key auth even though
later candidates may have had a valid bundle.

Also moved the try/except inside the loop so malformed JSON in one
candidate no longer aborts the search across all paths.
…ION 2.0.2 -> 2.0.4

Updates analyzer/models/quality_normalization_data.csv to the v3 percentile
table. The new curve shifts the lower tail upward (p0 0.0141 -> 0.0277)
and slightly compresses the high tail (p99 0.9999 -> 0.9998), so the
mapping of raw quality model output to percentile -> star rating moves
accordingly. No code changes needed; ml/quality.py reads the file at
load time.

Existing kestrel_metadata.json files written by older builds keep their
recorded kestrel_version, so downstream version-gating logic continues to
flag pre-2.0.4 .kestrel folders as outdated and re-runs them against the
new curve.
The original TF-era constraint (numpy<2.2) is gone — TensorFlow,
torch, and torchvision were removed when SpeciesNet was inlined and
SAM-HQ was migrated to ONNX Runtime. ONNX Runtime 1.23+ and the
remaining ML stack (opencv, pandas, pillow) all support numpy 2.4.x.

Local pre-build pytest (-m "not ui and not integration and not e2e")
passes 312/313 against numpy 2.4.4 with the same single pre-existing
test_reject_move case-sensitivity failure that fails on origin/dev.
Until now --api-probe loaded a synthetic 15-line probe HTML and the UI
test was excluded from CI entirely via -m "not ui". The probe proved the
JS<->Python bridge mechanism worked in isolation, but never proved that
the production visualizer.html + visualizer.js bundle actually saw
window.pywebview.api - which is the bug class we'd want to catch on a
frozen build before users do.

Changes:

1) visualizer.py: new --probe-target {synthetic,visualizer} flag
   (default synthetic for back-compat). 'visualizer' mode spins up the
   same local HTTP server the desktop app uses, points pywebview at
   http://127.0.0.1:<port>/, and waits for the JS side to call
   Api.report_bridge_ready. Probe payload now carries probe_target so
   the result JSON tells you which mode produced it. The static-root
   chdir logic was extracted into _chdir_to_static_root() so the probe
   resolves _internal/visualizer.html on a frozen PyInstaller exe the
   same way the real app does.

2) visualizer.js: one-line side-effect-free call to
   window.pywebview.api.report_bridge_ready() right after apiReady
   becomes true. Outside probe mode it returns a payload and the caller
   ignores it; inside probe mode it fires the threading.Event the
   Python side is waiting on.

3) tests/ui/test_pywebview_api.py: parametrized over both targets
   ('synthetic' on port 8799 with 30s deadline, 'visualizer' on port
   8798 with 60s deadline - the real bundle has CSS/JS/taxonomy
   payloads to fetch on boot). Conftest still skips the whole module
   cleanly when no display / no webview.

4) Both dev CI workflows (Windows + macOS): added a "UI probe (real
   visualizer.html) against frozen binary" step after --validate that
   runs ProjectKestrel(.exe) --api-probe --probe-target visualizer and
   asserts ok=true. continue-on-error so a probe miss doesn't block
   artifact publish, but it surfaces as a workflow warning AND uploads
   ui-probe-result.json as an artifact for postmortem.
PyQt6 has no imports anywhere in the codebase - the legacy PyQt GUI was
removed when the app consolidated onto pywebview + visualizer.html.
DEVELOPMENT.md and the test_session_lifecycle docstring already document
that PyQt is gone; the package was just left pinned in all three
requirements files.

pywebview uses platform-default backends (EdgeWebView2 on Windows,
WKWebView on macOS, GTK on Linux), not the optional Qt backend.
PyInstaller specs don't reference PyQt6 in hiddenimports or excludes.

Drops ~100MB from the dev pip install and shrinks PyInstaller's pickup
surface (PyInstaller does scan installed-but-unused packages).
…s-Nfky2

Pre-release: package version audit + full CLI parity with Advanced Analysis Settings
@SanjaySoniLV
SanjaySoniLV merged commit 4ab8e1e into main May 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants