-
Notifications
You must be signed in to change notification settings - Fork 14
Dev Image Culling Implementation Plan
Build an Image Culling workflow that is useful for real photographers, explainable, affordable to run, and compatible with the existing Lightroom + local backend architecture.
This plan assumes that the current LLM-based quality scoring remains inactive and is not used as the core ranking signal for the MVP.
Status note: The MVP described in this document has been implemented in the backend (
group_and_sort_imagesplus culling metrics and presets) and in the Lightroom plugin taskCull Similar Photos. The checklist at the end reflects the original implementation plan.
The first release should not try to answer the vague question "How good is this photo?" with a single expensive model score.
Instead, the workflow should:
- group similar photos into burst/stack candidates
- detect obvious rejects using technical and face-aware signals
- rank images relative to other images in the same group
- create Lightroom collections for picks, alternates, and reject candidates
- Prefer deterministic, explainable signals over opaque global scores.
- Rank relatively within similar groups, not only globally across the full catalog.
- Keep the hot path local and affordable. Do not use LLMs per image for core culling decisions.
- Store individual culling signals separately so they can be inspected, tuned, and reused.
- Make the first version review-friendly rather than fully automatic.
The MVP should support:
- culling within
selected photosandcurrent view - grouping near-duplicate and burst images
- picking the best image per group
- identifying weak images and reject candidates
- creating Lightroom collections from the result
- showing short reasons for ranking decisions
The MVP should not require:
- genre-specific tuning for every photography niche
- personalized taste learning
- LLM-generated critiques
- fully automatic star ratings across the whole catalog
Use a layered ranking model instead of a single score.
Group photos using a combination of:
capture_time-
pHashor another cheap near-duplicate signal - existing image embeddings
- optional face-count consistency for people-heavy bursts
Compute clear negative indicators such as:
- strong blur or missed focus
- face blur when faces are present
- eyes closed / blink
- severe exposure problems
- obvious occlusion or poor facial visibility
Within each group, reward:
- sharpest main subject
- best face quality
- best expression / eyes-open result
- cleanest exposure
- strongest relative composition or framing
Add a lightweight aesthetic model later as a weak secondary signal:
NIMA- CLIP-based aesthetic predictor
- newer IQA / aesthetics models after evaluation
This layer should influence ordering, but not override hard technical rejects.
Do not store only one overall quality field. Store granular culling signals.
Recommended new backend metadata fields:
cull_group_idcull_group_sizecull_group_rankcull_group_winnercull_scorecull_sharpnesscull_face_sharpnesscull_blink_penaltycull_exposurecull_noisecull_occlusioncull_aestheticcull_reject_candidatecull_reason_codescull_explanation
- Implement
group_and_sort_images(...)inserver/src/services/chroma.py. - Add burst grouping logic based on time window plus similarity threshold.
- Support both true duplicates and near-duplicates.
- Return groups in a structure that the plugin can map to Lightroom collections.
Acceptance criteria:
- Similar photos are grouped reliably for common burst sequences.
- Ungrouped photos still return as single-item groups.
- Output is deterministic for the same input set.
- Add image-level technical metrics in the backend.
- Start with cheap and robust signals:
- sharpness / blur
- exposure sanity
- highlight / shadow clipping approximation
- noise estimate
- Store metrics in Chroma metadata or a dedicated culling result structure.
Acceptance criteria:
- Clearly blurred or badly exposed images rank lower than clean alternatives in the same group.
- Metrics can be logged and inspected during tuning.
- Reuse existing face detection infrastructure.
- Add face-level quality checks when faces are present:
- face sharpness
- eye openness / blink
- face size / prominence
- occlusion / poor visibility where feasible
- Aggregate face signals into image-level culling fields.
Acceptance criteria:
- In portrait or group-photo bursts, images with sharper open eyes rank above blink shots.
- Photos without faces fall back cleanly to generic technical ranking.
- Define the first
cull_scoreformula. - Rank only inside each group first.
- Mark the top image as
group winner. - Mark bottom images with strong negative signals as
reject candidates. - Keep weights configurable in code for fast tuning.
Initial weight suggestion:
-
40%technical quality -
35%face-aware quality when faces exist -
15%relative framing / composition proxy -
10%aesthetic prior
Acceptance criteria:
- Every group has a stable winner.
- Ranking reasons can be explained from stored sub-scores.
- Add a dedicated Lightroom task, for example
Cull Similar Photos. - Support scopes:
- selected photos
- current view
- Provide conservative output options:
PicksAlternatesReject Candidates- optional
Duplicates / Near Duplicates
- Create collections and switch the user to the result collection set.
Acceptance criteria:
- A photographer can run culling on a selection and immediately review grouped results in Lightroom.
- No catalog metadata is overwritten unless explicitly requested.
- Surface short explanations for each winner or reject.
- Example reason codes:
sharpest_in_groupeyes_closedface_blurbetter_exposure_availablenear_duplicate_weaker
- Add debug logging or an internal diagnostics mode for score inspection.
Acceptance criteria:
- Ranking decisions are explainable enough to debug and improve.
- Thresholds can be tuned without redesigning the system.
Recommended new API endpoint behavior:
- keep
/group_similaras the technical grouping endpoint - add a higher-level culling endpoint later, for example
/cull - return structured groups, winners, alternates, and reject candidates
Recommended plugin additions:
- new task file for culling workflow
- collection creation helper shared with search / people workflows
- optional review dialog for thresholds and output mode
- Finish
group_and_sort_images(...) - Add technical metrics and per-group ranking
- Add Lightroom collection workflow
- Add face-aware ranking
- Add explanations and diagnostics
- Evaluate a small aesthetic model as secondary signal
- no LLM scoring in the hot path
- no full personalized taste model
- no genre auto-detection dependency
- no mandatory cloud service
- no automatic destructive reject action
Before full rollout, create a small internal benchmark set with:
- weddings / events
- portraits
- family / kids
- travel / street
For each set, compare:
- best-shot accuracy within groups
- reject precision
- number of wrong winners
- user trust in explanations
- runtime and cost
- Which sharpness metric performs best on RAW-derived previews in this pipeline?
- Should blink detection be implemented through landmarks, eye aspect ratio, or a lightweight classifier?
- Should culling results live only in Chroma metadata, or also in Lightroom plugin properties?
- Should the first UX focus on
collection output only, or also on an in-plugin review dialog?
- Implement similarity grouping backend in
services/chroma.py - Define JSON response schema for grouped culling results
- Add technical image metrics
- Add face-aware culling metrics
- Implement first
cull_scoreweighting - Create Lightroom culling task
- Create collections for picks / alternates / reject candidates
- Add explanation fields and debug output
- Build small benchmark dataset for evaluation
- Evaluate optional aesthetic model as secondary signal
This is the recommended first work package for a dedicated implementation branch.
Goal: Implement the missing grouping foundation so culling can operate on similar-image stacks instead of isolated photos.
Scope:
- implement
group_and_sort_images(...)inserver/src/services/chroma.py - combine
capture_time, embedding similarity, and a cheap duplicate signal - return stable grouped results for a provided list of photo IDs
- keep output deterministic and easy to debug
Suggested output shape:
group_idphoto_ids-
group_typesuch assingle,burst,near_duplicate - optional similarity/debug fields
Definition of done:
- the backend groups obvious bursts and near-duplicates reliably
- single photos still come back as one-item groups
- repeated runs produce the same grouping for the same input
Goal: Create the first cheap, explainable ranking basis without LLMs.
Scope:
- add image-level metrics for:
- sharpness / blur
- exposure sanity
- highlight / shadow clipping approximation
- noise estimate
- store these metrics in backend metadata or a dedicated culling result payload
- expose the metrics in logs or debug output for tuning
Definition of done:
- clearly blurred or badly exposed images score worse than stronger alternatives
- metrics can be inspected per photo during development
Goal: Turn groups plus technical metrics into a usable first-pass culling result.
Scope:
- define the first
cull_score - rank only within each group
- mark
group winner,alternates, andreject candidates - add short reason codes derived from the score components
Suggested first reason codes:
sharpest_in_groupblurredunderexposedoverexposednear_duplicate_weaker
Definition of done:
- every non-empty group has a stable winner
- weak images can be flagged without deleting anything
- ranking reasons are reproducible and understandable
Goal: Make the backend result usable in Lightroom without changing existing metadata workflows.
Scope:
- add a new plugin task such as
Cull Similar Photos - support
selected photosandcurrent view - call the grouping / culling API
- create result collections:
PicksAlternatesReject Candidates- optional
Duplicates / Near Duplicates
Definition of done:
- a user can run culling on a selection and immediately inspect the result in collections
- no destructive action happens automatically
Goal: Improve culling quality for portraits, weddings, events, and family photography.
Scope:
- reuse existing face detection
- add face-level signals:
- face sharpness
- eye openness / blink
- face prominence
- simple visibility / occlusion heuristics
- fold these into the
cull_scorewhen faces exist
Definition of done:
- in people-heavy bursts, sharp open-eye shots are preferred over blink shots
- photos without faces still rank correctly using generic signals
If implementation happens in a separate branch, use this order:
- Ticket 1
- Ticket 2
- Ticket 3
- Ticket 4
- Ticket 5
- lightweight aesthetic model such as
NIMAor a CLIP-based aesthetic predictor - user-adjustable presets for
portrait,event,action - in-plugin review dialog for thresholds and debug explanations
- learning from user keep/reject feedback