react-native-nitro-pose-exercises 1.1.6 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +78 -126
  2. package/android/src/main/java/com/margelo/nitro/nitroposeexercises/NitroPoseExercises.kt +121 -4
  3. package/ios/NitroPoseExercises.swift +128 -11
  4. package/lib/module/NitroPoseExercises.nitro.js.map +1 -1
  5. package/lib/module/config/bicep-curl.js +3 -0
  6. package/lib/module/config/bicep-curl.js.map +1 -1
  7. package/lib/module/config/boat-pose.js +3 -0
  8. package/lib/module/config/boat-pose.js.map +1 -1
  9. package/lib/module/config/bow-pose.js +3 -0
  10. package/lib/module/config/bow-pose.js.map +1 -1
  11. package/lib/module/config/bridge-pose.js +3 -0
  12. package/lib/module/config/bridge-pose.js.map +1 -1
  13. package/lib/module/config/calf-raise.js +3 -0
  14. package/lib/module/config/calf-raise.js.map +1 -1
  15. package/lib/module/config/camel-pose.js +3 -0
  16. package/lib/module/config/camel-pose.js.map +1 -1
  17. package/lib/module/config/chair-pose.js +3 -0
  18. package/lib/module/config/chair-pose.js.map +1 -1
  19. package/lib/module/config/childs-pose.js +3 -0
  20. package/lib/module/config/childs-pose.js.map +1 -1
  21. package/lib/module/config/cobra-pose.js +3 -0
  22. package/lib/module/config/cobra-pose.js.map +1 -1
  23. package/lib/module/config/cobra-wings.js +3 -0
  24. package/lib/module/config/cobra-wings.js.map +1 -1
  25. package/lib/module/config/dead-lift.js +3 -0
  26. package/lib/module/config/dead-lift.js.map +1 -1
  27. package/lib/module/config/downward-dog.js +3 -0
  28. package/lib/module/config/downward-dog.js.map +1 -1
  29. package/lib/module/config/extended-side-angle.js +3 -0
  30. package/lib/module/config/extended-side-angle.js.map +1 -1
  31. package/lib/module/config/fish-pose.js +3 -0
  32. package/lib/module/config/fish-pose.js.map +1 -1
  33. package/lib/module/config/front-raise.js +3 -0
  34. package/lib/module/config/front-raise.js.map +1 -1
  35. package/lib/module/config/glute-bridge.js +3 -0
  36. package/lib/module/config/glute-bridge.js.map +1 -1
  37. package/lib/module/config/hip-abduction.js +3 -0
  38. package/lib/module/config/hip-abduction.js.map +1 -1
  39. package/lib/module/config/knee-raise.js +3 -0
  40. package/lib/module/config/knee-raise.js.map +1 -1
  41. package/lib/module/config/lateral-raise.js +3 -0
  42. package/lib/module/config/lateral-raise.js.map +1 -1
  43. package/lib/module/config/leg-raise.js +3 -0
  44. package/lib/module/config/leg-raise.js.map +1 -1
  45. package/lib/module/config/lunge.js +3 -0
  46. package/lib/module/config/lunge.js.map +1 -1
  47. package/lib/module/config/mountain-pose.js +3 -0
  48. package/lib/module/config/mountain-pose.js.map +1 -1
  49. package/lib/module/config/overarm-reach.js +3 -0
  50. package/lib/module/config/overarm-reach.js.map +1 -1
  51. package/lib/module/config/plank.js +3 -0
  52. package/lib/module/config/plank.js.map +1 -1
  53. package/lib/module/config/pull-up.js +3 -0
  54. package/lib/module/config/pull-up.js.map +1 -1
  55. package/lib/module/config/pushup.js +4 -0
  56. package/lib/module/config/pushup.js.map +1 -1
  57. package/lib/module/config/reverse-warrior.js +3 -0
  58. package/lib/module/config/reverse-warrior.js.map +1 -1
  59. package/lib/module/config/shoulder-press.js +3 -0
  60. package/lib/module/config/shoulder-press.js.map +1 -1
  61. package/lib/module/config/side-lung.js +3 -0
  62. package/lib/module/config/side-lung.js.map +1 -1
  63. package/lib/module/config/side-plank.js +3 -0
  64. package/lib/module/config/side-plank.js.map +1 -1
  65. package/lib/module/config/situp.js +3 -0
  66. package/lib/module/config/situp.js.map +1 -1
  67. package/lib/module/config/squat.js +3 -0
  68. package/lib/module/config/squat.js.map +1 -1
  69. package/lib/module/config/sumo-squat.js +3 -0
  70. package/lib/module/config/sumo-squat.js.map +1 -1
  71. package/lib/module/config/tree-pose.js +3 -0
  72. package/lib/module/config/tree-pose.js.map +1 -1
  73. package/lib/module/config/triangle-pose.js +3 -0
  74. package/lib/module/config/triangle-pose.js.map +1 -1
  75. package/lib/module/config/tricep-dip.js +4 -0
  76. package/lib/module/config/tricep-dip.js.map +1 -1
  77. package/lib/module/config/v-up.js +3 -0
  78. package/lib/module/config/v-up.js.map +1 -1
  79. package/lib/module/config/wall-sit.js +3 -0
  80. package/lib/module/config/wall-sit.js.map +1 -1
  81. package/lib/module/config/warrior-i.js +3 -0
  82. package/lib/module/config/warrior-i.js.map +1 -1
  83. package/lib/module/config/warrior-ii.js +3 -0
  84. package/lib/module/config/warrior-ii.js.map +1 -1
  85. package/lib/module/config/warrior-iii.js +3 -0
  86. package/lib/module/config/warrior-iii.js.map +1 -1
  87. package/lib/typescript/src/NitroPoseExercises.nitro.d.ts +9 -1
  88. package/lib/typescript/src/NitroPoseExercises.nitro.d.ts.map +1 -1
  89. package/lib/typescript/src/config/bicep-curl.d.ts.map +1 -1
  90. package/lib/typescript/src/config/boat-pose.d.ts.map +1 -1
  91. package/lib/typescript/src/config/bow-pose.d.ts.map +1 -1
  92. package/lib/typescript/src/config/bridge-pose.d.ts.map +1 -1
  93. package/lib/typescript/src/config/calf-raise.d.ts.map +1 -1
  94. package/lib/typescript/src/config/camel-pose.d.ts.map +1 -1
  95. package/lib/typescript/src/config/chair-pose.d.ts.map +1 -1
  96. package/lib/typescript/src/config/childs-pose.d.ts.map +1 -1
  97. package/lib/typescript/src/config/cobra-pose.d.ts.map +1 -1
  98. package/lib/typescript/src/config/cobra-wings.d.ts.map +1 -1
  99. package/lib/typescript/src/config/dead-lift.d.ts.map +1 -1
  100. package/lib/typescript/src/config/downward-dog.d.ts.map +1 -1
  101. package/lib/typescript/src/config/extended-side-angle.d.ts.map +1 -1
  102. package/lib/typescript/src/config/fish-pose.d.ts.map +1 -1
  103. package/lib/typescript/src/config/front-raise.d.ts.map +1 -1
  104. package/lib/typescript/src/config/glute-bridge.d.ts.map +1 -1
  105. package/lib/typescript/src/config/hip-abduction.d.ts.map +1 -1
  106. package/lib/typescript/src/config/knee-raise.d.ts.map +1 -1
  107. package/lib/typescript/src/config/lateral-raise.d.ts.map +1 -1
  108. package/lib/typescript/src/config/leg-raise.d.ts.map +1 -1
  109. package/lib/typescript/src/config/lunge.d.ts.map +1 -1
  110. package/lib/typescript/src/config/mountain-pose.d.ts.map +1 -1
  111. package/lib/typescript/src/config/overarm-reach.d.ts.map +1 -1
  112. package/lib/typescript/src/config/plank.d.ts.map +1 -1
  113. package/lib/typescript/src/config/pull-up.d.ts.map +1 -1
  114. package/lib/typescript/src/config/pushup.d.ts.map +1 -1
  115. package/lib/typescript/src/config/reverse-warrior.d.ts.map +1 -1
  116. package/lib/typescript/src/config/shoulder-press.d.ts.map +1 -1
  117. package/lib/typescript/src/config/side-lung.d.ts.map +1 -1
  118. package/lib/typescript/src/config/side-plank.d.ts.map +1 -1
  119. package/lib/typescript/src/config/situp.d.ts.map +1 -1
  120. package/lib/typescript/src/config/squat.d.ts.map +1 -1
  121. package/lib/typescript/src/config/sumo-squat.d.ts.map +1 -1
  122. package/lib/typescript/src/config/tree-pose.d.ts.map +1 -1
  123. package/lib/typescript/src/config/triangle-pose.d.ts.map +1 -1
  124. package/lib/typescript/src/config/tricep-dip.d.ts.map +1 -1
  125. package/lib/typescript/src/config/v-up.d.ts.map +1 -1
  126. package/lib/typescript/src/config/wall-sit.d.ts.map +1 -1
  127. package/lib/typescript/src/config/warrior-i.d.ts.map +1 -1
  128. package/lib/typescript/src/config/warrior-ii.d.ts.map +1 -1
  129. package/lib/typescript/src/config/warrior-iii.d.ts.map +1 -1
  130. package/nitrogen/generated/android/c++/JCameraAngleType.hpp +58 -0
  131. package/nitrogen/generated/android/c++/JExerciseConfig.hpp +19 -3
  132. package/nitrogen/generated/android/c++/JHybridNitroPoseExercisesSpec.cpp +47 -0
  133. package/nitrogen/generated/android/c++/JHybridNitroPoseExercisesSpec.hpp +5 -0
  134. package/nitrogen/generated/android/c++/JPostureFamily.hpp +73 -0
  135. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/CameraAngleType.kt +23 -0
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/ExerciseConfig.kt +19 -4
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/HybridNitroPoseExercisesSpec.kt +32 -0
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/PostureFamily.kt +28 -0
  139. package/nitrogen/generated/ios/NitroPoseExercises-Swift-Cxx-Bridge.hpp +9 -0
  140. package/nitrogen/generated/ios/NitroPoseExercises-Swift-Cxx-Umbrella.hpp +6 -0
  141. package/nitrogen/generated/ios/c++/HybridNitroPoseExercisesSpecSwift.hpp +28 -0
  142. package/nitrogen/generated/ios/swift/CameraAngleType.swift +40 -0
  143. package/nitrogen/generated/ios/swift/ExerciseConfig.swift +17 -2
  144. package/nitrogen/generated/ios/swift/HybridNitroPoseExercisesSpec.swift +3 -0
  145. package/nitrogen/generated/ios/swift/HybridNitroPoseExercisesSpec_cxx.swift +76 -0
  146. package/nitrogen/generated/ios/swift/PostureFamily.swift +60 -0
  147. package/nitrogen/generated/shared/c++/CameraAngleType.hpp +76 -0
  148. package/nitrogen/generated/shared/c++/ExerciseConfig.hpp +20 -2
  149. package/nitrogen/generated/shared/c++/HybridNitroPoseExercisesSpec.cpp +5 -0
  150. package/nitrogen/generated/shared/c++/HybridNitroPoseExercisesSpec.hpp +5 -0
  151. package/nitrogen/generated/shared/c++/PostureFamily.hpp +96 -0
  152. package/package.json +1 -1
  153. package/src/NitroPoseExercises.nitro.ts +19 -0
  154. package/src/config/bicep-curl.ts +3 -0
  155. package/src/config/boat-pose.ts +3 -0
  156. package/src/config/bow-pose.ts +3 -0
  157. package/src/config/bridge-pose.ts +3 -0
  158. package/src/config/calf-raise.ts +3 -0
  159. package/src/config/camel-pose.ts +3 -0
  160. package/src/config/chair-pose.ts +3 -0
  161. package/src/config/childs-pose.ts +3 -0
  162. package/src/config/cobra-pose.ts +3 -0
  163. package/src/config/cobra-wings.ts +3 -0
  164. package/src/config/dead-lift.ts +3 -0
  165. package/src/config/downward-dog.ts +3 -0
  166. package/src/config/extended-side-angle.ts +3 -0
  167. package/src/config/fish-pose.ts +3 -0
  168. package/src/config/front-raise.ts +4 -0
  169. package/src/config/glute-bridge.ts +3 -0
  170. package/src/config/hip-abduction.ts +3 -0
  171. package/src/config/knee-raise.ts +3 -0
  172. package/src/config/lateral-raise.ts +3 -0
  173. package/src/config/leg-raise.ts +3 -0
  174. package/src/config/lunge.ts +3 -0
  175. package/src/config/mountain-pose.ts +3 -0
  176. package/src/config/overarm-reach.ts +3 -0
  177. package/src/config/plank.ts +3 -0
  178. package/src/config/pull-up.ts +3 -0
  179. package/src/config/pushup.ts +3 -0
  180. package/src/config/reverse-warrior.ts +3 -0
  181. package/src/config/shoulder-press.ts +3 -0
  182. package/src/config/side-lung.ts +3 -0
  183. package/src/config/side-plank.ts +3 -0
  184. package/src/config/situp.ts +3 -0
  185. package/src/config/squat.ts +3 -0
  186. package/src/config/sumo-squat.ts +4 -0
  187. package/src/config/tree-pose.ts +3 -0
  188. package/src/config/triangle-pose.ts +3 -0
  189. package/src/config/tricep-dip.ts +3 -0
  190. package/src/config/v-up.ts +4 -0
  191. package/src/config/wall-sit.ts +3 -0
  192. package/src/config/warrior-i.ts +3 -0
  193. package/src/config/warrior-ii.ts +3 -0
  194. package/src/config/warrior-iii.ts +3 -0
