Skip to content

fix(mobile): crop label scans to a real framing guide; add library upload to label mode - #2095

Merged
CodeWithCJ merged 2 commits into
CodeWithCJ:mainfrom
JonZavialov:fix/label-scan-crop-to-guide
Aug 13, 2026
Merged

fix(mobile): crop label scans to a real framing guide; add library upload to label mode#2095
CodeWithCJ merged 2 commits into
CodeWithCJ:mainfrom
JonZavialov:fix/label-scan-crop-to-guide

Conversation

@JonZavialov

@JonZavialov JonZavialov commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Description

What problem does this PR solve?

Label scans upload the full camera frame, so a label occupying a modest share of the frame reaches the vision model too small to read after encoder downscaling — and instead of failing, models fill the gaps with plausible wrong values (#2093, maintainer-invited PR). Label mode also lacked the gallery upload photo mode already has.

How did you implement the solution?

Both label paths now share one flow: get an image (system camera or library), crop it to the label with the system crop editor's adjustable handles, downscale (longest edge capped at 1600 px), scan. The pre-scan preview renders the entire image (contain on a black letterbox), so what the user confirms is exactly what the model receives.

Linked Issue: Closes #2093

How to Test

  1. Label mode → shutter → system camera → take a photo where the label is only part of the frame → crop to the label with the handles → the preview shows the full cropped image → Use Photo → correct values.
  2. Label mode → gallery button → pick an existing photo → same crop step → correct values.
  3. Confirm the preview letterboxes (full image visible) rather than filling the screen.

PR Type

  • Issue (bug fix)
  • New Feature
  • Refactor
  • Documentation

Checklist

All PRs:

  • [MANDATORY - ALL] Integrity & License: I certify this is my own work, free of malicious code, and I agree to the License terms.

New features only:

  • [MANDATORY for new feature] Alignment: I have raised a GitHub issue and it was reviewed/approved by maintainers or it was approved on Discord.

Frontend changes (SparkyFitnessFrontend/):

  • [MANDATORY for Frontend changes] Quality: I have run pnpm run validate and it passes.
  • [MANDATORY for Frontend changes] Translations: I have only updated the English (en) translation file.

Backend changes (SparkyFitnessServer/):

  • [MANDATORY for Backend changes] Code Quality: I have run typecheck, lint, and tests. New files use TypeScript, new endpoints have Zod schemas, and new endpoints include tests.
  • [MANDATORY for Backend changes] Database Security: I have updated rls_policies.sql for any new user-specific tables.

UI changes (components, screens, pages):

  • [MANDATORY for UI changes] Screenshots: I have attached Before/After screenshots below.

Mobile changes (SparkyFitnessMobile/):

  • [MANDATORY for Mobile changes] Tested on device or emulator: I have verified the changes work on iOS or Android.

Screenshots

Click to expand Screenshot_1786452392 Screenshot_1786452416 Screenshot_1786452731

Notes for Reviewers

Verified on an Android emulator against a live self-hosted instance (ollama vision service, gemma3:4b): capture → system crop → scan returned every label value correctly; the same photo scanned full-frame previously returned invalid JSON or hallucinated products. Pipeline-level reproduction with a public image is in #2093.

Tests: lint clean at --max-warnings 0; all 281 suites pass on this code (274 in a parallel run; 7 suites whose jest workers were SIGTERMed by the build host's OOM killer — unrelated to the change — pass 135/135 when re-run serially; FoodScanScreen.test.tsx passes in isolation).

Dependency note: expo-image-manipulator is added for the post-crop downscale (longest edge 1600 px), which bounds upload size without costing accuracy.

Summary by CodeRabbit

  • New Features

    • Added support for selecting nutrition-label photos from the device’s photo library.
    • Enabled image editing when capturing nutrition-label photos.
    • Added automatic image preparation to improve upload compatibility and efficiency.
  • Bug Fixes

    • Improved nutrition-label photo previews with better scaling and contrast.
    • Added safer handling for photo selection and processing errors.

…load

The label capture uploaded the full camera frame. Vision models misread
labels that occupy a small share of the frame -- the encoder's downscale
makes the digits illegible and the model fills gaps with plausible values
instead of failing (CodeWithCJ#2093). Label mode also lacked the gallery upload photo
mode has, so an existing photo could not be used at all.

- Label mode gets a portrait framing guide (300x400), and the capture is now
  CROPPED to it (plus an 8% tolerance margin) before upload, via
  expo-image-manipulator: cover-fit inverse mapping from view points to photo
  pixels, clamped to bounds, then capped at 1600px longest edge. The existing
  preview screen therefore shows exactly the region that will be analyzed, so
  a missed framing is a visible Retake, not silently wrong nutrition.
- Library button added to label mode with allowsEditing, so the system crop
  UI covers photos taken outside the app.

The mapping is a pure function (src/utils/labelCrop.ts) with 6 unit tests:
round-trips against the forward cover mapping, aspect preservation, padding
symmetry, bounds clamping, degenerate input. Full mobile suite passes (282
suites, 4718 tests) and lint is clean at --max-warnings 0.

Pipeline-level evidence that cropping is the fix, measured through the real
scan endpoint (self-hosted ollama vision service): a real 4032x3024 photo
with the label at ~1/4 frame returned invalid JSON once and a hallucinated
product the next run; the same photo cropped to the label region returned
every field exactly, 3/3 runs, deterministic.

Simulator verification was attempted and is honestly incomplete: the only
available hardware has VT-x disabled, and an unaccelerated qemu emulator
ANR-looped in system_server across API 35 and API 31 images before the app
reached interactive UI. Details in the PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added bug Something isn't working mobile labels Aug 11, 2026
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

PR Validation Results

Change Detection

  • 📱 Mobile changes detected

✅ All checks passed. Thank you!

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f37ceb7e-2fd6-4173-ab91-f54d1fc8251f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Nutrition-label scanning now supports editable camera and library photo selection. Selected images are resized, compressed, and encoded before use. Label previews preserve the full image within a black container, and barcode guide spacing uses a shared constant.

Changes

Nutrition label capture

Layer / File(s) Summary
Label image preparation
SparkyFitnessMobile/package.json, SparkyFitnessMobile/src/screens/FoodScanScreen.tsx
Added expo-image-manipulator and shared label-photo processing for resizing, JPEG compression, base64 preservation, and validation.
Camera and library label controls
SparkyFitnessMobile/src/screens/FoodScanScreen.tsx
Changed label capture to editable camera selection, added editable library selection, and added a library-picker button.
Label preview and guide layout
SparkyFitnessMobile/src/screens/FoodScanScreen.tsx
Updated label previews to use a black background with contain scaling. Replaced the barcode guide’s literal margin with GUIDE_BOTTOM_MARGIN.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FoodScanScreen
  participant SystemImagePicker
  participant ExpoImageManipulator
  User->>FoodScanScreen: select camera or library
  FoodScanScreen->>SystemImagePicker: launch editable picker
  SystemImagePicker-->>FoodScanScreen: return selected photo
  FoodScanScreen->>ExpoImageManipulator: resize and compress photo
  ExpoImageManipulator-->>FoodScanScreen: return prepared image data
  FoodScanScreen-->>User: display label preview
Loading

Suggested labels: enhancement, frontend

Suggested reviewers: codewithcj

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #2093 by adding adjustable cropping and library uploads for label scans, then sending the cropped image.
Out of Scope Changes check ✅ Passed The dependency and screen changes directly support cropping, image preparation, preview accuracy, and library uploads described in issue #2093.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly summarizes the main mobile changes: adjustable label cropping and library upload support.
Description check ✅ Passed The description covers the problem, implementation, testing steps, issue link, screenshots, checklist, and reviewer notes.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
SparkyFitnessMobile/src/screens/FoodScanScreen.tsx (1)

664-666: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Show the complete processed crop in the preview.

The label crop has a 3:4 aspect ratio. The full-screen preview is much taller. resizeMode="cover" crops the left and right sides again, so the user cannot verify the complete image that scanNutritionLabel receives.

Use resizeMode="contain" and a background color for this preview.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SparkyFitnessMobile/src/screens/FoodScanScreen.tsx` around lines 664 - 666,
Update the capturedPhoto preview in the FoodScanScreen render to use
resizeMode="contain" instead of "cover", and add a background color to the
surrounding preview or image styling so the complete processed crop remains
visible without side-cropping.
🧹 Nitpick comments (1)
SparkyFitnessMobile/src/screens/FoodScanScreen.tsx (1)

375-388: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cap library-selected label images before analysis.

The picker path skips LABEL_MAX_DIMENSION, so high-resolution images can create unnecessarily large in-memory base64 values and requests. Set base64: false, process asset.uri with ImageManipulator using the same dimension cap, JPEG format, and compression, then store the processed base64. The picker already documents JPEG base64 output, and server dispatch corrects sniffable MIME types, so MIME relabeling is not the issue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SparkyFitnessMobile/src/screens/FoodScanScreen.tsx` around lines 375 - 388,
Update the library-picker flow in FoodScanScreen around launchImageLibraryAsync
and asset handling to request base64: false, then process asset.uri through
ImageManipulator with LABEL_MAX_DIMENSION, JPEG output, and the existing
compression setting before storing the resulting base64 and URI in
setCapturedPhoto. Preserve the current validation and error behavior while using
the processed image data for analysis.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SparkyFitnessMobile/src/utils/labelCrop.ts`:
- Around line 68-77: Update the endpoint calculations in the label-cropping
function so x0 and x1 are both clamped to [0, photoWidth], while y0 and y1 are
both clamped to [0, photoHeight]. Add tests covering guides fully outside the
left, right, top, and bottom edges, while preserving the existing minimum crop
dimensions.

---

Outside diff comments:
In `@SparkyFitnessMobile/src/screens/FoodScanScreen.tsx`:
- Around line 664-666: Update the capturedPhoto preview in the FoodScanScreen
render to use resizeMode="contain" instead of "cover", and add a background
color to the surrounding preview or image styling so the complete processed crop
remains visible without side-cropping.

---

Nitpick comments:
In `@SparkyFitnessMobile/src/screens/FoodScanScreen.tsx`:
- Around line 375-388: Update the library-picker flow in FoodScanScreen around
launchImageLibraryAsync and asset handling to request base64: false, then
process asset.uri through ImageManipulator with LABEL_MAX_DIMENSION, JPEG
output, and the existing compression setting before storing the resulting base64
and URI in setCapturedPhoto. Preserve the current validation and error behavior
while using the processed image data for analysis.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4370b64a-1d34-43a2-8408-34d730251281

📥 Commits

Reviewing files that changed from the base of the PR and between 77a9308 and 6b3e5f6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • SparkyFitnessMobile/__tests__/utils/labelCrop.test.ts
  • SparkyFitnessMobile/package.json
  • SparkyFitnessMobile/src/screens/FoodScanScreen.tsx
  • SparkyFitnessMobile/src/utils/labelCrop.ts

Comment thread SparkyFitnessMobile/src/utils/labelCrop.ts Outdated
@JonZavialov
JonZavialov marked this pull request as draft August 11, 2026 10:01
…ll preview

Field feedback from real use of the first revision: a fixed on-screen guide
still constrains how precisely the label can be framed, and differs from the
library path's adjustable system crop for no user-visible reason. Both paths
now share one shape: get an image (system camera or library), crop it to the
label with the system editor's draggable handles, downscale, scan.

This DELETES the custom coordinate machinery the first revision added --
labelCrop.ts, its 6 unit tests, the guide overlay, and the camera-layout
tracking. The system editor crops in image space, so there is no view-to-photo
mapping left to get wrong; the open review finding about asymmetric clamping
in computeGuideCropRect is resolved by that removal.

The pre-scan preview now renders resizeMode="contain" on a black letterbox
instead of "cover": what the user confirms is exactly the full image the
model receives, not a screen-filling crop of it.

Shared prep helper caps the longest edge at 1600px and guarantees base64
output for assets that arrive without it.

Verified: lint clean at --max-warnings 0; all 281 suites pass on this code --
274 in the parallel run plus the 7 whose workers were SIGTERMed by the build
host's OOM guard (26 kills logged during the run), re-run serially: 135/135.
The FoodScanScreen suite passes in isolation. Flow exercised end-to-end on a
Windows-hosted emulator against a live self-hosted instance: capture -> crop
-> scan returned correct label values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JonZavialov
JonZavialov marked this pull request as ready for review August 11, 2026 12:59
@JonZavialov

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@CodeWithCJ
CodeWithCJ merged commit 1d0812d into CodeWithCJ:main Aug 13, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working mobile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Label scan uploads the full uncropped frame — framing guide is cosmetic, and label mode lacks the upload/crop options photo mode has

2 participants