react-native-nitro-pose-exercises 1.1.12 → 1.1.14
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 +1 -1
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/margelo/nitro/nitroposeexercises/NitroPoseExercises.kt +148 -124
- package/ios/NitroPoseExercises.swift +51 -15
- package/lib/module/NitroPoseExercises.nitro.js.map +1 -1
- package/lib/module/config/pushup.js +24 -43
- package/lib/module/config/pushup.js.map +1 -1
- package/lib/typescript/src/NitroPoseExercises.nitro.d.ts +2 -1
- package/lib/typescript/src/NitroPoseExercises.nitro.d.ts.map +1 -1
- package/lib/typescript/src/config/pushup.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridNitroPoseExercisesSpec.cpp +8 -2
- package/nitrogen/generated/android/c++/JHybridNitroPoseExercisesSpec.hpp +2 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroposeexercises/HybridNitroPoseExercisesSpec.kt +6 -1
- package/nitrogen/generated/ios/NitroPoseExercises-Swift-Cxx-Umbrella.hpp +1 -0
- package/nitrogen/generated/ios/c++/HybridNitroPoseExercisesSpecSwift.hpp +12 -2
- package/nitrogen/generated/ios/swift/HybridNitroPoseExercisesSpec.swift +2 -1
- package/nitrogen/generated/ios/swift/HybridNitroPoseExercisesSpec_cxx.swift +13 -2
- package/nitrogen/generated/shared/c++/HybridNitroPoseExercisesSpec.cpp +2 -1
- package/nitrogen/generated/shared/c++/HybridNitroPoseExercisesSpec.hpp +3 -1
- package/package.json +25 -4
- package/src/NitroPoseExercises.nitro.ts +10 -2
- package/src/config/pushup.ts +19 -43
- package/android/src/main/cpp/frame_helper.cpp +0 -37
- package/android/src/main/java/com/margelo/nitro/nitroposeexercises/FrameHelper.kt +0 -12
package/android/build.gradle
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
package com.margelo.nitro.nitroposeexercises
|
|
2
2
|
|
|
3
|
-
import android.graphics.Bitmap
|
|
4
3
|
import android.graphics.Matrix
|
|
5
|
-
import android.media.Image
|
|
6
4
|
import androidx.annotation.Keep
|
|
7
5
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
8
|
-
import com.google.mlkit.vision.common.InputImage
|
|
9
6
|
import com.google.mlkit.vision.pose.PoseDetection
|
|
10
7
|
import com.google.mlkit.vision.pose.PoseDetector
|
|
11
8
|
import com.google.mlkit.vision.pose.PoseLandmark
|
|
12
9
|
import com.google.mlkit.vision.pose.defaults.PoseDetectorOptions
|
|
13
10
|
import com.margelo.nitro.NitroModules
|
|
14
11
|
import com.margelo.nitro.core.Promise
|
|
15
|
-
import com.margelo.nitro.camera.HybridFrameSpec
|
|
16
12
|
import kotlin.math.acos
|
|
17
13
|
import kotlin.math.max
|
|
18
14
|
import kotlin.math.min
|
|
19
15
|
import kotlin.math.sqrt
|
|
20
16
|
|
|
17
|
+
import android.media.Image
|
|
18
|
+
import android.graphics.Bitmap
|
|
19
|
+
import com.google.mlkit.vision.common.InputImage
|
|
20
|
+
import com.margelo.nitro.core.ArrayBuffer
|
|
21
|
+
import java.nio.ByteBuffer
|
|
22
|
+
|
|
23
|
+
import java.nio.ByteOrder
|
|
24
|
+
|
|
25
|
+
import com.margelo.nitro.camera.HybridFrameSpec
|
|
26
|
+
|
|
21
27
|
@Keep
|
|
22
28
|
@DoNotStrip
|
|
23
29
|
class NitroPoseExercises : HybridNitroPoseExercisesSpec() {
|
|
@@ -205,88 +211,97 @@ override fun isReady(): Boolean {
|
|
|
205
211
|
// Frame Processing (ML Kit — async with cached results)
|
|
206
212
|
// ═══════════════════════════════════════════════════════════
|
|
207
213
|
|
|
208
|
-
override fun processFrame(frame: HybridFrameSpec) {
|
|
209
|
-
if (_status != SessionStatus.ACTIVE && _status != SessionStatus.COUNTDOWN) return
|
|
210
|
-
if (!isInitialized || poseDetector == null) return
|
|
211
|
-
|
|
212
|
-
frameCount++
|
|
213
|
-
if (frameCount % processEveryNFrames != 0) return
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
val nativeBuffer = frame.getNativeBuffer()
|
|
217
|
-
val bitmap = FrameHelper.hardwareBufferToBitmap(nativeBuffer.pointer) ?: return
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
val rotation = rotationDegreesFromFrame(frame)
|
|
221
|
-
val inputImage = InputImage.fromBitmap(bitmap, rotation)
|
|
222
|
-
|
|
223
|
-
val imageWidth = bitmap.width.toDouble()
|
|
224
|
-
val imageHeight = bitmap.height.toDouble()
|
|
225
|
-
|
|
226
|
-
poseDetector!!.process(inputImage)
|
|
227
|
-
.addOnSuccessListener { pose ->
|
|
228
|
-
val poseLandmarks = pose.allPoseLandmarks
|
|
229
|
-
|
|
230
|
-
if (poseLandmarks.isNotEmpty()) {
|
|
231
|
-
if (poseWasLost) {
|
|
232
|
-
poseWasLost = false
|
|
233
|
-
onPoseRegained?.invoke()
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
val landmarkArray = Array(34) { Landmark(x = 0.0, y = 0.0, z = 0.0, visibility = 0.0) }
|
|
237
|
-
|
|
238
|
-
for (poseLandmark in poseLandmarks) {
|
|
239
|
-
val mediaPipeIndex = mlKitToMediaPipeMap[poseLandmark.landmarkType] ?: continue
|
|
240
|
-
if (mediaPipeIndex >= 34) continue
|
|
241
|
-
|
|
242
|
-
landmarkArray[mediaPipeIndex] = Landmark(
|
|
243
|
-
x = poseLandmark.position.x.toDouble() / imageWidth,
|
|
244
|
-
y = poseLandmark.position.y.toDouble() / imageHeight,
|
|
245
|
-
z = poseLandmark.position3D.z.toDouble(),
|
|
246
|
-
visibility = poseLandmark.inFrameLikelihood.toDouble()
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
synchronized(landmarkLock) {
|
|
251
|
-
cachedLandmarks = landmarkArray
|
|
252
|
-
}
|
|
253
|
-
} else {
|
|
254
|
-
if (!poseWasLost) {
|
|
255
|
-
poseWasLost = true
|
|
256
|
-
onPoseLost?.invoke()
|
|
257
|
-
}
|
|
258
|
-
synchronized(landmarkLock) {
|
|
259
|
-
cachedLandmarks = emptyArray()
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
214
|
|
|
263
|
-
bitmap.recycle()
|
|
264
|
-
}
|
|
265
|
-
.addOnFailureListener { e ->
|
|
266
|
-
println("[PoseExercise] ML Kit error: ${e.message}")
|
|
267
|
-
bitmap.recycle()
|
|
268
|
-
}
|
|
269
215
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
currentLandmarks = cachedLandmarks.copyOf()
|
|
274
|
-
}
|
|
216
|
+
override fun processFrameAndroid(buffer: ArrayBuffer, width: Double, height: Double, rotation: Double) {
|
|
217
|
+
if (_status != SessionStatus.ACTIVE && _status != SessionStatus.COUNTDOWN) return
|
|
218
|
+
if (!isInitialized || poseDetector == null) return
|
|
275
219
|
|
|
276
|
-
|
|
220
|
+
frameCount++
|
|
221
|
+
if (frameCount % processEveryNFrames != 0) return
|
|
277
222
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
223
|
+
val w = width.toInt()
|
|
224
|
+
val h = height.toInt()
|
|
225
|
+
val rot = rotation.toInt()
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
val rgb = buffer.getBuffer(false)
|
|
229
|
+
rgb.rewind()
|
|
281
230
|
|
|
282
|
-
|
|
283
|
-
|
|
231
|
+
// RGB (3 bytes) -> ARGB_8888 (4 bytes, A=0xFF). Bitmap.Config.ARGB_8888 stores as RGBA in memory.
|
|
232
|
+
val pixelCount = w * h
|
|
233
|
+
val rgba = ByteBuffer.allocateDirect(pixelCount * 4).order(ByteOrder.nativeOrder())
|
|
234
|
+
for (i in 0 until pixelCount) {
|
|
235
|
+
val r = rgb.get()
|
|
236
|
+
val g = rgb.get()
|
|
237
|
+
val b = rgb.get()
|
|
238
|
+
rgba.put(r)
|
|
239
|
+
rgba.put(g)
|
|
240
|
+
rgba.put(b)
|
|
241
|
+
rgba.put(0xFF.toByte())
|
|
284
242
|
}
|
|
243
|
+
rgba.rewind()
|
|
244
|
+
|
|
245
|
+
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
|
246
|
+
bitmap.copyPixelsFromBuffer(rgba)
|
|
247
|
+
|
|
248
|
+
val inputImage = InputImage.fromBitmap(bitmap, rot)
|
|
249
|
+
|
|
250
|
+
val rotated = rot == 90 || rot == 270
|
|
251
|
+
val imageWidth = (if (rotated) h else w).toDouble()
|
|
252
|
+
val imageHeight = (if (rotated) w else h).toDouble()
|
|
253
|
+
|
|
254
|
+
poseDetector!!.process(inputImage)
|
|
255
|
+
.addOnSuccessListener { pose ->
|
|
256
|
+
val poseLandmarks = pose.allPoseLandmarks
|
|
257
|
+
|
|
258
|
+
if (poseLandmarks.isNotEmpty()) {
|
|
259
|
+
if (poseWasLost) {
|
|
260
|
+
poseWasLost = false
|
|
261
|
+
onPoseRegained?.invoke()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
val landmarkArray = Array(34) { Landmark(x = 0.0, y = 0.0, z = 0.0, visibility = 0.0) }
|
|
265
|
+
|
|
266
|
+
for (poseLandmark in poseLandmarks) {
|
|
267
|
+
val mediaPipeIndex = mlKitToMediaPipeMap[poseLandmark.landmarkType] ?: continue
|
|
268
|
+
if (mediaPipeIndex >= 34) continue
|
|
269
|
+
|
|
270
|
+
landmarkArray[mediaPipeIndex] = Landmark(
|
|
271
|
+
x = (poseLandmark.position3D.x / imageWidth).coerceIn(0.0, 1.0),
|
|
272
|
+
y = (poseLandmark.position3D.y / imageHeight).coerceIn(0.0, 1.0),
|
|
273
|
+
z = poseLandmark.position3D.z.toDouble(),
|
|
274
|
+
visibility = poseLandmark.inFrameLikelihood.toDouble()
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
synchronized(landmarkLock) {
|
|
279
|
+
cachedLandmarks = landmarkArray
|
|
280
|
+
_landmarks = landmarkArray
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
processExerciseLogic() // your actual method name
|
|
284
|
+
} else {
|
|
285
|
+
if (!poseWasLost) {
|
|
286
|
+
poseWasLost = true
|
|
287
|
+
onPoseLost?.invoke()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
.addOnFailureListener { e ->
|
|
292
|
+
println("[PoseExercise] ML Kit failure: ${e.message}")
|
|
293
|
+
}
|
|
294
|
+
.addOnCompleteListener {
|
|
295
|
+
bitmap.recycle()
|
|
296
|
+
}
|
|
297
|
+
} catch (e: Exception) {
|
|
298
|
+
println("[PoseExercise] processFrameAndroid error: ${e.message}")
|
|
285
299
|
}
|
|
300
|
+
}
|
|
286
301
|
|
|
287
|
-
|
|
288
|
-
//
|
|
289
|
-
|
|
302
|
+
override fun processFrameIOS(frame: HybridFrameSpec) {
|
|
303
|
+
// no-op on Android
|
|
304
|
+
}
|
|
290
305
|
|
|
291
306
|
private fun processExerciseLogic() {
|
|
292
307
|
val config = exerciseConfig ?: return
|
|
@@ -616,53 +631,62 @@ if (!isPostureValid(config.postureFamily, config.visibilityThreshold)) {
|
|
|
616
631
|
// Posture Gates
|
|
617
632
|
// ═══════════════════════════════════════════════════════════
|
|
618
633
|
|
|
619
|
-
|
|
620
|
-
|
|
634
|
+
private fun isPostureValid(family: PostureFamily, threshold: Double): Boolean {
|
|
635
|
+
if (_landmarks.size < 33) return false
|
|
621
636
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
637
|
+
val ls = _landmarks[11]; val rs = _landmarks[12]
|
|
638
|
+
val lh = _landmarks[23]; val rh = _landmarks[24]
|
|
639
|
+
val lk = _landmarks[25]; val rk = _landmarks[26]
|
|
640
|
+
val la = _landmarks[27]; val ra = _landmarks[28]
|
|
626
641
|
|
|
627
|
-
|
|
642
|
+
// Only require torso visible — knees and ankles are optional
|
|
643
|
+
val torsoVisible = ls.visibility > threshold && rs.visibility > threshold &&
|
|
628
644
|
lh.visibility > threshold && rh.visibility > threshold
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return when (family) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
645
|
+
if (!torsoVisible) return false
|
|
646
|
+
|
|
647
|
+
val shoulderY = (ls.y + rs.y) / 2
|
|
648
|
+
val hipY = (lh.y + rh.y) / 2
|
|
649
|
+
val shoulderX = (ls.x + rs.x) / 2
|
|
650
|
+
val hipX = (lh.x + rh.x) / 2
|
|
651
|
+
|
|
652
|
+
val kneesVisible = lk.visibility > threshold && rk.visibility > threshold
|
|
653
|
+
val anklesVisible = la.visibility > threshold && ra.visibility > threshold
|
|
654
|
+
val kneeY = if (kneesVisible) (lk.y + rk.y) / 2 else hipY
|
|
655
|
+
val ankleY = if (anklesVisible) (la.y + ra.y) / 2 else kneeY
|
|
656
|
+
|
|
657
|
+
return when (family) {
|
|
658
|
+
PostureFamily.HORIZONTALPRONE, PostureFamily.SUPINE -> {
|
|
659
|
+
val ys = if (anklesVisible)
|
|
660
|
+
listOf(shoulderY, hipY, ankleY)
|
|
661
|
+
else
|
|
662
|
+
listOf(shoulderY, hipY)
|
|
663
|
+
(ys.max() - ys.min()) < 0.25
|
|
664
|
+
}
|
|
665
|
+
PostureFamily.STANDINGUPRIGHT -> {
|
|
666
|
+
if (kneesVisible) {
|
|
667
|
+
shoulderY < hipY - 0.08 &&
|
|
668
|
+
hipY < kneeY + 0.05 &&
|
|
669
|
+
(if (anklesVisible) kneeY < ankleY else true)
|
|
670
|
+
} else {
|
|
671
|
+
shoulderY < hipY - 0.08
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
PostureFamily.SEATED -> {
|
|
675
|
+
if (kneesVisible) {
|
|
676
|
+
shoulderY < hipY - 0.05 && kotlin.math.abs(hipY - kneeY) < 0.20
|
|
677
|
+
} else {
|
|
678
|
+
shoulderY < hipY - 0.05
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
PostureFamily.SIDEPLANK -> {
|
|
682
|
+
val ySpread = kotlin.math.abs(shoulderY - hipY)
|
|
683
|
+
val shoulderHipDx = kotlin.math.abs(shoulderX - hipX)
|
|
684
|
+
ySpread < 0.20 && shoulderHipDx < 0.15
|
|
685
|
+
}
|
|
686
|
+
PostureFamily.INVERTED -> {
|
|
687
|
+
if (!anklesVisible) false
|
|
688
|
+
else hipY < shoulderY && hipY < ankleY
|
|
689
|
+
}
|
|
690
|
+
PostureFamily.NONE -> true
|
|
667
691
|
}
|
|
668
|
-
}
|
|
692
|
+
}}
|
|
@@ -178,7 +178,7 @@ private static func cgOrientation(orientation: CameraOrientation, isMirrored: Bo
|
|
|
178
178
|
// MARK: - Frame Processing (Apple Vision)
|
|
179
179
|
// ═══════════════════════════════════════════════════════════
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
func processFrameIOS(frame: any HybridFrameSpec) throws {
|
|
182
182
|
guard _status == .active || _status == .countdown else { return }
|
|
183
183
|
|
|
184
184
|
guard isInitialized else { return }
|
|
@@ -243,8 +243,6 @@ private static func cgOrientation(orientation: CameraOrientation, isMirrored: Bo
|
|
|
243
243
|
visibility: confidence
|
|
244
244
|
)
|
|
245
245
|
|
|
246
|
-
// Debug logging — uncomment to verify mapping
|
|
247
|
-
// print("[PoseExercise] \(jointName.rawValue.rawValue) → index \(mediaPipeIndex): x=\(String(format: "%.3f", point.location.x)) y=\(String(format: "%.3f", 1.0 - Double(point.location.y))) conf=\(String(format: "%.2f", confidence))")
|
|
248
246
|
|
|
249
247
|
} catch {
|
|
250
248
|
// Joint not detected — leave as zero visibility
|
|
@@ -263,6 +261,9 @@ private static func cgOrientation(orientation: CameraOrientation, isMirrored: Bo
|
|
|
263
261
|
}
|
|
264
262
|
}
|
|
265
263
|
|
|
264
|
+
func processFrameAndroid(buffer: ArrayBuffer, width: Double, height: Double, rotation: Double) {
|
|
265
|
+
// no-op on iOS
|
|
266
|
+
}
|
|
266
267
|
// ═══════════════════════════════════════════════════════════
|
|
267
268
|
// MARK: - Exercise Logic Engine
|
|
268
269
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -345,7 +346,7 @@ private func processExerciseLogic() {
|
|
|
345
346
|
// MARK: - Posture Gates
|
|
346
347
|
// ═══════════════════════════════════════════════════════════
|
|
347
348
|
|
|
348
|
-
private func isPostureValid(family: PostureFamily, threshold: Double) -> Bool
|
|
349
|
+
private func isPostureValid(family: PostureFamily, threshold: Double) -> Bool {
|
|
349
350
|
guard _landmarks.count >= 33 else { return false }
|
|
350
351
|
|
|
351
352
|
let ls = _landmarks[11], rs = _landmarks[12]
|
|
@@ -353,38 +354,73 @@ private func isPostureValid(family: PostureFamily, threshold: Double) -> Bool {
|
|
|
353
354
|
let lk = _landmarks[25], rk = _landmarks[26]
|
|
354
355
|
let la = _landmarks[27], ra = _landmarks[28]
|
|
355
356
|
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
// Only require torso visible — knees and ankles are optional
|
|
358
|
+
let torsoVisible = ls.visibility > threshold && rs.visibility > threshold
|
|
359
|
+
&& lh.visibility > threshold && rh.visibility > threshold
|
|
360
|
+
guard torsoVisible else { return false }
|
|
358
361
|
|
|
359
362
|
let shoulderY = (ls.y + rs.y) / 2
|
|
360
363
|
let hipY = (lh.y + rh.y) / 2
|
|
361
364
|
let shoulderX = (ls.x + rs.x) / 2
|
|
362
365
|
let hipX = (lh.x + rh.x) / 2
|
|
363
366
|
|
|
364
|
-
let kneesVisible = lk.visibility >
|
|
365
|
-
let anklesVisible = la.visibility >
|
|
367
|
+
let kneesVisible = lk.visibility > threshold && rk.visibility > threshold
|
|
368
|
+
let anklesVisible = la.visibility > threshold && ra.visibility > threshold
|
|
366
369
|
let kneeY = kneesVisible ? (lk.y + rk.y) / 2 : hipY
|
|
367
370
|
let ankleY = anklesVisible ? (la.y + ra.y) / 2 : kneeY
|
|
368
371
|
|
|
369
372
|
switch family {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
+
case .horizontalprone, .supine:
|
|
374
|
+
// Case A: side view — shoulders, hips, ankles in horizontal band
|
|
375
|
+
let ys: [Double] = anklesVisible
|
|
376
|
+
? [shoulderY, hipY, ankleY]
|
|
377
|
+
: [shoulderY, hipY]
|
|
378
|
+
let ySpread = (ys.max() ?? 0) - (ys.min() ?? 0)
|
|
379
|
+
if ySpread < 0.25 {
|
|
380
|
+
return true
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Case B: front-facing — body extends away from camera along Z.
|
|
384
|
+
// Y-spread is large (head close, feet far), so check upper-body geometry.
|
|
385
|
+
let le = _landmarks[13], re = _landmarks[14]
|
|
386
|
+
let lw = _landmarks[15], rw = _landmarks[16]
|
|
387
|
+
let upperBodyVisible = le.visibility > threshold && re.visibility > threshold
|
|
388
|
+
&& lw.visibility > threshold && rw.visibility > threshold
|
|
389
|
+
guard upperBodyVisible else { return false }
|
|
390
|
+
|
|
391
|
+
let wristY = (lw.y + rw.y) / 2
|
|
392
|
+
let shoulderWidth = abs(ls.x - rs.x)
|
|
393
|
+
guard wristY > shoulderY + 0.03 else { return false }
|
|
394
|
+
guard shoulderWidth > 0.10 else { return false }
|
|
395
|
+
|
|
396
|
+
return true
|
|
373
397
|
|
|
374
398
|
case .standingupright:
|
|
375
|
-
|
|
376
|
-
|
|
399
|
+
// Torso vertical (shoulders above hips). If knees visible, check chain.
|
|
400
|
+
if kneesVisible {
|
|
401
|
+
return shoulderY < hipY - 0.08
|
|
402
|
+
&& hipY < kneeY + 0.05
|
|
403
|
+
&& (anklesVisible ? kneeY < ankleY : true)
|
|
404
|
+
} else {
|
|
405
|
+
return shoulderY < hipY - 0.08
|
|
406
|
+
}
|
|
377
407
|
|
|
378
408
|
case .seated:
|
|
379
|
-
|
|
380
|
-
|
|
409
|
+
// Forward-leaning torso. If knees visible, also check hip-knee distance.
|
|
410
|
+
if kneesVisible {
|
|
411
|
+
return shoulderY < hipY - 0.05 && Swift.abs(hipY - kneeY) < 0.20
|
|
412
|
+
} else {
|
|
413
|
+
return shoulderY < hipY - 0.05
|
|
414
|
+
}
|
|
381
415
|
|
|
382
416
|
case .sideplank:
|
|
417
|
+
// Body horizontal, shoulders and hips stacked in x
|
|
383
418
|
let ySpread = Swift.abs(shoulderY - hipY)
|
|
384
419
|
let shoulderHipDx = Swift.abs(shoulderX - hipX)
|
|
385
420
|
return ySpread < 0.20 && shoulderHipDx < 0.15
|
|
386
421
|
|
|
387
422
|
case .inverted:
|
|
423
|
+
// Genuinely needs ankles — hips highest of all three
|
|
388
424
|
guard anklesVisible else { return false }
|
|
389
425
|
return hipY < shoulderY && hipY < ankleY
|
|
390
426
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NitroModules","nitroPoseExercises","createHybridObject"],"sourceRoot":"../../src","sources":["NitroPoseExercises.nitro.ts"],"mappings":";;AAAA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAI5E;;AAqBA;;AASA;;AAsCA;;AAmCA;;
|
|
1
|
+
{"version":3,"names":["NitroModules","nitroPoseExercises","createHybridObject"],"sourceRoot":"../../src","sources":["NitroPoseExercises.nitro.ts"],"mappings":";;AAAA,SAA4BA,YAAY,QAAQ,4BAA4B;;AAI5E;;AAqBA;;AASA;;AAsCA;;AAmCA;;AAoDA,MAAMC,kBAAkB,GACtBD,YAAY,CAACE,kBAAkB,CAAqB,cAAc,CAAC;AAErE,SAASD,kBAAkB","ignoreList":[]}
|
|
@@ -9,67 +9,48 @@
|
|
|
9
9
|
export const PUSHUP_CONFIG = {
|
|
10
10
|
name: 'Push-Up',
|
|
11
11
|
type: 'rep',
|
|
12
|
-
postureFamily: '
|
|
13
|
-
visibilityThreshold: 0.
|
|
14
|
-
// ← add
|
|
12
|
+
postureFamily: 'none',
|
|
13
|
+
visibilityThreshold: 0.3,
|
|
15
14
|
cameraAngle: 'front',
|
|
16
15
|
angles: [{
|
|
17
16
|
name: 'leftElbow',
|
|
18
17
|
landmarkA: 11,
|
|
19
|
-
// left shoulder
|
|
20
18
|
landmarkB: 13,
|
|
21
|
-
|
|
22
|
-
landmarkC: 15 // left wrist
|
|
19
|
+
landmarkC: 15
|
|
23
20
|
}, {
|
|
24
21
|
name: 'rightElbow',
|
|
25
22
|
landmarkA: 12,
|
|
26
|
-
// right shoulder
|
|
27
23
|
landmarkB: 14,
|
|
28
|
-
|
|
29
|
-
landmarkC: 16 // right wrist
|
|
30
|
-
}, {
|
|
31
|
-
name: 'leftHip',
|
|
32
|
-
landmarkA: 11,
|
|
33
|
-
// left shoulder
|
|
34
|
-
landmarkB: 23,
|
|
35
|
-
// left hip (vertex)
|
|
36
|
-
landmarkC: 27 // left ankle
|
|
37
|
-
}, {
|
|
38
|
-
name: 'rightHip',
|
|
39
|
-
landmarkA: 12,
|
|
40
|
-
// right shoulder
|
|
41
|
-
landmarkB: 24,
|
|
42
|
-
// right hip (vertex)
|
|
43
|
-
landmarkC: 28 // right ankle
|
|
24
|
+
landmarkC: 16
|
|
44
25
|
}],
|
|
45
|
-
phases: [
|
|
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
|
+
{
|
|
46
30
|
phase: 'up',
|
|
47
31
|
angleName: 'leftElbow',
|
|
48
|
-
minAngle:
|
|
32
|
+
minAngle: 130,
|
|
49
33
|
maxAngle: 180
|
|
50
34
|
}, {
|
|
51
35
|
phase: 'down',
|
|
52
36
|
angleName: 'leftElbow',
|
|
53
|
-
minAngle:
|
|
54
|
-
maxAngle:
|
|
37
|
+
minAngle: 40,
|
|
38
|
+
maxAngle: 80
|
|
55
39
|
}],
|
|
56
40
|
repSequence: ['up', 'down', 'up'],
|
|
57
|
-
formRules: [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
angleName: 'leftHip',
|
|
70
|
-
minAngle: 160,
|
|
71
|
-
maxAngle: 180
|
|
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.
|
|
72
53
|
}],
|
|
73
|
-
holdDurationMs: 0
|
|
54
|
+
holdDurationMs: 0
|
|
74
55
|
};
|
|
75
56
|
//# 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","message","severity","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,
|
|
1
|
+
{"version":3,"names":["PUSHUP_CONFIG","name","type","postureFamily","visibilityThreshold","cameraAngle","angles","landmarkA","landmarkB","landmarkC","phases","phase","angleName","minAngle","maxAngle","repSequence","formRules","message","severity","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,MAAM;EACrBC,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;EACN;EACA;EACA;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,EAAE;IAAEC,QAAQ,EAAE;EAAG,CAAC,CACtE;EACDC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;EACjCC,SAAS,EAAE;EACT;EACA;IACEf,IAAI,EAAE,YAAY;IAClBgB,OAAO,EAAE,UAAU;IACnBC,QAAQ,EAAE,MAAM;IAChBN,SAAS,EAAE,WAAW;IACtBC,QAAQ,EAAE,EAAE;IACZC,QAAQ,EAAE,GAAG,CAAE;IACf;IACA;IACA;EACF,CAAC,CACF;EACDK,cAAc,EAAE;AAClB,CAAC","ignoreList":[]}
|
|
@@ -80,7 +80,8 @@ interface NitroPoseExercises extends HybridObject<{
|
|
|
80
80
|
release(): void;
|
|
81
81
|
loadExercise(config: ExerciseConfig): void;
|
|
82
82
|
readonly status: SessionStatus;
|
|
83
|
-
|
|
83
|
+
processFrameIOS(frame: Frame): void;
|
|
84
|
+
processFrameAndroid(buffer: ArrayBuffer, width: number, height: number, rotation: number): void;
|
|
84
85
|
onRepComplete: ((data: RepData) => void) | undefined;
|
|
85
86
|
onPhaseChange: ((phase: ExercisePhase) => void) | undefined;
|
|
86
87
|
onFormFeedback: ((feedback: FormFeedback) => void) | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NitroPoseExercises.nitro.d.ts","sourceRoot":"","sources":["../../../src/NitroPoseExercises.nitro.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"NitroPoseExercises.nitro.d.ts","sourceRoot":"","sources":["../../../src/NitroPoseExercises.nitro.ts"],"names":[],"mappings":"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"}
|
|
@@ -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,cAgC3B,CAAC"}
|
|
@@ -91,6 +91,8 @@ namespace margelo::nitro::camera { class HybridFrameSpec; }
|
|
|
91
91
|
#include <memory>
|
|
92
92
|
#include <VisionCamera/HybridFrameSpec.hpp>
|
|
93
93
|
#include <VisionCamera/JHybridFrameSpec.hpp>
|
|
94
|
+
#include <NitroModules/ArrayBuffer.hpp>
|
|
95
|
+
#include <NitroModules/JArrayBuffer.hpp>
|
|
94
96
|
|
|
95
97
|
namespace margelo::nitro::nitroposeexercises {
|
|
96
98
|
|
|
@@ -329,10 +331,14 @@ namespace margelo::nitro::nitroposeexercises {
|
|
|
329
331
|
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JExerciseConfig> /* config */)>("loadExercise");
|
|
330
332
|
method(_javaPart, JExerciseConfig::fromCpp(config));
|
|
331
333
|
}
|
|
332
|
-
void JHybridNitroPoseExercisesSpec::
|
|
333
|
-
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<margelo::nitro::camera::JHybridFrameSpec::JavaPart> /* frame */)>("
|
|
334
|
+
void JHybridNitroPoseExercisesSpec::processFrameIOS(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame) {
|
|
335
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<margelo::nitro::camera::JHybridFrameSpec::JavaPart> /* frame */)>("processFrameIOS");
|
|
334
336
|
method(_javaPart, std::dynamic_pointer_cast<margelo::nitro::camera::JHybridFrameSpec>(frame)->getJavaPart());
|
|
335
337
|
}
|
|
338
|
+
void JHybridNitroPoseExercisesSpec::processFrameAndroid(const std::shared_ptr<ArrayBuffer>& buffer, double width, double height, double rotation) {
|
|
339
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<JArrayBuffer::javaobject> /* buffer */, double /* width */, double /* height */, double /* rotation */)>("processFrameAndroid");
|
|
340
|
+
method(_javaPart, JArrayBuffer::wrap(buffer), width, height, rotation);
|
|
341
|
+
}
|
|
336
342
|
void JHybridNitroPoseExercisesSpec::startSession(double targetReps, double countdownSeconds) {
|
|
337
343
|
static const auto method = _javaPart->javaClassStatic()->getMethod<void(double /* targetReps */, double /* countdownSeconds */)>("startSession");
|
|
338
344
|
method(_javaPart, targetReps, countdownSeconds);
|
|
@@ -78,7 +78,8 @@ namespace margelo::nitro::nitroposeexercises {
|
|
|
78
78
|
std::shared_ptr<Promise<void>> initialize(const std::string& modelPath) override;
|
|
79
79
|
void release() override;
|
|
80
80
|
void loadExercise(const ExerciseConfig& config) override;
|
|
81
|
-
void
|
|
81
|
+
void processFrameIOS(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame) override;
|
|
82
|
+
void processFrameAndroid(const std::shared_ptr<ArrayBuffer>& buffer, double width, double height, double rotation) override;
|
|
82
83
|
void startSession(double targetReps, double countdownSeconds) override;
|
|
83
84
|
void pauseSession() override;
|
|
84
85
|
void resumeSession() override;
|
|
@@ -12,6 +12,7 @@ import com.facebook.jni.HybridData
|
|
|
12
12
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
13
13
|
import com.margelo.nitro.core.Promise
|
|
14
14
|
import com.margelo.nitro.camera.HybridFrameSpec
|
|
15
|
+
import com.margelo.nitro.core.ArrayBuffer
|
|
15
16
|
import com.margelo.nitro.core.HybridObject
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -184,7 +185,11 @@ abstract class HybridNitroPoseExercisesSpec: HybridObject() {
|
|
|
184
185
|
|
|
185
186
|
@DoNotStrip
|
|
186
187
|
@Keep
|
|
187
|
-
abstract fun
|
|
188
|
+
abstract fun processFrameIOS(frame: com.margelo.nitro.camera.HybridFrameSpec): Unit
|
|
189
|
+
|
|
190
|
+
@DoNotStrip
|
|
191
|
+
@Keep
|
|
192
|
+
abstract fun processFrameAndroid(buffer: ArrayBuffer, width: Double, height: Double, rotation: Double): Unit
|
|
188
193
|
|
|
189
194
|
@DoNotStrip
|
|
190
195
|
@Keep
|
|
@@ -63,6 +63,7 @@ namespace margelo::nitro::nitroposeexercises { enum class SessionStatus; }
|
|
|
63
63
|
#include "RepData.hpp"
|
|
64
64
|
#include "SessionResult.hpp"
|
|
65
65
|
#include "SessionStatus.hpp"
|
|
66
|
+
#include <NitroModules/ArrayBuffer.hpp>
|
|
66
67
|
#include <NitroModules/Promise.hpp>
|
|
67
68
|
#include <NitroModules/Result.hpp>
|
|
68
69
|
#include <VisionCamera/HybridFrameSpec.hpp>
|
|
@@ -46,6 +46,8 @@ namespace margelo::nitro::nitroposeexercises { enum class PostureFamily; }
|
|
|
46
46
|
namespace margelo::nitro::nitroposeexercises { enum class CameraAngleType; }
|
|
47
47
|
// Forward declaration of `HybridFrameSpec` to properly resolve imports.
|
|
48
48
|
namespace margelo::nitro::camera { class HybridFrameSpec; }
|
|
49
|
+
// Forward declaration of `ArrayBufferHolder` to properly resolve imports.
|
|
50
|
+
namespace NitroModules { class ArrayBufferHolder; }
|
|
49
51
|
|
|
50
52
|
#include "SessionStatus.hpp"
|
|
51
53
|
#include "RepData.hpp"
|
|
@@ -70,6 +72,8 @@ namespace margelo::nitro::camera { class HybridFrameSpec; }
|
|
|
70
72
|
#include "CameraAngleType.hpp"
|
|
71
73
|
#include <memory>
|
|
72
74
|
#include <VisionCamera/HybridFrameSpec.hpp>
|
|
75
|
+
#include <NitroModules/ArrayBuffer.hpp>
|
|
76
|
+
#include <NitroModules/ArrayBufferHolder.hpp>
|
|
73
77
|
|
|
74
78
|
#include "NitroPoseExercises-Swift-Cxx-Umbrella.hpp"
|
|
75
79
|
|
|
@@ -218,8 +222,14 @@ namespace margelo::nitro::nitroposeexercises {
|
|
|
218
222
|
std::rethrow_exception(__result.error());
|
|
219
223
|
}
|
|
220
224
|
}
|
|
221
|
-
inline void
|
|
222
|
-
auto __result = _swiftPart.
|
|
225
|
+
inline void processFrameIOS(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame) override {
|
|
226
|
+
auto __result = _swiftPart.processFrameIOS(frame);
|
|
227
|
+
if (__result.hasError()) [[unlikely]] {
|
|
228
|
+
std::rethrow_exception(__result.error());
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
inline void processFrameAndroid(const std::shared_ptr<ArrayBuffer>& buffer, double width, double height, double rotation) override {
|
|
232
|
+
auto __result = _swiftPart.processFrameAndroid(ArrayBufferHolder(buffer), std::forward<decltype(width)>(width), std::forward<decltype(height)>(height), std::forward<decltype(rotation)>(rotation));
|
|
223
233
|
if (__result.hasError()) [[unlikely]] {
|
|
224
234
|
std::rethrow_exception(__result.error());
|
|
225
235
|
}
|
|
@@ -29,7 +29,8 @@ public protocol HybridNitroPoseExercisesSpec_protocol: HybridObject {
|
|
|
29
29
|
func initialize(modelPath: String) throws -> Promise<Void>
|
|
30
30
|
func release() throws -> Void
|
|
31
31
|
func loadExercise(config: ExerciseConfig) throws -> Void
|
|
32
|
-
func
|
|
32
|
+
func processFrameIOS(frame: (any HybridFrameSpec)) throws -> Void
|
|
33
|
+
func processFrameAndroid(buffer: ArrayBuffer, width: Double, height: Double, rotation: Double) throws -> Void
|
|
33
34
|
func startSession(targetReps: Double, countdownSeconds: Double) throws -> Void
|
|
34
35
|
func pauseSession() throws -> Void
|
|
35
36
|
func resumeSession() throws -> Void
|
|
@@ -487,9 +487,9 @@ open class HybridNitroPoseExercisesSpec_cxx {
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
@inline(__always)
|
|
490
|
-
public final func
|
|
490
|
+
public final func processFrameIOS(frame: bridge.std__shared_ptr_margelo__nitro__camera__HybridFrameSpec_) -> bridge.Result_void_ {
|
|
491
491
|
do {
|
|
492
|
-
try self.__implementation.
|
|
492
|
+
try self.__implementation.processFrameIOS(frame: { () -> any HybridFrameSpec in
|
|
493
493
|
let __unsafePointer = bridge.get_std__shared_ptr_margelo__nitro__camera__HybridFrameSpec_(frame)
|
|
494
494
|
let __instance = HybridFrameSpec_cxx.fromUnsafe(__unsafePointer)
|
|
495
495
|
return __instance.getHybridFrameSpec()
|
|
@@ -501,6 +501,17 @@ open class HybridNitroPoseExercisesSpec_cxx {
|
|
|
501
501
|
}
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
@inline(__always)
|
|
505
|
+
public final func processFrameAndroid(buffer: ArrayBuffer, width: Double, height: Double, rotation: Double) -> bridge.Result_void_ {
|
|
506
|
+
do {
|
|
507
|
+
try self.__implementation.processFrameAndroid(buffer: buffer, width: width, height: height, rotation: rotation)
|
|
508
|
+
return bridge.create_Result_void_()
|
|
509
|
+
} catch (let __error) {
|
|
510
|
+
let __exceptionPtr = __error.toCpp()
|
|
511
|
+
return bridge.create_Result_void_(__exceptionPtr)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
504
515
|
@inline(__always)
|
|
505
516
|
public final func startSession(targetReps: Double, countdownSeconds: Double) -> bridge.Result_void_ {
|
|
506
517
|
do {
|
|
@@ -39,7 +39,8 @@ namespace margelo::nitro::nitroposeexercises {
|
|
|
39
39
|
prototype.registerHybridMethod("initialize", &HybridNitroPoseExercisesSpec::initialize);
|
|
40
40
|
prototype.registerHybridMethod("release", &HybridNitroPoseExercisesSpec::release);
|
|
41
41
|
prototype.registerHybridMethod("loadExercise", &HybridNitroPoseExercisesSpec::loadExercise);
|
|
42
|
-
prototype.registerHybridMethod("
|
|
42
|
+
prototype.registerHybridMethod("processFrameIOS", &HybridNitroPoseExercisesSpec::processFrameIOS);
|
|
43
|
+
prototype.registerHybridMethod("processFrameAndroid", &HybridNitroPoseExercisesSpec::processFrameAndroid);
|
|
43
44
|
prototype.registerHybridMethod("startSession", &HybridNitroPoseExercisesSpec::startSession);
|
|
44
45
|
prototype.registerHybridMethod("pauseSession", &HybridNitroPoseExercisesSpec::pauseSession);
|
|
45
46
|
prototype.registerHybridMethod("resumeSession", &HybridNitroPoseExercisesSpec::resumeSession);
|
|
@@ -47,6 +47,7 @@ namespace margelo::nitro::camera { class HybridFrameSpec; }
|
|
|
47
47
|
#include "ExerciseConfig.hpp"
|
|
48
48
|
#include <memory>
|
|
49
49
|
#include <VisionCamera/HybridFrameSpec.hpp>
|
|
50
|
+
#include <NitroModules/ArrayBuffer.hpp>
|
|
50
51
|
|
|
51
52
|
namespace margelo::nitro::nitroposeexercises {
|
|
52
53
|
|
|
@@ -103,7 +104,8 @@ namespace margelo::nitro::nitroposeexercises {
|
|
|
103
104
|
virtual std::shared_ptr<Promise<void>> initialize(const std::string& modelPath) = 0;
|
|
104
105
|
virtual void release() = 0;
|
|
105
106
|
virtual void loadExercise(const ExerciseConfig& config) = 0;
|
|
106
|
-
virtual void
|
|
107
|
+
virtual void processFrameIOS(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame) = 0;
|
|
108
|
+
virtual void processFrameAndroid(const std::shared_ptr<ArrayBuffer>& buffer, double width, double height, double rotation) = 0;
|
|
107
109
|
virtual void startSession(double targetReps, double countdownSeconds) = 0;
|
|
108
110
|
virtual void pauseSession() = 0;
|
|
109
111
|
virtual void resumeSession() = 0;
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-pose-exercises",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14",
|
|
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",
|
|
7
|
+
"installConfig": {
|
|
8
|
+
"hoistingLimits": "workspaces"
|
|
9
|
+
},
|
|
7
10
|
"exports": {
|
|
8
11
|
".": {
|
|
9
12
|
"source": "./src/index.tsx",
|
|
@@ -73,6 +76,7 @@
|
|
|
73
76
|
"@react-native/eslint-config": "0.85.3",
|
|
74
77
|
"@react-native/jest-preset": "0.85.3",
|
|
75
78
|
"@release-it/conventional-changelog": "^10.0.6",
|
|
79
|
+
"@shopify/react-native-skia": "^2.6.4",
|
|
76
80
|
"@types/react": "^19.2.0",
|
|
77
81
|
"commitlint": "^20.5.0",
|
|
78
82
|
"del-cli": "^7.0.0",
|
|
@@ -88,8 +92,10 @@
|
|
|
88
92
|
"react-native": "0.85.3",
|
|
89
93
|
"react-native-builder-bob": "^0.41.0",
|
|
90
94
|
"react-native-nitro-modules": "^0.35.9",
|
|
95
|
+
"react-native-reanimated": "4.4.0",
|
|
91
96
|
"react-native-svg": "^15.15.5",
|
|
92
97
|
"react-native-vision-camera": "^5.0.11",
|
|
98
|
+
"react-native-vision-camera-resizer": "^5.0.11",
|
|
93
99
|
"react-native-vision-camera-worklets": "^5.0.11",
|
|
94
100
|
"react-native-worklets": "^0.9.1",
|
|
95
101
|
"release-it": "^19.2.4",
|
|
@@ -102,12 +108,27 @@
|
|
|
102
108
|
"react-native-nitro-modules": "*",
|
|
103
109
|
"react-native-reanimated": "*",
|
|
104
110
|
"react-native-vision-camera": "*",
|
|
111
|
+
"react-native-vision-camera-resizer": "*",
|
|
105
112
|
"react-native-vision-camera-worklets": "*",
|
|
106
113
|
"react-native-worklets": "*"
|
|
107
114
|
},
|
|
108
|
-
"workspaces":
|
|
109
|
-
"
|
|
110
|
-
|
|
115
|
+
"workspaces": {
|
|
116
|
+
"packages": [
|
|
117
|
+
"example"
|
|
118
|
+
],
|
|
119
|
+
"nohoist": [
|
|
120
|
+
"**/react-native",
|
|
121
|
+
"**/react-native/**",
|
|
122
|
+
"**/react-native-vision-camera",
|
|
123
|
+
"**/react-native-vision-camera/**",
|
|
124
|
+
"**/react-native-vision-camera-resizer",
|
|
125
|
+
"**/react-native-vision-camera-resizer/**",
|
|
126
|
+
"**/react-native-vision-camera-worklets",
|
|
127
|
+
"**/react-native-vision-camera-worklets/**",
|
|
128
|
+
"**/react-native-nitro-modules",
|
|
129
|
+
"**/react-native-nitro-modules/**"
|
|
130
|
+
]
|
|
131
|
+
},
|
|
111
132
|
"packageManager": "yarn@4.11.0",
|
|
112
133
|
"react-native-builder-bob": {
|
|
113
134
|
"source": "src",
|
|
@@ -121,8 +121,16 @@ interface NitroPoseExercises extends HybridObject<{
|
|
|
121
121
|
// Session control
|
|
122
122
|
readonly status: SessionStatus;
|
|
123
123
|
|
|
124
|
-
//
|
|
125
|
-
|
|
124
|
+
// iOS: zero-copy Vision path
|
|
125
|
+
processFrameIOS(frame: Frame): void;
|
|
126
|
+
|
|
127
|
+
// Android: pre-resized RGBA buffer from VisionCamera Resizer (Vulkan-accelerated)
|
|
128
|
+
processFrameAndroid(
|
|
129
|
+
buffer: ArrayBuffer,
|
|
130
|
+
width: number,
|
|
131
|
+
height: number,
|
|
132
|
+
rotation: number
|
|
133
|
+
): void;
|
|
126
134
|
|
|
127
135
|
// Callbacks (set from JS side)
|
|
128
136
|
onRepComplete: ((data: RepData) => void) | undefined;
|
package/src/config/pushup.ts
CHANGED
|
@@ -9,57 +9,33 @@ 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: 'none',
|
|
13
|
+
visibilityThreshold: 0.3,
|
|
14
14
|
cameraAngle: 'front',
|
|
15
15
|
angles: [
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
landmarkA: 11, // left shoulder
|
|
19
|
-
landmarkB: 13, // left elbow (vertex)
|
|
20
|
-
landmarkC: 15, // left wrist
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: 'rightElbow',
|
|
24
|
-
landmarkA: 12, // right shoulder
|
|
25
|
-
landmarkB: 14, // right elbow (vertex)
|
|
26
|
-
landmarkC: 16, // right wrist
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: 'leftHip',
|
|
30
|
-
landmarkA: 11, // left shoulder
|
|
31
|
-
landmarkB: 23, // left hip (vertex)
|
|
32
|
-
landmarkC: 27, // left ankle
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: 'rightHip',
|
|
36
|
-
landmarkA: 12, // right shoulder
|
|
37
|
-
landmarkB: 24, // right hip (vertex)
|
|
38
|
-
landmarkC: 28, // right ankle
|
|
39
|
-
},
|
|
16
|
+
{ name: 'leftElbow', landmarkA: 11, landmarkB: 13, landmarkC: 15 },
|
|
17
|
+
{ name: 'rightElbow', landmarkA: 12, landmarkB: 14, landmarkC: 16 },
|
|
40
18
|
],
|
|
41
19
|
phases: [
|
|
42
|
-
|
|
43
|
-
|
|
20
|
+
// Calibrated for 2D-projected angles in front-facing portrait filming.
|
|
21
|
+
// Real anatomical 180° appears as ~140-150° due to perspective foreshortening.
|
|
22
|
+
{ phase: 'up', angleName: 'leftElbow', minAngle: 130, maxAngle: 180 },
|
|
23
|
+
{ phase: 'down', angleName: 'leftElbow', minAngle: 40, maxAngle: 80 },
|
|
44
24
|
],
|
|
45
25
|
repSequence: ['up', 'down', 'up'],
|
|
46
26
|
formRules: [
|
|
27
|
+
// Trigger when descending but stalled above true-down threshold
|
|
47
28
|
{
|
|
48
|
-
name: '
|
|
49
|
-
message: '
|
|
50
|
-
severity: '
|
|
51
|
-
angleName: '
|
|
52
|
-
minAngle:
|
|
53
|
-
maxAngle:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
message: "Lower your hips — you're piking up",
|
|
58
|
-
severity: 'warning',
|
|
59
|
-
angleName: 'leftHip',
|
|
60
|
-
minAngle: 160,
|
|
61
|
-
maxAngle: 180,
|
|
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.
|
|
62
38
|
},
|
|
63
39
|
],
|
|
64
|
-
holdDurationMs: 0,
|
|
40
|
+
holdDurationMs: 0,
|
|
65
41
|
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
#include <jni.h>
|
|
2
|
-
#include <android/hardware_buffer_jni.h>
|
|
3
|
-
|
|
4
|
-
extern "C"
|
|
5
|
-
JNIEXPORT jobject JNICALL
|
|
6
|
-
Java_com_margelo_nitro_nitroposeexercises_FrameHelper_hardwareBufferToBitmap(
|
|
7
|
-
JNIEnv *env,
|
|
8
|
-
jclass clazz,
|
|
9
|
-
jlong pointer
|
|
10
|
-
) {
|
|
11
|
-
AHardwareBuffer *buffer = reinterpret_cast<AHardwareBuffer *>(pointer);
|
|
12
|
-
if (!buffer) return nullptr;
|
|
13
|
-
|
|
14
|
-
// Convert AHardwareBuffer* to Java HardwareBuffer
|
|
15
|
-
jobject hardwareBuffer = AHardwareBuffer_toHardwareBuffer(env, buffer);
|
|
16
|
-
if (!hardwareBuffer) return nullptr;
|
|
17
|
-
|
|
18
|
-
// Call Bitmap.wrapHardwareBuffer(hardwareBuffer, null) to create a hardware-backed Bitmap
|
|
19
|
-
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
|
|
20
|
-
jmethodID wrapMethod = env->GetStaticMethodID(
|
|
21
|
-
bitmapClass,
|
|
22
|
-
"wrapHardwareBuffer",
|
|
23
|
-
"(Landroid/hardware/HardwareBuffer;Landroid/graphics/ColorSpace;)Landroid/graphics/Bitmap;"
|
|
24
|
-
);
|
|
25
|
-
jobject hwBitmap = env->CallStaticObjectMethod(bitmapClass, wrapMethod, hardwareBuffer, nullptr);
|
|
26
|
-
if (!hwBitmap) return nullptr;
|
|
27
|
-
|
|
28
|
-
// Copy to a software ARGB_8888 Bitmap (ML Kit needs software bitmap)
|
|
29
|
-
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
|
|
30
|
-
jfieldID argbField = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
|
31
|
-
jobject argbConfig = env->GetStaticObjectField(configClass, argbField);
|
|
32
|
-
|
|
33
|
-
jmethodID copyMethod = env->GetMethodID(bitmapClass, "copy", "(Landroid/graphics/Bitmap$Config;Z)Landroid/graphics/Bitmap;");
|
|
34
|
-
jobject softBitmap = env->CallObjectMethod(hwBitmap, copyMethod, argbConfig, JNI_FALSE);
|
|
35
|
-
|
|
36
|
-
return softBitmap;
|
|
37
|
-
}
|