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.