commitf0b9b2af67Merge:373d65abbaa7bbAuthor: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Sat Sep 12 18:17:04 2026 +0800 Merge branch 'merge-testing' commit373d65a9d5Merge:28fc652dd0e6fdAuthor: harine <harinesumen@gmail.com> Date: Sat Sep 12 17:34:27 2026 +0800 Merge branch 'master' into Harine commit28fc652097Author: harine <harinesumen@gmail.com> Date: Sat Sep 12 17:34:07 2026 +0800 new stuff added yes commitbbaa7bb04fMerge:1c6ced2610bfd7Author: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Fri Sep 11 11:18:35 2026 +0800 Merge branch 'Gabriel' into merge-testing commit1c6ced2dceAuthor: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Fri Sep 11 11:12:30 2026 +0800 this good commitdd0e6fdc39Author: DefiantWanderer <yongwei1349@gmail.com> Date: Fri Sep 11 10:32:34 2026 +0800 Revert "fixing compatibility issues" This reverts commit564e7d83f8. commit564e7d83f8Author: DefiantWanderer <yongwei1349@gmail.com> Date: Fri Sep 11 10:28:40 2026 +0800 fixing compatibility issues commit610bfd7991Author: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Fri Sep 11 10:13:25 2026 +0800 Merged Khalil commitcfe76da1d3Merge:584818b9fcdc13Author: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Fri Sep 11 10:03:51 2026 +0800 Merge branch 'Khalil' into Gabriel commit584818b95aAuthor: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Thu Sep 10 12:47:06 2026 +0800 added back phase toggle button commit17fad430c1Author: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Thu Sep 10 12:09:46 2026 +0800 Cleaned up merge UI, added reset step button commitac4366d04dAuthor: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Thu Sep 10 00:31:47 2026 +0800 Added some UI to recording commitd601efcf43Author: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Thu Sep 10 00:13:06 2026 +0800 Corrected 5 step approach phases & added placeholders for future logic commit7cf217488aAuthor: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Wed Sep 9 22:48:06 2026 +0800 Cleared warnings commit981071ef74Author: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Wed Sep 9 20:09:18 2026 +0800 Fix Merge, cleaned up UI commitf30bbe2a34Merge:2f451157518594Author: Gabriel Low <73009315+gabriellow1111@users.noreply.github.com> Date: Tue Sep 8 14:11:21 2026 +0800 Merge branch 'au-au' into Gabriel commit9fcdc13699Author: Khalil Belabadia <belabakhalil@gmail.com> Date: Mon Sep 7 22:16:00 2026 +0800 Audio feedback update Added the following files : - AudioCue.kt - AudioFeedbackEngine - AudioFeedbackSettings Edited files : - BowlingCameraActivity (added the triggerAudiofeedback function) commit7518594440Merge:e7a9c2b7af128dAuthor: midnight-masala <2401021@sit.singaporetech.edu.sg> Date: Mon Sep 7 21:26:18 2026 +0800 Merge remote-tracking branch 'origin/au-au' into au-au # Conflicts: # app/src/main/java/com/example/jnicpp/bowling/BowlingCameraActivity.kt # app/src/main/res/layout-land/activity_bowling_camera.xml # app/src/main/res/layout/activity_bowling_camera.xml commite7a9c2b140Author: midnight-masala <2401021@sit.singaporetech.edu.sg> Date: Mon Sep 7 21:22:11 2026 +0800 Add live per-step form feedback based on joint angles Extends pose angle tracking with knee bend (hip-knee-ankle), then uses it alongside the existing elbow/shoulder angles in a new PoseStageAdvisor to give a short live cue for whichever step of the approach is in progress: push-away on step 2, downswing on step 3, backswing on step 4, and knee-bend/arm-extension on the final step. Thresholds are starting defaults, not measured coaching data, and are expected to be retuned against real approach footage. Also fixes a layout bug found while testing this live: the new feedback text was chained via ConstraintLayout's toBottomOf to the step banner above it, so whenever that banner was hidden (GONE) the feedback text rendered at its collapsed zero-height position instead of staying put, landing on top of the recording indicator. Both now sit in a plain vertical LinearLayout, which collapses GONE children correctly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> commit7af128d575Author: midnight-masala <2401021@sit.singaporetech.edu.sg> Date: Mon Sep 7 20:58:03 2026 +0800 steps identify the steps commit2bb7c6ca7bAuthor: midnight-masala <2401021@sit.singaporetech.edu.sg> Date: Mon Sep 7 20:58:03 2026 +0800 Add live final-position feedback and fix step detection accuracy Shows a live banner as each step of the approach is counted, ending in "FINAL POSITION - RELEASE!" once the 5th footfall lands. Also fixes LiveStepDetector's peak-prominence check, which compared a candidate footfall only to its immediate neighboring frame and silently dropped real steps that landed across several closely-spaced frames; it now tracks the true rise/fall trough on each side instead. Switches PoseAnalyzer to ML Kit's faster base pose model (the accurate model was starving the peak detector of frames on unaccelerated hardware), and makes LiveStepDetector's stillness-based reset toggleable, currently off for easier testing against recorded reference clips. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
128 lines
6.0 KiB
Kotlin
128 lines
6.0 KiB
Kotlin
/**
|
|
* @file PoseFrame.kt
|
|
* @brief Time-stamped, per-frame pose snapshot buffered across a recording session.
|
|
*/
|
|
package com.example.jnicpp.bowling
|
|
|
|
import com.google.mlkit.vision.pose.PoseLandmark
|
|
|
|
/**
|
|
* @brief A single joint's 2D position, in the same analysis-image pixel space as [SmoothedLandmark].
|
|
*
|
|
* Deliberately drops the confidence field -- by the time a landmark becomes
|
|
* part of a [PoseFrame] it's already passed the
|
|
* [PoseSkeletonRenderer.MIN_LIKELIHOOD] gate (see [buildPoseFrame]), so
|
|
* downstream step-detection logic only ever sees positions worth trusting.
|
|
*
|
|
* @param x Pixel x coordinate, in analysis-image space.
|
|
* @param y Pixel y coordinate, in analysis-image space.
|
|
*/
|
|
data class LandmarkPoint(val x: Float, val y: Float)
|
|
|
|
/**
|
|
* @brief One time-stamped sample of a bowler's pose, buffered by
|
|
* [CameraViewModel] over a recording session for step-detection
|
|
* logic to analyze frame-by-frame movement.
|
|
*
|
|
* Ankle/hip fields hold the position after [AnkleHipMovingAverageFilter]'s
|
|
* simple moving average on top of [PoseLandmarkSmoother]'s per-frame EMA --
|
|
* those are the joints step timing is derived from, so they get the extra
|
|
* jitter reduction. The matching `*Raw` fields keep the pre-SMA (but still
|
|
* EMA'd) position alongside it, so the smoothing can be compared against or
|
|
* re-tuned later without needing to re-record; [LiveStepDetector] also
|
|
* deliberately reads the `*Raw` fields itself for peak detection, since the
|
|
* heavier SMA smoothing risks flattening a footfall's brief motion. Every
|
|
* field is nullable because a joint isn't always reliably detected in a
|
|
* given frame -- see [buildPoseFrame].
|
|
*
|
|
* @param timestampMs Wall-clock time this frame was captured, in milliseconds.
|
|
* @param leftAnkle SMA-smoothed left ankle position, or null if unreliable.
|
|
* @param rightAnkle SMA-smoothed right ankle position, or null if unreliable.
|
|
* @param leftAnkleRaw EMA-only (pre-SMA) left ankle position, or null if unreliable.
|
|
* @param rightAnkleRaw EMA-only (pre-SMA) right ankle position, or null if unreliable.
|
|
* @param leftKnee Left knee position, or null if unreliable.
|
|
* @param rightKnee Right knee position, or null if unreliable.
|
|
* @param leftHip SMA-smoothed left hip position, or null if unreliable.
|
|
* @param rightHip SMA-smoothed right hip position, or null if unreliable.
|
|
* @param leftHipRaw EMA-only (pre-SMA) left hip position, or null if unreliable.
|
|
* @param rightHipRaw EMA-only (pre-SMA) right hip position, or null if unreliable.
|
|
* @param leftShoulder Left shoulder position, or null if unreliable.
|
|
* @param rightShoulder Right shoulder position, or null if unreliable.
|
|
* @param leftElbow Left elbow position, or null if unreliable.
|
|
* @param rightElbow Right elbow position, or null if unreliable.
|
|
* @param leftWrist Left wrist position, or null if unreliable.
|
|
* @param rightWrist Right wrist position, or null if unreliable.
|
|
* @param angles Joint angles computed for this same frame.
|
|
*/
|
|
data class PoseFrame(
|
|
val timestampMs: Long,
|
|
val leftAnkle: LandmarkPoint? = null,
|
|
val rightAnkle: LandmarkPoint? = null,
|
|
val leftAnkleRaw: LandmarkPoint? = null,
|
|
val rightAnkleRaw: LandmarkPoint? = null,
|
|
val leftKnee: LandmarkPoint? = null,
|
|
val rightKnee: LandmarkPoint? = null,
|
|
val leftHip: LandmarkPoint? = null,
|
|
val rightHip: LandmarkPoint? = null,
|
|
val leftHipRaw: LandmarkPoint? = null,
|
|
val rightHipRaw: LandmarkPoint? = null,
|
|
val leftShoulder: LandmarkPoint? = null,
|
|
val rightShoulder: LandmarkPoint? = null,
|
|
val leftElbow: LandmarkPoint? = null,
|
|
val rightElbow: LandmarkPoint? = null,
|
|
val leftWrist: LandmarkPoint? = null,
|
|
val rightWrist: LandmarkPoint? = null,
|
|
val angles: PoseAngles = PoseAngles()
|
|
)
|
|
|
|
/**
|
|
* @brief Builds a [PoseFrame] from one frame's detection results.
|
|
*
|
|
* @param timestampMs Wall-clock time this frame was captured, in milliseconds.
|
|
* @param landmarks EMA-smoothed landmarks for this frame, keyed by ML Kit's `PoseLandmark` type constant.
|
|
* @param smoothedAnkleHip SMA-smoothed ankle/hip positions for this frame,
|
|
* expected to come from an [AnkleHipMovingAverageFilter] fed the
|
|
* same [landmarks], keyed the same way as [landmarks] itself.
|
|
* @param angles Joint angles computed for this same frame.
|
|
* @return The assembled [PoseFrame], with each landmark field null wherever
|
|
* it wasn't reliably detected.
|
|
*/
|
|
fun buildPoseFrame(
|
|
timestampMs: Long,
|
|
landmarks: Map<Int, SmoothedLandmark>,
|
|
smoothedAnkleHip: Map<Int, LandmarkPoint>,
|
|
angles: PoseAngles
|
|
): PoseFrame {
|
|
/**
|
|
* @brief Looks up one landmark and converts it to a [LandmarkPoint], gated by confidence.
|
|
* @param type ML Kit `PoseLandmark` type constant to look up.
|
|
* @return The landmark's position, or null if missing or below [PoseSkeletonRenderer.MIN_LIKELIHOOD].
|
|
*/
|
|
fun point(type: Int): LandmarkPoint? {
|
|
val landmark = landmarks[type] ?: return null
|
|
if (landmark.inFrameLikelihood < PoseSkeletonRenderer.MIN_LIKELIHOOD) return null
|
|
return LandmarkPoint(landmark.x, landmark.y)
|
|
}
|
|
|
|
return PoseFrame(
|
|
timestampMs = timestampMs,
|
|
leftAnkle = smoothedAnkleHip[PoseLandmark.LEFT_ANKLE],
|
|
rightAnkle = smoothedAnkleHip[PoseLandmark.RIGHT_ANKLE],
|
|
leftAnkleRaw = point(PoseLandmark.LEFT_ANKLE),
|
|
rightAnkleRaw = point(PoseLandmark.RIGHT_ANKLE),
|
|
leftKnee = point(PoseLandmark.LEFT_KNEE),
|
|
rightKnee = point(PoseLandmark.RIGHT_KNEE),
|
|
leftHip = smoothedAnkleHip[PoseLandmark.LEFT_HIP],
|
|
rightHip = smoothedAnkleHip[PoseLandmark.RIGHT_HIP],
|
|
leftHipRaw = point(PoseLandmark.LEFT_HIP),
|
|
rightHipRaw = point(PoseLandmark.RIGHT_HIP),
|
|
leftShoulder = point(PoseLandmark.LEFT_SHOULDER),
|
|
rightShoulder = point(PoseLandmark.RIGHT_SHOULDER),
|
|
leftElbow = point(PoseLandmark.LEFT_ELBOW),
|
|
rightElbow = point(PoseLandmark.RIGHT_ELBOW),
|
|
leftWrist = point(PoseLandmark.LEFT_WRIST),
|
|
rightWrist = point(PoseLandmark.RIGHT_WRIST),
|
|
angles = angles
|
|
)
|
|
}
|