> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pool.day/llms.txt
> Use this file to discover all available pages before exploring further.

# People

> Group photos of the same person on the device, unnamed, with an apparent age range and gender when the model is sure.

A photo app can show "photos of the same person" the way Photos does. PoolKit groups faces on the device into unnamed people. It never says who anyone is, and nothing about a face ever leaves the device.

## Ask for people

People need their own category, `.people`, and the `people` index option. No other category implies it, no default grant includes it, and the person always sees it named. Ask for it on its own, in plain words.

```swift theme={null}
var options = IndexOptions()
options.people = true
try await pool.sources.setIndexOptions(options)

let grant = try await pool.authorization.requestAccess(
    AccessRequest(
        sources: [.photos],
        data: [.people],
        operations: [.search],
        purpose: "Group photos of the same person, on this iPhone"
    )
)
```

Faces are biometric data under some laws, such as the GDPR (article 9) and Illinois' BIPA. Before you ask, make sure your consent screen and privacy policy cover grouping faces, even on the device.

## Read the groups

```swift theme={null}
let people = try await pool.context.people(using: grant, minimumPhotos: 3)

for person in people {
    print(person.photoCount, person.firstSeen ?? .distantPast, person.lastSeen ?? .distantPast)
    // Show the clearest face with person.representativeAssetID.
}

let photos = try await pool.context.photos(of: people[0].id, using: grant, limit: 50)
```

| On PersonGroup | Meaning |
| - | - |
| `id` | Stable while the group exists. Use it with `photos(of:)`. |
| `photoCount` | Photos with this face inside the grant. |
| `firstSeen`, `lastSeen` | When the face was first and last photographed. |
| `representativeAssetID` | The photo with the clearest face, to show as the group's picture. |
| `ageRange` | An apparent age range, `0-9` or `20-39` for now, with its `confidence`. |
| `apparentGender` | How the face appears, `female`, `male` or `unknown`, with its `confidence`. |

`people` returns groups with at least `minimumPhotos` photos (2 by default), most photos first, 50 by default. `photos(of:)` returns the group's photos newest first, as [search](https://docs.pool.day/search.md) hits with the reason `.person`, and pages with `limit`, `offset` and `cursor`.

PoolKit never names a group. Your app may let the person name one; keep that name in your app.

## Age and gender are guesses

`ageRange` and `apparentGender` are how a face appears to a model, never who the person is or how they identify. Do not use them to decide what someone may see, buy or pay. Show them, if at all, as a guess the person can correct.

* The face model is small (about 0.8 MB) and downloads with PoolKit's other models. It reads each face crop on the device. Photos read before it arrived are read again, and their groups stay as they were.
* Each label has its own bar, set so that at least 85% of the labels it gives are right on faces it was not trained on. A group shows an estimate only when its faces agree with at least 0.7 confidence, so many groups have neither.
* `apparentGender` is given for about 8 faces in 10. When it is given it is right about 91% of the time, and about 83% of the time for children and teens.
* `ageRange` is `0-9` or `20-39` for now. Teens, `40-59` and `60+` are not returned until the model is right about them often enough. About 3 faces in 10 get an age range.
* Both are `nil` until the face model has downloaded.

The model was trained on [FairFace](https://github.com/joojs/fairface) (CC BY 4.0), a face set balanced across seven ethnic groups. PoolKit's `NOTICE` file carries its credit.

## How grouping works

* Only photos count: not screenshots, not videos, and only photos where Vision found a face.
* A face must be clear enough, at least 6% of the photo's shorter side, and turned no more than 45 degrees.
* Each face becomes a vector on the device. A face joins a group only when it is very close to it, and close groups merge.
* The bar is set so that two people should never share a group. The cost is that one person can appear as several groups, for example in very different light or years apart.

## Taking it back

Turning the `people` option off deletes every face and group at once. When no active grant includes `.people` any more, the next indexing run does the same. Deleting a photo deletes its faces.

Nothing about a face is in any request to Pool's service or in [cloud assist](https://docs.pool.day/cloud-assist.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.