package/README.md CHANGED
@@ -12,6 +12,7 @@ A **React Native Nitro Module** for real-time, on-device exercise tracking using
12
12
  * 🔄 **Rep Counting** — Automatic rep detection with configurable state machines
13
13
  * 🧘 **Hold Tracking** — Duration and stability tracking for planks, yoga poses, and isometric holds
14
14
  * 📐 **Form Validation** — Real-time form feedback with angle-based rules
15
+ * 🚦 **Posture Gating** — Refuses to count reps unless the user is in valid posture; "Get in position" feedback before sessions start
15
16
  * 💀 **Skeleton Overlay** — Skia-powered skeleton with glow effects and live angle badges
16
17
  * ⚡ **Fully Native** — OS-level pose detection via Nitro Modules, zero JS bridge overhead
17
18
  * 📦 **Zero Model Bundling** — No ML model files to download or ship with your app
@@ -79,6 +80,7 @@ cd ios && pod install
79
80
  | --- | --- |
80
81
  | **Rep-Based Exercises** | Cyclic state machine (UP → DOWN → UP = 1 rep). Push-ups, squats, curls, and more. |
81
82
  | **Hold-Based Exercises** | Single target pose with duration + stability tracking. Planks, wall sits, yoga poses. |
83
+ | **Posture Gate** | Family-based posture validation. Refuses to start or count reps until user is in correct position (e.g. horizontal for pushups, upright for squats). |
82
84
  | **Form Feedback** | Angle-based rules with throttled real-time callbacks. Bad form blocks rep counting. |
