Rust Integration Guide
// Check for Mocopi sensor (base kit has 6) assert!(LimbId::Hip.has_mocopi_sensor()); assert!(!LimbId::LeftElbow.has_mocopi_sensor()); // No sensor on elbows ```
Full Public Reader
Rust Integration Guide
Complete guide for using cc-collection in Rust applications.
Installation
Add to your `Cargo.toml`:
[dependencies]
cc-collection = { path = "path/to/core/cc-collection" }
# Optional: Enable features
# cc-collection = { path = "...", features = ["python", "wasm"] }Initialization
use cc_collection::{init, VERSION};
fn main() {
// Initialize library (recommended at startup)
init();
println!("cc-collection v{}", VERSION);
}---
Core Types
LimbId Enum
The `LimbId` enum represents the 14 tracked limbs:
use cc_collection::LimbId;
// All available limbs
let all_limbs: Vec<LimbId> = LimbId::all().collect();
assert_eq!(all_limbs.len(), 14);
// Access specific limb
let hip = LimbId::Hip;
let head = LimbId::Head;
// From index (0-13)
let limb = LimbId::from_index(0).unwrap(); // Hip
assert_eq!(limb, LimbId::Hip);
// To index
assert_eq!(LimbId::Head.to_index(), 1);
// Get name
assert_eq!(LimbId::LeftWrist.name(), "left_wrist");
// Check for Mocopi sensor (base kit has 6)
assert!(LimbId::Hip.has_mocopi_sensor());
assert!(!LimbId::LeftElbow.has_mocopi_sensor()); // No sensor on elbowsLimbState
Per-limb state after fusion:
use cc_collection::{LimbState, LimbId, DataSource};
let state = LimbState {
limb: LimbId::Hip,
position: [0.0, 1.0, 0.0], // World position [x, y, z]
quaternion: [1.0, 0.0, 0.0, 0.0], // Rotation [w, x, y, z]
linear_velocity: [0.0, 0.0, 0.0], // Velocity [vx, vy, vz]
angular_velocity: [0.0, 0.0, 0.0], // Angular velocity [wx, wy, wz]
orientation_source: DataSource::Fused,
position_source: DataSource::Fused,
confidence: 0.95, // [0, 1]
};FusedSkeleton
Complete skeleton with all limb states:
use cc_collection::{FusedSkeleton, LimbId};
// Create new skeleton
let mut skeleton = FusedSkeleton::new("session_001".to_string());
// Access limbs
let hip = skeleton.get_limb(LimbId::Hip);
println!("Hip position: {:?}", hip.position);
// Mutable access
let head = skeleton.get_limb_mut(LimbId::Head);
head.position = [0.0, 1.7, 0.0];
// Iterate all limbs
for (i, limb_state) in skeleton.limbs.iter().enumerate() {
let limb_id = LimbId::from_index(i).unwrap();
println!("{}: {:?}", limb_id.name(), limb_state.position);
}DataSource
Tracks origin of sensor data:
use cc_collection::DataSource;
match data_source {
DataSource::None => println!("No data"),
DataSource::Mocopi => println!("From Mocopi IMU"),
DataSource::MediaPipe => println!("From camera"),
DataSource::Fused => println!("Combined from both"),
DataSource::Interpolated => println!("Recovered from occlusion"),
}FusionMode
Current fusion state:
use cc_collection::FusionMode;
match mode {
FusionMode::None => println!("No sensors active"),
FusionMode::MocopiOnly => println!("Mocopi only"),
FusionMode::MediaPipeOnly => println!("MediaPipe only"),
FusionMode::Fused => println!("Both sensors fused"),
}---
Sensor Fusion
FusionConfig
use cc_collection::{FusionConfig, FusionEngine};
use cc_collection::fusion::{KalmanConfig, OcclusionConfig, TemporalConfig};
// Default configuration
let config = FusionConfig::default();
// Custom configuration
let config = FusionConfig {
session_id: "performance_001".to_string(),
min_confidence: 0.5, // Minimum confidence threshold
enable_smoothing: true, // Enable temporal smoothing
kalman: KalmanConfig::default(),
occlusion: OcclusionConfig::default(),
temporal: TemporalConfig::default(),
};
let mut engine = FusionEngine::new(config);FusionEngine
Main sensor fusion processor:
use cc_collection::{FusionEngine, FusionConfig, MocopiFrame, MediaPipeFrame};
// Create engine
let mut engine = FusionEngine::new(FusionConfig::default());
// Process frame with both sensors
let skeleton = engine.process_frame(
Some(&mocopi_frame),
Some(&mediapipe_frame),
1.0 / 60.0, // dt in seconds
);
// Process with only Mocopi
let skeleton = engine.process_frame(
Some(&mocopi_frame),
None,
1.0 / 60.0,
);
// Process with only MediaPipe
let skeleton = engine.process_frame(
None,
Some(&mediapipe_frame),
1.0 / 60.0,
);
// Check current state
println!("Frame: {}", engine.frame_number());
println!("Mode: {:?}", engine.fusion_mode());
// Get detailed statistics
let stats = engine.stats();
println!("Occluded limbs: {}", stats.occluded_limbs);
println!("Clock offset: {:.2}ms", stats.clock_offset_ms);
println!("Synchronized: {}", stats.is_synchronized);
// Reset for new session
engine.reset();---
Protocol Parsing
MocopiParser
Parse Sony Mocopi UDP packets:
use cc_collection::MocopiParser;
let mut parser = MocopiParser::new();
// Parse binary UDP packet
let frame = parser.parse(&udp_packet_bytes)?;
println!("Frame {}, {} bones", frame.frame_number, frame.bones.len());
// Access bone data
for bone in &frame.bones {
println!("Bone {}: pos={:?}, quat={:?}",
bone.bone_id,
bone.position,
bone.quaternion,
);
}
// Parse from JSON (alternative format)
let json_str = r#"{
"timestamp_ms": 100.0,
"frame_number": 42,
"bones": [
{"bone_id": 0, "position": [0, 1, 0], "quaternion": [0, 0, 0, 1]}
]
}"#;
let frame = parser.parse_json(json_str)?;MediaPipeParser
Parse MediaPipe Holistic JSON:
use cc_collection::MediaPipeParser;
let mut parser = MediaPipeParser::new();
// Parse JSON from camera processor
let frame = parser.parse_json(&mediapipe_json)?;
// Check available data
if let Some(pose) = &frame.pose_world_landmarks {
println!("Pose: {} landmarks", pose.len());
}
if frame.face_landmarks.is_some() {
println!("Face detected");
}
if frame.left_hand_landmarks.is_some() {
println!("Left hand detected");
}
// Confidence scores
println!("Pose confidence: {:.2}", frame.confidence.pose);
println!("Face confidence: {:.2}", frame.confidence.face);---
Motion Transforms
To25DTransform
Convert skeleton to 25D ML vector:
use cc_collection::To25DTransform;
use cc_collection::transform::To25DConfig;
// Simple creation
let mut transform = To25DTransform::new(60.0); // 60 FPS
// With custom config
let config = To25DConfig {
frame_rate: 60.0,
smooth_velocity: true,
smoothing_alpha: 0.3, // Higher = more smoothing, more lag
normalize: true,
scale: 2.0, // 2m reference space
clamp_output: true,
max_position: 5.0,
max_velocity: 20.0,
};
let mut transform = To25DTransform::with_config(config);
// Transform skeleton
let beat_phase = 0.5; // [0, 1] - position in beat
let motion: [f32; 25] = transform.transform(&skeleton, beat_phase);
// Access motion components
let hip_position = &motion[0..3]; // [x, y, z]
let hip_velocity = &motion[3..6]; // [vx, vy, vz]
let head_relative = &motion[6..9]; // Head relative to hip
let left_wrist_rel = &motion[9..12]; // Left wrist relative to hip
let right_wrist_rel = &motion[12..15]; // Right wrist relative to hip
let left_ankle_rel = &motion[15..18]; // Left ankle relative to hip
let right_ankle_rel = &motion[18..21]; // Right ankle relative to hip
let orientation = &motion[21..24]; // [yaw, pitch, roll]
let beat = motion[24]; // Beat phase
// Stats
println!("Frames: {}", transform.frame_count());
println!("Gimbal locks: {}", transform.gimbal_lock_frames());
// Reset for new session
transform.reset();To63DTransform
Extended transform with face and hand features:
use cc_collection::To63DTransform;
use cc_collection::transform::To63DConfig;
let mut transform = To63DTransform::new(60.0);
let motion: [f32; 63] = transform.transform(&skeleton, beat_phase);
// 63D layout
// 0-24: Core motion (same as 25D)
// 25-34: Face state (10 features)
// 35-48: Left hand (14 features)
// 49-62: Right hand (14 features)---
Capture Session
Record motion data with beat synchronization:
use cc_collection::{CaptureSession, FusedSkeleton};
use cc_collection::capture::{CaptureConfig, SessionState};
// Create session
let config = CaptureConfig {
name: "dance_recording_001".to_string(),
target_fps: 60.0,
min_quality: 0.7,
target_bpm: 120.0,
capacity: 36000, // ~10 minutes at 60fps
};
let mut session = CaptureSession::new(config);
// Session lifecycle
session.start();
assert_eq!(session.state(), SessionState::Recording);
// Record frames
for (skeleton, beat_phase, quality) in frames {
if session.capture(&skeleton, beat_phase, quality) {
// Frame accepted
} else {
// Rejected (below min_quality or session not recording)
}
}
// Pause/resume
session.pause();
session.resume();
// Stop recording
session.stop();
// Access statistics
println!("Frames: {}", session.frame_count());
println!("Duration: {:.1}s", session.duration_secs());
println!("Avg quality: {:.2}", session.average_quality());
println!("Avg FPS: {:.1}", session.average_fps());---
Validation
MotionValidator
Physics-based validation:
use cc_collection::{MotionValidator, ValidationReport};
use cc_collection::validation::ValidationConfig;
let validator = MotionValidator::new();
// Validate skeleton
let report: ValidationReport = validator.validate(&skeleton);
if report.is_valid {
println!("Valid! Quality: {:.2}", report.quality);
} else {
println!("Invalid! Issues:");
for issue in &report.issues {
println!(" - {} (limb: {:?})", issue.message, issue.limb);
}
}
// Validation checks:
// - Joint angle constraints
// - Velocity limits (no teleportation)
// - Anatomical plausibility
// - Sensor confidence---
Error Handling
cc-collection provides typed errors:
use cc_collection::error::{
Error, Result, FusionError, CaptureError,
TransformError, ProtocolError, ValidationError
};
fn process_frame() -> Result<()> {
let parser = MocopiParser::new();
// Parse with error handling
let frame = parser.parse(&data).map_err(|e| {
match e {
Error::Protocol(ProtocolError::InvalidHeader) => {
println!("Bad packet header");
}
Error::Protocol(ProtocolError::InvalidData(msg)) => {
println!("Invalid data: {}", msg);
}
_ => println!("Other error: {}", e),
}
e
})?;
Ok(())
}---
Complete Example
Real-time fusion pipeline:
use cc_collection::{
init, FusionEngine, FusionConfig,
MocopiParser, MediaPipeParser,
To25DTransform, MotionValidator,
CaptureSession, CaptureConfig,
};
use cc_collection::error::Result;
fn main() -> Result<()> {
// Initialize
init();
// Create components
let mut engine = FusionEngine::new(FusionConfig {
session_id: "live_capture".to_string(),
min_confidence: 0.5,
enable_smoothing: true,
..Default::default()
});
let mut mocopi_parser = MocopiParser::new();
let mut mediapipe_parser = MediaPipeParser::new();
let mut transform = To25DTransform::new(60.0);
let validator = MotionValidator::new();
let mut session = CaptureSession::new(CaptureConfig {
name: "session_001".to_string(),
target_fps: 60.0,
min_quality: 0.7,
target_bpm: 120.0,
capacity: 36000,
});
// Start capture
session.start();
// Processing loop (pseudo-code)
let dt = 1.0 / 60.0;
let mut beat_phase = 0.0;
loop {
// Get sensor data (from your data sources)
let mocopi_data = receive_mocopi_udp();
let mediapipe_data = receive_mediapipe_ws();
// Parse
let mocopi_frame = mocopi_data
.map(|data| mocopi_parser.parse(&data))
.transpose()?;
let mediapipe_frame = mediapipe_data
.map(|data| mediapipe_parser.parse_json(&data))
.transpose()?;
// Fuse
let skeleton = engine.process_frame(
mocopi_frame.as_ref(),
mediapipe_frame.as_ref(),
dt,
);
// Validate
let report = validator.validate(&skeleton);
// Transform for ML
let motion_25d = transform.transform(&skeleton, beat_phase);
// Record if valid
if report.is_valid {
session.capture(&skeleton, beat_phase, report.quality);
// Send to ML model, visualization, etc.
send_to_model(&motion_25d);
}
// Update beat phase (example: 120 BPM)
beat_phase = (beat_phase + dt * 2.0) % 1.0;
// Break condition
if should_stop() {
break;
}
}
// Finish
session.stop();
println!("Captured {} frames", session.frame_count());
Ok(())
}
// Placeholder functions
fn receive_mocopi_udp() -> Option<Vec<u8>> { None }
fn receive_mediapipe_ws() -> Option<String> { None }
fn send_to_model(_: &[f32; 25]) {}
fn should_stop() -> bool { true }---
Performance Tips
Reuse Objects
// Create once, reuse for entire session
let mut engine = FusionEngine::new(config);
let mut transform = To25DTransform::new(60.0);
let validator = MotionValidator::new();
// Process many frames without recreating
for _ in 0..10000 {
let skeleton = engine.process_frame(...);
let motion = transform.transform(&skeleton, beat_phase);
let report = validator.validate(&skeleton);
}Batch Processing
// For offline processing, batch transforms
let mut transform = To25DTransform::new(60.0);
let mut motions = Vec::with_capacity(skeletons.len());
for (skeleton, phase) in &skeletons {
motions.push(transform.transform(skeleton, *phase));
}Feature Flags
# Minimal build (no bindings)
cc-collection = { path = "..." }
# With Python bindings (adds PyO3 dependency)
cc-collection = { path = "...", features = ["python"] }
# With WASM bindings (adds wasm-bindgen)
cc-collection = { path = "...", features = ["wasm"] }---
Thread Safety
- `FusionEngine`, `To25DTransform`, `To63DTransform`, and `CaptureSession` are not thread-safe
- Create one instance per thread if needed
- Use channels or mutexes for cross-thread communication
use std::sync::mpsc;
use std::thread;
// Producer thread (sensor input)
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
loop {
let data = receive_sensor_data();
tx.send(data).unwrap();
}
});
// Consumer thread (fusion)
let mut engine = FusionEngine::new(config);
for data in rx {
let skeleton = engine.process_frame(...);
// Process skeleton
}---
Integration with cc-mcs
For server integration, see the cc-mcs fusion bridge:
// In cc-mcs server
use cc_collection::{FusionEngine, FusionConfig};
pub struct FusionBridge {
engine: FusionEngine,
transform: To25DTransform,
}
impl FusionBridge {
pub fn new(session_id: &str) -> Self {
let config = FusionConfig {
session_id: session_id.to_string(),
..Default::default()
};
Self {
engine: FusionEngine::new(config),
transform: To25DTransform::new(60.0),
}
}
pub fn process(&mut self, mocopi: Option<&MocopiFrame>,
mediapipe: Option<&MediaPipeFrame>,
beat_phase: f32) -> [f32; 25] {
let skeleton = self.engine.process_frame(
mocopi, mediapipe, 1.0 / 60.0
);
self.transform.transform(&skeleton, beat_phase)
}
}---
Testing
# Run all tests
cargo test
# Run with Python feature tests
cargo test --features python
# Run benchmarks
cargo bench
# Run specific test
cargo test fusion_engine
# Run with logging
RUST_LOG=debug cargo test -- --nocapture---
Logging
cc-collection uses the `tracing` crate:
use tracing_subscriber;
fn main() {
// Enable logging
tracing_subscriber::fmt()
.with_env_filter("cc_collection=debug")
.init();
// Now cc-collection will log debug info
let mut engine = FusionEngine::new(config);
}Environment variable:
RUST_LOG=cc_collection=debug cargo runPromotion Decision
Attach run IDs, datasets, metrics, and reproduction commands.
Source Anchor
Comp-Core/core/motion/cc-collection/docs/RUST_GUIDE.md
Detected Structure
Evaluation · Architecture