krites cull¶
Description¶
Judge a shoot: analyse every frame and assign a keep, maybe or reject verdict with reasons. For each frame krites measures model-free quality signals, focus (variance of the Laplacian) and exposure (blown-highlight and crushed-shadow clipping), and resolves a verdict through the cull profile. Hard gates reject, soft floors demote to maybe, and a clean frame is a keep. Near-duplicate bursts are then grouped by perceptual hash: the best frame is kept and the rest demoted to maybe, tagged with the burst they belong to.
krites proposes and you decide: a verdict is a label written to
.krites/verdicts.yaml, never a file operation. Originals are never touched.
Each cull also writes an analysis cache (.krites/analysis.yaml) holding the
raw per-frame signals, and on every verdict records the machine's proposed
ruling alongside your final one. That history is what a learning loop would train
on; it also makes verdicts explainable and re-culls fast.
Re-culling reuses that cache, so tuning a threshold and re-culling is near-instant on a big shoot. The exact conditions for reuse, and the ones that force a fresh pass, are in The analysis cache.
The shoot must already be registered with krites ingest.
Usage¶
The shoot is taken from --shoot, or the current working directory if
omitted.
Flags¶
| Flag | Description | Default |
|---|---|---|
--shoot |
Shoot directory to cull | the current directory |
--profile |
Assert which profile this shoot uses | (unset: no assertion) |
--reanalyze |
Recompute analysis, ignoring the cache | false |
Plus the global flags on every command, including
--output text|json.
--profile does not select a profile¶
There is only one profile, wedding-default, and --profile cannot switch to
another. It is an assertion: krites resolves the shoot's profile from the
config cascade, then checks the name you passed matches, and fails if it does
not.
Left unset, which is the default, no assertion is made and the shoot's profile is
used. Its thresholds are the cull.profile.* keys, which cascade global-to-shoot; see
Tune a cull profile.
Examples¶
# Cull the shoot in the current directory
cd ~/shoots/smith-wedding && krites cull
# Or target it by path, with JSON output
krites cull --shoot ~/shoots/smith-wedding --output json
Text output:
Shoot "smith-wedding" — 4 frames
keep 1 maybe 0 reject 3
keep 01-sharp.jpg
reject 02-flat.jpg — out of focus (sharpness 0 below 50)
reject 03-dark.jpg — out of focus (sharpness 0 below 50); crushed shadows (100% over 30%)
reject 04-bright.jpg — out of focus (sharpness 0 below 50); blown highlights (100% over 10%)
Eye and blink signal¶
With face analysis on, the cull also judges eyes open or closed. The
wedding-default profile carries an eye gate, and the worst face's eye-open
probability drives the verdict:
- a likely blink (below
eye_open_soft, seed0.50) demotes a frame to maybe with asubject blinking …reason; eye_open_hardis disabled by default (0), so a closed-eye frame is never auto-rejected: it may be the only record of a moment, so the call is yours. Raise it to opt into auto-reject;- only faces clearing
min_face_box(seed0.10of the frame's shorter side) count, so a blinking guest in the far background doesn't demote a portrait; - within a near-duplicate burst, an open-eyed frame is kept over a sharper blink.
Face analysis runs local ONNX models with no cgo, and it is off by default. Turn it on with one key:
krites fetches and verifies the models and the ONNX Runtime library on first use; there's no path to set. By default a detector (YuNet) finds the faces and MediaPipe's FaceMesh reads the eyes. The alternatives, the calibration anchors and the licence of each model are in Enable face/eye analysis and the configuration reference.
With it on, the cull demotes blinks to maybe, as subject blinking (N% eye-open,
below 50%), and records min_eye_open per frame. Off, the cull runs
model-free (no faces, so no eye penalty). The gate, the signal projection and
the burst ranking are pure Go and WASM-safe; only the native adapter is gated
behind config.
Expression signal: smile and facing the camera¶
With face analysis on, the same pass also reads smile and facing the camera
(a head-pose estimate, not iris gaze) per face, as probabilities in [0,1],
worst face across the frame like eyes. Under the default mediapipe strategy the
smile comes from a trained blendshape model; under the ear fallback it is a
geometric mouth-curvature estimate.
By default they rank bursts and are recorded; they don't reject. The
wedding-default profile ships them verdict-neutral (smile_soft = 0,
facing_soft = 0), because a solemn ceremony face or a candid glance away is
normal at a wedding, not a defect. What they do by default:
- within a near-duplicate burst, among open-eyed frames the better-smiling
frame is preferred, then the one more facing the camera. The ranking chain
is eyes open, then smile, then facing, then aesthetic, then sharpness, and a
demotion names the tier that decided it (
kept the smiling frame); - every frame records
min_smileandmin_facingin the analysis cache, alongside sharpness and eyes, for the learner.
Opt into demote-to-maybe by raising the soft floors in the profile
(smile_soft, facing_soft, and frown_soft under mediapipe).
The facing anchors, and the smile anchors used by the ear fallback, are
provisional: seeded, not yet calibrated against a real-frame distribution the
way the eye anchors were. That is exactly why expression ships verdict-neutral. An
uncalibrated estimator can inform a ranking but can't move a verdict.
Aesthetic signal (deferred)¶
The cull carries an aesthetic ranking tier and an aesthetic cache slot, but
no scorer ships yet. The whole-frame "is this a strong photograph" model is
deferred to Phase 4, where it would be built as a CLIP model plus an aesthetic
head, so the learning loop gets a reusable per-frame embedding from the same
pass. Until then the aesthetic tier is inert, the ranking skips it, and
aesthetic_floor stays 0; it is never a hard gate by design.
Notes and current limits¶
wedding-defaultis the only profile; its focus, exposure and eye thresholds are starting points you tune. A profile catalog command (krites profile …) is specified and not built.- Re-culls reuse the analysis cache: tuning a threshold and re-culling
re-resolves verdicts from the cached signals, with no re-decode and no model
pass.
--reanalyzeforces a full recompute. The cache is invalidated automatically whenmin_face_boxchanges, when eye detection is turned on or off, and when any face setting that shapes a measurement changes (the detector, the strategy, a calibration anchor). It does not check a file's modification time, so replacing an original in place after a cull needs--reanalyze. - Face analysis runs on the CPU by default. On Apple Silicon,
face.execution_provider: coremluses CoreML and falls back to the CPU if it isn't available. - RAW frames are culled on the JPEG preview embedded in them; krites doesn't decode RAW sensor data.