You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Faces: Report the markers a migration would sample above the clustering bar
A vector drawn from too few pixels is indistinguishable from one that was
not, so an operator whose thumbnails are smaller than their faces need -
or whose migration could not write a rendition - has nothing to read. The
audit now counts the markers whose recorded extent is below the bar, and
how many of those their original could still supply, which separates what
a re-run recovers from what no thumbnail size can.
Copy file name to clipboardExpand all lines: internal/ai/face/README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -195,7 +195,7 @@ Two caveats apply to the benchmark-derived recommendations. The measured centroi
195
195
196
196
**Each detector also carries its own cutoff** (`Detector.MinScore`), because they do not score alike: SCRFD emits one calibrated sigmoid and sits at 50, while YuNet scores as `sqrt(cls x obj)` and sits at the 65 its own corpus measured. Both bars are registered on the **0-100 scale** markers and the `FACE_*` options use, and the engine converts to the 0-1 one its decoder reports, so the registry holds one scale rather than two. **Every one of them is an operator override away** - `FACE_SCORE` replaces `MinScore`, `FACE_CLUSTER_SCORE` replaces `ClusterScore` and `FACE_MIGRATE_SCORE` replaces `MigrateScore`, in either direction - so a threshold can be explored on a running instance rather than through a rebuild. Going below the knee was tried at 9/20, the pair the last stable release ran, and the demo library answered within a day: three "people" on a photograph of a bee, two of them blurred background. That evidence bounds the migration floor as well as the index one, which is why `MigrateScore` sits at 50 rather than at the 9 a migration would otherwise want: the measurement weighs false positives and not **re-detection**, so the floor takes the recall the evidence leaves available and no more. All three remain open until the preview returns attributable data points. `TestDetectorRecall` pins the measured recall at the calibrated cutoff explicitly, so a threshold decision cannot silently rewrite it. A cutoff copied from another detector is not calibration, which `TestDetectorMinScore` states.
197
197
-`SizeThreshold` (`FACE_SIZE`, default 25 px) and `ClusterSizeThreshold` (`FACE_CLUSTER_SIZE`, default 112 px) are the size pair, and they do different jobs: the first decides whether a marker is created at all, the second whether that face may contribute to automatic clustering. **They are also in different units** - `FACE_SIZE` counts detection-thumbnail pixels, because it gates whether a box is emitted before any rendition is chosen, while `FACE_CLUSTER_SIZE` counts the pixels an embedding was sampled from. A face below the second never seeds a person even though it is detected and shown.
198
-
- **The clustering default is `ArcFaceTemplateSize`, and it is a bar on the pixels an embedding was sampled from.** 112 is where embedding quality turns: distance to a per-person centroid averages 0.864 below it against 0.804 at or above, flat thereafter, which agrees with the template geometry. The quantity compared is `markers.thumb_size`, the face's extent in the rendition its embedding was drawn from, recorded where the vector is produced and never recomputed. A marker predating that column, or one placed by hand with no embedding, falls back to `markers.size` - detection-thumbnail pixels, which are a **lower bound** on the sampled extent, since `Fit720` is the narrowest rendition a crop is ever taken from. The fallback therefore rejects well-resolved faces it cannot prove are well-resolved and never admits an invented one. `photoprism faces migrate` treats a missing extent as stale, so a re-run measures it; `photoprism faces audit` reports how many are still without one and never writes the column, since it would have to *predict* it from a vector sampled earlier.
198
+
- **The clustering default is `ArcFaceTemplateSize`, and it is a bar on the pixels an embedding was sampled from.** 112 is where embedding quality turns: distance to a per-person centroid averages 0.864 below it against 0.804 at or above, flat thereafter, which agrees with the template geometry. The quantity compared is `markers.thumb_size`, the face's extent in the rendition its embedding was drawn from, recorded where the vector is produced and never recomputed. A marker predating that column, or one placed by hand with no embedding, falls back to `markers.size` - detection-thumbnail pixels, which are a **lower bound** on the sampled extent, since `Fit720` is the narrowest rendition a crop is ever taken from. The fallback therefore rejects well-resolved faces it cannot prove are well-resolved and never admits an invented one. `photoprism faces migrate` treats a missing extent as stale, so a re-run measures it; `photoprism faces audit` reports how many are still without one and never writes the column, since it would have to *predict* it from a vector sampled earlier. It also counts the markers whose recorded extent is *below* the bar, and how many of those their original could still supply: a vector drawn from too few pixels is indistinguishable from one that was not, so that number exists nowhere else, and it separates what a re-run recovers from what no thumbnail size can.
199
199
- **Both are measured in pixels of the detection thumbnail (`Fit720`), not of the original and not of the crop the embedder receives.** The crop comes from `crop.ImageFromIdealThumb`, which opens the smallest cached rendition wide enough to fill the 112 px template and falls back to the widest one cached, so a marker is compared against one image and embedded from another. A marker at exactly `FACE_CLUSTER_SIZE` needs a source no wider than `Fit720` itself, so that is what the crop path opens and the warp onto the template is 1:1 - on every aspect ratio, because the crop area is square and the required width therefore tracks the thumbnail's own. The bar is a guarantee of no upscaling rather than any headroom above it. A **smaller** marker needs a wider source, and there the aspect ratio decides how much it gets: at the default `THUMB_SIZE` of 1920 a rendition supplies 2.67x the detection size for a 16:9 original, 2.5x for a 3:2 one, 2.22x for a 4:3 one, and 1.67x for a square or 3:4 portrait one - the last two share a factor, because `Fit1920` bounds both by its 1200 px height.
200
200
-**This is the mechanism behind the `THUMB_SIZE` warning in [Advanced Settings](https://docs.photoprism.app/user-guide/settings/advanced/#static-and-dynamic-size-limits).** Lowering the static size limit does not change any face threshold, but it lowers the rendition every crop is drawn from, so each face reaches the embedder with fewer real pixels. The size thresholds keep comparing the same numbers while the crops behind them get worse, which is why the effect is easy to miss.
201
201
- Two detections count as the same face when their area overlap exceeds `OverlapThresholdFloor` (41 %), which is `OverlapThreshold` (42 %) relaxed by one point to absorb rounding. Tests rely on that value (e.g., `Markers.Contains/SameFace`).
0 commit comments