Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
27b0467
chore: migrate rust/face-recognition to icp-cli
marc0olo Jun 16, 2026
2f7a5b0
chore(rust/face-recognition): test.sh over Makefile, bump CI image to…
marc0olo Jun 17, 2026
800a359
fix(rust/face-recognition): fix build, update deps, align frontend wi…
marc0olo Jul 6, 2026
f154be4
fix(rust/face-recognition): add missing test image asset
marc0olo Jul 6, 2026
e5df64a
fix(rust/face-recognition): use relative path in wasi2ic step
marc0olo Jul 6, 2026
2e14a85
fix(rust/face-recognition): return Err instead of panicking when mode…
marc0olo Jul 6, 2026
3668537
docs(rust/face-recognition): clarify that UI requires model upload be…
marc0olo Jul 6, 2026
c55ba56
fix(rust/face-recognition): add models_ready() query, show setup warn…
marc0olo Jul 6, 2026
cd375b2
fix(rust/face-recognition): add models_ready to backend.did
marc0olo Jul 6, 2026
c830998
docs(rust/face-recognition): add export_candid!() and Candid interfac…
marc0olo Jul 6, 2026
11b68d2
fix(rust/face-recognition): auto-install ic-file-uploader if not present
marc0olo Jul 6, 2026
8c5c7bc
fix(rust/face-recognition): check model files exist before uploading
marc0olo Jul 6, 2026
4915e08
fix(rust/face-recognition): auto-upload models via icp.yaml sync step
marc0olo Jul 6, 2026
df1623e
fix(rust/face-recognition): add debug output and verify setup_models …
marc0olo Jul 6, 2026
57d09ff
fix(rust/face-recognition): auto-download face detection model in syn…
marc0olo Jul 6, 2026
fa2dd2e
fix(rust/face-recognition): fully automate model generation in sync step
marc0olo Jul 6, 2026
666b109
fix(rust/face-recognition): use --prefer-binary for pip install
marc0olo Jul 6, 2026
c1e62a3
fix(rust/face-recognition): stable memory reload, full model automati…
marc0olo Jul 6, 2026
09d7999
fix(rust/face-recognition): remove "Onchain" jargon from frontend tit…
marc0olo Jul 6, 2026
ef7e9cf
docs(rust/face-recognition): document benchmarking.rs purpose and imp…
marc0olo Jul 6, 2026
e91272a
fix(rust/face-recognition): guard post_upgrade model reload with meta…
marc0olo Jul 6, 2026
27bc909
docs(rust/face-recognition): add CLI commands for run_detection and r…
marc0olo Jul 6, 2026
93b03f7
docs(rust/face-recognition): show icp canister logs command to see in…
marc0olo Jul 6, 2026
1308553
docs(rust/face-recognition): simplify benchmarking example to run_det…
marc0olo Jul 6, 2026
293ccf8
docs(rust/face-recognition): describe benchmarking endpoints without …
marc0olo Jul 6, 2026
eb1b2c5
fix(rust/face-recognition): remove setup_models() fast path from uplo…
marc0olo Jul 6, 2026
2ee3c0a
docs(rust/face-recognition): advertise run_recognition + icp canister…
marc0olo Jul 6, 2026
883bcfe
fix(rust/face-recognition): remove unused colorgrad dep, add icp.yaml…
marc0olo Jul 6, 2026
95f5b38
fix(rust/face-recognition): fix SSL recursion, did mismatch, storage …
marc0olo Jul 6, 2026
6e27faa
fix(rust/face-recognition): jq/python3/default fallback for REPLICA_U…
marc0olo Jul 6, 2026
18bcc1f
fix(rust/face-recognition): fix Python version range check (3.9-3.12,…
marc0olo Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/face-recognition.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: face-recognition

on:
push:
branches: [master]
pull_request:
paths:
- rust/face-recognition/**
- .github/workflows/face-recognition.yml

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
rust-face-recognition:
runs-on: ubuntu-24.04
container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1
env:
ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Install build dependencies
run: apt-get update && apt-get install -y --no-install-recommends build-essential
- name: Deploy and test
working-directory: rust/face-recognition
run: |
icp network start -d
icp deploy
bash test.sh
46 changes: 0 additions & 46 deletions .github/workflows/rust-face-recognition-example.yaml

This file was deleted.

5 changes: 0 additions & 5 deletions rust/face-recognition/.cargo/config.toml

This file was deleted.

8 changes: 4 additions & 4 deletions rust/face-recognition/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.dfx/
build/
node_modules/
dist/
Expand All @@ -8,6 +7,7 @@ _MACOSX
target/
*.old.did
.idea
src/backend/assets/version-RFB-320.onnx
src/backend/assets/facerec.onnx
.env
# ONNX model files are downloaded/generated at runtime — do not commit
*.onnx
frontend/src/bindings/
.icp/cache/
2 changes: 1 addition & 1 deletion rust/face-recognition/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]
members = ["src/backend"]
members = ["backend"]
resolver = "2"
117 changes: 56 additions & 61 deletions rust/face-recognition/README.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,91 @@
# ICP face recognition

This is an ICP smart contract runs face detection and face recognition of user's photo that can be uploaded either from a camera or a local file.
This example demonstrates running face detection and face recognition inside an ICP canister using the [Tract ONNX inference engine](https://github.com/sonos/tract). Users can upload photos from a camera or local file, detect faces, and identify people by name.

The smart contract consists of two canisters:
The example consists of two canisters:

- The backend canister embeds the [the Tract ONNX inference engine](https://github.com/sonos/tract) with two ONNX models. One model is used to detect a face in the photo and return its bounding box. Another model is used for computing face embeddings.
- The frontend canister contains the Web assets such as HTML, JS, CSS that are served to the browser.
- **backend** — embeds the Tract ONNX inference engine. Exposes endpoints for uploading ONNX model files in chunks, loading them into memory, detecting faces, computing face embeddings, and recognizing people. Also includes `run_detection` (query) and `run_recognition` (update) endpoints that run the models against a built-in test image — useful during development for capacity planning. Since query calls don't persist logs on ICP, only the update call produces a visible instruction count:

# Models
```bash
icp canister call backend run_recognition '()'
icp canister logs backend
```
- **frontend** — serves the web UI (HTML/JS/CSS).

The smart contract uses two models: one for detecting the face and another for recognizing the face.
## Models

## Face detection
The backend uses two ONNX models that are too large to embed in the Wasm binary and must be uploaded after deployment. `icp deploy` handles this automatically via its sync phase:

A face detection model finds the bounding box of a face in the image.
You can download [Ultraface](https://github.com/onnx/models/tree/main/validated/vision/body_analysis/ultraface) - ultra-lightweight face detection model - [[here](https://github.com/onnx/models/blob/bec48b6a70e5e9042c0badbaafefe4454e072d08/validated/vision/body_analysis/ultraface/models/version-RFB-320.onnx)].
- **Face detection** ([Ultraface](https://github.com/onnx/models/tree/main/validated/vision/body_analysis/ultraface)) — downloaded automatically.
- **Face recognition** ([facenet-pytorch](https://github.com/timesler/facenet-pytorch) InceptionResnetV1) — generated automatically if Python 3.9–3.12 is available (`facenet-pytorch`, `torch`, and `onnx` are installed via pip). If no compatible Python is found, place `face-recognition.onnx` in the project root manually and run `icp deploy` again.

Alternatively, you can run
Models are stored in stable memory and survive canister upgrades — they reload automatically without re-uploading. Note: the persons database (added via the frontend) is stored in heap memory and is cleared on upgrade.

```
./download-face-detection-model.sh
```

## Face recognition

A face recognition model computes a vector embedding of an image with a face.
You can obtain a pretrained model from [facenet-pytorch](https://github.com/timesler/facenet-pytorch) as follows.
## Build and deploy from the command line

- #### Step 1: Install `python` and `pip`: https://packaging.python.org/en/latest/tutorials/installing-packages/.
### Prerequisites

- #### Step 2: Install `facenet-pytorch` and `torch`:
**Required:**

```
pip install facenet-pytorch
pip install torch
pip install onnx
```
- [Node.js](https://nodejs.org/) v18+
- [icp-cli](https://cli.internetcomputer.org/): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm`
- [Rust](https://www.rust-lang.org/tools/install) v1.85+ with `wasm32-wasip1` target: `rustup target add wasm32-wasip1`
- [wasi2ic](https://github.com/wasm-forge/wasi2ic): `cargo install wasi2ic`

- #### Step 3: Export ONNX model. Start a python shell and run the following commands or create a python file and run it:
`wasm-opt` is installed automatically on first deploy if not already present.

```
import torch
import facenet_pytorch
resnet = facenet_pytorch.InceptionResnetV1(pretrained='vggface2').eval()
input = torch.randn(1, 3, 160, 160)
torch.onnx.export(resnet, input, "face-recognition.onnx", verbose=False, opset_version=11)
```
**Optional (for automatic face recognition model generation):**

- #### Step 4: This should produce `face-recognition.onnx`. Copy the file to the root of this repository.
- Python 3.9–3.12 with pip — the sync phase auto-installs `facenet-pytorch`, `torch`, and `onnx` and generates `face-recognition.onnx`. Python 3.13+ is not yet supported by torch.

## Prerequisites
### Install

- [x] Install the [IC
SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install). For local testing, `dfx >= 0.22.0` is required.
- [x] Clone the example dapp project: `git clone https://github.com/dfinity/examples`
- [x] Install `wasi2ic`: Follow the steps in https://github.com/wasm-forge/wasi2ic and make sure that `wasi2ic` binary is in your `$PATH`.
- [x] Install `wasm-opt`: `cargo install wasm-opt`
```bash
git clone https://github.com/dfinity/examples
cd examples/rust/face-recognition
```

## Build the application
### Deploy

```
dfx start --background
dfx deploy
```bash
icp network start -d
icp deploy
icp network stop
```

If the deployment is successful, the it will show the `frontend` URL.
Open that URL in browser to interact with the smart contract.
`icp deploy` runs three phases:
1. **Build** — compiles the Rust backend to WASM (via wasm32-wasip1 + wasi2ic).
2. **Deploy** — installs the backend and frontend canisters.
3. **Sync** — downloads the face detection model, generates the face recognition model (if Python 3.9–3.12 is available), and uploads both to the canister. Skipped on redeployment if models are already loaded.

## Chunk uploading of models
After deployment the CLI prints the frontend URL. Open it in a browser to interact with the canister.

Since the models are large, they cannot be embedded into the Wasm binary of the smart contract.
Instead they should be uploaded separately.
### Test

[DecideAI](https://decideai.xyz/) implemented a tool for incremental uploading of models: https://github.com/modclub-app/ic-file-uploader/tree/main.
```bash
bash test.sh
```

You can install the tool with
`test.sh` exercises the model management API without requiring models to be loaded. The frontend shows a setup instruction if models are not yet uploaded.

```
cargo install ic-file-uploader
For frontend development with hot reload:

```bash
npm run dev --prefix frontend
```

Afterwards, execute the `upload-models-to-canister.sh` script, which runs the following commands:
## Updating the Candid interface

```
dfx canister call backend clear_face_detection_model_bytes
dfx canister call backend clear_face_recognition_model_bytes
ic-file-uploader backend append_face_detection_model_bytes version-RFB-320.onnx
ic-file-uploader backend append_face_recognition_model_bytes face-recognition.onnx
dfx canister call backend setup_models
Only needed if you change the backend endpoints. Requires `candid-extractor` (`cargo install candid-extractor`):

```bash
icp build backend && candid-extractor ./target/wasm32-wasip1/release/backend.wasm > backend/backend.did
```

## Credits

Thanks to [DecideAI](https://decideai.xyz/) for discussions and providing [ic-file-uploader](https://github.com/modclub-app/ic-file-uploader/tree/main).
Thanks to [DecideAI](https://decideai.xyz/) for discussions and providing [ic-file-uploader](https://github.com/decide-ai/ic-file-uploader).

## Security considerations and best practices

See the [ICP security best practices](https://docs.internetcomputer.org/guides/security/overview).
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
[package]
edition = "2021"
name = "backend"
version = "1.1.0"
edition = "2024"

[lib]
# cdylib produces a .wasm binary suitable for deployment as an ICP canister.
crate-type = ["cdylib"]

[dependencies]
anyhow = "1.0"
bytes = "1.5.0"
candid = "0.10"
colorgrad = "0.6"
ic-cdk = "0.14.0"
ic-stable-structures = "0.6"
ic-wasi-polyfill = "0.4.1"
ic-cdk = "0.20"
ic-stable-structures = "0.7"
ic-wasi-polyfill = "0.13"
image = { version = "0.25.1", features = ["png"], default-features = false }
prost = "0.11.0"
prost-types = "0.11.0"
serde = { version = "1.0", features = ["derive"] }
tract-onnx = { git = "https://github.com/sonos/tract", rev = "2a2914ac29390cc08963301c9f3d437b52dd321a" }

Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ service : {
"recognize": (image: blob) -> (Recognition);
"add": (label: text, image: blob) -> (Addition);

// Returns true once both ONNX models have been loaded via setup_models().
"models_ready": () -> (bool) query;

// These endpoints are used for incremental uploading of model files.
"clear_face_detection_model_bytes": () -> ();
"clear_face_recognition_model_bytes": () -> ();
"append_face_detection_model_bytes": (bytes: blob) -> ();
"append_face_recognition_model_bytes": (bytes: blob) -> ();
"setup_models": () -> ();
"setup_models": () -> (variant { Ok: null; Err: text });

// These endpoints are used only for testing and benchmarking.
"run_detection": () -> (Detection) query;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
// The code below is used for testing and benchmarking.
// Benchmarking and smoke-test endpoints.
//
// run_detection() and run_recognition() run the respective models against a
// hardcoded test image embedded in the Wasm binary. They log the IC instruction
// count (visible via `icp canister logs backend`) so you can measure the
// computational cost of each inference call.
//
// run_detection is a query (fast, single-node execution).
// run_recognition is an update (replicated execution, consistent with recognize()).

use crate::{onnx, Detection, Error, Recognition};

const IMAGE: &'static [u8] = include_bytes!("../assets/image.png");

/// Formats thousands for the specified `u64` integer (helper function).
/// Formats a u64 with thousands separators, e.g. 1_234_567.
fn fmt(n: u64) -> String {
n.to_string()
.as_bytes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,26 @@ fn append_face_recognition_model_bytes(bytes: Vec<u8>) {
storage::append_bytes(FACE_RECOGNITION_FILE, bytes);
}

/// Returns true if both ONNX models have been loaded via setup_models().
/// The frontend uses this on page load to show a setup warning if models are missing.
#[ic_cdk::query]
fn models_ready() -> bool {
onnx::models_ready()
}

/// Once the model files have been incrementally uploaded,
/// this function loads them into in-memory models.
#[ic_cdk::update]
fn setup_models() -> Result<(), String> {
if std::fs::metadata(FACE_DETECTION_FILE).is_err()
|| std::fs::metadata(FACE_RECOGNITION_FILE).is_err()
{
return Err(
"Model files not found — upload them first using append_face_detection_model_bytes \
and append_face_recognition_model_bytes"
.to_string(),
);
}
setup(
storage::bytes(FACE_DETECTION_FILE),
storage::bytes(FACE_RECOGNITION_FILE),
Expand All @@ -140,4 +156,18 @@ fn init() {
fn post_upgrade() {
let wasi_memory = MEMORY_MANAGER.with(|m| m.borrow().get(WASI_MEMORY_ID));
ic_wasi_polyfill::init_with_memory(&[0u8; 32], &[], wasi_memory);
// Reload models from stable memory if they were uploaded before this upgrade.
// Guard with metadata check first — storage::bytes() panics on missing files.
if std::fs::metadata(FACE_DETECTION_FILE).is_ok()
&& std::fs::metadata(FACE_RECOGNITION_FILE).is_ok()
{
if let Err(err) = setup(
storage::bytes(FACE_DETECTION_FILE),
storage::bytes(FACE_RECOGNITION_FILE),
) {
ic_cdk::println!("post_upgrade: model reload failed: {}", err);
}
}
}

ic_cdk::export_candid!();
Loading
Loading