Building the
New Global Standard
of Hand & Wrist
Assessments

DIGITS™ brings computer-vision assessments to your research pipeline. Validated against certified current gold standard.
No hardware. Just structured data.

2°–8° ROM accuracy
r = 0.88 Dexterity correlation
±9.5% Swelling vs. volumeter
7 studies ICC 0.82–0.96

IN COLLABORATION WITH

American Society for Surgery of the Hand Western University Ontario Centre of Innovation Intellectual Property Ontario TechAlliance of Southwestern Ontario BioNext iF

Published Research

Peer-reviewed validation across the hand exam

DIGITS has been evaluated in studies published in the Journal of Hand Surgery and the Journal of Hand Therapy — spanning range of motion, finger dexterity, and hand volume.

🎤  Upcoming Presentation

79th CSPS Annual General Meeting
Kelowna, British Columbia

June 9–13, 2026

DIGITS Health will be presented at the Canadian Society of Plastic Surgeons Annual General Meeting in a scientific paper session on digital innovation in hand surgery outcomes measurement.

CS
Caitlin Symonette, MSc, MD, FRCSC
"

The goniometer has been the gold standard for hand ROM measurement since the 1970s — it is cheap, familiar, and entirely dependent on who is holding it. We set out to ask whether computer vision could match clinical-grade repeatability without the clinician. The answer is that it can — and from a patient's own phone.

Abstract

Concurrent Validity of Augmented Reality-Based Finger Joint Range of Motion Measurement Versus Manual Goniometry in Pathologic Hands

Steven Wang, BASc; Darshan Patel, MASc; Carmine Spedaliere, BEng; Jacob Davidson, MSc; Caitlin Symonette, MD

Purpose: Finger range of motion (ROM) measurement is essential for tracking performance in the setting of hand injury or pathology. Augmented-reality (AR) tracking is developing as an alternative to manual goniometry. This study aimed to evaluate the concurrent validity of AR measurements compared against goniometry in a pathologic hand population.

Methods: Augmented-reality landmark tracking using the Google MediaPipe Hands framework calculates finger joint ROM angles in real time. Participants performed a series of hand poses captured from three camera orientations: palmar, radial oblique, and ulnar oblique. Flexion and extension ROM was measured at the metacarpophalangeal (MCP), proximal interphalangeal (PIP), and distal interphalangeal (DIP) joints of the pathologic index through small fingers. AR measurements were compared to manual goniometry, with concurrent validity categorized by median difference <10° (great), 10–20° (moderate), and >20° (poor).

Results: Thirty-two hands with pathology were included in the analysis. The median age was 59.0 years (interquartile range: 16.0–72.3) and 62.5% of participants were male. The most common hand pathologies were degenerative (46.9%) and trauma (43.8%). For flexion measurements, 50.0% of median differences in joint ROM were within 10° and 41.7% were between 10–20° of the goniometer. For extension, 66.7% of median differences in joint ROM were within 10° and 33.3% were between 10–20°. Overall, 95.8% of all ROM measurements fell within 20° of median difference. Radial oblique camera views provided the most accurate measurements for 45.8% of joints, followed by ulnar oblique (37.5%) and palmar (16.7%).

Conclusion: Augmented reality tracking demonstrates high concurrent validity for extension ROM measurements, with joint-dependent variability. Flexion measurements were less consistent due to obstructed camera views of the fingers. Further optimization and training using pathologic-hand data is required prior to widespread adoption of ROM measurements using augmented reality.

Developer Experience

Everything you need to build on hand health data

A RESTful API built for research workflows. Structured JSON, FHIR R4 export, and SDKs for Python and R.

📐

Clinically validated ROM measurements

Landmark detection delivers joint angles across all fingers with excellent test-retest reliability (ICC >0.75), validated against a the current gold standard. Ongoing validation in clinical populations is in progress.

📊

Bring your own data, get standardised scores

Already collecting hand data? Submit timestamped captures as CSV or JSON and receive standardised joint-angle scores and normative comparisons in return — no DIGITS hardware or app required. Query the growing normative database to benchmark cohorts by age, sex, and injury type, or spot population-level trends across your study.

🔒

Secure, compliant by design

Data is encrypted in transit and at rest, with role-based access controls, audit logging, and de-identification at capture. Our data-usage, storage, and retention practices are built to meet the privacy and security standards research institutions require, including HIPAA and PIPEDA.

92% patient satisfaction

Participants rate the experience highly, and both patients and researchers can attach validated questionnaires and instruments (such as PROMs) to any capture — pairing objective joint-angle data with standardised patient-reported outcomes.

drc_quickstart.py
# DIGITS Research Connect — API Quickstart
import digits_rc
from datetime import datetime, timedelta

client = digits_rc.Client(
    api_key="drc_live_••••••••••••"
)

# ── List recent completed assessments ───────
page = client.assessments.list(
    status="completed",
    since=datetime.now() - timedelta(days=7)
)
print(f"Found {page.total} assessments")

# ── Retrieve joint-angle data ────────────────
result = client.assessments.retrieve(
    id="asmt_Kp9mXjQ2vR"
)
for joint in result.joints:
    print(joint.name, joint.flexion_deg,
          joint.normative_deg)

# MCP_INDEX    85.2°  (norm 90°)  ✓
# PIP_INDEX    89.7°  (norm 100°) ✓
# DIP_INDEX    70.1°  (norm 80°)  ✓
# MCP_MIDDLE   88.0°  (norm 90°)  ✓
# PIP_MIDDLE   91.4°  (norm 100°) ✓
# THUMB_CMC    44.8°  (norm 50°)  ✓

# ── Normative z-score comparison ────────────
norm = client.norms.compare(
    subject_id="subj_Wp3nKq8mL",
    age=42, sex="M"
)
for row in norm.deviations:
    flag = "⚠" if row.z_score < -1.5 else "✓"
    print(f"{row.joint:16} z={row.z_score:+.2f} {flag}")

# ── FHIR R4 export ───────────────────────────
fhir = result.to_fhir()
client.fhir.push(fhir, endpoint=ehr_url)

# ── Register webhook ─────────────────────────
hook = client.webhooks.create(
    url="https://api.clinic.io/drc/events",
    events=["assessment.completed"],
    secret="whs_••••••••••••"
)
print(f"Webhook active: {hook.id}")

# ── Batch subject import ─────────────────────
subjects = client.subjects.bulk_create([
    {"external_id": "PT-001", "dob": "1982-03-14"},
    {"external_id": "PT-002", "dob": "1965-08-29"},
    {"external_id": "PT-003", "dob": "1991-11-07"},
])
print(f"Imported {len(subjects)} subjects")

# ── ICC validation report ────────────────────
report = client.validation.icc_report(
    site_id="site_MAYO_01",
    joint="PIP_INDEX",
    n_raters=3
)
print(f"ICC(3,1) = {report.icc:.3f} "
      f"CI [{report.ci_lower:.3f}, {report.ci_upper:.3f}]")

# ── Longitudinal cohort aggregate ────────────
agg = client.reports.aggregate(
    trial_id="trial_FRACT_2026_B",
    metric="pip_index_flexion",
    group_by="week"
)
for site in agg.sites:
    print(f"{site.name:20} n={site.n:4} "
          f"mean={site.mean:.1f}°")
# DIGITS Research Connect — API Quickstart
import digits_rc
from datetime import datetime, timedelta

client = digits_rc.Client(
    api_key="drc_live_••••••••••••"
)

# ── List recent completed assessments ───────
page = client.assessments.list(
    status="completed",
    since=datetime.now() - timedelta(days=7)
)
print(f"Found {page.total} assessments")

# ── Retrieve joint-angle data ────────────────
result = client.assessments.retrieve(
    id="asmt_Kp9mXjQ2vR"
)
for joint in result.joints:
    print(joint.name, joint.flexion_deg,
          joint.normative_deg)

# MCP_INDEX    85.2°  (norm 90°)  ✓
# PIP_INDEX    89.7°  (norm 100°) ✓
# DIP_INDEX    70.1°  (norm 80°)  ✓
# MCP_MIDDLE   88.0°  (norm 90°)  ✓
# PIP_MIDDLE   91.4°  (norm 100°) ✓
# THUMB_CMC    44.8°  (norm 50°)  ✓

# ── Normative z-score comparison ────────────
norm = client.norms.compare(
    subject_id="subj_Wp3nKq8mL",
    age=42, sex="M"
)
for row in norm.deviations:
    flag = "⚠" if row.z_score < -1.5 else "✓"
    print(f"{row.joint:16} z={row.z_score:+.2f} {flag}")

# ── FHIR R4 export ───────────────────────────
fhir = result.to_fhir()
client.fhir.push(fhir, endpoint=ehr_url)

# ── Register webhook ─────────────────────────
hook = client.webhooks.create(
    url="https://api.clinic.io/drc/events",
    events=["assessment.completed"],
    secret="whs_••••••••••••"
)
print(f"Webhook active: {hook.id}")

# ── Batch subject import ─────────────────────
subjects = client.subjects.bulk_create([
    {"external_id": "PT-001", "dob": "1982-03-14"},
    {"external_id": "PT-002", "dob": "1965-08-29"},
    {"external_id": "PT-003", "dob": "1991-11-07"},
])
print(f"Imported {len(subjects)} subjects")

# ── ICC validation report ────────────────────
report = client.validation.icc_report(
    site_id="site_MAYO_01",
    joint="PIP_INDEX",
    n_raters=3
)
print(f"ICC(3,1) = {report.icc:.3f} "
      f"CI [{report.ci_lower:.3f}, {report.ci_upper:.3f}]")

# ── Longitudinal cohort aggregate ────────────
agg = client.reports.aggregate(
    trial_id="trial_FRACT_2026_B",
    metric="pip_index_flexion",
    group_by="week"
)
for site in agg.sites:
    print(f"{site.name:20} n={site.n:4} "
          f"mean={site.mean:.1f}°")
# DIGITS Research Connect — API Quickstart
import digits_rc
from datetime import datetime, timedelta

client = digits_rc.Client(
    api_key="drc_live_••••••••••••"
)

# ── List recent completed assessments ───────
page = client.assessments.list(
    status="completed",
    since=datetime.now() - timedelta(days=7)
)
print(f"Found {page.total} assessments")

# ── Retrieve joint-angle data ────────────────
result = client.assessments.retrieve(
    id="asmt_Kp9mXjQ2vR"
)
for joint in result.joints:
    print(joint.name, joint.flexion_deg,
          joint.normative_deg)

# MCP_INDEX    85.2°  (norm 90°)  ✓
# PIP_INDEX    89.7°  (norm 100°) ✓
# DIP_INDEX    70.1°  (norm 80°)  ✓
# MCP_MIDDLE   88.0°  (norm 90°)  ✓
# PIP_MIDDLE   91.4°  (norm 100°) ✓
# THUMB_CMC    44.8°  (norm 50°)  ✓

# ── Normative z-score comparison ────────────
norm = client.norms.compare(
    subject_id="subj_Wp3nKq8mL",
    age=42, sex="M"
)
for row in norm.deviations:
    flag = "⚠" if row.z_score < -1.5 else "✓"
    print(f"{row.joint:16} z={row.z_score:+.2f} {flag}")

# ── FHIR R4 export ───────────────────────────
fhir = result.to_fhir()
client.fhir.push(fhir, endpoint=ehr_url)

# ── Register webhook ─────────────────────────
hook = client.webhooks.create(
    url="https://api.clinic.io/drc/events",
    events=["assessment.completed"],
    secret="whs_••••••••••••"
)
print(f"Webhook active: {hook.id}")

# ── Batch subject import ─────────────────────
subjects = client.subjects.bulk_create([
    {"external_id": "PT-001", "dob": "1982-03-14"},
    {"external_id": "PT-002", "dob": "1965-08-29"},
    {"external_id": "PT-003", "dob": "1991-11-07"},
])
print(f"Imported {len(subjects)} subjects")

# ── ICC validation report ────────────────────
report = client.validation.icc_report(
    site_id="site_MAYO_01",
    joint="PIP_INDEX",
    n_raters=3
)
print(f"ICC(3,1) = {report.icc:.3f} "
      f"CI [{report.ci_lower:.3f}, {report.ci_upper:.3f}]")

# ── Longitudinal cohort aggregate ────────────
agg = client.reports.aggregate(
    trial_id="trial_FRACT_2026_B",
    metric="pip_index_flexion",
    group_by="week"
)
for site in agg.sites:
    print(f"{site.name:20} n={site.n:4} "
          f"mean={site.mean:.1f}°")
# DIGITS Research Connect — API Quickstart
import digits_rc
from datetime import datetime, timedelta

client = digits_rc.Client(
    api_key="drc_live_••••••••••••"
)

# ── List recent completed assessments ───────
page = client.assessments.list(
    status="completed",
    since=datetime.now() - timedelta(days=7)
)
print(f"Found {page.total} assessments")

# ── Retrieve joint-angle data ────────────────
result = client.assessments.retrieve(
    id="asmt_Kp9mXjQ2vR"
)
for joint in result.joints:
    print(joint.name, joint.flexion_deg,
          joint.normative_deg)

# MCP_INDEX    85.2°  (norm 90°)  ✓
# PIP_INDEX    89.7°  (norm 100°) ✓
# DIP_INDEX    70.1°  (norm 80°)  ✓
# MCP_MIDDLE   88.0°  (norm 90°)  ✓
# PIP_MIDDLE   91.4°  (norm 100°) ✓
# THUMB_CMC    44.8°  (norm 50°)  ✓

# ── Normative z-score comparison ────────────
norm = client.norms.compare(
    subject_id="subj_Wp3nKq8mL",
    age=42, sex="M"
)
for row in norm.deviations:
    flag = "⚠" if row.z_score < -1.5 else "✓"
    print(f"{row.joint:16} z={row.z_score:+.2f} {flag}")

# ── FHIR R4 export ───────────────────────────
fhir = result.to_fhir()
client.fhir.push(fhir, endpoint=ehr_url)

# ── Register webhook ─────────────────────────
hook = client.webhooks.create(
    url="https://api.clinic.io/drc/events",
    events=["assessment.completed"],
    secret="whs_••••••••••••"
)
print(f"Webhook active: {hook.id}")

# ── Batch subject import ─────────────────────
subjects = client.subjects.bulk_create([
    {"external_id": "PT-001", "dob": "1982-03-14"},
    {"external_id": "PT-002", "dob": "1965-08-29"},
    {"external_id": "PT-003", "dob": "1991-11-07"},
])
print(f"Imported {len(subjects)} subjects")

# ── ICC validation report ────────────────────
report = client.validation.icc_report(
    site_id="site_MAYO_01",
    joint="PIP_INDEX",
    n_raters=3
)
print(f"ICC(3,1) = {report.icc:.3f} "
      f"CI [{report.ci_lower:.3f}, {report.ci_upper:.3f}]")

# ── Longitudinal cohort aggregate ────────────
agg = client.reports.aggregate(
    trial_id="trial_FRACT_2026_B",
    metric="pip_index_flexion",
    group_by="week"
)
for site in agg.sites:
    print(f"{site.name:20} n={site.n:4} "
          f"mean={site.mean:.1f}°")

Ready to Embrace the
New Global Standard?

Apply for API access today. Research institutions receive priority onboarding and a free 100-assessment pilot allocation.

Get Started Read the Docs