83
85
  | **Skeleton Overlay** | Glow-effect bones, color-coded joints, and live angle badges drawn over camera via Skia. |
84
86
  | **Bilateral Tracking** | Left and right side angles tracked independently. |
@@ -236,132 +238,6 @@ const styles = StyleSheet.create({
236
238
  });
237
239
  ```
238
240
 
239
- ### Skeleton Overlay with Angle Badges — SkiaCamera
240
-
241
- > **Critical:** Create all `Skia.Paint()` and `Skia.Font()` objects **outside** the `onFrame` callback. Creating them inside causes memory leaks and crashes within seconds.
242
-
243
- ```tsx
244
- import { Skia } from '@shopify/react-native-skia';
245
- import { SkiaCamera } from 'react-native-vision-camera-skia';
246
- import { nitroPoseExercises } from 'react-native-nitro-pose-exercises';
247
-
248
- // Create paints ONCE at module level — NEVER inside onFrame
249
- const GLOW_PAINT = Skia.Paint();
250
- GLOW_PAINT.setColor(Skia.Color('rgba(0, 255, 102, 0.3)'));
251
- GLOW_PAINT.setStrokeWidth(14);
252
- GLOW_PAINT.setStyle(1);
253
- GLOW_PAINT.setStrokeCap(1);
254
- GLOW_PAINT.setAntiAlias(true);
255
-
256
- const BONE_PAINT = Skia.Paint();
257
- BONE_PAINT.setColor(Skia.Color('#00FF66'));
258
- BONE_PAINT.setStrokeWidth(6);
259
- BONE_PAINT.setStyle(1);
260
- BONE_PAINT.setStrokeCap(1);
261
- BONE_PAINT.setAntiAlias(true);
262
-
263
- const JOINT_PAINT = Skia.Paint();
264
- JOINT_PAINT.setColor(Skia.Color('#FF3366'));
265
- JOINT_PAINT.setStyle(0);
266
- JOINT_PAINT.setAntiAlias(true);
267
-
268
- const KEY_JOINT_PAINT = Skia.Paint();
269
- KEY_JOINT_PAINT.setColor(Skia.Color('#00FFFF'));
270
- KEY_JOINT_PAINT.setStyle(0);
271
- KEY_JOINT_PAINT.setAntiAlias(true);
272
-
273
- const ANGLE_BG_PAINT = Skia.Paint();
274
- ANGLE_BG_PAINT.setColor(Skia.Color('rgba(0, 0, 0, 0.7)'));
275
- ANGLE_BG_PAINT.setStyle(0);
276
-
277
- const ANGLE_TEXT_FONT = Skia.Font(null, 14);
278
- const ANGLE_TEXT_PAINT = Skia.Paint();
279
- ANGLE_TEXT_PAINT.setColor(Skia.Color('#FFFFFF'));
280
-
281
- const SKELETON_CONNECTIONS = [
282
- [11, 12], [11, 23], [12, 24], [23, 24],
283
- [11, 13], [13, 15], [12, 14], [14, 16],
284
- [23, 25], [25, 27], [24, 26], [26, 28],
285
- ];
286
-
287
- const KEY_LANDMARKS = [11, 12, 13, 14, 15, 16, 23, 24, 25, 26, 27, 28];
288
-
289
- <SkiaCamera
290
- style={StyleSheet.absoluteFill}
291
- isActive={true}
292
- device="back"
293
- pixelFormat="rgb"
294
- onFrame={(frame, render) => {
295
- 'worklet';
296
- try {
297
- nitroPoseExercises.processFrame(frame);
298
- const landmarks = nitroPoseExercises.landmarks;
299
-
300
- render(({ frameTexture, canvas }) => {
301
- canvas.drawImage(frameTexture, 0, 0);
302
-
303
- if (landmarks && landmarks.length > 0) {
304
- const w = frame.width;
305
- const h = frame.height;
306
-
307
- // Glow layer (thick translucent)
308
- for (const [i, j] of SKELETON_CONNECTIONS) {
309
- if (i < landmarks.length && j < landmarks.length) {
310
- const a = landmarks[i];
311
- const b = landmarks[j];
312
- if (a.visibility > 0.5 && b.visibility > 0.5) {
313
- canvas.drawLine(a.x * w, a.y * h, b.x * w, b.y * h, GLOW_PAINT);
314
- }
315
- }
316
- }
317
-
318
- // Solid bones on top
319
- for (const [i, j] of SKELETON_CONNECTIONS) {
320
- if (i < landmarks.length && j < landmarks.length) {
321
- const a = landmarks[i];
322
- const b = landmarks[j];
323
- if (a.visibility > 0.5 && b.visibility > 0.5) {
324
- canvas.drawLine(a.x * w, a.y * h, b.x * w, b.y * h, BONE_PAINT);
325
- }
326
- }
327
- }
328
-
329
- // Joints with glow rings
330
- for (let idx = 0; idx < landmarks.length; idx++) {
331
- const lm = landmarks[idx];
332
- if (lm && lm.visibility > 0.5) {
333
- const isKey = KEY_LANDMARKS.includes(idx);
334
- if (isKey) canvas.drawCircle(lm.x * w, lm.y * h, 12, GLOW_PAINT);
335
- canvas.drawCircle(lm.x * w, lm.y * h, isKey ? 8 : 4, isKey ? KEY_JOINT_PAINT : JOINT_PAINT);
336
- }
337
- }
338
-
339
- // Elbow angle badges
340
- for (const [shoulderIdx, elbowIdx, wristIdx] of [[11, 13, 15], [12, 14, 16]]) {
341
- const s = landmarks[shoulderIdx];
342
- const e = landmarks[elbowIdx];
343
- const wr = landmarks[wristIdx];
344
- if (s?.visibility > 0.5 && e?.visibility > 0.5 && wr?.visibility > 0.5) {
345
- const vaX = s.x - e.x, vaY = s.y - e.y;
346
- const vcX = wr.x - e.x, vcY = wr.y - e.y;
347
- const dot = vaX * vcX + vaY * vcY;
348
- const mag = Math.sqrt(vaX * vaX + vaY * vaY) * Math.sqrt(vcX * vcX + vcY * vcY);
349
- const angle = Math.round(Math.acos(Math.max(-1, Math.min(1, dot / mag))) * (180 / Math.PI));
350
- const tx = e.x * w + 15;
351
- const ty = e.y * h - 10;
352
- canvas.drawRoundRect({ x: tx - 4, y: ty - 14, width: 48, height: 20 }, 6, 6, ANGLE_BG_PAINT);
353
- canvas.drawText(`${angle}°`, tx, ty, ANGLE_TEXT_PAINT, ANGLE_TEXT_FONT);
354
- }
355
- }
356
- }
357
- });
358
- } finally {
359
- frame.dispose();
360
- }
361
- }}
362
- />
363
- ```
364
-
365
241
  ---
366
242
 
367
243
  ## 🧩 API Reference
@@ -389,6 +265,9 @@ startSession(targetReps: number, countdownSeconds: number): void
389
265
  pauseSession(): void
390
266
  resumeSession(): void
391
267
  stopSession(): void
268
+ // Returns true if the user is currently in valid posture for the loaded exercise.
269
+ // Poll this before starting a session, e.g. show "Get in position" until ready.
270
+ isReady(): boolean
392
271
  ```
