# People and faces

Person detection and face detection are beta features that locate people and faces in images and can estimate an age range for each face. They're off unless a request turns them on, and their results are informational: they never change an asset's moderation decision, status or policy action.

## Turning them on

Set `processingOptions.features.personDetection.enabled` or `faceDetection.enabled` to `true` on an upload, and `faceDetection.ageEstimation` to `true` for age ranges. Each feature is billed per image it runs on. See [Processing features](/docs/guides/moderating-assets#processing-features) for how features are chosen and saved.

**python**

```python
import os
from calmlens import CalmLensServerClient

client = CalmLensServerClient(
    api_key=os.environ["CALMLENS_API_KEY"],
    project_id=os.environ["CALMLENS_PROJECT_ID"],
)

with open("photo.jpg", "rb") as content:
    asset = client.assets.moderate({
        "name": "photo.jpg",
        "content": {"data": content, "type": "image/jpeg"},
        "processingOptions": {
            "features": {
                "personDetection": {"enabled": True},
                "faceDetection": {"enabled": True, "ageEstimation": True},
            },
        },
    })

report = asset.get("report")
faces = report.get("features", {}).get("faceDetection") if report else None
if faces and faces["status"] == "evaluated":
    for face in faces["faces"]:
        print(face["box"], face.get("ageRange"))
```

**go**

```go
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    calmlens "github.com/Censkh/CalmLens/packages/go-sdk"
)

func main() {
    client, err := calmlens.NewServerClient(calmlens.Options{APIKey: os.Getenv("CALMLENS_API_KEY"), ProjectID: os.Getenv("CALMLENS_PROJECT_ID")})
    if err != nil { log.Fatal(err) }
    content, err := os.Open("photo.jpg")
    if err != nil { log.Fatal(err) }
    defer content.Close()
    asset, err := client.Assets.Moderate(context.Background(), calmlens.SubmitAssetOptions{
        Name:    "photo.jpg",
        Content: &calmlens.Content{Data: content, Type: "image/jpeg"},
        ProcessingOptions: &calmlens.ProcessingOptions{Features: &calmlens.ProcessingFeatures{
            PersonDetection: &calmlens.PersonDetectionFeatureOptions{Enabled: true},
            FaceDetection:   &calmlens.FaceDetectionFeatureOptions{Enabled: true, AgeEstimation: calmlens.Ptr(true)},
        }},
    })
    if err != nil { log.Fatal(err) }
    if asset.Report != nil && asset.Report.Features != nil && asset.Report.Features.FaceDetection != nil {
        for _, face := range asset.Report.Features.FaceDetection.Faces {
            fmt.Println(face.Box, face.AgeRange)
        }
    }
}

```

**JavaScript**

```javascript
import { readFile } from "node:fs/promises";
import { CalmLensServerClient } from "@calmlens/sdk/server";

const client = new CalmLensServerClient({
  apiKey: process.env.CALMLENS_API_KEY!,
  projectId: process.env.CALMLENS_PROJECT_ID!,
});

const asset = await client.assets.moderate({
  name: "photo.jpg",
  content: { buffer: await readFile("./photo.jpg"), type: "image/jpeg" },
  processingOptions: {
    features: {
      personDetection: { enabled: true },
      faceDetection: { enabled: true, ageEstimation: true },
    },
  },
});

const faces = asset.report?.features?.faceDetection;
if (faces?.status === "evaluated") {
  for (const face of faces.faces) console.log(face.box, face.ageRange);
}
```

**ruby**

```ruby
require 'calmlens'

client = CalmLens::ServerClient.new(api_key: ENV.fetch('CALMLENS_API_KEY'), project_id: ENV.fetch('CALMLENS_PROJECT_ID'))
asset = client.assets.moderate(
  name: 'photo.jpg',
  content: {data: File.binread('photo.jpg'), type: 'image/jpeg'},
  processing_options: {features: {
    person_detection: {enabled: true},
    face_detection: {enabled: true, age_estimation: true}
  }}
)
faces = asset.dig('report', 'features', 'faceDetection')
faces['faces'].each { |face| puts face['box'], face['ageRange'] } if faces && faces['status'] == 'evaluated'
client.close
```

**rust**

```rust
use calmlens::{models::*, ServerClient};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = ServerClient::new(std::env::var("CALMLENS_API_KEY")?, std::env::var("CALMLENS_PROJECT_ID")?)?;
    let bytes = std::fs::read("photo.jpg")?;
    let asset = client.assets().moderate(SubmitAsset {
        processing_options: Some(ProcessingOptions {
            features: Some(ProcessingFeatures {
                person_detection: Some(FeatureOptions::enabled()),
                face_detection: Some(FaceDetectionOptions { age_estimation: Some(true), ..FaceDetectionOptions::enabled() }),
                ..Default::default()
            }),
            ..Default::default()
        }),
        ..SubmitAsset::new("photo.jpg", Content::bytes(bytes, "image/jpeg"))
    }).await?;
    let faces = asset.report.and_then(|r| r.features).and_then(|f| f.face_detection).and_then(|f| f.faces);
    for face in faces.unwrap_or_default() {
        println!("{:?} {:?}", face.bounding_box, face.age_range);
    }
    Ok(())
}
```

**java**

```java
import com.calmlens.CalmLensServerClient;
import com.calmlens.Models.*;

public class Example {
  public static void main(String[] args) throws Exception {
    var client = new CalmLensServerClient(System.getenv("CALMLENS_API_KEY"), System.getenv("CALMLENS_PROJECT_ID"));
    byte[] bytes = java.nio.file.Files.readAllBytes(java.nio.file.Path.of("photo.jpg"));
    Asset asset = client.assets.moderate(new SubmitAsset("photo.jpg", Content.bytes(bytes, "image/jpeg"))
        .processingOptions(new ProcessingOptions().features(new ProcessingFeatures()
            .personDetection(new FeatureOptions(true))
            .faceDetection(new FaceDetectionOptions().enabled(true).ageEstimation(true)))));
    FaceDetectionResult faces = asset.report().features().faceDetection();
    if ("evaluated".equals(faces.status())) faces.faces().forEach(face -> System.out.println(face.box() + " " + face.ageRange()));
  }
}
```

**curl**

```sh
curl -X POST "https://api.calmlens.com/v1/projects/$CALMLENS_PROJECT_ID/assets?name=photo.jpg&contentType=image%2Fjpeg&queue=false&processingOptions=%7B%22features%22%3A%7B%22personDetection%22%3A%7B%22enabled%22%3Atrue%7D%2C%22faceDetection%22%3A%7B%22enabled%22%3Atrue%2C%22ageEstimation%22%3Atrue%7D%7D%7D"   -H "X-Api-Key: $CALMLENS_API_KEY"   -H "X-CalmLens-Upload: content"   -H "Content-Type: image/jpeg"   --data-binary @photo.jpg
```

## Reading the results

Results appear in `report.features`, under the same key as the request. Each result has a `status` and the feature's `stage`:

- `evaluated`: the feature ran. Person detection lists `people` and face detection lists `faces`. An empty list means none were found.
- `not-evaluated`: the feature couldn't run for this asset, with `reason` set to `unavailable`. The moderation decision is unaffected, and the feature isn't billed. Retry the asset to run it again.

```json
"features": {
  "personDetection": {
    "status": "evaluated",
    "stage": "beta",
    "people": [
      { "box": { "left": 0.12, "top": 0.08, "width": 0.31, "height": 0.86 }, "confidence": 0.98 }
    ]
  },
  "faceDetection": {
    "status": "evaluated",
    "stage": "beta",
    "faces": [
      {
        "box": { "left": 0.19, "top": 0.11, "width": 0.14, "height": 0.2 },
        "confidence": 0.99,
        "ageRange": { "low": 24, "high": 32 }
      }
    ]
  }
}
```

- `box` gives the position and size as fractions of the image's width and height, from 0 to 1. `left` and `top` can be slightly negative when a person or face extends past the edge.
- `confidence` is the detection confidence from 0 to 1.
- `ageRange` is present only when the request set `ageEstimation: true`. It's an estimated range in years, and estimates can be off by several years, so don't treat it as proof of anyone's age.
- Person detection also finds people whose faces aren't visible. Face detection finds faces whether or not the rest of the person is in the image.

## Privacy

Faces are located, not recognised: CalmLens doesn't identify people, compare faces or store face templates. The boxes, confidences and age ranges are stored in the asset's report and are deleted with the asset. Depending on where you and your users are, age estimation from faces may be regulated; check your obligations before turning it on.

## Beta

While these features are in beta, the shape of their results may change. Every result carries `stage` so you can tell when a feature has become generally available.
