Activity Analysis
Once an activity has been recorded and processed, you can run an analysis to extract meaningful metrics—provided a built-in analysis module is available for that activity.
Available Analysis Types
The SDK currently supports 12 analysis types:
- Overground walking: Straight walking, no turns
- Treadmill walking: Walking on a treadmill
- Overground running: Straight running, no turns
- Treadmill running: Running on a treadmill
- Counter movement jump: Single or double leg, 1+ repetitions
- Drop vertical jump: Single or double leg, 1+ repetitions
- Hop: Single, double, or triple (crossover)
- Squat: Single or double leg, 1+ repetitions
- 505 test: Agility test involving a 180° turn
- Cut: Change of direction (eg, 45° and 90° cuts)
- Range of motion (ROM) assessment: ROM measured throughout the trial (activity-independent)
- Sit-to-stand transfer: 1+ repetitions
Starting Analysis
There are two ways to run an analysis:
- Manually — call
startAnalysisafter the activity reaches.readystatus, as shown below. - Automatically — pass an
ActivityConfigwith an activity type when callingstartRecording. Analysis starts automatically once processing completes and the activity status becomes.analyzing. See Activity Recording.
Manual Analysis
- Swift
- TypeScript
- Python
// Ensure activity is ready
let status = try await service.activityStatus(for: activity)
guard case .ready = status else {
print("Activity not ready for analysis")
return
}
// Start analysis
let task = try await service.startAnalysis(
.counterMovementJump,
for: activity,
in: session
)
print("Analysis task started: \(task.id)")
// Ensure activity is ready
const status = await service.activityStatus(activity);
if (status.type !== "ready") {
console.log("Activity not ready for analysis");
return;
}
// Start analysis
const task = await service.startAnalysis(
"counter_movement_jump",
activity,
session
);
console.log("Analysis task started:", task.taskId);
from modelhealth import ActivityStatus
# Ensure activity is ready
status = service.activity_status(activity)
if status != ActivityStatus.ready:
print("Activity not ready for analysis")
else:
# Start analysis
task = service.start_analysis(
"counter_movement_jump",
activity,
session
)
print("Analysis task started:", task.id)
Monitoring Analysis Progress
Analysis should take less than 30 seconds to complete. Poll the status:
- Swift
- TypeScript
- Python
var analysisStatus = try await service.analysisStatus(for: task)
while case .processing = analysisStatus {
print("Analysis in progress...")
try await Task.sleep(nanoseconds: 10_000_000_000) // 10 seconds
analysisStatus = try await service.analysisStatus(for: task)
}
switch analysisStatus {
case .completed:
print("Analysis complete!")
case .failed:
print("Analysis failed")
default:
break
}
let analysisStatus = await service.analysisStatus(task);
while (analysisStatus.type === "processing") {
console.log("Analysis in progress...");
await new Promise(resolve => setTimeout(resolve, 10000));
analysisStatus = await service.analysisStatus(task);
}
if (analysisStatus.type === "completed") {
console.log("Analysis complete!");
} else if (analysisStatus.type === "failed") {
console.log("Analysis failed");
}
import time
from modelhealth import AnalysisStatus
status = service.analysis_status(task)
while status == AnalysisStatus.processing:
print("Analysis in progress...")
time.sleep(10)
status = service.analysis_status(task)
if status == AnalysisStatus.completed:
print("Analysis complete!")
elif status == AnalysisStatus.failed:
print("Analysis failed")
Downloading Results
Once analysis is complete, download the results:
- Swift
- TypeScript
- Python
if case .completed = analysisStatus {
let results = await service.analysisData(ofType: [.metrics], for: activity)
if let metricsEntry = results.first(where: { $0.type == .metrics }),
let json = try? JSONSerialization.jsonObject(with: metricsEntry.data) as? [String: Any] {
// json contains analysis_title, analysis_description, metrics dictionary, etc.
print("Metrics:", json)
}
}
if (analysisStatus.type === "completed") {
const results = await service.analysisDataForActivity(activity, ["metrics"]);
const metricsEntry = results.find((r) => r.type === "metrics");
if (metricsEntry?.data) {
const result = JSON.parse(new TextDecoder().decode(metricsEntry.data));
// result contains biomechanical metrics
const jumpHeight = result.metrics?.["00_jump_height_COM"]?.value?.value;
const peakVelocity = result.metrics?.["04_peak_vertical_COM_speed_during_takeoff"]?.value?.value;
if (jumpHeight != null) console.log("Jump height:", jumpHeight, "cm");
if (peakVelocity != null) console.log("Peak velocity:", peakVelocity, "m/s");
}
}
import json
if status == AnalysisStatus.completed:
results = service.analysis_data_for_activity(activity, ["metrics"])
for r in results:
if r.type == "metrics":
data = json.loads(r.data)
# data contains analysis_title, analysis_description, metrics, etc.
jump_height = data["metrics"].get("00_jump_height_COM", {}).get("value", {}).get("value")
peak_velocity = data["metrics"].get("04_peak_vertical_COM_speed_during_takeoff", {}).get("value", {}).get("value")
if jump_height is not None:
print(f"Jump height: {jump_height} cm")
if peak_velocity is not None:
print(f"Peak velocity: {peak_velocity} m/s")
Result Types
The analysis result contains:
- Metrics - JSON with key metrics and their descriptions
- Report - PDF report of the analysis
- Data - Zip with CSV files containing joint angles and positions, angular velocities and speeds and extended metrics list
Complete Example
Here's a complete workflow from recording to analysis:
- Swift
- TypeScript
- Python
// Record
let activity = try await service.startRecording(activityNamed: "cmj", in: session)
// ... subject performs jump ...
try await service.stopRecording(session)
// Wait for processing
var activityStatus = try await service.activityStatus(for: activity)
while case .processing = activityStatus {
try await Task.sleep(nanoseconds: 10_000_000_000)
activityStatus = try await service.activityStatus(for: activity)
}
// Start analysis
guard case .ready = activityStatus else {
print("Activity failed processing")
return
}
let task = try await service.startAnalysis(.counterMovementJump, for: activity, in: session)
// Wait for analysis
var analysisStatus = try await service.analysisStatus(for: task)
while case .processing = analysisStatus {
try await Task.sleep(nanoseconds: 10_000_000_000)
analysisStatus = try await service.analysisStatus(for: task)
}
// Download results
if case .completed = analysisStatus {
let results = await service.analysisData(ofType: [.metrics], for: activity)
if let metricsEntry = results.first(where: { $0.type == .metrics }),
let json = try? JSONSerialization.jsonObject(with: metricsEntry.data) as? [String: Any],
let metrics = json["metrics"] as? [String: [String: Any]] {
for (key, metric) in metrics {
print("\(key):", metric)
}
}
}
// Record
const activity = await service.startRecording("cmj", session);
// ... subject performs jump ...
await service.stopRecording(session);
// Wait for processing
let activityStatus = await service.activityStatus(activity);
while (activityStatus.type === "processing" || activityStatus.type === "uploading") {
await new Promise(resolve => setTimeout(resolve, 10000));
activityStatus = await service.activityStatus(activity);
}
// Start analysis
if (activityStatus.type !== "ready") {
console.log("Activity failed processing");
return;
}
const task = await service.startAnalysis("counter_movement_jump", activity, session);
// Wait for analysis
let analysisStatus = await service.analysisStatus(task);
while (analysisStatus.type === "processing") {
await new Promise(resolve => setTimeout(resolve, 10000));
analysisStatus = await service.analysisStatus(task);
}
// Download results
if (analysisStatus.type === "completed") {
const results = await service.analysisDataForActivity(activity, ["metrics"]);
const metricsEntry = results.find((r) => r.type === "metrics");
if (metricsEntry?.data) {
const result = JSON.parse(new TextDecoder().decode(metricsEntry.data));
console.log("Analysis complete:", result.analysis_title);
for (const [key, metric] of Object.entries(result.metrics || {})) {
console.log(`${metric.label}:`, metric.value);
}
}
}
import json
import time
from modelhealth import ModelHealthService, ActivityStatus, ActivityStatusUploading, AnalysisStatus
service = ModelHealthService("your-api-key-here")
# Select a session and activity
session = service.session_list()[0]
activity = service.activity_list(session)[0]
# Wait for processing
activity_status = service.activity_status(activity)
while isinstance(activity_status, ActivityStatusUploading) or activity_status == ActivityStatus.processing:
time.sleep(10)
activity_status = service.activity_status(activity)
if activity_status != ActivityStatus.ready:
print("Activity failed processing")
else:
# Start analysis
task = service.start_analysis("counter_movement_jump", activity, session)
# Wait for analysis
analysis_status = service.analysis_status(task)
while analysis_status == AnalysisStatus.processing:
time.sleep(10)
analysis_status = service.analysis_status(task)
# Download results
if analysis_status == AnalysisStatus.completed:
results = service.analysis_data_for_activity(activity, ["metrics"])
for r in results:
if r.type == "metrics":
data = json.loads(r.data)
print("Analysis complete:", data["analysis_title"])
for key, metric in data["metrics"].items():
print(f" {metric['label']}:", metric["value"])
Error Handling
Always handle potential errors during analysis:
- Swift
- TypeScript
- Python
do {
let task = try await service.startAnalysis(.counterMovementJump, for: activity, in: session)
// Monitor and download results...
} catch {
print("Analysis error: \(error)")
// Handle error appropriately
}
try {
const task = await service.startAnalysis("counter_movement_jump", activity, session);
// Monitor and download results...
} catch (error) {
console.error("Analysis error:", error);
// Handle error appropriately
}
from modelhealth import ModelHealthError
try:
task = service.start_analysis("counter_movement_jump", activity, session)
# Monitor and download results...
except ModelHealthError as e:
print(f"Analysis error: {e}")
# Handle error appropriately