393
272
 
394
273
  ### Frame Processing
@@ -416,6 +295,8 @@ onFormFeedback: ((feedback: FormFeedback) => void) | undefined
416
295
  onHoldProgress: ((progress: HoldProgress) => void) | undefined
417
296
  onPoseLost: (() => void) | undefined
418
297
  onPoseRegained: (() => void) | undefined
298
+ onPostureLost: (() => void) | undefined
299
+ onPostureRegained: (() => void) | undefined
419
300
  onSessionComplete: ((result: SessionResult) => void) | undefined
420
301
  ```
421
302
 
@@ -454,6 +335,74 @@ onSessionComplete: ((result: SessionResult) => void) | undefined
454
335
  angleHistory: AngleSnapshot[]
455
336
  }
456
337
  ```
338
+ ---
339
+
340
+ ## 🚦 Posture Gating
341
+
342
+ Each exercise config declares a **posture family** that defines what body position is required before reps are counted. This prevents false counts — e.g. waving your arm while standing won't count as a push-up.
343
+
344
+ ### Posture Families
345
+
346
+ | Family | Description | Used For |
347
+ | --- | --- | --- |
348
+ | `horizontalProne` | Body horizontal, face down. Shoulders, hips, ankles in a horizontal band. | Push-ups, planks, cobra, mountain climbers |
349
+ | `standingUpright` | Standing, shoulders above hips above knees. | Squats, lunges, curls, presses, most yoga poses |
350
+ | `seated` | Hips near knees, shoulders above hips. | Boat pose, seated yoga, child's pose |
351
+ | `supine` | Body horizontal, face up. | Sit-ups, glute bridge, leg raises |
352
+ | `sidePlank` | Body horizontal, rotated to one side. | Side plank, side leg raises |
353
+ | `inverted` | Hips higher than shoulders and ankles. | Downward dog, handstand |
354
+ | `none` | No posture gating. | Custom or unconstrained exercises |
355
+
356
+ ### Flow
357
+
358
+ loadExercise(config) → poll isReady() → user gets in position →
359
+ isReady() returns true → startSession() → reps counted normally
360
+
361
+ if posture breaks mid-session
362
+ → onPostureLost fires
363
+ → phase detection pauses
364
+ → in-progress rep discarded
365
+
366
+ user re-enters position
367
+ → onPostureRegained fires
368
+ → counting resumes
369
+
370
+ ### Example: Wait for Position Before Starting
371
+
372
+ ```tsx
373
+ const [isInPosition, setIsInPosition] = useState(false);
374
+
375
+ useEffect(() => {
376
+ // Wait for the user to get into position before starting
377
+ const checkInterval = setInterval(() => {
378
+ if (nitroPoseExercises.isReady()) {
379
+ clearInterval(checkInterval);
380
+ nitroPoseExercises.startSession(10, 3);
381
+ }
382
+ }, 300);
383
+ return () => clearInterval(interval);
384
+ }, []);
385
+
386
+ useEffect(() => {
387
+ nitroPoseExercises.onPostureLost = () => {
388
+ setMessage('Get back into position');
389
+ };
390
+ nitroPoseExercises.onPostureRegained = () => {
391
+ setMessage('');
392
+ };
393
+ }, []);
394
+
395
+ return (
396
+ <>
397
+ {!isInPosition && <Text>Get into push-up position</Text>}
398
+ {isInPosition && <Text>Hold still — starting...</Text>}
399
+ </>
400
+ );
401
+ ```
402
+
403
+ ### Tuning
404
+
405
+ Posture gates use a **10-frame hysteresis** (about 1 second at 30fps with frame throttling) — single-frame failures don't pause the session. This prevents flicker from momentary occlusion or visibility drops.
457
406
 
