Overview

Focal points, as a service.

A small Go HTTP service that finds the best crop focus in an image. It prioritizes confidently detected faces with YuNet, then falls back to U²-Net saliency. You can also switch to Appwrite's FocalNet model. It never crops, stores, identifies, or modifies the submitted image.

Face-first · saliency fallbackJPEG · PNG · WebP · GIFCPU-only
How it works

Face-first, with a safe fallback

The current pipeline runs YuNet first. A reliable face becomes the focal point immediately; without one, the same U²-Net saliency path continues unchanged. These comparisons use real model output from the integration fixtures.

Clear portrait

A face outranks clothing

Saliency settles on the subject’s torso. Face priority moves the crop anchor 34% of the image height upward.

−33.7% y
BeforeSaliency only
Centered portrait with the saliency-only focal point on the subject’s torso
x 0.4760y 0.6078source saliencyConfidence 1.0000
AfterFace priority
The same centered portrait with the face-priority focal point on the subject’s face
x 0.4849y 0.2705source faceConfidence 0.9317
Multiple faces

The most prominent face wins

Both faces clear the threshold. Area-weighted priority selects the larger foreground face.

2 faces detected
BeforeSaliency only
Portrait with two people and the saliency-only focal point on the foreground subject’s torso
x 0.3160y 0.6148source saliencyConfidence 1.0000
AfterFace priority
The same two-person portrait with both faces detected and the foreground face selected
x 0.3188y 0.3368source faceConfidence 0.9425
No reliable face

Fallback stays intact

A strongly blurred face does not pass the detector threshold, so U²-Net supplies the same saliency point as before.

0 faces detected
BeforeSaliency only
Portrait with a strongly blurred face and its saliency focal point
x 0.4884y 0.6575source saliencyConfidence 1.0000
AfterSaliency fallback
The same blurred portrait with the unchanged saliency fallback point
x 0.4884y 0.6575source saliencyConfidence 1.0000

Green boxes are detected faces, the thicker box is the selected primary face, and each crosshair is the returned normalized gravity coordinate. No identity recognition is performed.

How it works

Appwrite's FocalNet model

FocalNet is Appwrite's own model. It picks a crop and uses the crop's center as the focal point, without a separate face detector.

Run FocalNet
$ make model-focalnet
$ MODEL_BACKEND=focalnet ./autogravity
$ docker run --rm -p 8080:8080 \
    -e MODEL_BACKEND=focalnet \
    ghcr.io/appwrite/autogravity
Integration

Storage preview

Appwrite Storage accepts gravity=auto on the preview endpoint. When both width and height are set, autogravity finds the main subject and crops around its focal point instead of the geometric centre.

Golden retriever sitting on grass in a park
Original
400 by 400 preview cropped with gravity center
gravity=centerGeometric centre — subject cut off
400 by 400 preview cropped with gravity auto
gravity=autoFocal point — subject kept in frame
GET/v1/storage/buckets/{bucketId}/files/{fileId}/preview

Pass width, height, and gravity=auto to crop around the detected focal point.

Example
?width=400&height=400&gravity=auto
Start

Install

Fastest path is Docker. The image includes verified YuNet and U²-Net models plus the CPU-only ONNX Runtime library.

Docker
$ docker run --rm -p 8080:8080 ghcr.io/appwrite/autogravity
$ docker build -t autogravity .
$ docker run --rm -p 8080:8080 autogravity

Or build from source with Go 1.25 or newer. make model downloads and verifies the ONNX models. Use make model-focalnet if you want Appwrite's FocalNet weights.

Source
$ make model
$ make build
$ ./autogravity
Start

Configuration

VariableDefaultPurpose
ADDR:8080HTTP listen address
MODEL_BACKENDu2netu2net (YuNet + U²-Net) or focalnet
MODEL_PRECISIONint8int8 or fp32 for the U²-Net backend
MODEL_PATHunsetExplicit ONNX path; overrides precision / FocalNet default
FACE_MODEL_PATHmodels/face_detection_yunet_2023mar.onnxYuNet model path
FACE_SCORE_THRESHOLD0.85Minimum reliable face score
ONNXRUNTIME_LIBrequiredFull ONNX Runtime shared-library path
MAX_REQUEST_SIZE10MiBMaximum /analyze request body, including multipart overhead
Reference

API

Send a JPEG, PNG, WebP, or GIF image as a multipart image field, or as the raw request body.

POST/analyze

Returns a face center, a saliency focal point, or a FocalNet crop center.

Request
curl -sS -X POST \
  http://localhost:8080/analyze \
  -F 'image=@photo.jpg'
Response
{
  "gravity": {
    "x": 0.68,
    "y": 0.37
  },
  "confidence": 0.91,
  "source": "face"
}

Coordinates are in [0.0, 1.0], measured from the oriented image's top-left corner. EXIF orientation is applied before analysis. On the default backend a reliable face supplies its bounding-box center; otherwise U²-Net supplies the saliency centroid. Set MODEL_BACKEND=focalnet to use Appwrite's model instead. source is then focalnet and the response includes a crop rectangle. Confidence is that strategy's model score, not an identity match or a calibrated probability.

FocalNet request
curl -sS -X POST \
  http://localhost:8080/analyze?aspect_ratio=16:9 \
  -F 'image=@photo.jpg'
FocalNet response
{
  "gravity": {
    "x": 0.52,
    "y": 0.41
  },
  "confidence": 0.91,
  "source": "focalnet",
  "crop": {
    "left": 120,
    "top": 40,
    "width": 480,
    "height": 270,
    "retained_importance": 0.88
  }
}
GET/healthz

Returns 503 until the models are loaded, then 200 with {"status":"ok"}.

Reference

Limits

Requests default to a 10 MiB body limit (MAX_REQUEST_SIZE) and decoded images to 48 megapixels. Set MAX_REQUEST_SIZE to a positive byte count such as 10MiB or 10485760. Separate upload and analysis admission limits bound buffered-body and decoded-image memory without allowing slow uploads to reserve inference capacity. Both models are loaded once at startup and their inference sessions are reused across requests.

Reference

Performance

InputDimensionsTime per image
Landscape JPEG1280 × 720391.4 ms
Portrait PNG720 × 1080291.3 ms

Historical U²-Net fallback timings on Apple M3 Pro, CPU-only ONNX Runtime 1.23.2, Go 1.25.14. Median of five sequential benchmark samples. Face-selected requests skip U²-Net. Performance varies with hardware and input images.