How OpenCleaner finds “the same memory”

$ everything below runs on your device — nothing is uploaded

OpenCleaner's job is to group the photos of a time range into clusters of variants of the same memory: bursts, retries, near-duplicates taken seconds apart. The design has one strong bias — a wrong split costs you a few extra taps, but a wrong merge could suggest deleting a photo you wanted. When in doubt, the algorithm keeps photos apart.

  ┌────────────────────────────┐
    photo library                your selected time range
  └─────────────┬──────────────┘
                  PhotoKit fetch (metadata only)
                
  ┌────────────────────────────┐
    stage 1 · time grouping      consecutive photos chained
    gap ≤ 10 s                   while taken ≤ 10 s apart
  └─────────────┬──────────────┘
                  groups of ≥ 2 photos only
                
  ┌────────────────────────────┐
    stage 2 · vision             512×512 thumbnail per photo
    feature prints               → compact visual fingerprint
    (on-device neural net)         (Apple Vision framework)
  └─────────────┬──────────────┘
                  distance ≤ 0.6 → same memory
                
  ┌────────────────────────────┐
    clusters                     you pick what to keep,
    (2+ similar photos)          the rest becomes pending
  └────────────────────────────┘
    

Stage 1 — time grouping

Photos are sorted by creation date and chained: a photo joins the current group when it was taken within 10 seconds of the previous one. The window is relative to the previous photo, not the group's first — so a long burst stays one group. Groups with a single photo are discarded immediately: a singleton can't be a duplicate, and this keeps the expensive stage small.

Stage 2 — visual refinement

Time proximity alone isn't enough: a landscape and a restaurant menu shot seconds apart must not be offered as duplicates. Each time group is therefore split by visual similarity:

Tuning parameters

parametervaluemeaning
timeWindow10 smax gap between consecutive photos of one group
maxFeatureDistance0.6max fingerprint distance to count as the same memory
analysisSize512×512thumbnail size used for fingerprinting

The safety nets

Want the exact code? It's ~150 lines: github.com/arthurbricq/open-cleaner — see Clusterer.swift.