Circadify

Quickstart

Capture contactless vital signs in five lines with the Circadify Python SDK, from a webcam, a video file, your own frames, or the sandbox.

The shortest path to a VitalSigns result: construct a CircadifyClient with a sandbox key, call measure_vitals(), and read heart_rate and confidence. The Python SDK is a full capture-capable client — it opens the camera, runs on-device face detection and ROI extraction, and ships only a cropped tensor to the server. Raw video never leaves the device.

Prerequisites

  • The camera extra installed: pip install "circadify[camera]". This pulls in opencv-python + mediapipe, which measure_vitals, measure_from_video, and measure_from_frames require. The core pip install circadify (just requests + numpy) is enough for the no-camera sandbox path below.
  • A sandbox API key of the form ck_test_.... Sandbox keys are zero-quota and return deterministic synthetic vitals — ideal for a first run. Read it from the environment; never hardcode a key.
  • A webcam, or a sample video file, or your own frames. None of these are needed for the sandbox scan_tensor path.

Your first scan

This is the complete webcam-to-vitals flow. It opens the camera, runs a readiness gate, captures, and returns a VitalSigns.

from circadify import CircadifyClient
 
client = CircadifyClient(api_key="ck_test_...")
result = client.measure_vitals()          # opens the webcam, captures, returns vitals
print(result.heart_rate, result.confidence)
python
Tip

Pass your key in from the environment rather than inlining it: CircadifyClient(api_key=os.environ["CIRCADIFY_API_KEY"]). client.key_environment reports "test" or "live" so you can confirm which environment you are in.

Other capture sources

The same client measures from a webcam, a video file, an iterable of frames you supply, or — with the core install only — a synthetic tensor against the sandbox. Each call returns a VitalSigns.

Capture live from the default camera. Requires the [camera] extra.

result = client.measure_vitals(camera_index=0)
python

Face-glow overlay

The SDK ships the camera / face-glow pipeline — the thermal face-mesh glow that makes a Circadify scan recognizable — and lets you build the window around it. Draw the glow on each frame with render_face_glow, fed by the on_frame callback:

import cv2
from circadify import CircadifyClient
from circadify.ui import render_face_glow
 
def on_frame(ev):
    bgr = cv2.cvtColor(ev.rgb, cv2.COLOR_RGB2BGR)   # on_frame gives RGB
    render_face_glow(bgr, ev.landmarks, t=ev.collected / 30.0)
    cv2.imshow("scan", bgr)
    cv2.waitKey(1)
 
result = CircadifyClient(api_key="ck_test_...", on_frame=on_frame).measure_vitals()
python
Note

render_face_glow needs the [camera] extra (for OpenCV) but is a pure draw call — it works in a desktop window, Streamlit, a notebook, or a web stream. A complete example window (camera + glow + vitals sidebar) is in demo_gui.py in the test app. See Face-Glow Overlay for themes and the full pattern.

Reading the result

Every method returns a VitalSigns dataclass. heart_rate and confidence are always present; the remaining fields are optional and may be None depending on configuration. Call .to_dict() for a serializable view.

FieldTypeDescription
heart_ratefloatHeart rate in bpm — always present.
confidencefloatMeasurement confidence, 0–1 — always present.
respiratory_rateOptional[float]Breaths per minute.
hrvOptional[float]Heart-rate variability (SDNN) in ms.
spo2Optional[float]Blood oxygen saturation in %.
systolic_bpOptional[float]Systolic blood pressure in mmHg.
diastolic_bpOptional[float]Diastolic blood pressure in mmHg.
session_idOptional[str]Server session identifier for this scan.
timestampfloatUnix timestamp of the result.
is_fallbackboolTrue only if synthetic fallback vitals were returned.
processing_time_msOptional[float]Server-side inference time in ms.
Tip

Treat confidence as a gate: above 0.7 the reading is reliable, and below 0.4 you should retry the scan with better lighting and less motion.

With demographics

Pass demographics to improve estimates. Methods accept a Demographics instance, a plain dict, or None.

from circadify import CircadifyClient, Demographics
 
client = CircadifyClient(api_key="ck_test_...")
 
# As a dict
result = client.measure_vitals(demographics={"age": 34, "sex": "F", "fitzpatrick": 3})
 
# As a Demographics object
result = client.measure_vitals(
    demographics=Demographics(age=34, sex="F", fitzpatrick=3)
)
python

sex is "M" or "F" (or Sex.MALE / Sex.FEMALE), and fitzpatrick is a skin-tone scale from 1 to 6. All three fields are optional.

Caution

By default the SDK raises the real error and never fabricates vitals — a failed scan surfaces as a CircadifyError subclass, not a fake reading. Returning synthetic vitals on transient infra failures is strictly opt-in via allow_fallback_vitals=True, and those results carry is_fallback=True.

Next Steps

  • Methods — Every capture and REST method on CircadifyClient
  • Camera & Input — Webcam, video, frames, and tensor sources in depth
  • Face-Glow Overlayrender_face_glow, themes, and building your own scan UI
  • Error Handling — Catch CircadifyError, retry on transient failures