Skip to content

Commit 95f5590

Browse files
authored
chore: migrate rust/face-recognition to icp-cli (#1421)
1 parent 6414181 commit 95f5590

42 files changed

Lines changed: 438 additions & 5259 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: face-recognition
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
paths:
8+
- rust/face-recognition/**
9+
- .github/workflows/face-recognition.yml
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
rust-face-recognition:
17+
runs-on: ubuntu-24.04
18+
container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1
19+
env:
20+
ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21+
steps:
22+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
23+
- name: Install build dependencies
24+
run: apt-get update && apt-get install -y --no-install-recommends build-essential
25+
- name: Deploy and test
26+
working-directory: rust/face-recognition
27+
run: |
28+
icp network start -d
29+
icp deploy
30+
bash test.sh

.github/workflows/rust-face-recognition-example.yaml

Lines changed: 0 additions & 46 deletions
This file was deleted.

rust/face-recognition/.cargo/config.toml

Lines changed: 0 additions & 5 deletions
This file was deleted.

rust/face-recognition/.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
.dfx/
21
build/
32
node_modules/
43
dist/
@@ -8,6 +7,7 @@ _MACOSX
87
target/
98
*.old.did
109
.idea
11-
src/backend/assets/version-RFB-320.onnx
12-
src/backend/assets/facerec.onnx
13-
.env
10+
# ONNX model files are downloaded/generated at runtime — do not commit
11+
*.onnx
12+
frontend/src/bindings/
13+
.icp/cache/

rust/face-recognition/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[workspace]
2-
members = ["src/backend"]
2+
members = ["backend"]
33
resolver = "2"

rust/face-recognition/README.md

Lines changed: 56 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,91 @@
11
# ICP face recognition
22

3-
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.
3+
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.
44

5-
The smart contract consists of two canisters:
5+
The example consists of two canisters:
66

7-
- 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.
8-
- The frontend canister contains the Web assets such as HTML, JS, CSS that are served to the browser.
7+
- **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:
98

10-
# Models
9+
```bash
10+
icp canister call backend run_recognition '()'
11+
icp canister logs backend
12+
```
13+
- **frontend** — serves the web UI (HTML/JS/CSS).
1114

12-
The smart contract uses two models: one for detecting the face and another for recognizing the face.
15+
## Models
1316

14-
## Face detection
17+
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:
1518

16-
A face detection model finds the bounding box of a face in the image.
17-
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)].
19+
- **Face detection** ([Ultraface](https://github.com/onnx/models/tree/main/validated/vision/body_analysis/ultraface)) — downloaded automatically.
20+
- **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.
1821

19-
Alternatively, you can run
22+
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.
2023

21-
```
22-
./download-face-detection-model.sh
23-
```
24-
25-
## Face recognition
26-
27-
A face recognition model computes a vector embedding of an image with a face.
28-
You can obtain a pretrained model from [facenet-pytorch](https://github.com/timesler/facenet-pytorch) as follows.
24+
## Build and deploy from the command line
2925

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

32-
- #### Step 2: Install `facenet-pytorch` and `torch`:
28+
**Required:**
3329

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

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

42-
```
43-
import torch
44-
import facenet_pytorch
45-
resnet = facenet_pytorch.InceptionResnetV1(pretrained='vggface2').eval()
46-
input = torch.randn(1, 3, 160, 160)
47-
torch.onnx.export(resnet, input, "face-recognition.onnx", verbose=False, opset_version=11)
48-
```
37+
**Optional (for automatic face recognition model generation):**
4938

50-
- #### Step 4: This should produce `face-recognition.onnx`. Copy the file to the root of this repository.
39+
- 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.
5140

52-
## Prerequisites
41+
### Install
5342

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

60-
## Build the application
48+
### Deploy
6149

62-
```
63-
dfx start --background
64-
dfx deploy
50+
```bash
51+
icp network start -d
52+
icp deploy
53+
icp network stop
6554
```
6655

67-
If the deployment is successful, the it will show the `frontend` URL.
68-
Open that URL in browser to interact with the smart contract.
56+
`icp deploy` runs three phases:
57+
1. **Build** — compiles the Rust backend to WASM (via wasm32-wasip1 + wasi2ic).
58+
2. **Deploy** — installs the backend and frontend canisters.
59+
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.
6960

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

72-
Since the models are large, they cannot be embedded into the Wasm binary of the smart contract.
73-
Instead they should be uploaded separately.
63+
### Test
7464

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

77-
You can install the tool with
69+
`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.
7870

79-
```
80-
cargo install ic-file-uploader
71+
For frontend development with hot reload:
72+
73+
```bash
74+
npm run dev --prefix frontend
8175
```
8276

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

85-
```
86-
dfx canister call backend clear_face_detection_model_bytes
87-
dfx canister call backend clear_face_recognition_model_bytes
88-
ic-file-uploader backend append_face_detection_model_bytes version-RFB-320.onnx
89-
ic-file-uploader backend append_face_recognition_model_bytes face-recognition.onnx
90-
dfx canister call backend setup_models
79+
Only needed if you change the backend endpoints. Requires `candid-extractor` (`cargo install candid-extractor`):
80+
81+
```bash
82+
icp build backend && candid-extractor ./target/wasm32-wasip1/release/backend.wasm > backend/backend.did
9183
```
9284

9385
## Credits
9486

95-
Thanks to [DecideAI](https://decideai.xyz/) for discussions and providing [ic-file-uploader](https://github.com/modclub-app/ic-file-uploader/tree/main).
87+
Thanks to [DecideAI](https://decideai.xyz/) for discussions and providing [ic-file-uploader](https://github.com/decide-ai/ic-file-uploader).
88+
89+
## Security considerations and best practices
9690

91+
See the [ICP security best practices](https://docs.internetcomputer.org/guides/security/overview).
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
[package]
2-
edition = "2021"
32
name = "backend"
43
version = "1.1.0"
4+
edition = "2024"
55

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

910
[dependencies]
1011
anyhow = "1.0"
1112
bytes = "1.5.0"
1213
candid = "0.10"
13-
colorgrad = "0.6"
14-
ic-cdk = "0.14.0"
15-
ic-stable-structures = "0.6"
16-
ic-wasi-polyfill = "0.4.1"
14+
ic-cdk = "0.20"
15+
ic-stable-structures = "0.7"
16+
ic-wasi-polyfill = "0.13"
1717
image = { version = "0.25.1", features = ["png"], default-features = false }
1818
prost = "0.11.0"
1919
prost-types = "0.11.0"
2020
serde = { version = "1.0", features = ["derive"] }
2121
tract-onnx = { git = "https://github.com/sonos/tract", rev = "2a2914ac29390cc08963301c9f3d437b52dd321a" }
22-
File renamed without changes.

rust/face-recognition/src/backend/backend.did renamed to rust/face-recognition/backend/backend.did

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,15 @@ service : {
3838
"recognize": (image: blob) -> (Recognition);
3939
"add": (label: text, image: blob) -> (Addition);
4040

41+
// Returns true once both ONNX models have been loaded via setup_models().
42+
"models_ready": () -> (bool) query;
43+
4144
// These endpoints are used for incremental uploading of model files.
4245
"clear_face_detection_model_bytes": () -> ();
4346
"clear_face_recognition_model_bytes": () -> ();
4447
"append_face_detection_model_bytes": (bytes: blob) -> ();
4548
"append_face_recognition_model_bytes": (bytes: blob) -> ();
46-
"setup_models": () -> ();
49+
"setup_models": () -> (variant { Ok: null; Err: text });
4750

4851
// These endpoints are used only for testing and benchmarking.
4952
"run_detection": () -> (Detection) query;

rust/face-recognition/src/backend/src/benchmarking.rs renamed to rust/face-recognition/backend/src/benchmarking.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
// The code below is used for testing and benchmarking.
1+
// Benchmarking and smoke-test endpoints.
2+
//
3+
// run_detection() and run_recognition() run the respective models against a
4+
// hardcoded test image embedded in the Wasm binary. They log the IC instruction
5+
// count (visible via `icp canister logs backend`) so you can measure the
6+
// computational cost of each inference call.
7+
//
8+
// run_detection is a query (fast, single-node execution).
9+
// run_recognition is an update (replicated execution, consistent with recognize()).
210

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

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

7-
/// Formats thousands for the specified `u64` integer (helper function).
15+
/// Formats a u64 with thousands separators, e.g. 1_234_567.
816
fn fmt(n: u64) -> String {
917
n.to_string()
1018
.as_bytes()

0 commit comments

Comments
 (0)