thumbnail: add HEIC/HEIF support via pi-heif#5006
Conversation
Decode-only HEIF/HEIC: register pi-heif's Pillow opener at import so .heic/.heif ride read_image()'s existing bioio-imageio fallback (no new pipeline branch). pi-heif over pillow-heif: it bundles only libheif + libde265 (decoder), not the GPL-2.0 x265 encoder we'd never use and would otherwise convey in the image. Catalog: add .heic/.heif to SUPPORTED_EXTENSIONS (drives thumbnails, image preview, and bucket summaries). Tests + cell.heic fixture. Preview.md doc update deferred until release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #5006 +/- ##
==========================================
+ Coverage 47.44% 47.48% +0.04%
==========================================
Files 837 837
Lines 34646 34675 +29
Branches 5882 5882
==========================================
+ Hits 16438 16467 +29
Misses 16211 16211
Partials 1997 1997
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:
|
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sir-sigurd
left a comment
There was a problem hiding this comment.
Notes from an AI-assisted self-review pass on the HEIC/HEIF change.
Two should-fix items below — a decode defect (multi-image HEIC returns 500) and a missing wiring into the Qurator image list — plus a couple of cleanup nits. Holding the line-level polish pass for now: the multi-image fix would touch the shared image-prep path and make any nits there stale.
P2
Multi-image HEIC/HEIF returns HTTP 500 (reshape). A HEIC/HEIF holding more than one top-level image (image collections, animated HEIF, some burst-style containers) decodes through the bioio_imageio fallback as a 4-D (T, Y, X, S) array. format_aicsimage_to_prepped only collapses the time axis for the czi / ome_tiff / tifffile readers, so the fallback's 4-D array reaches Image.fromarray (lambdas/thumbnail/src/t4_lambda_thumbnail/__init__.py:700) and raises TypeError: Cannot handle this data type, surfacing as a 500. Reproduced by running a 2-frame HEIC through lambda_handler: single-image → 200, multi-image → 500. The common single-photo iPhone HEIC is unaffected — its depth/HDR maps are auxiliary items, not frames, so n_frames == 1. Direction: collapse to the primary/first frame before Image.fromarray on the fallback path; add a multi-frame fixture alongside test_heic_thumbnail if fixed.
HEIC not wired into the Qurator/Assistant image list. SUPPORTED_IMAGE_EXTENSIONS in catalog/app/components/Assistant/Model/GlobalContext/preview.ts:240 is a second hardcoded copy of the image-extension list (identical to the old SUPPORTED_EXTENSIONS minus the new entries). detectFileType keys off it (:381) to classify a file as an image for model-context attachment, so .heic/.heif won't be attachable to Qurator even though they now render as thumbnails, in the image preview, and in bucket summaries. Not a regression (HEIC was never attachable there), but a completeness gap for this change's goal. Add .heic/.heif here too — or, to stop the two lists drifting on the next format, import the shared SUPPORTED_EXTENSIONS from components/Thumbnail.
Nits / cleanup
pi-heif~=1.0floors lower than the resolved1.4.0. Sibling pins floor at the resolved minor (pillow~=12.2,numpy~=2.3);pi-heif~=1.0admits the whole 1.x.uv.lockpins the exact build and the<2.0cap matches the siblings, so this is cosmetic — tighten to~=1.4only if pin-floor consistency is wanted.- Greptile's
#XXXX-placeholder note is already resolved — both CHANGELOG links read#5006as of6a072fbb.
Checked and clean
Decode-only dependency choice and license floor (no GPL encoder conveyed); wheel/arch coverage for the build target (cp313 manylinux x86_64/aarch64, glibc-compatible with the trixie base); the integration approach (riding the existing fallback, opener registered once at import — idempotent, and test_read_image_fallback fails loudly if a bioio reader ever claims these extensions); the untrusted-decode attack surface (on par with the existing native decoders, and MAX_IMAGE_PIXELS does cover the HEIF decode path); cold-start and image-size cost (~3.8 MB of native libs, negligible against the existing image); and catalog extension matching (case-insensitive in all three consumers).
🤖 Drafted with Claude; findings vetted by the reviewer — sanity-check rather than take as authoritative.
Reviewed against 6a072fb
Address self-review of #5006: - Multi-image HEIF (image collections, animated/burst HEIC) decodes through the bioio-imageio fallback as a 4-D (T,Y,X,S) array, which reached Image.fromarray and 500'd. Collapse the leading frame axis to the primary (first) frame in format_aicsimage_to_prepped's fallback branch; single-frame images are unchanged. Adds cell-multiframe.heic fixture + test. - Add .heic/.heif to the Assistant SUPPORTED_IMAGE_EXTENSIONS so HEIC files attach to Qurator, with a note to keep it in sync with Thumbnail's list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Description
Adds HEIC/HEIF image thumbnails and previews.
HEIC has no bioio reader, so it rides
read_image()'s existing bioio-imageio fallback once a Pillow HEIF opener is registered. We register pi-heif's opener at module import — no new code path in the thumbnail pipeline.pi-heif, not pillow-heif: pi-heif's wheels bundle only libheif + libde265 (decoder); pillow-heif additionally bundles libx265, a GPL-2.0 HEVC encoder we never use (thumbnails are re-encoded as PNG) and would otherwise convey in the lambda image. Decode-only keeps the bundled-license floor at LGPL. Self-contained manylinux wheels for x86_64 and aarch64, so no Dockerfile/apt changes.
Catalog:
.heic/.heifadded toSUPPORTED_EXTENSIONS, which drives thumbnails, the image preview renderer, and bucket summaries — all via the lambda (PNG out), so HEIC renders in every browser, not just Safari.The user-facing docs (
docs/Catalog/Preview.mdformat list) are intentionally deferred until this ships in a release.Note: the lambda still applies no EXIF orientation (pre-existing, JPEG included), so iPhone HEICs render rotated-as-stored — a separate follow-up if desired.
TODO
Greptile Summary
Adds HEIC/HEIF thumbnail and image preview support to both the thumbnail lambda and the catalog frontend. The lambda gains
pi-heifas a decode-only dependency whose Pillow opener is registered at module import, letting HEIC files ride the existingbioio-imageiofallback inread_image()with no new code paths.pi_heif.register_heif_opener()called once at import; HEIC thumbnails are always re-encoded as PNG, so the decode-only dependency is sufficient. Newtest_read_image_fallbackcase pins the dispatch mechanism;test_heic_thumbnailprovides an end-to-end golden test using a fuzzy pixel comparison (max diff ≤ 30) to account for HEVC chroma subsampling..heicand.heifadded toSUPPORTED_EXTENSIONSinThumbnail.jsx; the image preview loader and bucket summary already consume this list, so no further frontend changes are needed.CHANGELOG.mdfiles use#XXXXas a placeholder PR link rather than the actual PR number thumbnail: add HEIC/HEIF support via pi-heif #5006.Confidence Score: 5/5
Safe to merge; the change is additive and isolated — a new Pillow plugin registration at import plus two new extension strings, with no modifications to existing decode or resize paths.
The integration is minimal: one import, one module-level registration call, and two extension strings in the frontend. The new code path is fully covered by both a unit test that pins the dispatch mechanism and a standalone end-to-end test with a real HEIC fixture. The decode-only dependency choice is well-justified in the PR description and in code comments.
Both CHANGELOG.md files carry a #XXXX placeholder link that should be replaced with the actual PR number before the release is cut.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["Lambda receives .heic/.heif URL"] --> B["download to tempfile"] B --> C["read_image(path)"] C --> D{"BioImage(path)"} D -- "UnsupportedFileFormatError\n(no bioio reader for HEIC)" --> E["BioImage(path, reader=bioio_imageio.Reader)"] D -- "success (other formats)" --> F["format_aicsimage_to_prepped()"] E --> F F --> G["generate_thumbnail()"] G --> H["re-encode as PNG"] subgraph "Module import (once)" I["pi_heif.register_heif_opener()"] --> J["Pillow can now open HEIC/HEIF"] end J -.-> E%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A["Lambda receives .heic/.heif URL"] --> B["download to tempfile"] B --> C["read_image(path)"] C --> D{"BioImage(path)"} D -- "UnsupportedFileFormatError\n(no bioio reader for HEIC)" --> E["BioImage(path, reader=bioio_imageio.Reader)"] D -- "success (other formats)" --> F["format_aicsimage_to_prepped()"] E --> F F --> G["generate_thumbnail()"] G --> H["re-encode as PNG"] subgraph "Module import (once)" I["pi_heif.register_heif_opener()"] --> J["Pillow can now open HEIC/HEIF"] end J -.-> EReviews (1): Last reviewed commit: "thumbnail: add HEIC/HEIF support via pi-..." | Re-trigger Greptile