Skip to content

The analysis cache, and why re-culling is instant

The first cull of a four-thousand-frame wedding is slow: every frame is decoded and measured, and with the face provider on, a model runs over each one. The second cull of the same shoot is near-instant. This page explains the split that makes that true, and the exact conditions under which the cache is reused.

Measuring and judging are separate steps

A cull is two things that are easy to conflate:

Measuring means decoding the frame and computing its sharpness, its clipping, its perceptual hash and, if a model is running, the eye-open, smile and facing-camera probabilities for each face. This is expensive. It scales with pixels and with model passes.

Judging means taking those numbers, comparing them against the profile's thresholds, and resolving a verdict with reasons. This is arithmetic over a few floats per frame. It costs nothing.

.krites/analysis.yaml stores the measurements. Verdicts are re-derived from them every time.

This is why tuning a threshold and re-culling is fast: changing soft_sharpness changes the judging, not the measuring. Nothing is re-decoded and no model runs. Threshold changes are deliberately not part of the cache validity test. That is the whole point of the design, not an oversight.

The exact conditions for reuse

The cache is reused only when all of these hold:

  1. --reanalyze was not passed.
  2. .krites/analysis.yaml exists and parses.
  3. Its cache version matches the version the running krites expects. This is bumped whenever a signal's computation changes, so an upgrade that alters how sharpness is measured invalidates every cache rather than mixing old numbers with new.
  4. The profile's min_face_box is unchanged. That threshold decides which faces count at all, so it is baked into the measurement rather than applied afterwards.
  5. Whether the eye/face model ran is unchanged. Turning face.enabled on or off invalidates the cache in both directions.
  6. The face analyzer's model id is unchanged. It fingerprints everything that shapes a face measurement: the detector, the strategy, the model files, and every face.* calibration value (the EAR, smile and facing anchors, the mesh margin, the expression weights). Change any of them and the next cull re-measures.
  7. Whether the aesthetic scorer ran is unchanged.
  8. Every frame in the manifest has both a signal entry and a perceptual hash in the cache. Adding a frame to the shoot invalidates it.

Fail any one and krites re-measures the whole shoot. There is no partial reuse.

Which settings force a fresh pass, and which do not

Change Cache survives?
cull.profile.min_sharpness, soft_sharpness Yes, re-judged from cached signals
max_clipped_highlights, max_clipped_shadows Yes
eye_open_soft, eye_open_hard, smile_soft, facing_soft, frown_soft Yes
dedup_distance Yes, bursts are re-clustered from cached hashes
cull.profile.min_face_box No
face.enabled on or off No
face.detector, face.strategy, any face.* calibration value No, these change what the model produces
face.execution_provider, onnxruntime.library_path Yes, they change how the model runs, not what it measures
Upgrading krites across a signal change No
Adding frames to the folder and re-ingesting No

When to reach for --reanalyze

krites cull --reanalyze skips the cache check entirely and re-measures everything. Use it when:

  • you replaced or edited an original file in place. The cache does not check file modification times, so krites will otherwise happily judge the new file from the old file's numbers;
  • you want to confirm a result you suspect came from a stale cache;
  • a krites upgrade did not bump the cache version but you want the current measurement anyway.

--reanalyze is also the right instinct when a cull comes out wrong, rather than krites reset shoot. It rebuilds the measurements and deletes nothing: no verdicts, no ratings, no edits, no removal patches.

Your verdicts survive a re-cull

Re-culling does not overwrite decisions you made. For any frame you overrode by hand, or gave a star rating:

  • kept, because they are yours: the verdict, the override marker, the rating, the timestamp you decided it, and the burst pick;
  • refreshed, because they are the machine's current opinion: what krites now proposes for that frame, and the reasons behind it.

That split is deliberate and was a correction to an earlier implementation which restored the whole record. Under that behaviour an overridden frame kept its original machine proposal forever, so a re-cull under tuned thresholds could never show that krites had come round to your view. That made "is this getting better?" unanswerable and any agreement statistic meaningless.

Why the cache exists at all, beyond speed

Speed is the visible benefit. The durable one is that the cache is a record of what was measured, frame by frame, alongside what the machine proposed and what you decided.

That is the data a learning loop would need: not "you rejected this frame", but "you rejected this frame, which had this sharpness, this exposure, these faces at these eye-open probabilities, when the machine wanted to keep it". Keeping it now costs a YAML file per shoot. Reconstructing it later would mean asking a photographer to re-cull a year of weddings.