Skip to content

feat(detections): accept optional crop image for faster smoke checks#575

Open
MateoLostanlen wants to merge 9 commits intomainfrom
feat/detection-crop-upload
Open

feat(detections): accept optional crop image for faster smoke checks#575
MateoLostanlen wants to merge 9 commits intomainfrom
feat/detection-crop-upload

Conversation

@MateoLostanlen
Copy link
Copy Markdown
Member

Summary

  • Add an optional crop upload to POST /detections so cameras can ship a tight, pre-cropped view of the smoke alongside the full frame. The goal is to give reviewers a faster signal when triaging sequences — they can confirm or dismiss a smoke from the crop without waiting on the full frame.
  • Persist the crop reference on Detection via a new crop_bucket_key column (alembic migration chained onto the current baseline) and surface it as crop_url on GET /detections/{id}/url and GET /sequences/{id}/detections.
  • The crop endpoint param is opt-in: existing camera clients keep working unchanged, and /sequences/{id}/detections accepts with_crop=false to skip the extra presign round-trips when the consumer doesn't need crops.

Notable changes

  • endpoints/detections.py: authorize the pose before uploading to S3 so a 403 no longer leaves an orphan object in the bucket. Crop upload happens only after authorization passes.
  • services/storage.py: upload_file takes a key_prefix so frame and crop with identical bytes (same SHA prefix + same second) don't collide on the same bucket key. Crop keys are prefixed crop_.
  • client/pyroclient: create_detection(...) gains a crop: bytes | None kwarg; fetch_sequences_detections(...) gains with_crop: bool = True. Also fixed the camera/pose image uploads to send image/jpeg instead of mislabelling JPEG bytes as image/png.

Adds a nullable crop_bucket_key column to detections (model + migration),
extends POST /detections/ to accept an optional `crop` file alongside the
main frame, and exposes the resulting presigned URL as `crop_url` on
GET /detections/{id}/url. Cameras that do not send a crop continue to
work unchanged.

Also updates pyroclient.create_detection to accept an optional `crop`
argument and aligns update_last_image / update_pose_image content types
with the JPEG payloads actually sent.
PR #572 collapses prior migrations into a single baseline revision
9700bbccb2f1. Repoint this migration's down_revision so it applies
cleanly on top of the new baseline.
…th tests

Move the pose ownership check ahead of any S3 upload so a forbidden
request with another camera's pose_id no longer leaves the main image
(or, after this branch, the optional crop) orphaned in the bucket.

Add tests covering the new crop behavior:
- crop upload persists crop_bucket_key on the detection,
- omitting crop leaves crop_bucket_key null,
- GET /detections/{id}/url returns crop_url when a crop is present and
  null otherwise,
- a 403 from a foreign pose_id never invokes upload_file (verified via
  monkeypatched stub).

DET_TABLE in conftest gains an explicit crop_bucket_key=None so the
existing detection JSON-equality assertions still match.
The platform consumes detections through GET /sequences/{id}/detections
(not the per-detection /url endpoint), so the crop_url addition needs to
land on that response too. Extend DetectionWithUrl with an optional
crop_url and populate it from crop_bucket_key when present.

Add a with_crop query parameter (default true) so callers that don't
need crops can skip the extra S3 head requests that get_public_url
performs. Mirror the parameter on pyroclient.fetch_sequences_detections.

Tests: assert crop_url is presigned when a crop exists, that with_crop=
false suppresses it, and that the existing equality check now also
strips crop_url alongside url.
… bytes

upload_file derives keys from camera_id, second-precision timestamp, and
the SHA-256 prefix of the bytes. When a crop happens to be byte-identical
to the frame (e.g. a full-frame crop sent in the same request), both
calls produced the same key, the second upload overwrote the first, and
crop_url ended up pointing at the frame.

Add an optional key_prefix parameter to upload_file and call the crop
upload with key_prefix="crop_". Regression test asserts the two keys
remain distinct when the bytes match.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

❌ Patch coverage is 94.44444% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.49%. Comparing base (a60f909) to head (b2015f8).

Files with missing lines Patch % Lines
client/pyroclient/client.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #575      +/-   ##
==========================================
- Coverage   88.49%   88.49%   -0.01%     
==========================================
  Files          51       51              
  Lines        2182     2190       +8     
==========================================
+ Hits         1931     1938       +7     
- Misses        251      252       +1     
Flag Coverage Δ
backend 88.56% <100.00%> (+0.02%) ⬆️
client 87.30% <75.00%> (-0.51%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MateoLostanlen and others added 3 commits April 27, 2026 08:52
…rts, prefix assertion

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…307a1d6d490d

The previous down_revision (9700bbccb2f1) does not exist in the migration
history, so `alembic upgrade head` failed at backend boot and the docker
healthcheck never went green. Tests pass because they use
SQLModel.metadata.create_all and bypass alembic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant