DIGITS™ brings computer-vision assessments to your research pipeline.
Validated against certified current gold standard.
No hardware. Just structured data.
IN COLLABORATION WITH
Published Research
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.
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. After 2.4 million assessments, the answer is that it can — and from a patient's own phone.
Abstract — Scientific Paper Session 4B
Background: Goniometric measurement of hand range of motion (ROM) is the standard for outcomes assessment following hand and wrist injury, surgery, or rehabilitation. Inter-rater reliability remains a known limitation of physical goniometry. This study evaluates the validity and test–retest reliability of a cloud-based, computer-vision goniometry platform (DIGITS Research Connect) against physical goniometry performed by certified hand therapists.
Methods: A prospective multi-site validation study was conducted across three Level I hand surgery centres (n = 312 participants). Each subject completed a standardised active ROM capture using the DRC mobile interface, immediately followed by physical goniometry from a blinded CHT. Primary endpoints were intraclass correlation coefficients (ICC) for each of 20 measured joint angles bilaterally.
Results: Mean ICC across all joints was 0.94 (95% CI 0.91–0.96). Mean absolute error was 2.8° ± 1.4°. The DRC platform demonstrated superior test–retest reliability (ICC 0.97) versus the physical goniometer reference (ICC 0.88), attributed to elimination of positioning variance. No clinically significant systematic bias was detected.
Conclusions: Computer-vision goniometry via the DIGITS Research Connect platform is clinically equivalent to physical goniometry for active ROM of the hand and wrist, with superior reliability. The API architecture enables scalable deployment across decentralised research sites — a significant advance in outcomes capture for multi-site hand surgery trials.
Developer Experience
A RESTful API built for research workflows. Structured JSON, FHIR R4 export, and SDKs for Python and R.
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.
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.
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.
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.
# 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}°")