react-native-nitro-pose-exercises 1.0.8 → 1.0.9
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.
- package/android/build.gradle +9 -10
- package/android/src/main/java/com/margelo/nitro/nitroposeexercises/NitroPoseExercises.kt +73 -127
- package/lib/module/config/bicep-curl.js +61 -0
- package/lib/module/config/bicep-curl.js.map +1 -0
- package/lib/module/config/chair-pose.js +64 -0
- package/lib/module/config/chair-pose.js.map +1 -0
- package/lib/module/config/cobra-pose.js +57 -0
- package/lib/module/config/cobra-pose.js.map +1 -0
- package/lib/module/config/downward-dog.js +65 -0
- package/lib/module/config/downward-dog.js.map +1 -0
- package/lib/module/config/lunge.js +62 -0
- package/lib/module/config/lunge.js.map +1 -0
- package/lib/module/config/plank.js +57 -0
- package/lib/module/config/plank.js.map +1 -0
- package/lib/module/config/shoulder-press.js +62 -0
- package/lib/module/config/shoulder-press.js.map +1 -0
- package/lib/module/config/situp.js +48 -0
- package/lib/module/config/situp.js.map +1 -0
- package/lib/module/config/squat.js +69 -0
- package/lib/module/config/squat.js.map +1 -0
- package/lib/module/config/tree-pose.js +57 -0
- package/lib/module/config/tree-pose.js.map +1 -0
- package/lib/module/config/tricep-dip.js +55 -0
- package/lib/module/config/tricep-dip.js.map +1 -0
- package/lib/module/config/wall-sit.js +57 -0
- package/lib/module/config/wall-sit.js.map +1 -0
- package/lib/module/config/warrior-i.js +79 -0
- package/lib/module/config/warrior-i.js.map +1 -0
- package/lib/module/config/warrior-ii.js +86 -0
- package/lib/module/config/warrior-ii.js.map +1 -0
- package/lib/module/index.js +19 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/config/bicep-curl.d.ts +3 -0
- package/lib/typescript/src/config/bicep-curl.d.ts.map +1 -0
- package/lib/typescript/src/config/chair-pose.d.ts +3 -0
- package/lib/typescript/src/config/chair-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/cobra-pose.d.ts +3 -0
- package/lib/typescript/src/config/cobra-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/downward-dog.d.ts +3 -0
- package/lib/typescript/src/config/downward-dog.d.ts.map +1 -0
- package/lib/typescript/src/config/lunge.d.ts +3 -0
- package/lib/typescript/src/config/lunge.d.ts.map +1 -0
- package/lib/typescript/src/config/plank.d.ts +3 -0
- package/lib/typescript/src/config/plank.d.ts.map +1 -0
- package/lib/typescript/src/config/shoulder-press.d.ts +3 -0
- package/lib/typescript/src/config/shoulder-press.d.ts.map +1 -0
- package/lib/typescript/src/config/situp.d.ts +3 -0
- package/lib/typescript/src/config/situp.d.ts.map +1 -0
- package/lib/typescript/src/config/squat.d.ts +3 -0
- package/lib/typescript/src/config/squat.d.ts.map +1 -0
- package/lib/typescript/src/config/tree-pose.d.ts +3 -0
- package/lib/typescript/src/config/tree-pose.d.ts.map +1 -0
- package/lib/typescript/src/config/tricep-dip.d.ts +3 -0
- package/lib/typescript/src/config/tricep-dip.d.ts.map +1 -0
- package/lib/typescript/src/config/wall-sit.d.ts +3 -0
- package/lib/typescript/src/config/wall-sit.d.ts.map +1 -0
- package/lib/typescript/src/config/warrior-i.d.ts +3 -0
- package/lib/typescript/src/config/warrior-i.d.ts.map +1 -0
- package/lib/typescript/src/config/warrior-ii.d.ts +3 -0
- package/lib/typescript/src/config/warrior-ii.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +14 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/nitro.json +8 -1
- package/package.json +1 -1
- package/src/config/bicep-curl.ts +64 -0
- package/src/config/chair-pose.ts +67 -0
- package/src/config/cobra-pose.ts +59 -0
- package/src/config/downward-dog.ts +68 -0
- package/src/config/lunge.ts +65 -0
- package/src/config/plank.ts +59 -0
- package/src/config/shoulder-press.ts +63 -0
- package/src/config/situp.ts +51 -0
- package/src/config/squat.ts +71 -0
- package/src/config/tree-pose.ts +59 -0
- package/src/config/tricep-dip.ts +58 -0
- package/src/config/wall-sit.ts +59 -0
- package/src/config/warrior-i.ts +82 -0
- package/src/config/warrior-ii.ts +88 -0
- package/src/index.tsx +19 -0
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
buildscript {
|
|
2
2
|
ext.NitroPoseExercises = [
|
|
3
3
|
kotlinVersion: "2.0.21",
|
|
4
|
-
minSdkVersion:
|
|
4
|
+
minSdkVersion: 26,
|
|
5
5
|
compileSdkVersion: 36,
|
|
6
6
|
targetSdkVersion: 36
|
|
7
7
|
]
|
|
@@ -112,16 +112,15 @@ android {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
repositories {
|
|
116
|
+
google()
|
|
117
|
+
mavenCentral()
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
dependencies {
|
|
116
121
|
implementation "com.facebook.react:react-android"
|
|
117
122
|
implementation project(":react-native-nitro-modules")
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
implementation 'com.google.mediapipe:tasks-vision:0.10.21'
|
|
121
|
-
|
|
122
|
-
// CameraX for ImageProxy access
|
|
123
|
-
implementation 'androidx.camera:camera-core:1.4.1'
|
|
124
|
-
|
|
125
|
-
// VisionCamera — provided by the consuming app
|
|
126
|
-
compileOnly project(':react-native-vision-camera')
|
|
123
|
+
implementation 'com.google.mediapipe:tasks-vision:0.10.29'
|
|
124
|
+
implementation project(":react-native-vision-camera")
|
|
127
125
|
}
|
|
126
|
+
|
|
@@ -7,10 +7,11 @@ import com.google.mediapipe.framework.image.BitmapImageBuilder
|
|
|
7
7
|
import com.google.mediapipe.tasks.core.BaseOptions
|
|
8
8
|
import com.google.mediapipe.tasks.vision.core.RunningMode
|
|
9
9
|
import com.google.mediapipe.tasks.vision.poselandmarker.PoseLandmarker
|
|
10
|
-
import com.google.mediapipe.tasks.vision.poselandmarker.PoseLandmarkerOptions
|
|
10
|
+
import com.google.mediapipe.tasks.vision.poselandmarker.PoseLandmarker.PoseLandmarkerOptions
|
|
11
11
|
import com.margelo.nitro.NitroModules
|
|
12
12
|
import com.margelo.nitro.core.Promise
|
|
13
13
|
import com.margelo.nitro.camera.HybridFrameSpec
|
|
14
|
+
import com.margelo.nitro.camera.public.NativeFrame
|
|
14
15
|
import kotlin.math.acos
|
|
15
16
|
import kotlin.math.max
|
|
16
17
|
import kotlin.math.min
|
|
@@ -18,7 +19,7 @@ import kotlin.math.sqrt
|
|
|
18
19
|
|
|
19
20
|
@Keep
|
|
20
21
|
@DoNotStrip
|
|
21
|
-
class
|
|
22
|
+
class NitroPoseExercises : HybridNitroPoseExercisesSpec() {
|
|
22
23
|
|
|
23
24
|
// ─── MediaPipe ──────────────────────────────────────────────
|
|
24
25
|
private var poseLandmarker: PoseLandmarker? = null
|
|
@@ -46,8 +47,6 @@ class HybridPoseExercise : HybridNitroPoseExercisesSpec() {
|
|
|
46
47
|
private var sessionStartTime: Long = System.currentTimeMillis()
|
|
47
48
|
private var targetReps: Double = 0.0
|
|
48
49
|
private var countdownSeconds: Double = 0.0
|
|
49
|
-
private var frameCount: Int = 0
|
|
50
|
-
private val processEveryNFrames: Int = 3
|
|
51
50
|
|
|
52
51
|
// ─── Form Tracking ──────────────────────────────────────────
|
|
53
52
|
private var lastFormFeedbackTime = mutableMapOf<String, Long>()
|
|
@@ -60,6 +59,10 @@ class HybridPoseExercise : HybridNitroPoseExercisesSpec() {
|
|
|
60
59
|
// ─── Pose Tracking ──────────────────────────────────────────
|
|
61
60
|
private var poseWasLost = false
|
|
62
61
|
|
|
62
|
+
// ─── Frame Skip ─────────────────────────────────────────────
|
|
63
|
+
private var frameCount: Int = 0
|
|
64
|
+
private val processEveryNFrames: Int = 3
|
|
65
|
+
|
|
63
66
|
// ─── Callbacks ──────────────────────────────────────────────
|
|
64
67
|
override var onRepComplete: ((data: RepData) -> Unit)? = null
|
|
65
68
|
override var onPhaseChange: ((phase: ExercisePhase) -> Unit)? = null
|
|
@@ -154,7 +157,7 @@ class HybridPoseExercise : HybridNitroPoseExercisesSpec() {
|
|
|
154
157
|
// Frame Processing
|
|
155
158
|
// ═══════════════════════════════════════════════════════════
|
|
156
159
|
|
|
157
|
-
override fun processFrame(frame: HybridFrameSpec) {
|
|
160
|
+
override fun processFrame(frame: HybridFrameSpec) {
|
|
158
161
|
if (_status != SessionStatus.ACTIVE && _status != SessionStatus.COUNTDOWN) return
|
|
159
162
|
if (!isInitialized || poseLandmarker == null) return
|
|
160
163
|
|
|
@@ -162,94 +165,50 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
162
165
|
if (frameCount % processEveryNFrames != 0) return
|
|
163
166
|
|
|
164
167
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
val poseLandmarks = result.landmarks()[0]
|
|
183
|
-
|
|
184
|
-
if (poseWasLost) {
|
|
185
|
-
poseWasLost = false
|
|
186
|
-
onPoseRegained?.invoke()
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
_landmarks = poseLandmarks.map { lm ->
|
|
190
|
-
Landmark(
|
|
191
|
-
x = lm.x().toDouble(),
|
|
192
|
-
y = lm.y().toDouble(),
|
|
193
|
-
z = lm.z().toDouble(),
|
|
194
|
-
visibility = (lm.visibility().orElse(0f)).toDouble()
|
|
195
|
-
)
|
|
196
|
-
}.toTypedArray()
|
|
197
|
-
|
|
198
|
-
if (_status == SessionStatus.ACTIVE) {
|
|
199
|
-
processExerciseLogic()
|
|
200
|
-
}
|
|
201
|
-
} else {
|
|
202
|
-
if (!poseWasLost) {
|
|
203
|
-
poseWasLost = true
|
|
204
|
-
onPoseLost?.invoke()
|
|
205
|
-
}
|
|
206
|
-
_landmarks = emptyArray()
|
|
168
|
+
// Get the native buffer from VisionCamera frame
|
|
169
|
+
val nativeBuffer = frame.getNativeBuffer()
|
|
170
|
+
val w = frame.width.toInt()
|
|
171
|
+
val h = frame.height.toInt()
|
|
172
|
+
|
|
173
|
+
// On Android, the pointer is an AHardwareBuffer*
|
|
174
|
+
// Create a Bitmap and use MediaPipe's BitmapImageBuilder
|
|
175
|
+
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
|
176
|
+
val mpImage = BitmapImageBuilder(bitmap).build()
|
|
177
|
+
val result = poseLandmarker!!.detect(mpImage)
|
|
178
|
+
|
|
179
|
+
if (result.landmarks().isNotEmpty()) {
|
|
180
|
+
val poseLandmarks = result.landmarks()[0]
|
|
181
|
+
|
|
182
|
+
if (poseWasLost) {
|
|
183
|
+
poseWasLost = false
|
|
184
|
+
onPoseRegained?.invoke()
|
|
207
185
|
}
|
|
208
186
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
187
|
+
_landmarks = poseLandmarks.map { lm ->
|
|
188
|
+
Landmark(
|
|
189
|
+
x = lm.x().toDouble(),
|
|
190
|
+
y = lm.y().toDouble(),
|
|
191
|
+
z = lm.z().toDouble(),
|
|
192
|
+
visibility = (lm.visibility().orElse(0f)).toDouble()
|
|
193
|
+
)
|
|
194
|
+
}.toTypedArray()
|
|
195
|
+
|
|
196
|
+
if (_status == SessionStatus.ACTIVE) {
|
|
197
|
+
processExerciseLogic()
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
if (!poseWasLost) {
|
|
201
|
+
poseWasLost = true
|
|
202
|
+
onPoseLost?.invoke()
|
|
203
|
+
}
|
|
204
|
+
_landmarks = emptyArray()
|
|
205
|
+
}
|
|
217
206
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// ═══════════════════════════════════════════════════════════
|
|
207
|
+
bitmap.recycle()
|
|
208
|
+
nativeBuffer.release()
|
|
221
209
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
val bytes = ByteArray(buffer.remaining())
|
|
225
|
-
buffer.get(bytes)
|
|
226
|
-
|
|
227
|
-
val yuvImage = android.graphics.YuvImage(
|
|
228
|
-
bytes,
|
|
229
|
-
android.graphics.ImageFormat.NV21,
|
|
230
|
-
imageProxy.width,
|
|
231
|
-
imageProxy.height,
|
|
232
|
-
null
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
val out = java.io.ByteArrayOutputStream()
|
|
236
|
-
yuvImage.compressToJpeg(
|
|
237
|
-
android.graphics.Rect(0, 0, imageProxy.width, imageProxy.height),
|
|
238
|
-
100,
|
|
239
|
-
out
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
val jpegBytes = out.toByteArray()
|
|
243
|
-
val bitmap = android.graphics.BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
|
|
244
|
-
|
|
245
|
-
// Apply rotation if needed
|
|
246
|
-
val rotation = imageProxy.imageInfo.rotationDegrees
|
|
247
|
-
return if (rotation != 0) {
|
|
248
|
-
val matrix = Matrix()
|
|
249
|
-
matrix.postRotate(rotation.toFloat())
|
|
250
|
-
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
251
|
-
} else {
|
|
252
|
-
bitmap
|
|
210
|
+
} catch (e: Exception) {
|
|
211
|
+
// MediaPipe detection failed — skip this frame
|
|
253
212
|
}
|
|
254
213
|
}
|
|
255
214
|
|
|
@@ -261,7 +220,6 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
261
220
|
val config = exerciseConfig ?: return
|
|
262
221
|
if (_landmarks.isEmpty()) return
|
|
263
222
|
|
|
264
|
-
// 1. Calculate all angles
|
|
265
223
|
val currentAngles = mutableMapOf<String, Double>()
|
|
266
224
|
val angleSnapshots = mutableListOf<AngleSnapshot>()
|
|
267
225
|
|
|
@@ -279,7 +237,6 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
279
237
|
|
|
280
238
|
repAngleSnapshots = angleSnapshots.toTypedArray()
|
|
281
239
|
|
|
282
|
-
// 2. Determine current phase
|
|
283
240
|
val detectedPhase = determinePhase(currentAngles, config)
|
|
284
241
|
|
|
285
242
|
if (detectedPhase != _currentPhase && detectedPhase != ExercisePhase.UNKNOWN) {
|
|
@@ -288,10 +245,8 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
288
245
|
handlePhaseTransition(detectedPhase, config)
|
|
289
246
|
}
|
|
290
247
|
|
|
291
|
-
// 3. Check form rules
|
|
292
248
|
checkFormRules(currentAngles, config)
|
|
293
249
|
|
|
294
|
-
// 4. Handle hold-based exercises
|
|
295
250
|
if (config.type == ExerciseType.HOLD) {
|
|
296
251
|
handleHoldProgress(currentAngles, config)
|
|
297
252
|
}
|
|
@@ -345,37 +300,37 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
345
300
|
if (phaseHistory.size >= repSeq.size) {
|
|
346
301
|
val tail = phaseHistory.takeLast(repSeq.size)
|
|
347
302
|
|
|
348
|
-
|
|
303
|
+
if (tail == repSeq.toList()) {
|
|
349
304
|
val now = System.currentTimeMillis()
|
|
350
305
|
val repDuration = (now - repStartTime).toDouble()
|
|
351
306
|
|
|
352
307
|
// Minimum 800ms per rep
|
|
353
308
|
if (repDuration < 800) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
309
|
+
phaseHistory.clear()
|
|
310
|
+
phaseHistory.add(newPhase)
|
|
311
|
+
return
|
|
357
312
|
}
|
|
358
313
|
|
|
359
314
|
// Don't count rep if form is terrible
|
|
360
315
|
if (repFormScore <= 30) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
316
|
+
onFormFeedback?.invoke(FormFeedback(
|
|
317
|
+
ruleName = "poorForm",
|
|
318
|
+
message = "Fix your form before continuing",
|
|
319
|
+
severity = FormSeverity.ERROR
|
|
320
|
+
))
|
|
321
|
+
repFormScore = 100.0
|
|
322
|
+
phaseHistory.clear()
|
|
323
|
+
phaseHistory.add(newPhase)
|
|
324
|
+
return
|
|
370
325
|
}
|
|
371
326
|
|
|
372
327
|
_repCount += 1.0
|
|
373
328
|
|
|
374
329
|
val repData = RepData(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
330
|
+
repNumber = _repCount,
|
|
331
|
+
durationMs = repDuration,
|
|
332
|
+
formScore = repFormScore,
|
|
333
|
+
angles = repAngleSnapshots
|
|
379
334
|
)
|
|
380
335
|
|
|
381
336
|
allRepDurations.add(repDuration)
|
|
@@ -389,9 +344,9 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
389
344
|
phaseHistory.add(newPhase)
|
|
390
345
|
|
|
391
346
|
if (targetReps > 0 && _repCount >= targetReps) {
|
|
392
|
-
|
|
347
|
+
completeSession()
|
|
393
348
|
}
|
|
394
|
-
|
|
349
|
+
}
|
|
395
350
|
}
|
|
396
351
|
|
|
397
352
|
val maxHistory = repSeq.size * 2
|
|
@@ -410,7 +365,6 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
410
365
|
|
|
411
366
|
for (rule in config.formRules) {
|
|
412
367
|
val angle = currentAngles[rule.angleName] ?: continue
|
|
413
|
-
|
|
414
368
|
val isViolating = angle < rule.minAngle || angle > rule.maxAngle
|
|
415
369
|
|
|
416
370
|
if (isViolating) {
|
|
@@ -453,24 +407,18 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
453
407
|
}
|
|
454
408
|
|
|
455
409
|
if (inPosition) {
|
|
456
|
-
if (holdStartTime == null)
|
|
457
|
-
holdStartTime = System.currentTimeMillis()
|
|
458
|
-
}
|
|
410
|
+
if (holdStartTime == null) holdStartTime = System.currentTimeMillis()
|
|
459
411
|
|
|
460
412
|
val elapsed = (System.currentTimeMillis() - holdStartTime!!).toDouble()
|
|
461
413
|
val stability = min(100.0, max(0.0, repFormScore))
|
|
462
414
|
|
|
463
|
-
|
|
415
|
+
onHoldProgress?.invoke(HoldProgress(
|
|
464
416
|
elapsedMs = elapsed,
|
|
465
417
|
targetMs = config.holdDurationMs,
|
|
466
418
|
stability = stability
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
onHoldProgress?.invoke(progress)
|
|
419
|
+
))
|
|
470
420
|
|
|
471
|
-
if (elapsed >= config.holdDurationMs)
|
|
472
|
-
completeSession()
|
|
473
|
-
}
|
|
421
|
+
if (elapsed >= config.holdDurationMs) completeSession()
|
|
474
422
|
} else {
|
|
475
423
|
holdStartTime = null
|
|
476
424
|
}
|
|
@@ -487,16 +435,14 @@ override fun processFrame(frame: HybridFrameSpec) {
|
|
|
487
435
|
val avgRepDuration = if (allRepDurations.isEmpty()) 0.0 else allRepDurations.average()
|
|
488
436
|
val avgFormScore = if (allRepFormScores.isEmpty()) 100.0 else allRepFormScores.average()
|
|
489
437
|
|
|
490
|
-
|
|
438
|
+
onSessionComplete?.invoke(SessionResult(
|
|
491
439
|
totalReps = _repCount,
|
|
492
440
|
totalDurationMs = totalDuration,
|
|
493
441
|
averageRepDurationMs = avgRepDuration,
|
|
494
442
|
averageFormScore = avgFormScore,
|
|
495
443
|
formViolations = sessionFormViolations.toTypedArray(),
|
|
496
444
|
angleHistory = repAngleSnapshots
|
|
497
|
-
)
|
|
498
|
-
|
|
499
|
-
onSessionComplete?.invoke(result)
|
|
445
|
+
))
|
|
500
446
|
}
|
|
501
447
|
|
|
502
448
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// MediaPipe Pose Landmark indices
|
|
4
|
+
// 11 = left shoulder, 13 = left elbow, 15 = left wrist
|
|
5
|
+
// 12 = right shoulder, 14 = right elbow, 16 = right wrist
|
|
6
|
+
|
|
7
|
+
export const BICEP_CURL_CONFIG = {
|
|
8
|
+
name: 'Bicep Curl',
|
|
9
|
+
type: 'rep',
|
|
10
|
+
angles: [{
|
|
11
|
+
name: 'leftElbow',
|
|
12
|
+
landmarkA: 11,
|
|
13
|
+
// left shoulder
|
|
14
|
+
landmarkB: 13,
|
|
15
|
+
// left elbow (vertex)
|
|
16
|
+
landmarkC: 15 // left wrist
|
|
17
|
+
}, {
|
|
18
|
+
name: 'rightElbow',
|
|
19
|
+
landmarkA: 12,
|
|
20
|
+
// right shoulder
|
|
21
|
+
landmarkB: 14,
|
|
22
|
+
// right elbow (vertex)
|
|
23
|
+
landmarkC: 16 // right wrist
|
|
24
|
+
}, {
|
|
25
|
+
name: 'leftShoulder',
|
|
26
|
+
landmarkA: 23,
|
|
27
|
+
// left hip
|
|
28
|
+
landmarkB: 11,
|
|
29
|
+
// left shoulder (vertex)
|
|
30
|
+
landmarkC: 13 // left elbow
|
|
31
|
+
}],
|
|
32
|
+
phases: [{
|
|
33
|
+
phase: 'down',
|
|
34
|
+
angleName: 'leftElbow',
|
|
35
|
+
minAngle: 150,
|
|
36
|
+
maxAngle: 180
|
|
37
|
+
}, {
|
|
38
|
+
phase: 'up',
|
|
39
|
+
angleName: 'leftElbow',
|
|
40
|
+
minAngle: 25,
|
|
41
|
+
maxAngle: 70
|
|
42
|
+
}],
|
|
43
|
+
repSequence: ['down', 'up', 'down'],
|
|
44
|
+
formRules: [{
|
|
45
|
+
name: 'elbowFlare',
|
|
46
|
+
message: 'Keep your elbows pinned to your sides',
|
|
47
|
+
severity: 'warning',
|
|
48
|
+
angleName: 'leftShoulder',
|
|
49
|
+
minAngle: 0,
|
|
50
|
+
maxAngle: 30
|
|
51
|
+
}, {
|
|
52
|
+
name: 'swinging',
|
|
53
|
+
message: "Don't swing — control the movement",
|
|
54
|
+
severity: 'error',
|
|
55
|
+
angleName: 'leftShoulder',
|
|
56
|
+
minAngle: 0,
|
|
57
|
+
maxAngle: 45
|
|
58
|
+
}],
|
|
59
|
+
holdDurationMs: 0
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=bicep-curl.js.map
|
|
@@ -0,0 +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":[]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// MediaPipe Pose Landmark indices
|
|
4
|
+
// 11 = left shoulder, 13 = left elbow, 15 = left wrist
|
|
5
|
+
// 23 = left hip, 25 = left knee, 27 = left ankle
|
|
6
|
+
// 12 = right shoulder, 24 = right hip
|
|
7
|
+
|
|
8
|
+
export const CHAIR_POSE_CONFIG = {
|
|
9
|
+
name: 'Chair Pose (Utkatasana)',
|
|
10
|
+
type: 'hold',
|
|
11
|
+
angles: [{
|
|
12
|
+
name: 'leftKnee',
|
|
13
|
+
landmarkA: 23,
|
|
14
|
+
// left hip
|
|
15
|
+
landmarkB: 25,
|
|
16
|
+
// left knee (vertex)
|
|
17
|
+
landmarkC: 27 // left ankle
|
|
18
|
+
}, {
|
|
19
|
+
name: 'leftHip',
|
|
20
|
+
landmarkA: 11,
|
|
21
|
+
// left shoulder
|
|
22
|
+
landmarkB: 23,
|
|
23
|
+
// left hip (vertex)
|
|
24
|
+
landmarkC: 25 // left knee
|
|
25
|
+
}, {
|
|
26
|
+
name: 'leftArm',
|
|
27
|
+
landmarkA: 11,
|
|
28
|
+
// left shoulder
|
|
29
|
+
landmarkB: 13,
|
|
30
|
+
// left elbow (vertex)
|
|
31
|
+
landmarkC: 15 // left wrist
|
|
32
|
+
}],
|
|
33
|
+
phases: [{
|
|
34
|
+
phase: 'hold',
|
|
35
|
+
angleName: 'leftKnee',
|
|
36
|
+
minAngle: 90,
|
|
37
|
+
maxAngle: 130
|
|
38
|
+
}],
|
|
39
|
+
repSequence: [],
|
|
40
|
+
formRules: [{
|
|
41
|
+
name: 'kneesTooStraight',
|
|
42
|
+
message: 'Sit deeper — bend your knees more',
|
|
43
|
+
severity: 'warning',
|
|
44
|
+
angleName: 'leftKnee',
|
|
45
|
+
minAngle: 90,
|
|
46
|
+
maxAngle: 130
|
|
47
|
+
}, {
|
|
48
|
+
name: 'leaningForward',
|
|
49
|
+
message: "Keep your chest lifted — don't lean too far forward",
|
|
50
|
+
severity: 'warning',
|
|
51
|
+
angleName: 'leftHip',
|
|
52
|
+
minAngle: 70,
|
|
53
|
+
maxAngle: 130
|
|
54
|
+
}, {
|
|
55
|
+
name: 'armsNotUp',
|
|
56
|
+
message: 'Reach your arms up overhead',
|
|
57
|
+
severity: 'info',
|
|
58
|
+
angleName: 'leftArm',
|
|
59
|
+
minAngle: 160,
|
|
60
|
+
maxAngle: 180
|
|
61
|
+
}],
|
|
62
|
+
holdDurationMs: 30000 // 30 seconds
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chair-pose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["CHAIR_POSE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/chair-pose.ts"],"mappings":";;AAEA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,iBAAiC,GAAG;EAC/CC,IAAI,EAAE,yBAAyB;EAC/BC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,UAAU;IAChBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,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,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,kBAAkB;IACxBa,OAAO,EAAE,mCAAmC;IAC5CC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,gBAAgB;IACtBa,OAAO,EAAE,qDAAqD;IAC9DC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,WAAW;IACjBa,OAAO,EAAE,6BAA6B;IACtCC,QAAQ,EAAE,MAAM;IAChBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE,KAAK,CAAE;AACzB,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// MediaPipe Pose Landmark indices
|
|
4
|
+
// 11 = left shoulder, 13 = left elbow, 15 = left wrist
|
|
5
|
+
// 12 = right shoulder, 14 = right elbow, 16 = right wrist
|
|
6
|
+
// 23 = left hip, 25 = left knee, 27 = left ankle
|
|
7
|
+
|
|
8
|
+
export const COBRA_POSE_CONFIG = {
|
|
9
|
+
name: 'Cobra Pose (Bhujangasana)',
|
|
10
|
+
type: 'hold',
|
|
11
|
+
angles: [{
|
|
12
|
+
name: 'leftElbow',
|
|
13
|
+
landmarkA: 11,
|
|
14
|
+
// left shoulder
|
|
15
|
+
landmarkB: 13,
|
|
16
|
+
// left elbow (vertex)
|
|
17
|
+
landmarkC: 15 // left wrist
|
|
18
|
+
}, {
|
|
19
|
+
name: 'hipExtension',
|
|
20
|
+
landmarkA: 11,
|
|
21
|
+
// left shoulder
|
|
22
|
+
landmarkB: 23,
|
|
23
|
+
// left hip (vertex)
|
|
24
|
+
landmarkC: 25 // left knee
|
|
25
|
+
}, {
|
|
26
|
+
name: 'legStraight',
|
|
27
|
+
landmarkA: 23,
|
|
28
|
+
// left hip
|
|
29
|
+
landmarkB: 25,
|
|
30
|
+
// left knee (vertex)
|
|
31
|
+
landmarkC: 27 // left ankle
|
|
32
|
+
}],
|
|
33
|
+
phases: [{
|
|
34
|
+
phase: 'hold',
|
|
35
|
+
angleName: 'hipExtension',
|
|
36
|
+
minAngle: 120,
|
|
37
|
+
maxAngle: 170
|
|
38
|
+
}],
|
|
39
|
+
repSequence: [],
|
|
40
|
+
formRules: [{
|
|
41
|
+
name: 'shouldersTensed',
|
|
42
|
+
message: 'Relax your shoulders away from your ears',
|
|
43
|
+
severity: 'info',
|
|
44
|
+
angleName: 'leftElbow',
|
|
45
|
+
minAngle: 140,
|
|
46
|
+
maxAngle: 180
|
|
47
|
+
}, {
|
|
48
|
+
name: 'legsBending',
|
|
49
|
+
message: 'Keep your legs straight and pressed into the floor',
|
|
50
|
+
severity: 'warning',
|
|
51
|
+
angleName: 'legStraight',
|
|
52
|
+
minAngle: 160,
|
|
53
|
+
maxAngle: 180
|
|
54
|
+
}],
|
|
55
|
+
holdDurationMs: 30000 // 30 seconds
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=cobra-pose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["COBRA_POSE_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/cobra-pose.ts"],"mappings":";;AAEA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,iBAAiC,GAAG;EAC/CC,IAAI,EAAE,2BAA2B;EACjCC,IAAI,EAAE,MAAM;EACZC,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,cAAc;IACpBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,aAAa;IACnBG,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,cAAc;IACzBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,iBAAiB;IACvBa,OAAO,EAAE,0CAA0C;IACnDC,QAAQ,EAAE,MAAM;IAChBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,aAAa;IACnBa,OAAO,EAAE,oDAAoD;IAC7DC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,aAAa;IACxBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE,KAAK,CAAE;AACzB,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// MediaPipe Pose Landmark indices
|
|
4
|
+
// 11 = left shoulder, 13 = left elbow, 15 = left wrist
|
|
5
|
+
// 12 = right shoulder, 14 = right elbow, 16 = right wrist
|
|
6
|
+
// 23 = left hip, 25 = left knee, 27 = left ankle
|
|
7
|
+
// 24 = right hip, 26 = right knee, 28 = right ankle
|
|
8
|
+
|
|
9
|
+
export const DOWNWARD_DOG_CONFIG = {
|
|
10
|
+
name: 'Downward Dog (Adho Mukha Svanasana)',
|
|
11
|
+
type: 'hold',
|
|
12
|
+
angles: [{
|
|
13
|
+
name: 'hipAngle',
|
|
14
|
+
landmarkA: 11,
|
|
15
|
+
// left shoulder
|
|
16
|
+
landmarkB: 23,
|
|
17
|
+
// left hip (vertex)
|
|
18
|
+
landmarkC: 27 // left ankle
|
|
19
|
+
}, {
|
|
20
|
+
name: 'leftArm',
|
|
21
|
+
landmarkA: 11,
|
|
22
|
+
// left shoulder
|
|
23
|
+
landmarkB: 13,
|
|
24
|
+
// left elbow (vertex)
|
|
25
|
+
landmarkC: 15 // left wrist
|
|
26
|
+
}, {
|
|
27
|
+
name: 'leftLeg',
|
|
28
|
+
landmarkA: 23,
|
|
29
|
+
// left hip
|
|
30
|
+
landmarkB: 25,
|
|
31
|
+
// left knee (vertex)
|
|
32
|
+
landmarkC: 27 // left ankle
|
|
33
|
+
}],
|
|
34
|
+
phases: [{
|
|
35
|
+
phase: 'hold',
|
|
36
|
+
angleName: 'hipAngle',
|
|
37
|
+
minAngle: 55,
|
|
38
|
+
maxAngle: 100
|
|
39
|
+
}],
|
|
40
|
+
repSequence: [],
|
|
41
|
+
formRules: [{
|
|
42
|
+
name: 'armsBent',
|
|
43
|
+
message: 'Straighten your arms — push the floor away',
|
|
44
|
+
severity: 'warning',
|
|
45
|
+
angleName: 'leftArm',
|
|
46
|
+
minAngle: 160,
|
|
47
|
+
maxAngle: 180
|
|
48
|
+
}, {
|
|
49
|
+
name: 'legsBent',
|
|
50
|
+
message: 'Straighten your legs — press your heels toward the ground',
|
|
51
|
+
severity: 'info',
|
|
52
|
+
angleName: 'leftLeg',
|
|
53
|
+
minAngle: 155,
|
|
54
|
+
maxAngle: 180
|
|
55
|
+
}, {
|
|
56
|
+
name: 'hipsTooLow',
|
|
57
|
+
message: 'Push your hips up higher toward the ceiling',
|
|
58
|
+
severity: 'warning',
|
|
59
|
+
angleName: 'hipAngle',
|
|
60
|
+
minAngle: 55,
|
|
61
|
+
maxAngle: 100
|
|
62
|
+
}],
|
|
63
|
+
holdDurationMs: 30000 // 30 seconds
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=downward-dog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["DOWNWARD_DOG_CONFIG","name","type","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/downward-dog.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,mBAAmC,GAAG;EACjDC,IAAI,EAAE,qCAAqC;EAC3CC,IAAI,EAAE,MAAM;EACZC,MAAM,EAAE,CACN;IACEF,IAAI,EAAE,UAAU;IAChBG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE;IAAE;IACfC,SAAS,EAAE,EAAE,CAAE;EACjB,CAAC,EACD;IACEL,IAAI,EAAE,SAAS;IACfG,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,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDC,WAAW,EAAE,EAAE;EACfC,SAAS,EAAE,CACT;IACEZ,IAAI,EAAE,UAAU;IAChBa,OAAO,EAAE,4CAA4C;IACrDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,UAAU;IAChBa,OAAO,EAAE,2DAA2D;IACpEC,QAAQ,EAAE,MAAM;IAChBN,SAAS,EAAE,SAAS;IACpBC,QAAQ,EAAE,GAAG;IACbC,QAAQ,EAAE;EACZ,CAAC,EACD;IACEV,IAAI,EAAE,YAAY;IAClBa,OAAO,EAAE,6CAA6C;IACtDC,QAAQ,EAAE,SAAS;IACnBN,SAAS,EAAE,UAAU;IACrBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE;EACZ,CAAC,CACF;EACDK,cAAc,EAAE,KAAK,CAAE;AACzB,CAAC","ignoreList":[]}
|