Skip to content

Grayscale monochrome AVIF BLE photo pipeline with text-region crop#3413

Draft
nic-olo wants to merge 5 commits into
devfrom
cursor/grayscale-avif-ble-pipeline-9235
Draft

Grayscale monochrome AVIF BLE photo pipeline with text-region crop#3413
nic-olo wants to merge 5 commits into
devfrom
cursor/grayscale-avif-ble-pipeline-9235

Conversation

@nic-olo

@nic-olo nic-olo commented Jul 11, 2026

Copy link
Copy Markdown
Member

Scope

BLE photos exist to be transferred to the phone for text recognition, not viewing. This reworks the glasses-side compressAndSendViaBle() pipeline around that:

  1. Grayscale luma pipeline (GrayscaleBleProcessor) — subsampled RGB_565 JPEG decode, row-wise luma extraction, contrast stretch, single-channel unsharp, bilinear resize. Every intermediate buffer is 1 byte/pixel instead of the old full-res ARGB decode → createScaledBitmap → RGB sharpen chain. Peak transient memory per BLE photo drops from ~36 MB to ~4 MB.
  2. Monochrome (YUV400) AVIF encode from the raw luma plane — new encodeAvifMono() in a Mentra fork of awxkee/avif-coder 1.7.3 (additive-only patch; same package/classes, drop-in AAR in asg_client/app/libs/, patch + rebuild instructions in asg_client/third_party/avif-coder-mono/). No Bitmap/RGBA round-trip anywhere; the bitstream carries no chroma planes. Also exposes AOM speed (cpu-used): BLE_AVIF_ENCODE_SPEED = 8 benchmarked ~2.6x faster than the plugin default on the software AV1 encode that dominates BLE photo latency (4–5.5 s of ~7.4 s end-to-end on MTK8766).
  3. Text-region ROI crop (TextRegionDetector) — Sobel edge-density grid on a ~320 px decode; crops to the concentrated text region (10% pad) so the byte budget concentrates on what OCR reads. Conservative: weak/spread signal or a >80%-area crop falls back to full frame; any detection failure degrades to previous behavior.

Kill switches: ENABLE_GRAYSCALE_BLE_PHOTOS (falls back to the legacy full-color YUV420 path, kept verbatim) and ENABLE_BLE_TEXT_ROI_CROP. Size logging now prints original and AVIF bytes/KB per photo.

No SDK API, transfer-protocol, WiFi-path, or gallery changes. Phone-side code is unchanged except a new test fixture.

