Pruebas y grabaciones
El motor de KinesiaMotion no necesita cámara ni UI, así que se puede probar con swift test en macOS y en CI.
RecordedPoseProvider
Sección titulada «RecordedPoseProvider»Reproduce una lista de poses como si vinieran de la cámara.
let provider = RecordedPoseProvider(poses, realTime: false)realTime: false(por defecto): entrega las poses lo más rápido posible. Ideal para pruebas.realTime: true: espera entre poses según sustimestamp, así una demo se ve en vivo.
Desde un video: VideoPoseProvider
Sección titulada «Desde un video: VideoPoseProvider»Saca las poses de un video (.mov o .mp4) con el mismo detector de cuerpo de Apple Vision que usa la cámara en vivo. El video se procesa en el mismo dispositivo y no se sube a ningún lado. Está en el módulo KinesiaMotionVision y funciona en iOS y macOS.
import KinesiaMotionimport KinesiaMotionVision
let video = VideoPoseProvider(url: videoURL)
// Igual que la cámara:await session.run(with: video)
// O todas las poses de una vez, para guardarlas o reusarlas:let poses = try await video.readAll()let replay = RecordedPoseProvider(poses)- Sirve para validar contra un goniómetro: graba la medición con el goniómetro al lado y compara los grados después, con calma.
- Lee videos del iPhone en cualquier orientación.
mirrored: truesi el video se ve como en un espejo (una selfie que guardó la imagen volteada): así los lados quedan como los del paciente.maximumFrameRate(30 por defecto) salta cuadros para ir más rápido;realTime: trueespera entre cuadros como la cámara.readAll()lanza un error si el archivo no se puede leer;poses()simplemente termina.
Poses sintéticas
Sección titulada «Poses sintéticas»Para una prueba basta con las articulaciones que mide el ejercicio. Este helper construye un brazo derecho con una flexión de codo exacta:
import CoreGraphicsimport KinesiaMotion
/// Right arm with an exact elbow flexion. y grows downward.func elbowPose(flexion: Double, time: TimeInterval) -> Pose { let r = Double.pi / 180 let shoulder = CGPoint(x: 0, y: 0) let elbow = CGPoint(x: 0, y: 100) let wrist = CGPoint(x: elbow.x + 100 * sin(flexion * r), y: elbow.y + 100 * cos(flexion * r)) return Pose(points: [.rightShoulder: shoulder, .rightElbow: elbow, .rightWrist: wrist], timestamp: time)}
/// 0 → peak → 0 in 10° steps at 24 FPS.func oneRep(peak: Double, startingAt start: TimeInterval = 0) -> [Pose] { var up = Array(stride(from: 0.0, to: peak, by: 10)) up.append(peak) let angles = up + up.reversed().dropFirst() return angles.enumerated().map { i, angle in elbowPose(flexion: angle, time: start + Double(i) / 24) }}Una prueba completa
Sección titulada «Una prueba completa»import Testingimport KinesiaMotion
@MainActor@Test func countsTwoCleanReps() async { let session = MotionSession(exercise: ExerciseLibrary.elbowFlexion, side: .right, smoothing: 1) let poses = oneRep(peak: 120) + oneRep(peak: 110, startingAt: 2)
await session.run(with: RecordedPoseProvider(poses))
#expect(session.snapshot.repCount == 2) #expect(session.peaks == [120, 110])}Usa run(with:) en lugar de start(with:): espera a que la grabación termine, así puedes revisar el resultado justo después.
Sin sesión: RepEngine
Sección titulada «Sin sesión: RepEngine»Si solo te interesa el conteo, RepEngine es un struct puro y síncrono:
var engine = RepEngine(definition: ExerciseLibrary.elbowFlexion, side: .right)engine.smoothing = 1for pose in oneRep(peak: 120) { let events = engine.process(pose) // ...}engine.count // 1Grabar sesiones reales
Sección titulada «Grabar sesiones reales»Pose no es Codable (por CGPoint y Joint como clave de diccionario), así que para guardar una grabación conviene un tipo propio:
struct RecordedFrame: Codable { var t: TimeInterval var points: [String: [Double]] // "rightElbow": [x, y]}
extension Pose { init(_ frame: RecordedFrame) { var points: [Joint: CGPoint] = [:] for (name, xy) in frame.points { if let joint = Joint(rawValue: name), xy.count == 2 { points[joint] = CGPoint(x: xy[0], y: xy[1]) } } self.init(points: points, timestamp: frame.t) }}Graba unos segundos con VisionPoseProvider, guárdalos como JSON junto a tus pruebas y tendrás casos reales (mala luz, ropa holgada, movimientos compensados) para ajustar umbrales sin depender de un paciente.