react-native-nitro-pose-exercises 1.1.15 → 1.1.17
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/src/main/java/com/margelo/nitro/nitroposeexercises/NitroPoseExercises.kt +86 -57
- package/ios/NitroPoseExercises.swift +41 -33
- package/lib/module/config/pushup.js +6 -21
- package/lib/module/config/pushup.js.map +1 -1
- package/lib/typescript/src/config/pushup.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/config/pushup.ts +4 -19
|
@@ -130,7 +130,7 @@ override var onPostureRegained: (() -> Unit)? = null
|
|
|
130
130
|
|
|
131
131
|
// ─── Posture Gate ──────────────────────────────────────────
|
|
132
132
|
private var consecutivePostureFailures: Int = 0
|
|
133
|
-
private val postureFailureThreshold: Int =
|
|
133
|
+
private val postureFailureThreshold: Int = 30 // ~3s — tolerant of pushup occlusion
|
|
134
134
|
private var postureWasLost = false
|
|
135
135
|
|
|
136
136
|
// ─── Hold Tracking ──────────────────────────────────────────
|
|
@@ -304,23 +304,31 @@ private fun processExerciseLogic() {
|
|
|
304
304
|
val config = exerciseConfig ?: return
|
|
305
305
|
if (_landmarks.isEmpty()) return
|
|
306
306
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
307
|
+
// Hold exercises get 3x more tolerance — stationary poses suffer from
|
|
308
|
+
// visibility flicker more than active reps.
|
|
309
|
+
val failureThreshold = if (config.type == ExerciseType.HOLD) {
|
|
310
|
+
postureFailureThreshold * 3
|
|
311
|
+
} else {
|
|
312
|
+
postureFailureThreshold
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Posture gate with hysteresis
|
|
316
|
+
if (!isPostureValid(config.postureFamily, config.visibilityThreshold)) {
|
|
317
|
+
consecutivePostureFailures += 1
|
|
318
|
+
if (consecutivePostureFailures >= failureThreshold) {
|
|
319
|
+
if (!postureWasLost) {
|
|
320
|
+
postureWasLost = true
|
|
321
|
+
onPostureLost?.invoke()
|
|
322
|
+
}
|
|
323
|
+
// Only nuke phase history after EXTENDED loss (3x threshold).
|
|
324
|
+
// Brief occlusions during a pushup shouldn't wipe an in-progress rep.
|
|
325
|
+
if (consecutivePostureFailures >= failureThreshold * 3) {
|
|
326
|
+
_currentPhase = ExercisePhase.UNKNOWN
|
|
327
|
+
phaseHistory = mutableListOf()
|
|
328
|
+
}
|
|
318
329
|
}
|
|
319
|
-
|
|
320
|
-
phaseHistory.clear()
|
|
330
|
+
return
|
|
321
331
|
}
|
|
322
|
-
return
|
|
323
|
-
}
|
|
324
332
|
|
|
325
333
|
consecutivePostureFailures = 0
|
|
326
334
|
if (postureWasLost) {
|
|
@@ -328,41 +336,53 @@ if (!isPostureValid(config.postureFamily, config.visibilityThreshold)) {
|
|
|
328
336
|
onPostureRegained?.invoke()
|
|
329
337
|
}
|
|
330
338
|
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
// Angle calculation
|
|
340
|
+
val visThreshold = config.visibilityThreshold
|
|
341
|
+
val currentAngles = mutableMapOf<String, Double>()
|
|
342
|
+
val angleSnapshots = mutableListOf<AngleSnapshot>()
|
|
333
343
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
344
|
+
for (angleDef in config.angles) {
|
|
345
|
+
val a = angleDef.landmarkA.toInt()
|
|
346
|
+
val b = angleDef.landmarkB.toInt()
|
|
347
|
+
val c = angleDef.landmarkC.toInt()
|
|
338
348
|
|
|
339
|
-
|
|
349
|
+
if (a >= _landmarks.size || b >= _landmarks.size || c >= _landmarks.size) continue
|
|
340
350
|
|
|
341
|
-
|
|
342
|
-
|
|
351
|
+
if (_landmarks[a].visibility <= visThreshold ||
|
|
352
|
+
_landmarks[b].visibility <= visThreshold ||
|
|
353
|
+
_landmarks[c].visibility <= visThreshold) continue
|
|
343
354
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
355
|
+
val angle = calculateAngle(
|
|
356
|
+
pointA = _landmarks[a],
|
|
357
|
+
vertex = _landmarks[b],
|
|
358
|
+
pointC = _landmarks[c]
|
|
359
|
+
)
|
|
348
360
|
|
|
349
|
-
|
|
361
|
+
currentAngles[angleDef.name] = angle
|
|
362
|
+
angleSnapshots.add(AngleSnapshot(name = angleDef.name, value = angle))
|
|
363
|
+
}
|
|
350
364
|
|
|
351
|
-
|
|
365
|
+
repAngleSnapshots = angleSnapshots
|
|
352
366
|
|
|
353
|
-
|
|
354
|
-
_currentPhase = detectedPhase
|
|
355
|
-
onPhaseChange?.invoke(detectedPhase)
|
|
356
|
-
handlePhaseTransition(detectedPhase, config)
|
|
357
|
-
}
|
|
367
|
+
val detectedPhase = determinePhase(currentAngles, config)
|
|
358
368
|
|
|
359
|
-
|
|
369
|
+
// Debug log — remove once reps count reliably
|
|
370
|
+
println("[Pose] angles=$currentAngles current=$_currentPhase detected=$detectedPhase history=$phaseHistory reps=$_repCount")
|
|
360
371
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
372
|
+
if (detectedPhase != _currentPhase && detectedPhase != ExercisePhase.UNKNOWN) {
|
|
373
|
+
val previousPhase = _currentPhase
|
|
374
|
+
_currentPhase = detectedPhase
|
|
375
|
+
onPhaseChange?.invoke(detectedPhase)
|
|
376
|
+
handlePhaseTransition(previousPhase, detectedPhase, config)
|
|
364
377
|
}
|
|
365
378
|
|
|
379
|
+
checkFormRules(currentAngles, config)
|
|
380
|
+
|
|
381
|
+
if (config.type == ExerciseType.HOLD) {
|
|
382
|
+
handleHoldProgress(currentAngles, config)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
366
386
|
// ═══════════════════════════════════════════════════════════
|
|
367
387
|
// Angle Calculation
|
|
368
388
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -635,6 +655,7 @@ private fun isPostureValid(family: PostureFamily, threshold: Double): Boolean {
|
|
|
635
655
|
val lh = _landmarks[23]; val rh = _landmarks[24]
|
|
636
656
|
val lk = _landmarks[25]; val rk = _landmarks[26]
|
|
637
657
|
val la = _landmarks[27]; val ra = _landmarks[28]
|
|
658
|
+
val lw = _landmarks[15]; val rw = _landmarks[16]
|
|
638
659
|
|
|
639
660
|
// Shoulders mandatory; everything below is optional for close-range framing
|
|
640
661
|
val shouldersVisible = ls.visibility > threshold && rs.visibility > threshold
|
|
@@ -643,6 +664,8 @@ private fun isPostureValid(family: PostureFamily, threshold: Double): Boolean {
|
|
|
643
664
|
val hipsVisible = lh.visibility > threshold && rh.visibility > threshold
|
|
644
665
|
val kneesVisible = lk.visibility > threshold && rk.visibility > threshold
|
|
645
666
|
val anklesVisible = la.visibility > threshold && ra.visibility > threshold
|
|
667
|
+
val wristsVisible = lw.visibility > threshold && rw.visibility > threshold
|
|
668
|
+
val oneWristVisible = lw.visibility > threshold || rw.visibility > threshold
|
|
646
669
|
|
|
647
670
|
val shoulderY = (ls.y + rs.y) / 2
|
|
648
671
|
val shoulderX = (ls.x + rs.x) / 2
|
|
@@ -656,25 +679,31 @@ private fun isPostureValid(family: PostureFamily, threshold: Double): Boolean {
|
|
|
656
679
|
|
|
657
680
|
return when (family) {
|
|
658
681
|
PostureFamily.HORIZONTALPRONE, PostureFamily.SUPINE -> {
|
|
659
|
-
|
|
682
|
+
// Case A: side view — full body in horizontal band
|
|
683
|
+
if (hipsVisible) {
|
|
684
|
+
val ys = if (anklesVisible)
|
|
685
|
+
listOf(shoulderY, hipY, ankleY)
|
|
686
|
+
else
|
|
687
|
+
listOf(shoulderY, hipY)
|
|
688
|
+
if ((ys.max() - ys.min()) < 0.25) return true
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Case B: front-facing prone — minimum signature is shoulders + at least
|
|
692
|
+
// one wrist. Hips often occluded by the body in pushup framings.
|
|
693
|
+
if (!oneWristVisible) return false
|
|
660
694
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
listOf(shoulderY, hipY, ankleY)
|
|
695
|
+
val wristY = if (wristsVisible)
|
|
696
|
+
(lw.y + rw.y) / 2
|
|
664
697
|
else
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
val
|
|
671
|
-
val
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
if (!upperBodyVisible) return false
|
|
675
|
-
|
|
676
|
-
val wristY = (lw.y + rw.y) / 2
|
|
677
|
-
wristY > shoulderY + 0.03 && shoulderWidth > 0.10
|
|
698
|
+
kotlin.math.max(lw.y, rw.y)
|
|
699
|
+
|
|
700
|
+
// Geometry: wrists at or below shoulder line, torso reasonably wide.
|
|
701
|
+
// Tolerance loosened for deep DOWN phase where shoulders drop close
|
|
702
|
+
// to wrist level.
|
|
703
|
+
val handsLowerOrLevel = wristY > shoulderY - 0.08
|
|
704
|
+
val torsoFacing = shoulderWidth > 0.06
|
|
705
|
+
|
|
706
|
+
handsLowerOrLevel && torsoFacing
|
|
678
707
|
}
|
|
679
708
|
|
|
680
709
|
PostureFamily.STANDINGUPRIGHT -> {
|
|
@@ -72,7 +72,7 @@ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
|
|
|
72
72
|
|
|
73
73
|
// ─── Posture Gate ──────────────────────────────────────────
|
|
74
74
|
private var consecutivePostureFailures: Int = 0
|
|
75
|
-
private let postureFailureThreshold: Int =
|
|
75
|
+
private let postureFailureThreshold: Int = 30 // ~3s at 30fps with throttle=3 — tolerant of pushup occlusion
|
|
76
76
|
private var postureWasLost = false
|
|
77
77
|
|
|
78
78
|
// ─── Pose Tracking ──────────────────────────────────────────
|
|
@@ -287,8 +287,12 @@ private func processExerciseLogic() {
|
|
|
287
287
|
postureWasLost = true
|
|
288
288
|
onPostureLost?()
|
|
289
289
|
}
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
// Only nuke phase history after EXTENDED loss (3x threshold).
|
|
291
|
+
// Brief occlusions during a pushup shouldn't wipe an in-progress rep.
|
|
292
|
+
if consecutivePostureFailures >= failureThreshold * 3 {
|
|
293
|
+
_currentPhase = .unknown
|
|
294
|
+
phaseHistory = []
|
|
295
|
+
}
|
|
292
296
|
}
|
|
293
297
|
return
|
|
294
298
|
}
|
|
@@ -329,6 +333,9 @@ private func processExerciseLogic() {
|
|
|
329
333
|
|
|
330
334
|
let detectedPhase = determinePhase(from: currentAngles, config: config)
|
|
331
335
|
|
|
336
|
+
// Debug log — remove once reps count reliably
|
|
337
|
+
print("[Pose] angles=\(currentAngles) current=\(_currentPhase) detected=\(detectedPhase) history=\(phaseHistory) reps=\(_repCount)")
|
|
338
|
+
|
|
332
339
|
if detectedPhase != _currentPhase && detectedPhase != .unknown {
|
|
333
340
|
let previousPhase = _currentPhase
|
|
334
341
|
_currentPhase = detectedPhase
|
|
@@ -354,14 +361,17 @@ private func isPostureValid(family: PostureFamily, threshold: Double) -> Bool {
|
|
|
354
361
|
let lh = _landmarks[23], rh = _landmarks[24]
|
|
355
362
|
let lk = _landmarks[25], rk = _landmarks[26]
|
|
356
363
|
let la = _landmarks[27], ra = _landmarks[28]
|
|
364
|
+
let lw = _landmarks[15], rw = _landmarks[16]
|
|
357
365
|
|
|
358
|
-
// Shoulders
|
|
366
|
+
// Shoulders mandatory; everything below is optional for close-range framing
|
|
359
367
|
let shouldersVisible = ls.visibility > threshold && rs.visibility > threshold
|
|
360
368
|
guard shouldersVisible else { return false }
|
|
361
369
|
|
|
362
370
|
let hipsVisible = lh.visibility > threshold && rh.visibility > threshold
|
|
363
371
|
let kneesVisible = lk.visibility > threshold && rk.visibility > threshold
|
|
364
372
|
let anklesVisible = la.visibility > threshold && ra.visibility > threshold
|
|
373
|
+
let wristsVisible = lw.visibility > threshold && rw.visibility > threshold
|
|
374
|
+
let oneWristVisible = lw.visibility > threshold || rw.visibility > threshold
|
|
365
375
|
|
|
366
376
|
let shoulderY = (ls.y + rs.y) / 2
|
|
367
377
|
let shoulderX = (ls.x + rs.x) / 2
|
|
@@ -370,40 +380,40 @@ private func isPostureValid(family: PostureFamily, threshold: Double) -> Bool {
|
|
|
370
380
|
let kneeY = kneesVisible ? (lk.y + rk.y) / 2 : hipY
|
|
371
381
|
let ankleY = anklesVisible ? (la.y + ra.y) / 2 : kneeY
|
|
372
382
|
|
|
373
|
-
//
|
|
374
|
-
// whether the front camera mirror has been applied or not.
|
|
383
|
+
// Mirror-invariant — works for front and back camera identically
|
|
375
384
|
let shoulderWidth = (ls.x - rs.x).magnitude
|
|
376
385
|
|
|
377
386
|
switch family {
|
|
378
387
|
case .horizontalprone, .supine:
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return true
|
|
388
|
+
// Case A: side view — full body in horizontal band
|
|
389
|
+
if hipsVisible {
|
|
390
|
+
let ys: [Double] = anklesVisible
|
|
391
|
+
? [shoulderY, hipY, ankleY]
|
|
392
|
+
: [shoulderY, hipY]
|
|
393
|
+
let ySpread = (ys.max() ?? 0) - (ys.min() ?? 0)
|
|
394
|
+
if ySpread < 0.25 {
|
|
395
|
+
return true
|
|
396
|
+
}
|
|
389
397
|
}
|
|
390
398
|
|
|
391
|
-
// Case B: front-facing prone
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
399
|
+
// Case B: front-facing prone — minimum signature is shoulders + at least
|
|
400
|
+
// one wrist. We do NOT require hips because in pushup framings hips are
|
|
401
|
+
// often occluded by the body itself.
|
|
402
|
+
guard oneWristVisible else { return false }
|
|
403
|
+
|
|
404
|
+
let wristY = wristsVisible
|
|
405
|
+
? (lw.y + rw.y) / 2
|
|
406
|
+
: max(lw.y, rw.y) // whichever wrist we can see
|
|
407
|
+
|
|
408
|
+
// Geometry: wrists at or below shoulder line, torso reasonably wide.
|
|
409
|
+
// Tolerance loosened to handle deep DOWN phase where shoulders drop
|
|
410
|
+
// close to wrist level.
|
|
411
|
+
let handsLowerOrLevel = wristY > shoulderY - 0.08
|
|
412
|
+
let torsoFacing = shoulderWidth > 0.06
|
|
413
|
+
|
|
414
|
+
return handsLowerOrLevel && torsoFacing
|
|
403
415
|
|
|
404
416
|
case .standingupright:
|
|
405
|
-
// Front-facing standing: user often crops hips/knees at close range.
|
|
406
|
-
// Use a tiered check based on what's visible.
|
|
407
417
|
if hipsVisible {
|
|
408
418
|
if kneesVisible {
|
|
409
419
|
return shoulderY < hipY - 0.05
|
|
@@ -412,8 +422,7 @@ private func isPostureValid(family: PostureFamily, threshold: Double) -> Bool {
|
|
|
412
422
|
}
|
|
413
423
|
return shoulderY < hipY - 0.05
|
|
414
424
|
}
|
|
415
|
-
// Hips
|
|
416
|
-
// which means the person is upright and facing (or backing) the camera.
|
|
425
|
+
// Hips cropped (close-range front-facing) — accept based on shoulder span
|
|
417
426
|
return shoulderWidth > 0.08
|
|
418
427
|
|
|
419
428
|
case .seated:
|
|
@@ -423,7 +432,6 @@ private func isPostureValid(family: PostureFamily, threshold: Double) -> Bool {
|
|
|
423
432
|
if hipsVisible {
|
|
424
433
|
return shoulderY < hipY - 0.05
|
|
425
434
|
}
|
|
426
|
-
// Seated with hips out of frame is rare; fall back to upright shoulder span
|
|
427
435
|
return shoulderWidth > 0.08
|
|
428
436
|
|
|
429
437
|
case .sideplank:
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
export const PUSHUP_CONFIG = {
|
|
10
10
|
name: 'Push-Up',
|
|
11
11
|
type: 'rep',
|
|
12
|
-
postureFamily: '
|
|
13
|
-
visibilityThreshold: 0.
|
|
12
|
+
postureFamily: 'horizontalProne',
|
|
13
|
+
visibilityThreshold: 0.1,
|
|
14
14
|
cameraAngle: 'front',
|
|
15
15
|
angles: [{
|
|
16
16
|
name: 'leftElbow',
|
|
@@ -23,10 +23,7 @@ export const PUSHUP_CONFIG = {
|
|
|
23
23
|
landmarkB: 14,
|
|
24
24
|
landmarkC: 16
|
|
25
25
|
}],
|
|
26
|
-
phases: [
|
|
27
|
-
// Calibrated for 2D-projected angles in front-facing portrait filming.
|
|
28
|
-
// Real anatomical 180° appears as ~140-150° due to perspective foreshortening.
|
|
29
|
-
{
|
|
26
|
+
phases: [{
|
|
30
27
|
phase: 'up',
|
|
31
28
|
angleName: 'leftElbow',
|
|
32
29
|
minAngle: 130,
|
|
@@ -34,23 +31,11 @@ export const PUSHUP_CONFIG = {
|
|
|
34
31
|
}, {
|
|
35
32
|
phase: 'down',
|
|
36
33
|
angleName: 'leftElbow',
|
|
37
|
-
minAngle:
|
|
38
|
-
maxAngle:
|
|
34
|
+
minAngle: 0,
|
|
35
|
+
maxAngle: 120
|
|
39
36
|
}],
|
|
40
37
|
repSequence: ['up', 'down', 'up'],
|
|
41
|
-
formRules: [
|
|
42
|
-
// Trigger when descending but stalled above true-down threshold
|
|
43
|
-
{
|
|
44
|
-
name: 'shallowRep',
|
|
45
|
-
message: 'Go lower',
|
|
46
|
-
severity: 'info',
|
|
47
|
-
angleName: 'leftElbow',
|
|
48
|
-
minAngle: 80,
|
|
49
|
-
maxAngle: 130 // "limbo zone" — too low to be up, too high to be down
|
|
50
|
-
// Note: ideally this only fires when angle has stalled, not on the way down.
|
|
51
|
-
// If your formRule engine doesn't support velocity/stall detection,
|
|
52
|
-
// accept that it'll fire briefly during transitions too.
|
|
53
|
-
}],
|
|
38
|
+
formRules: [],
|
|
54
39
|
holdDurationMs: 0
|
|
55
40
|
};
|
|
56
41
|
//# sourceMappingURL=pushup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["PUSHUP_CONFIG","name","type","postureFamily","visibilityThreshold","cameraAngle","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","
|
|
1
|
+
{"version":3,"names":["PUSHUP_CONFIG","name","type","postureFamily","visibilityThreshold","cameraAngle","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","holdDurationMs"],"sourceRoot":"../../../src","sources":["config/pushup.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAEA,OAAO,MAAMA,aAA6B,GAAG;EAC3CC,IAAI,EAAE,SAAS;EACfC,IAAI,EAAE,KAAK;EACXC,aAAa,EAAE,iBAAiB;EAChCC,mBAAmB,EAAE,GAAG;EACxBC,WAAW,EAAE,OAAO;EACpBC,MAAM,EAAE,CACN;IAAEL,IAAI,EAAE,WAAW;IAAEM,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,EAClE;IAAER,IAAI,EAAE,YAAY;IAAEM,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE,EAAE;IAAEC,SAAS,EAAE;EAAG,CAAC,CACpE;EACDC,MAAM,EAAE,CACN;IAAEC,KAAK,EAAE,IAAI;IAAEC,SAAS,EAAE,WAAW;IAAEC,QAAQ,EAAE,GAAG;IAAEC,QAAQ,EAAE;EAAI,CAAC,EACrE;IAAEH,KAAK,EAAE,MAAM;IAAEC,SAAS,EAAE,WAAW;IAAEC,QAAQ,EAAE,CAAC;IAAEC,QAAQ,EAAE;EAAI,CAAC,CACtE;EACDC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;EACjCC,SAAS,EAAE,EAAE;EACbC,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pushup.d.ts","sourceRoot":"","sources":["../../../../src/config/pushup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAQlE,eAAO,MAAM,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"pushup.d.ts","sourceRoot":"","sources":["../../../../src/config/pushup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAQlE,eAAO,MAAM,aAAa,EAAE,cAiB3B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-pose-exercises",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"description": "Real-time on-device exercise tracking for React Native. Rep counting, form validation, and skeleton overlay powered by Apple Vision (iOS) and Google ML Kit (Android) with VisionCamera v5 via Nitro Modules.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/config/pushup.ts
CHANGED
|
@@ -9,33 +9,18 @@ import type { ExerciseConfig } from '../NitroPoseExercises.nitro';
|
|
|
9
9
|
export const PUSHUP_CONFIG: ExerciseConfig = {
|
|
10
10
|
name: 'Push-Up',
|
|
11
11
|
type: 'rep',
|
|
12
|
-
postureFamily: '
|
|
13
|
-
visibilityThreshold: 0.
|
|
12
|
+
postureFamily: 'horizontalProne',
|
|
13
|
+
visibilityThreshold: 0.1,
|
|
14
14
|
cameraAngle: 'front',
|
|
15
15
|
angles: [
|
|
16
16
|
{ name: 'leftElbow', landmarkA: 11, landmarkB: 13, landmarkC: 15 },
|
|
17
17
|
{ name: 'rightElbow', landmarkA: 12, landmarkB: 14, landmarkC: 16 },
|
|
18
18
|
],
|
|
19
19
|
phases: [
|
|
20
|
-
// Calibrated for 2D-projected angles in front-facing portrait filming.
|
|
21
|
-
// Real anatomical 180° appears as ~140-150° due to perspective foreshortening.
|
|
22
20
|
{ phase: 'up', angleName: 'leftElbow', minAngle: 130, maxAngle: 180 },
|
|
23
|
-
{ phase: 'down', angleName: 'leftElbow', minAngle:
|
|
21
|
+
{ phase: 'down', angleName: 'leftElbow', minAngle: 0, maxAngle: 120 },
|
|
24
22
|
],
|
|
25
23
|
repSequence: ['up', 'down', 'up'],
|
|
26
|
-
formRules: [
|
|
27
|
-
// Trigger when descending but stalled above true-down threshold
|
|
28
|
-
{
|
|
29
|
-
name: 'shallowRep',
|
|
30
|
-
message: 'Go lower',
|
|
31
|
-
severity: 'info',
|
|
32
|
-
angleName: 'leftElbow',
|
|
33
|
-
minAngle: 80,
|
|
34
|
-
maxAngle: 130, // "limbo zone" — too low to be up, too high to be down
|
|
35
|
-
// Note: ideally this only fires when angle has stalled, not on the way down.
|
|
36
|
-
// If your formRule engine doesn't support velocity/stall detection,
|
|
37
|
-
// accept that it'll fire briefly during transitions too.
|
|
38
|
-
},
|
|
39
|
-
],
|
|
24
|
+
formRules: [],
|
|
40
25
|
holdDurationMs: 0,
|
|
41
26
|
};
|