Test evidence

  • Encoder-stack probe with the exact bundled versions (libheif 1.17.6 + AOM 3.8): monochrome encode round-trips (colorspace=monochrome, no chroma planes); speed=8 encoded 1920x1440 in 75 ms vs 198 ms host-side, with comparable size.
  • Independent decode check: avifdec (libavif + dav1d, the same decoder family the phone's HeifCoder bundles) decodes the mono output as Format: YUV400 into a correct 8-bit grayscale PNG.
  • scripts/check-android-compile.sh asg and full :app:testDebugUnitTest green, including new Robolectric suites for GrayscaleBleProcessor (aspect fit, contrast, ROI crop, gray rehydration) and TextRegionDetector (concentrated block detected, flat/noise frames rejected, source-coordinate mapping).
  • scripts/check-android-compile.sh bluetooth-sdk green; phone-side AvifExifStripperTest extended with a monochrome+EXIF fixture generated by the exact glasses encoder stack — strip + JPEG conversion pass.

Outstanding hardware verification

Requires physical devices (not possible from this environment):

  • Glasses: real encode time/size at each tier (⏱️ Compression took / ✅ Compressed photo for BLE logs), ROI crop behavior on real documents/scenes.
  • iOS: UIImage(data:) render of a monochrome AVIF on iOS 16/17/18 devices (Android decode is covered by the dav1d-family check above). If a specific iOS version misbehaves, flip ENABLE_GRAYSCALE_BLE_PHOTOS or ship YUV420 gray via the legacy path.
  • End-to-end OCR sanity check on a photographed document.
Open in Web Open in Cursor 

cursoragent and others added 5 commits July 11, 2026 06:14
BLE photos exist for on-phone text recognition, not viewing. Replace the
full-res ARGB decode + createScaledBitmap + RGB sharpen chain with a
1-byte/pixel luma pipeline (subsampled RGB_565 decode, row-wise luma
extraction, contrast stretch, luma unsharp, bilinear resize), cutting
peak transient memory per BLE photo from ~36MB to ~9MB. Legacy color
path kept behind ENABLE_GRAYSCALE_BLE_PHOTOS. Adds bytes/KB size and
dimension logging around the AVIF encode.

Co-authored-by: Nicolo Micheletti <michelettinik@gmail.com>
Forks awxkee/avif-coder 1.7.3 (additive only): new encodeAvifMono()
taking a raw 8-bit luma plane and emitting a true monochrome (YUV400)
AVIF via libheif heif_colorspace_monochrome, plus the AOM speed
(cpu-used) parameter upstream never exposes. Same package/classes as
upstream so existing encodeAvif/decode call sites are untouched. ABIs
trimmed to armeabi-v7a/arm64-v8a. Patch + rebuild instructions in
third_party/avif-coder-mono/; prebuilt AAR in app/libs/.

Validated against the exact bundled libs (libheif 1.17.6, AOM 3.8.1):
monochrome encode round-trips, and speed=8 cut encode time ~2.6x in a
1920x1440 synthetic document benchmark.

Co-authored-by: Nicolo Micheletti <michelettinik@gmail.com>
Route the grayscale BLE path through the forked encoder's
encodeAvifMono(): GrayscaleBleProcessor now hands its 1-byte/pixel luma
buffer directly to the encoder (LumaImage), eliminating the final RGBA
bitmap rehydration (~5-6MB at 1920x1440) so no color buffer exists
anywhere in the pipeline. New encodeAvifForBleMono() in
PhotoExifMetadataWriter reuses the BMFF EXIF injector for IMU metadata.
BLE_AVIF_ENCODE_SPEED=8 sets AOM cpu-used, ~2.6x faster than the plugin
default on the software AV1 encoder that dominates BLE photo latency.
Legacy full-color path unchanged behind ENABLE_GRAYSCALE_BLE_PHOTOS.
Adds Robolectric coverage for the luma pipeline (aspect fit, contrast,
ROI crop, gray bitmap rehydration).

Co-authored-by: Nicolo Micheletti <michelettinik@gmail.com>
Edge-density ROI detection (Sobel magnitude on a ~320px grayscale
decode, 8x6 grid, bounding box of cells >=1.5x frame mean, 10% pad) per
docs/agents/BLE_PHOTO_QUALITY_PLAN.md. Cropping keeps native resolution
on the region OCR actually reads instead of downscaling the whole
frame. Detection is conservative: weak signal, edges spread across the
frame, or a crop covering >80% of the frame all fall back to full
frame, and any detection failure degrades to the previous behavior.
ENABLE_BLE_TEXT_ROI_CROP kill switch for on-device tuning.
Fixture generated with the exact glasses encoder stack (libheif 1.17.6
+ AOM, monochrome colorspace, EXIF item): AvifExifStripper strips the
Exif tail and BlePhotoUploadService converts to JPEG for the webhook,
proving the phone-side BLE photo path handles YUV400 input unchanged.
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

📋 PR Review Helper

📱 Mobile App Build

Ready to test! (commit ec8aac8)

📥 Download APK

🕶️ ASG Client Build

Ready to test! (commit ec8aac8)

📥 Download ASG APK


🔀 Test Locally

gh pr checkout 3413

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

PR Agent Orchestrator State

{
  "cycle": 3,
  "fixRound": 0,
  "totalReviewerRuns": 3,
  "consecutiveNoNewReviews": 0,
  "openFindings": [],
  "resolvedFindings": [],
  "nitFindings": [
    {
      "id": "bb5f98",
      "fingerprint": "asg_client/app/libs/avif-coder-mono-1.7.3-mono1.aar:bcada628f89e",
      "source": "standards",
      "severity": "nit",
      "file": "asg_client/app/libs/avif-coder-mono-1.7.3-mono1.aar",
      "line": 0,
      "message": "~8.36MB prebuilt AAR committed directly to git (not Git LFS) permanently bloats repo history. Well-documented in third_party/avif-coder-mono/, but consider Git LFS or a published JitPack fork as the README itself suggests. Non-blocking.",
      "status": "open",
      "introducedCycle": 2,
      "lastSeenCycle": 2
    }
  ],
  "phase": "discovery",
  "status": "in_progress",
  "lastPair": [
    "bugbot",
    "standards"
  ],
  "stagnationFixRounds": 0,
  "lastOpenCount": 0,
  "fingerprintReopenCounts": {},
  "mutedFingerprints": []
}

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Agent Review — cycle 3

✅ No blocking findings · 0 blocking · 1 nit
Reviewers this cycle: bugbot, standards

Claude — standards — ✅ approve

Commander, recon complete. The mission is resolved — I've inspected the governing AGENTS.md files (root + asg_client/), traced the changed source across files, and confirmed runtime wiring. Here's the after-action report.

Review — PR #3413 (BLE monochrome AVIF photo pipeline)

Standards & conventions

  • Java naming/style (asg_client): Compliant. New classes GrayscaleBleProcessor, TextRegionDetector are PascalCase; methods camelCase; constants (CENTER_W, ANALYSIS_MAX_DIM, ENABLE_GRAYSCALE_BLE_PHOTOS, BLE_AVIF_ENCODE_SPEED) UPPER_SNAKE_CASE; 4-space indent per asg_client/AGENTS.md. Data-holder fields (LumaImage.luma/width/height) aren't m-prefixed, but that matches the pre-existing BleParams holder in the same file — consistent, not a regression.
  • Javadoc: Public/package methods and classes are documented. Good.
  • Wiring verified: encodeAvifForBleMono resolves HeifCoder, AvifBmffExifInjector, buildExifApp1Segment, hasImuMetadata (all present); encodeAvifMono(luma,w,h,stride=w,q,speed) matches the patched Kotlin signature and buffer-size precondition (w*h >= w*(h-1)+w) holds. Locale/File imports present for the new size logging. Legacy color path preserved behind ENABLE_GRAYSCALE_BLE_PHOTOS kill switch.

Testing (per AGENTS.md — BLE features need unit coverage + E2E path)

  • New logic is covered: GrayscaleBleProcessorTest (aspect fit, contrast, ROI crop, gray rehydration) and TextRegionDetectorTest (concentrated block, flat frame, whole-frame noise, source-pixel mapping), plus a phone-side mono-AVIF decode fixture test in AvifExifStripperTest. Adequate.

Commit / PR hygiene

  • No AI co-author trailers and no "Generated with" lines. The Co-authored-by: Nicolo Micheletti trailer names a human — allowed. Commit subjects are imperative and scope is focused on the mono AVIF pipeline. Compliant.

Security

  • No committed secrets. No cloud/Docker/Mongo changes, so the localhost-binding rule is untouched.

Nit (non-blocking)

  • asg_client/app/libs/avif-coder-mono-1.7.3-mono1.aar is an ~8.36 MB prebuilt binary committed directly to git history (not Git LFS; check-attr shows no LFS filter). It's well-justified and documented (third_party/avif-coder-mono/ README + patch + rebuild steps), and there's no repo LFS convention to violate — but a multi-MB blob permanently bloats history. Consider LFS or a published JitPack fork (the README already flags this as the longer-term plan).

Verdict: no blocking issues or regressions — the diff is self-contained, tested, and standards-compliant. Approving.

Updated automatically by the PR Agent Orchestrator each review cycle. Nits do not block merge.

@github-actions

Copy link
Copy Markdown
Contributor

bugbot run

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.

2 participants