Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa25d897dc | ||
|
|
ac3eb98b9c |
@@ -0,0 +1,37 @@
|
|||||||
|
# Git LFS tracking for BowlEye
|
||||||
|
# Binary/large-asset types are stored via LFS and lockable, since binaries
|
||||||
|
# can't be merged. Run `git lfs lock <file>` before editing one of these.
|
||||||
|
# See docs/ and the "So you got your VM" onboarding deck for context.
|
||||||
|
|
||||||
|
# ML models (pose detection, custom-trained)
|
||||||
|
*.tflite filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.onnx filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.pt filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.pb filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
|
||||||
|
# Video / audio (bowling capture footage, test clips)
|
||||||
|
*.mp4 filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.mov filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.wav filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.mp3 filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
|
||||||
|
# Images
|
||||||
|
*.png filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.jpg filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.jpeg filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.webp filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.psd filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
|
||||||
|
# Android build / signing artifacts
|
||||||
|
*.apk filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.aab filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.jar filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.aar filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.so filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.keystore filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.jks filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
|
||||||
|
# Misc large/binary
|
||||||
|
*.zip filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.fbx filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
|
*.blend filter=lfs diff=lfs merge=lfs -text lockable
|
||||||
@@ -89,13 +89,17 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
|||||||
// class for FeedbackUI
|
// class for FeedbackUI
|
||||||
private lateinit var feedbackUI: FeedbackUI
|
private lateinit var feedbackUI: FeedbackUI
|
||||||
|
|
||||||
|
// The team's 5-step terminology, in order -- deliberately not one entry
|
||||||
|
// per BowlingPhase, since some phases span two of these (see
|
||||||
|
// PosePhaseDetector.phaseForStep).
|
||||||
private val stepLabels = listOf(
|
private val stepLabels = listOf(
|
||||||
R.string.pose_phase_starting_stance,
|
R.string.pose_phase_starting_stance,
|
||||||
R.string.pose_phase_approach,
|
R.string.step_term_half_step,
|
||||||
|
R.string.step_term_preparation_step,
|
||||||
R.string.pose_phase_pushaway,
|
R.string.pose_phase_pushaway,
|
||||||
R.string.pose_phase_back_swing,
|
|
||||||
R.string.pose_phase_power_step,
|
R.string.pose_phase_power_step,
|
||||||
R.string.pose_phase_slide_and_release,
|
R.string.step_term_slide,
|
||||||
|
R.string.step_term_finishing_position,
|
||||||
)
|
)
|
||||||
private var currentPhaseToggleIndex = 0
|
private var currentPhaseToggleIndex = 0
|
||||||
|
|
||||||
@@ -137,6 +141,7 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
|||||||
stepCounterUi = StepCounterUiController(
|
stepCounterUi = StepCounterUiController(
|
||||||
cardStepCounter = binding.cardStepCounter,
|
cardStepCounter = binding.cardStepCounter,
|
||||||
textStepCountBig = binding.textStepCountBig,
|
textStepCountBig = binding.textStepCountBig,
|
||||||
|
textStepCounterLabel = binding.textStepCounterLabel,
|
||||||
)
|
)
|
||||||
feedbackUI = FeedbackUI(binding.root)
|
feedbackUI = FeedbackUI(binding.root)
|
||||||
audioFeedbackSettings = AudioFeedbackSettings(applicationContext)
|
audioFeedbackSettings = AudioFeedbackSettings(applicationContext)
|
||||||
@@ -259,7 +264,7 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
|||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
viewModel.stepEvents.collect { events ->
|
viewModel.stepEvents.collect { events ->
|
||||||
stepCounterUi.renderStepCount(events.size)
|
stepCounterUi.renderStepCount(events.size, events.lastOrNull()?.poseConfirmed ?: true)
|
||||||
if (events.isNotEmpty()) {
|
if (events.isNotEmpty()) {
|
||||||
Log.d(TAG, "Step ${events.size}: ${events.last()}")
|
Log.d(TAG, "Step ${events.size}: ${events.last()}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ class CameraViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
angles = angles,
|
angles = angles,
|
||||||
timestampMs = System.currentTimeMillis(),
|
timestampMs = System.currentTimeMillis(),
|
||||||
isStartingPosition = isStartingStance,
|
isStartingPosition = isStartingStance,
|
||||||
|
currentPhase = phaseResult.phase,
|
||||||
)
|
)
|
||||||
val currentStepCount = stepEvents.value.size
|
val currentStepCount = stepEvents.value.size
|
||||||
_poseStageFeedback.value = PoseStageAdvisor.feedback(
|
_poseStageFeedback.value = PoseStageAdvisor.feedback(
|
||||||
|
|||||||
@@ -399,14 +399,16 @@ class CameraXController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun drawPhaseBadgeOverlay(canvas: Canvas, phase: BowlingPhase, scale: Float) {
|
private fun drawPhaseBadgeOverlay(canvas: Canvas, phase: BowlingPhase, scale: Float) {
|
||||||
val label = when (phase) {
|
val label = appContext.getString(
|
||||||
BowlingPhase.STARTING_STANCE -> "Starting Stance"
|
when (phase) {
|
||||||
BowlingPhase.APPROACH -> "Approach"
|
BowlingPhase.STARTING_STANCE -> R.string.pose_phase_starting_stance
|
||||||
BowlingPhase.PUSHAWAY -> "Pushaway"
|
BowlingPhase.APPROACH -> R.string.pose_phase_approach
|
||||||
BowlingPhase.BACK_SWING -> "Backswing"
|
BowlingPhase.PUSHAWAY -> R.string.pose_phase_pushaway
|
||||||
BowlingPhase.POWER_STEP -> "Power Step"
|
BowlingPhase.BACK_SWING -> R.string.pose_phase_back_swing
|
||||||
BowlingPhase.SLIDE_AND_RELEASE -> "Slide & Release"
|
BowlingPhase.POWER_STEP -> R.string.pose_phase_power_step
|
||||||
}
|
BowlingPhase.SLIDE_AND_RELEASE -> R.string.pose_phase_slide_and_release
|
||||||
|
}
|
||||||
|
)
|
||||||
val colorRes = when (phase) {
|
val colorRes = when (phase) {
|
||||||
BowlingPhase.STARTING_STANCE -> R.color.Starting_stance_ready
|
BowlingPhase.STARTING_STANCE -> R.color.Starting_stance_ready
|
||||||
BowlingPhase.APPROACH -> R.color.Approach_ready
|
BowlingPhase.APPROACH -> R.color.Approach_ready
|
||||||
|
|||||||
@@ -303,32 +303,40 @@ class PosePhaseDetector(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* @brief Maps a 5-step approach step count (0..5) to its corresponding [BowlingPhase].
|
* @brief Maps a 5-step approach step count (0..5) to the [BowlingPhase]
|
||||||
|
* the bowler should be in once that step has landed.
|
||||||
*
|
*
|
||||||
* step 0 -> STARTING_STANCE
|
* Follows the team's 5-step terminology: starting position -> 1/2 step
|
||||||
* step 1 -> APPROACH
|
* -> preparation step -> push away & backswing -> power step -> slide
|
||||||
* step 2 -> PUSHAWAY
|
* -> finishing position. The 1/2 and preparation steps are both
|
||||||
* step 3 -> BACK_SWING
|
* [BowlingPhase.APPROACH] (ball still held, CG moving forward); the
|
||||||
* step 4 -> POWER_STEP
|
* ball is pushed away into the swing on step 3, reaches the peak of
|
||||||
* step 5 -> SLIDE_AND_RELEASE
|
* the backswing as the very short power step lands on step 4, and is
|
||||||
|
* released during the slide on step 5.
|
||||||
|
*
|
||||||
|
* step 0 -> STARTING_STANCE (starting position)
|
||||||
|
* steps 1-2 -> APPROACH (1/2 step, preparation step)
|
||||||
|
* step 3 -> PUSHAWAY (push away & backswing; BACK_SWING shares this step)
|
||||||
|
* step 4 -> POWER_STEP
|
||||||
|
* step 5+ -> SLIDE_AND_RELEASE (slide, finishing position)
|
||||||
*/
|
*/
|
||||||
fun phaseForStep(stepCount: Int): BowlingPhase = when {
|
fun phaseForStep(stepCount: Int): BowlingPhase = when {
|
||||||
stepCount <= 0 -> BowlingPhase.STARTING_STANCE
|
stepCount <= 0 -> BowlingPhase.STARTING_STANCE
|
||||||
stepCount == 1 -> BowlingPhase.APPROACH
|
stepCount <= 2 -> BowlingPhase.APPROACH
|
||||||
stepCount == 2 -> BowlingPhase.PUSHAWAY
|
stepCount == 3 -> BowlingPhase.PUSHAWAY
|
||||||
stepCount == 3 -> BowlingPhase.BACK_SWING
|
|
||||||
stepCount == 4 -> BowlingPhase.POWER_STEP
|
stepCount == 4 -> BowlingPhase.POWER_STEP
|
||||||
else -> BowlingPhase.SLIDE_AND_RELEASE
|
else -> BowlingPhase.SLIDE_AND_RELEASE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Maps a [BowlingPhase] to its corresponding 5-step approach step count.
|
* @brief The first step of a 5-step approach at which [phase] begins
|
||||||
|
* -- see [phaseForStep] for the full mapping. APPROACH spans
|
||||||
|
* steps 1-2, and PUSHAWAY/BACK_SWING both begin on step 3.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
|
||||||
fun stepForPhase(phase: BowlingPhase): Int = when (phase) {
|
fun stepForPhase(phase: BowlingPhase): Int = when (phase) {
|
||||||
BowlingPhase.STARTING_STANCE -> 0
|
BowlingPhase.STARTING_STANCE -> 0
|
||||||
BowlingPhase.APPROACH -> 1
|
BowlingPhase.APPROACH -> 1
|
||||||
BowlingPhase.PUSHAWAY -> 2
|
BowlingPhase.PUSHAWAY -> 3
|
||||||
BowlingPhase.BACK_SWING -> 3
|
BowlingPhase.BACK_SWING -> 3
|
||||||
BowlingPhase.POWER_STEP -> 4
|
BowlingPhase.POWER_STEP -> 4
|
||||||
BowlingPhase.SLIDE_AND_RELEASE -> 5
|
BowlingPhase.SLIDE_AND_RELEASE -> 5
|
||||||
|
|||||||
@@ -31,19 +31,24 @@ package com.example.jnicpp.bowling
|
|||||||
*/
|
*/
|
||||||
object PoseStageAdvisor {
|
object PoseStageAdvisor {
|
||||||
|
|
||||||
// Step-2 cue: ball still close to the body just after push-away, so the
|
// Step names follow the team's 5-step terminology (see
|
||||||
// swing-arm shoulder angle (elbow-shoulder-hip) should still be small.
|
// PosePhaseDetector.phaseForStep): 1/2 step, preparation step, push away
|
||||||
private const val PUSH_AWAY_MAX_SHOULDER_DEG = 30f
|
// & backswing, power step, slide -> finishing position.
|
||||||
|
|
||||||
// Step-3 cue: arm swinging down and back past the body.
|
// Preparation-step cue: the ball is still held close to the body before
|
||||||
private const val DOWNSWING_MIN_SHOULDER_DEG = 25f
|
// the push away, so the swing-arm shoulder angle (elbow-shoulder-hip)
|
||||||
private const val DOWNSWING_MAX_SHOULDER_DEG = 75f
|
// should still be small.
|
||||||
|
private const val PREPARATION_MAX_SHOULDER_DEG = 30f
|
||||||
|
|
||||||
// Step-4 cue: arm swinging well back behind the body.
|
// Push away & backswing cue: ball pushed out and swinging past the body.
|
||||||
private const val BACKSWING_MIN_SHOULDER_DEG = 60f
|
private const val PUSH_AWAY_MIN_SHOULDER_DEG = 25f
|
||||||
|
private const val PUSH_AWAY_MAX_SHOULDER_DEG = 75f
|
||||||
|
|
||||||
// Final-position cues: front knee bent to lower the slide, swing arm
|
// Power-step cue: ball at the peak of the backswing, well behind the body.
|
||||||
// relatively straight through the release.
|
private const val POWER_STEP_MIN_SHOULDER_DEG = 60f
|
||||||
|
|
||||||
|
// Slide / finishing-position cues: front knee bent to lower the slide,
|
||||||
|
// swing arm relatively straight through the release.
|
||||||
private const val RELEASE_MAX_KNEE_DEG = 140f
|
private const val RELEASE_MAX_KNEE_DEG = 140f
|
||||||
private const val RELEASE_MIN_ELBOW_DEG = 150f
|
private const val RELEASE_MIN_ELBOW_DEG = 150f
|
||||||
|
|
||||||
@@ -61,25 +66,35 @@ object PoseStageAdvisor {
|
|||||||
val frontKnee = smallerOf(angles.leftKnee, angles.rightKnee)
|
val frontKnee = smallerOf(angles.leftKnee, angles.rightKnee)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
(stepNumber == null || stepNumber <= 1) -> "Starting position - stay relaxed"
|
(stepNumber == null || stepNumber <= 0) -> "Starting position - stay relaxed"
|
||||||
|
|
||||||
|
stepNumber == 1 -> "½ step - small step, weight shifting forward"
|
||||||
|
|
||||||
stepNumber == 2 -> swingShoulder?.let {
|
stepNumber == 2 -> swingShoulder?.let {
|
||||||
if (it <= PUSH_AWAY_MAX_SHOULDER_DEG) "Good push-away" else "Push the ball out first"
|
if (it <= PREPARATION_MAX_SHOULDER_DEG) {
|
||||||
|
"Good preparation step"
|
||||||
|
} else {
|
||||||
|
"Keep the ball close until the push away"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stepNumber == 3 -> swingShoulder?.let {
|
stepNumber == 3 -> swingShoulder?.let {
|
||||||
if (it in DOWNSWING_MIN_SHOULDER_DEG..DOWNSWING_MAX_SHOULDER_DEG) {
|
if (it in PUSH_AWAY_MIN_SHOULDER_DEG..PUSH_AWAY_MAX_SHOULDER_DEG) {
|
||||||
"Good downswing"
|
"Good push away & backswing"
|
||||||
} else {
|
} else {
|
||||||
"Let the arm swing naturally"
|
"Push the ball up and let it swing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stepNumber == 4 -> swingShoulder?.let {
|
stepNumber == 4 -> swingShoulder?.let {
|
||||||
if (it >= BACKSWING_MIN_SHOULDER_DEG) "Good backswing" else "Swing the arm further back"
|
if (it >= POWER_STEP_MIN_SHOULDER_DEG) {
|
||||||
|
"Good power step - ball at the top"
|
||||||
|
} else {
|
||||||
|
"Let the ball swing higher on the power step"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> { // final step (5+)
|
else -> { // slide & finishing position (5+)
|
||||||
val kneeGood = frontKnee != null && frontKnee <= RELEASE_MAX_KNEE_DEG
|
val kneeGood = frontKnee != null && frontKnee <= RELEASE_MAX_KNEE_DEG
|
||||||
val armGood = swingElbow != null && swingElbow >= RELEASE_MIN_ELBOW_DEG
|
val armGood = swingElbow != null && swingElbow >= RELEASE_MIN_ELBOW_DEG
|
||||||
when {
|
when {
|
||||||
|
|||||||
@@ -6,16 +6,22 @@ package com.example.jnicpp.bowling
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.example.jnicpp.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Owns rendering for [BowlingCameraActivity]'s step-counter card.
|
* @brief Owns rendering for [BowlingCameraActivity]'s step-counter card.
|
||||||
*
|
*
|
||||||
* @param cardStepCounter The step-counter card container view.
|
* @param cardStepCounter The step-counter card container view.
|
||||||
* @param textStepCountBig The large step-count number TextView.
|
* @param textStepCountBig The large step-count number TextView.
|
||||||
|
* @param textStepCounterLabel The small "STEPS" label below the count,
|
||||||
|
* repurposed to flag when the latest step wasn't corroborated by
|
||||||
|
* [PosePhaseDetector] -- see [renderStepCount]'s `latestPoseConfirmed`.
|
||||||
*/
|
*/
|
||||||
class StepCounterUiController(
|
class StepCounterUiController(
|
||||||
private val cardStepCounter: View,
|
private val cardStepCounter: View,
|
||||||
private val textStepCountBig: TextView,
|
private val textStepCountBig: TextView,
|
||||||
|
private val textStepCounterLabel: TextView,
|
||||||
) {
|
) {
|
||||||
// Last step count rendered, so pulse() in renderStepCount only plays
|
// Last step count rendered, so pulse() in renderStepCount only plays
|
||||||
// when a new step actually pushed the count up.
|
// when a new step actually pushed the count up.
|
||||||
@@ -36,14 +42,34 @@ class StepCounterUiController(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Renders the current step count, pulsing the card if a new step was just confirmed.
|
* @brief Renders the current step count, pulsing the card if a new step was just confirmed.
|
||||||
|
*
|
||||||
|
* Never withholds or delays a step because of [latestPoseConfirmed] --
|
||||||
|
* the ankle-peak count is always trusted (see [StepEvent.poseConfirmed]'s
|
||||||
|
* doc for why); this only swaps the small label below the number to flag
|
||||||
|
* a disagreement for whoever's testing/tuning detection to notice.
|
||||||
|
*
|
||||||
* @param stepCount Total steps counted so far in the current attempt.
|
* @param stepCount Total steps counted so far in the current attempt.
|
||||||
|
* @param latestPoseConfirmed Whether the most recently counted step (if
|
||||||
|
* any) was corroborated by [PosePhaseDetector] at the time it was
|
||||||
|
* counted; ignored when [stepCount] is 0.
|
||||||
*/
|
*/
|
||||||
fun renderStepCount(stepCount: Int) {
|
fun renderStepCount(stepCount: Int, latestPoseConfirmed: Boolean = true) {
|
||||||
textStepCountBig.text = stepCount.toString()
|
textStepCountBig.text = stepCount.toString()
|
||||||
if (stepCount > lastRenderedStepCount) {
|
if (stepCount > lastRenderedStepCount) {
|
||||||
pulse()
|
pulse()
|
||||||
}
|
}
|
||||||
lastRenderedStepCount = stepCount
|
lastRenderedStepCount = stepCount
|
||||||
|
|
||||||
|
val flagged = stepCount > 0 && !latestPoseConfirmed
|
||||||
|
textStepCounterLabel.text = textStepCounterLabel.context.getString(
|
||||||
|
if (flagged) R.string.step_counter_label_unconfirmed else R.string.step_counter_label
|
||||||
|
)
|
||||||
|
textStepCounterLabel.setTextColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
textStepCounterLabel.context,
|
||||||
|
if (flagged) R.color.recording_red else R.color.step_counter_accent,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Briefly scales the step counter up and back down, drawing the eye to a newly confirmed step. */
|
/** @brief Briefly scales the step counter up and back down, drawing the eye to a newly confirmed step. */
|
||||||
|
|||||||
@@ -57,12 +57,17 @@ class StepCountingSession {
|
|||||||
* @param angles Joint angles computed for this same frame.
|
* @param angles Joint angles computed for this same frame.
|
||||||
* @param timestampMs Wall-clock time this frame was analyzed, in milliseconds.
|
* @param timestampMs Wall-clock time this frame was analyzed, in milliseconds.
|
||||||
* @param isStartingPosition Whether the bowler is currently in the starting position.
|
* @param isStartingPosition Whether the bowler is currently in the starting position.
|
||||||
|
* @param currentPhase [PosePhaseDetector]'s currently-classified delivery
|
||||||
|
* phase for this same frame, or null if none currently validates
|
||||||
|
* -- used only to set each new [StepEvent.poseConfirmed] below,
|
||||||
|
* never to gate step counting itself (see that field's doc).
|
||||||
*/
|
*/
|
||||||
fun onFrame(
|
fun onFrame(
|
||||||
landmarks: Map<Int, SmoothedLandmark>,
|
landmarks: Map<Int, SmoothedLandmark>,
|
||||||
angles: PoseAngles,
|
angles: PoseAngles,
|
||||||
timestampMs: Long,
|
timestampMs: Long,
|
||||||
isStartingPosition: Boolean = false,
|
isStartingPosition: Boolean = false,
|
||||||
|
currentPhase: BowlingPhase? = null,
|
||||||
) {
|
) {
|
||||||
val smoothedAnkleHip = ankleHipSmoother.smooth(landmarks)
|
val smoothedAnkleHip = ankleHipSmoother.smooth(landmarks)
|
||||||
val frame = buildPoseFrame(
|
val frame = buildPoseFrame(
|
||||||
@@ -78,7 +83,15 @@ class StepCountingSession {
|
|||||||
_stepEvents.value = emptyList()
|
_stepEvents.value = emptyList()
|
||||||
}
|
}
|
||||||
if (result.newSteps.isNotEmpty()) {
|
if (result.newSteps.isNotEmpty()) {
|
||||||
_stepEvents.value += result.newSteps
|
// Compared phase-to-phase (via each phase's starting step) rather
|
||||||
|
// than against the raw step number, since one phase can span
|
||||||
|
// several steps -- e.g. APPROACH covers steps 1-2, so a pose still
|
||||||
|
// in APPROACH is correct when step 2 lands.
|
||||||
|
val posePhaseStart = PosePhaseDetector.stepForPhase(currentPhase ?: BowlingPhase.STARTING_STANCE)
|
||||||
|
_stepEvents.value += result.newSteps.map { step ->
|
||||||
|
val expectedPhaseStart = PosePhaseDetector.stepForPhase(PosePhaseDetector.phaseForStep(step.stepIndex))
|
||||||
|
step.copy(poseConfirmed = posePhaseStart >= expectedPhaseStart)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,21 @@ enum class Foot { LEFT, RIGHT }
|
|||||||
* @param timestampMs Time this foot-plant was detected, in milliseconds.
|
* @param timestampMs Time this foot-plant was detected, in milliseconds.
|
||||||
* @param foot Which foot planted.
|
* @param foot Which foot planted.
|
||||||
* @param stepIndex 1-based position of this step in the overall approach sequence.
|
* @param stepIndex 1-based position of this step in the overall approach sequence.
|
||||||
|
* @param poseConfirmed Whether [PosePhaseDetector]'s independently-classified
|
||||||
|
* posture had already reached the delivery phase this step index
|
||||||
|
* expects (see [PosePhaseDetector.phaseForStep]) at the moment this
|
||||||
|
* step was counted -- see [StepCountingSession.onFrame]. The ankle-peak
|
||||||
|
* count is trusted either way (this never blocks a step from being
|
||||||
|
* counted); false just flags that the two signals disagreed, e.g. a
|
||||||
|
* step 1 landing before pose ever confirmed the bowler had actually
|
||||||
|
* left the starting stance. Always true from [StepDetector.detect]'s
|
||||||
|
* batch pass, which has no phase information to compare against.
|
||||||
*/
|
*/
|
||||||
data class StepEvent(
|
data class StepEvent(
|
||||||
val timestampMs: Long,
|
val timestampMs: Long,
|
||||||
val foot: Foot,
|
val foot: Foot,
|
||||||
val stepIndex: Int,
|
val stepIndex: Int,
|
||||||
|
val poseConfirmed: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -102,6 +102,7 @@
|
|||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/text_step_counter_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/step_counter_label"
|
android:text="@string/step_counter_label"
|
||||||
|
|||||||
@@ -142,6 +142,7 @@
|
|||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/text_step_counter_label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/step_counter_label"
|
android:text="@string/step_counter_label"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<string name="recording_timer_placeholder">00:00</string>
|
<string name="recording_timer_placeholder">00:00</string>
|
||||||
<string name="step_count_big_placeholder">0</string>
|
<string name="step_count_big_placeholder">0</string>
|
||||||
<string name="step_counter_label">STEPS</string>
|
<string name="step_counter_label">STEPS</string>
|
||||||
|
<string name="step_counter_label_unconfirmed">STEPS · UNCONFIRMED BY POSE</string>
|
||||||
<string name="reset_counter">Reset Steps</string>
|
<string name="reset_counter">Reset Steps</string>
|
||||||
<string name="reset_hint_idle">✋ Raise a hand, hold 5s to reset</string>
|
<string name="reset_hint_idle">✋ Raise a hand, hold 5s to reset</string>
|
||||||
<string name="reset_hint_holding">Keep holding… %1$d%%</string>
|
<string name="reset_hint_holding">Keep holding… %1$d%%</string>
|
||||||
@@ -50,12 +51,16 @@
|
|||||||
<string name="editor_save">Save</string>
|
<string name="editor_save">Save</string>
|
||||||
<string name="editor_saved_toast">Settings saved</string>
|
<string name="editor_saved_toast">Settings saved</string>
|
||||||
<string name="editor_invalid_value_toast">Enter a valid number for every field</string>
|
<string name="editor_invalid_value_toast">Enter a valid number for every field</string>
|
||||||
<string name="pose_phase_starting_stance">Starting Stance</string>
|
<string name="pose_phase_starting_stance">Starting Position</string>
|
||||||
<string name="pose_phase_approach">Approach</string>
|
<string name="pose_phase_approach">½ Step / Preparation Step</string>
|
||||||
<string name="pose_phase_pushaway">Pushaway</string>
|
<string name="pose_phase_pushaway">Push Away & Backswing</string>
|
||||||
<string name="pose_phase_back_swing">Backswing</string>
|
<string name="pose_phase_back_swing">@string/pose_phase_pushaway</string>
|
||||||
<string name="pose_phase_power_step">Power Step</string>
|
<string name="pose_phase_power_step">Power Step</string>
|
||||||
<string name="pose_phase_slide_and_release">Slide & Release</string>
|
<string name="pose_phase_slide_and_release">Slide & Finishing Position</string>
|
||||||
<string name="pose_phase_waiting">Waiting for stance…</string>
|
<string name="pose_phase_waiting">Waiting for starting position…</string>
|
||||||
|
<string name="step_term_half_step">½ Step</string>
|
||||||
|
<string name="step_term_preparation_step">Preparation Step</string>
|
||||||
|
<string name="step_term_slide">Slide</string>
|
||||||
|
<string name="step_term_finishing_position">Finishing Position</string>
|
||||||
<string name="pose_metrics_format">Torso: %1$s · Knee L: %2$s R: %3$s\nElbow L: %4$s R: %5$s</string>
|
<string name="pose_metrics_format">Torso: %1$s · Knee L: %2$s R: %3$s\nElbow L: %4$s R: %5$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user