458
407
  ---
459
408
 
@@ -528,6 +477,7 @@ import type { ExerciseConfig } from 'react-native-nitro-pose-exercises';
528
477
  const MY_EXERCISE: ExerciseConfig = {
529
478
  name: 'Custom Exercise',
530
479
  type: 'rep', // 'rep' | 'hold'
480
+ postureFamily: 'standingUpright', // ← required: see table above
531
481
  angles: [
532
482
  { name: 'myAngle', landmarkA: 11, landmarkB: 13, landmarkC: 15 },
533
483
  ],
@@ -597,6 +547,8 @@ Each exercise config includes a `cameraAngle` recommendation (`'side'` or `'fron
597
547
  | **Pose lost detection** | `onPoseLost` / `onPoseRegained` callbacks when user exits/enters frame |
598
548
  | **Frame throttle** | Processes every 3rd frame to reduce CPU load without losing accuracy |
599
549
  | **Visibility filter** | Landmarks with confidence below 0.3 are excluded from angle calculations |
550
+ | **Posture entry gate** | Sessions don't start counting until `isReady()` returns true |
551
+ | **Posture hysteresis** | 10 consecutive failed frames required to fire `onPostureLost` — prevents flicker |
600
552
 
601
553
  ---
602
554
 
@@ -26,6 +26,7 @@ class NitroPoseExercises : HybridNitroPoseExercisesSpec() {
26
26
  private var poseDetector: PoseDetector? = null
27
27
  private var isInitialized = false
28
28
 
29
+
29
30
  // ─── Cached Landmarks (ML Kit is async, we cache last result) ──
30
31
  private var cachedLandmarks: Array<Landmark> = emptyArray()
31
32
  private val landmarkLock = Any()
@@ -115,6 +116,14 @@ class NitroPoseExercises : HybridNitroPoseExercisesSpec() {
115
116
  override var onPoseRegained: (() -> Unit)? = null
116
117
  override var onSessionComplete: ((result: SessionResult) -> Unit)? = null
117
118
 
119
+ override var onPostureLost: (() -> Unit)? = null
120
+ override var onPostureRegained: (() -> Unit)? = null
121
+
122
+ // ─── Posture Gate ──────────────────────────────────────────
123
+ private var consecutivePostureFailures: Int = 0
124
+ private val postureFailureThreshold: Int = 10
125
+ private var postureWasLost = false
126
+
118
127
  // ─── Hold Tracking ──────────────────────────────────────────
119
128
  private var holdStartTime: Long? = null
120
129
 
@@ -143,6 +152,12 @@ class NitroPoseExercises : HybridNitroPoseExercisesSpec() {
143
152
  resetSession()
144
153
  }
145
154
 
155
+ override fun isReady(): Boolean {
156
+ val config = exerciseConfig ?: return false
157
+ if (_landmarks.isEmpty()) return false
158
+ return isPostureValid(config.postureFamily)
159
+ }
160
+
146
161
  // ═══════════════════════════════════════════════════════════
147
162
  // Exercise Setup
148
163
  // ═══════════════════════════════════════════════════════════
@@ -201,7 +216,10 @@ override fun processFrame(frame: HybridFrameSpec) {
201
216
  val nativeBuffer = frame.getNativeBuffer()
202
217
  val bitmap = FrameHelper.hardwareBufferToBitmap(nativeBuffer.pointer) ?: return
203
218
 
204
- val inputImage = InputImage.fromBitmap(bitmap, 0)
219
+
220
+ val rotation = rotationDegreesFromFrame(frame)
221
+ val inputImage = InputImage.fromBitmap(bitmap, rotation)
222
+
205
223
  val imageWidth = bitmap.width.toDouble()
206
224
  val imageHeight = bitmap.height.toDouble()
207
225
 
@@ -270,9 +288,29 @@ override fun processFrame(frame: HybridFrameSpec) {
270
288
  // Exercise Logic Engine
271
289
  // ═══════════════════════════════════════════════════════════
272
290
 
273
- private fun processExerciseLogic() {
274
- val config = exerciseConfig ?: return
275
- if (_landmarks.isEmpty()) return
291
+ private fun processExerciseLogic() {
292
+ val config = exerciseConfig ?: return
293
+ if (_landmarks.isEmpty()) return
294
+
295
+ // Posture gate with hysteresis
296
+ if (!isPostureValid(config.postureFamily)) {
297
+ consecutivePostureFailures++
298
+ if (consecutivePostureFailures >= postureFailureThreshold) {
299
+ if (!postureWasLost) {
300
+ postureWasLost = true
301
+ onPostureLost?.invoke()
302
+ }
303
+ _currentPhase = ExercisePhase.UNKNOWN
304
+ phaseHistory.clear()
305
+ }
306
+ return
307
+ }
308
+
309
+ consecutivePostureFailures = 0
310
+ if (postureWasLost) {
311
+ postureWasLost = false
312
+ onPostureRegained?.invoke()
313
+ }
276
314
 
277
315
  val currentAngles = mutableMapOf<String, Double>()
278
316
  val angleSnapshots = mutableListOf<AngleSnapshot>()
@@ -511,6 +549,83 @@ override fun processFrame(frame: HybridFrameSpec) {
511
549
  onSessionComplete?.invoke(result)
512
550
  }
513
551
 
552
+ // ═══════════════════════════════════════════════════════════
553
+ // Orientation Helpers
554
+ // ═══════════════════════════════════════════════════════════
555
+
556
+ private fun rotationDegreesFromFrame(frame: HybridFrameSpec): Int {
557
+ return when (frame.orientation.name.lowercase()) {
558
+ "up" -> 0
559
+ "right" -> 90
560
+ "down" -> 180
561
+ "left" -> 270
562
+ else -> 0
563
+ }
564
+ }
565
+
566
+ // ═══════════════════════════════════════════════════════════
567
+ // Posture Gates
568
+ // ═══════════════════════════════════════════════════════════
569
+
570
+ private func isPostureValid(_ family: String) -> Bool {
571
+ guard _landmarks.count >= 33 else { return false }
572
+
573
+ let visThreshold = exerciseConfig?.visibilityThreshold ?? 0.3
574
+
575
+ if (_landmarks.size < 33) return false
576
+
577
+ val ls = _landmarks[11]; val rs = _landmarks[12]
578
+ val lh = _landmarks[23]; val rh = _landmarks[24]
579
+ val lk = _landmarks[25]; val rk = _landmarks[26]
580
+ val la = _landmarks[27]; val ra = _landmarks[28]
581
+
582
+ val keyVisible = ls.visibility > 0.3 && rs.visibility > 0.3 &&
583
+ lh.visibility > 0.3 && rh.visibility > 0.3
584
+ if (!keyVisible) return false
585
+
586
+ val shoulderY = (ls.y + rs.y) / 2
587
+ val hipY = (lh.y + rh.y) / 2
588
+ val shoulderX = (ls.x + rs.x) / 2
589
+ val hipX = (lh.x + rh.x) / 2
590
+
591
+ val kneesVisible = lk.visibility > 0.3 && rk.visibility > 0.3
592
+ val anklesVisible = la.visibility > 0.3 && ra.visibility > 0.3
593
+ val kneeY = if (kneesVisible) (lk.y + rk.y) / 2 else hipY
594
+ val ankleY = if (anklesVisible) (la.y + ra.y) / 2 else kneeY
595
+
596
+ return when (family) {
597
+ "horizontalProne" -> {
598
+ val ys = listOf(shoulderY, hipY, ankleY)
599
+ (ys.max() - ys.min()) < 0.25
600
+ }
601
+ "standingUpright" -> {
602
+ if (!kneesVisible) false
603
+ else shoulderY < hipY - 0.08 &&
604
+ hipY < kneeY + 0.05 &&
605
+ (if (anklesVisible) kneeY < ankleY else true)
606
+ }
607
+ "seated" -> {
608
+ if (!kneesVisible) false
609
+ else shoulderY < hipY - 0.05 && kotlin.math.abs(hipY - kneeY) < 0.20
610
+ }
611
+ "supine" -> {
612
+ val ys = listOf(shoulderY, hipY, ankleY)
613
+ (ys.max() - ys.min()) < 0.25
614
+ }
615
+ "sidePlank" -> {
616
+ val ySpread = kotlin.math.abs(shoulderY - hipY)
617
+ val shoulderHipDx = kotlin.math.abs(shoulderX - hipX)
618
+ ySpread < 0.20 && shoulderHipDx < 0.15
619
+ }
620
+ "inverted" -> {
621
+ if (!anklesVisible) false
622
+ else hipY < shoulderY && hipY < ankleY
623
+ }
624
+ "none" -> true
625
+ else -> true
626
+ }
627
+ }
628
+
514
629
  // ═══════════════════════════════════════════════════════════
515
630
  // Countdown
516
631
  // ═══════════════════════════════════════════════════════════
@@ -549,6 +664,8 @@ override fun processFrame(frame: HybridFrameSpec) {
549
664
  targetReps = 0.0
550
665
  countdownSeconds = 0.0
551
666
  frameCount = 0
667
+ consecutivePostureFailures = 0
668
+ postureWasLost = false
552
669
  synchronized(landmarkLock) {
553
670
  cachedLandmarks = emptyArray()
554
671
  }
@@ -70,6 +70,11 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
70
70
  private var allRepDurations: [Double] = []
71
71
  private var allRepFormScores: [Double] = []
72
72
 
73
+ // ─── Posture Gate ──────────────────────────────────────────
74
+ private var consecutivePostureFailures: Int = 0
75
+ private let postureFailureThreshold: Int = 10 // ~1s at 30fps with throttle=3
76
+ private var postureWasLost = false
77
+
73
78
  // ─── Pose Tracking ──────────────────────────────────────────
74
79
  private var poseWasLost = false
75
80
 
@@ -85,6 +90,8 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
85
90
  var onPoseLost: (() -> Void)?
86
91
  var onPoseRegained: (() -> Void)?
87
92
  var onSessionComplete: ((_ result: SessionResult) -> Void)?
93
+ var onPostureLost: (() -> Void)?
94
+ var onPostureRegained: (() -> Void)?
88
95
 
89
96
  // ─── Hold Tracking ──────────────────────────────────────────
90
97
  private var holdStartTime: Date?
@@ -108,6 +115,12 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
108
115
  resetSession()
109
116
  }
110
117
 
118
+ func isReady() throws -> Bool {
119
+ guard let config = exerciseConfig else { return false }
120
+ guard !_landmarks.isEmpty else { return false }
121
+ return isPostureValid(config.postureFamily)
122
+ }
123
+
111
124
  // ═══════════════════════════════════════════════════════════
112
125
  // MARK: - Exercise Setup
113
126
  // ═══════════════════════════════════════════════════════════
@@ -151,12 +164,23 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
151
164
  completeSession()
152
165
  }
153
166
 
167
+ private static func cgOrientation(orientation: CameraOrientation, isMirrored: Bool) -> CGImagePropertyOrientation {
168
+ switch orientation {
169
+ case .up: return isMirrored ? .upMirrored : .up
170
+ case .down: return isMirrored ? .downMirrored : .down
171
+ case .left: return isMirrored ? .leftMirrored : .left
172
+ case .right: return isMirrored ? .rightMirrored : .right
173
+ @unknown default: return .up
174
+ }
175
+ }
176
+
154
177
  // ═══════════════════════════════════════════════════════════
155
178
  // MARK: - Frame Processing (Apple Vision)
156
179
  // ═══════════════════════════════════════════════════════════
157
180
 
158
181
  func processFrame(frame: any HybridFrameSpec) throws {
159
182
  guard _status == .active || _status == .countdown else { return }
183
+
160
184
  guard isInitialized else { return }
161
185
 
162
186
  // Frame throttle
@@ -173,9 +197,11 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
173
197
  // Create Vision request
174
198
  let request = VNDetectHumanBodyPoseRequest()
175
199
 
176
- // Run synchronously on this frame processor thread
177
- let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
200
+ print("[PoseExercise] orientation=\(frame.orientation) isMirrored=\(frame.isMirrored) type=\(type(of: frame.orientation))")
178
201
 
202
+ let cgOrient = Self.cgOrientation(orientation: frame.orientation, isMirrored: frame.isMirrored)
203
+ let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: cgOrient, options: [:])
204
+
179
205
  do {
180
206
  try handler.perform([request])
181
207
 
@@ -241,13 +267,37 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
241
267
  // MARK: - Exercise Logic Engine
242
268
  // ═══════════════════════════════════════════════════════════
243
269
 
244
- private func processExerciseLogic() {
245
- guard let config = exerciseConfig else { return }
246
- guard !_landmarks.isEmpty else { return }
270
+ private func processExerciseLogic() {
271
+
272
+ guard let config = exerciseConfig else { return }
273
+ guard !_landmarks.isEmpty else { return }
274
+
275
+ // Posture gate with hysteresis
276
+ if !isPostureValid(family: config.postureFamily, threshold: config.visibilityThreshold) {
277
+ consecutivePostureFailures += 1
278
+ if consecutivePostureFailures >= postureFailureThreshold {
279
+ if !postureWasLost {
280
+ postureWasLost = true
281
+ onPostureLost?()
282
+ }
283
+ _currentPhase = .unknown
284
+ phaseHistory = []
285
+ }
286
+ return
287
+ }
288
+
289
+ consecutivePostureFailures = 0
290
+
291
+ if postureWasLost {
292
+ postureWasLost = false
293
+ onPostureRegained?()
294
+ }
247
295
 
248
296
  var currentAngles: [String: Double] = [:]
249
297
  var angleSnapshots: [AngleSnapshot] = []
250
298
 
299
+ let visThreshold = config.visibilityThreshold
300
+
251
301
  for angleDef in config.angles {
252
302
  let a = Int(angleDef.landmarkA)
253
303
  let b = Int(angleDef.landmarkB)
@@ -255,10 +305,10 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
255
305
 
256
306
  guard a < _landmarks.count, b < _landmarks.count, c < _landmarks.count else { continue }
257
307
 
258
- // Only calculate if all three landmarks have reasonable confidence
259
- guard _landmarks[a].visibility > 0.3,
260
- _landmarks[b].visibility > 0.3,
261
- _landmarks[c].visibility > 0.3 else { continue }
308
+ guard _landmarks[a].visibility > visThreshold,
309
+ _landmarks[b].visibility > visThreshold,
310
+ _landmarks[c].visibility > visThreshold else { continue }
311
+
262
312
 
263
313
  let angle = calculateAngle(
264
314
  pointA: _landmarks[a],
@@ -293,6 +343,71 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
293
343
  }
294
344
  }
295
345
 
346
+ // ═══════════════════════════════════════════════════════════
347
+ // MARK: - Posture Gates
348
+ // ═══════════════════════════════════════════════════════════
349
+
350
+ private func isPostureValid(_ family: String) -> Bool {
351
+ guard _landmarks.count >= 33 else { return false }
352
+
353
+ let ls = _landmarks[11], rs = _landmarks[12]
354
+ let lh = _landmarks[23], rh = _landmarks[24]
355
+ let lk = _landmarks[25], rk = _landmarks[26]
356
+ let la = _landmarks[27], ra = _landmarks[28]
357
+
358
+ let key = [ls, rs, lh, rh]
359
+ guard key.allSatisfy({ $0.visibility > 0.3 }) else { return false }
360
+
361
+ let shoulderY = (ls.y + rs.y) / 2
362
+ let hipY = (lh.y + rh.y) / 2
363
+ let shoulderX = (ls.x + rs.x) / 2
364
+ let hipX = (lh.x + rh.x) / 2
365
+
366
+ let kneesVisible = lk.visibility > 0.3 && rk.visibility > 0.3
367
+ let anklesVisible = la.visibility > 0.3 && ra.visibility > 0.3
368
+ let kneeY = kneesVisible ? (lk.y + rk.y) / 2 : hipY
369
+ let ankleY = anklesVisible ? (la.y + ra.y) / 2 : kneeY
370
+
371
+ switch family {
372
+ case "horizontalProne":
373
+ let ys = [shoulderY, hipY, ankleY]
374
+ let spread = (ys.max() ?? 0) - (ys.min() ?? 0)
375
+ return spread < 0.25
376
+
377
+ case "standingUpright":
378
+ guard kneesVisible else { return false }
379
+ return shoulderY < hipY - 0.08
380
+ && hipY < kneeY + 0.05
381
+ && (anklesVisible ? kneeY < ankleY : true)
382
+
383
+ case "seated":
384
+ guard kneesVisible else { return false }
385
+ return shoulderY < hipY - 0.05
386
+ && abs(hipY - kneeY) < 0.20
387
+
388
+ case "supine":
389
+ let ys = [shoulderY, hipY, ankleY]
390
+ let spread = (ys.max() ?? 0) - (ys.min() ?? 0)
391
+ return spread < 0.25
392
+
393
+ case "sidePlank":
394
+ let ys = [shoulderY, hipY]
395
+ let ySpread = (ys.max() ?? 0) - (ys.min() ?? 0)
396
+ let shoulderHipDx = abs(shoulderX - hipX)
397
+ return ySpread < 0.20 && shoulderHipDx < 0.15
398
+
399
+ case "inverted":
400
+ guard anklesVisible else { return false }
401
+ return hipY < shoulderY && hipY < ankleY
402
+
403
+ case "none":
404
+ return true
405
+
406
+ default:
407
+ return true
408
+ }
409
+ }
410
+
296
411
  // ═══════════════════════════════════════════════════════════
297
412
  // MARK: - Angle Calculation
298
413
  // ═══════════════════════════════════════════════════════════
@@ -547,6 +662,8 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
547
662
  countdownSeconds = 0
548
663
  countdownTimer?.invalidate()
549
664
  countdownTimer = nil
550
- frameCount = 0
665
+ frameCount = 0,
666
+ consecutivePostureFailures = 0
667
+ postureWasLost = false
551
668
  }
552
- }
669
+ }
@@ -1 +1 @@
1
- {"version":3,"names":["NitroModules","nitroPoseExercises","createHybridObject"],"sourceRoot":"../../src","sources":["NitroPoseExercises.nitro.ts"],"mappings":";;AAAA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAI5E;;AAUA;;AASA;;AAmCA;;AAmCA;;AAwCA,MAAMC,kBAAkB,GACtBD,YAAY,CAACE,kBAAkB,CAAqB,cAAc,CAAC;AAErE,SAASD,kBAAkB","ignoreList":[]}
1
+ {"version":3,"names":["NitroModules","nitroPoseExercises","createHybridObject"],"sourceRoot":"../../src","sources":["NitroPoseExercises.nitro.ts"],"mappings":";;AAAA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAI5E;;AAqBA;;AASA;;AAsCA;;AAmCA;;AA4CA,MAAMC,kBAAkB,GACtBD,YAAY,CAACE,kBAAkB,CAAqB,cAAc,CAAC;AAErE,SAASD,kBAAkB","ignoreList":[]}
@@ -7,6 +7,9 @@
7
7
  export const BICEP_CURL_CONFIG = {
8
8
  name: 'Bicep Curl',
9
9
  type: 'rep',
10
+ postureFamily: 'standingUpright',
11
+ visibilityThreshold: 0.2,
12
+ cameraAngle: 'front',
10
13
  angles: [{
11
14
  name: 'leftElbow',
12
15
  landmarkA: 11,
@@ -1 +1 @@
1
- {"version":3,"names":["BICEP_CURL_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/bicep-curl.ts"],"mappings":";;AAEA;AACA;AACA;;AAEA,OAAO,MAAMA,iBAAiC,GAAG;EAC/CC,IAAI,EAAE,YAAY;EAClBC,IAAI,EAAE,KAAK;EACXC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,WAAW;IACjBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,YAAY;IAClBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,cAAc;IACpBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,CACF;EACDC,MAAM,EAAE,CACN;IACEC,KAAK,EAAE,MAAM;IACbC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEH,KAAK,EAAE,IAAI;IACXC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;EACnCC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,YAAY;IAClBa,OAAO,EAAE,uCAAuC;IAChDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,cAAc;IACzBC,QAAQ,EAAE,CAAC;IACXC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,UAAU;IAChBa,OAAO,EAAE,oCAAoC;IAC7CC,QAAQ,EAAE,OAAO;IACjBN,SAAS,EAAE,cAAc;IACzBC,QAAQ,EAAE,CAAC;IACXC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["BICEP_CURL_CONFIG","name","type","postureFamily","visibilityThreshold","cameraAngle","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/bicep-curl.ts"],"mappings":";;AAEA;AACA;AACA;;AAEA,OAAO,MAAMA,iBAAiC,GAAG;EAC/CC,IAAI,EAAE,YAAY;EAClBC,IAAI,EAAE,KAAK;EACXC,aAAa,EAAE,iBAAiB;EAChCC,mBAAmB,EAAE,GAAG;EACxBC,WAAW,EAAE,OAAO;EACpBC,MAAM,EAAE,CACN;IACEL,IAAI,EAAE,WAAW;IACjBM,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACER,IAAI,EAAE,YAAY;IAClBM,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACER,IAAI,EAAE,cAAc;IACpBM,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,CACF;EACDC,MAAM,EAAE,CACN;IACEC,KAAK,EAAE,MAAM;IACbC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEH,KAAK,EAAE,IAAI;IACXC,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;EACnCC,SAAS,EAAE,CACT;IACEf,IAAI,EAAE,YAAY;IAClBgB,OAAO,EAAE,uCAAuC;IAChDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,cAAc;IACzBC,QAAQ,EAAE,CAAC;IACXC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEb,IAAI,EAAE,UAAU;IAChBgB,OAAO,EAAE,oCAAoC;IAC7CC,QAAQ,EAAE,OAAO;IACjBN,SAAS,EAAE,cAAc;IACzBC,QAAQ,EAAE,CAAC;IACXC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
@@ -3,6 +3,9 @@
3
3
  export const BOAT_POSE_CONFIG = {
4
4
  name: 'Boat Pose (Navasana)',
5
5
  type: 'hold',
6
+ postureFamily: 'seated',
7
+ visibilityThreshold: 0.3,
8
+ cameraAngle: 'side',
6
9
  angles: [{
7
10
  name: 'hipFlexion',
8
11
  landmarkA: 11,
@@ -1 +1 @@
1
- {"version":3,"names":["BOAT_POSE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/boat-pose.ts"],"mappings":";;AAEA,OAAO,MAAMA,gBAAgC,GAAG;EAC9CC,IAAI,EAAE,sBAAsB;EAC5BC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IAAEF,IAAI,EAAE,YAAY;IAAEG,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,EACnE;IAAEL,IAAI,EAAE,WAAW;IAAEG,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,CACnE;EACDC,MAAM,EAAE,CACN;IAAEC,KAAK,EAAE,MAAM;IAAEC,SAAS,EAAE,YAAY;IAAEC,QAAQ,EAAE,EAAE;IAAEC,QAAQ,EAAE;EAAI,CAAC,CACxE;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,cAAc;IACpBa,OAAO,EAAE,6CAA6C;IACtDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,YAAY;IACvBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,cAAc;IACpBa,OAAO,EAAE,uBAAuB;IAChCC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["BOAT_POSE_CONFIG","name","type","postureFamily","visibilityThreshold","cameraAngle","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/boat-pose.ts"],"mappings":";;AAEA,OAAO,MAAMA,gBAAgC,GAAG;EAC9CC,IAAI,EAAE,sBAAsB;EAC5BC,IAAI,EAAE,MAAM;EACZC,aAAa,EAAE,QAAQ;EACvBC,mBAAmB,EAAE,GAAG;EACxBC,WAAW,EAAE,MAAM;EACnBC,MAAM,EAAE,CACN;IAAEL,IAAI,EAAE,YAAY;IAAEM,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,EACnE;IAAER,IAAI,EAAE,WAAW;IAAEM,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,CACnE;EACDC,MAAM,EAAE,CACN;IAAEC,KAAK,EAAE,MAAM;IAAEC,SAAS,EAAE,YAAY;IAAEC,QAAQ,EAAE,EAAE;IAAEC,QAAQ,EAAE;EAAI,CAAC,CACxE;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEf,IAAI,EAAE,cAAc;IACpBgB,OAAO,EAAE,6CAA6C;IACtDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,YAAY;IACvBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEb,IAAI,EAAE,cAAc;IACpBgB,OAAO,EAAE,uBAAuB;IAChCC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}