starting pose detection
This commit is contained in:
@@ -17,9 +17,11 @@ import androidx.camera.core.CameraSelector
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.example.jnicpp.R
|
||||
import com.example.jnicpp.databinding.ActivityBowlingCameraBinding
|
||||
import com.google.mlkit.vision.pose.PoseLandmark
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.ExecutorService
|
||||
@@ -184,6 +186,24 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deliberately its own collector, independent of stepEvents
|
||||
// above -- delivery-phase feedback and step counting are
|
||||
// separate concerns (see PosePhaseDetector's class doc).
|
||||
// Combined with poseEnabled (rather than posePhase alone) so
|
||||
// the label can tell "pose off" (hidden) apart from "pose on
|
||||
// but not yet in the target posture" (amber prompt) -- both
|
||||
// cases otherwise report a null phase.
|
||||
launch {
|
||||
combine(viewModel.poseEnabled, viewModel.posePhase) { enabled, phase -> enabled to phase }
|
||||
.collect { (enabled, phase) -> renderPosePhase(enabled, phase) }
|
||||
}
|
||||
// Raw angle readout backing the label above -- its own
|
||||
// collector since it's driven by a separate StateFlow
|
||||
// (poseMetrics is null on its own whenever pose detection is
|
||||
// off, so no need to combine with poseEnabled here).
|
||||
launch {
|
||||
viewModel.poseMetrics.collect { metrics -> renderPoseMetrics(metrics) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,6 +255,66 @@ class BowlingCameraActivity : AppCompatActivity(), CameraXController.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shows or hides the delivery-phase feedback label, and colors/labels
|
||||
* it for whether [phase] currently validates.
|
||||
*
|
||||
* Visible for the entire time [poseEnabled] is on -- not just at the
|
||||
* moment a phase is confirmed -- so the bowler gets a continuous
|
||||
* "not yet"/"confirmed" signal to line themselves up against, rather
|
||||
* than a label that silently disappears whenever they drift out of
|
||||
* position. Independent of [renderRecordingState]/the step counter --
|
||||
* see [PosePhaseDetector]'s class doc for why phase feedback and step
|
||||
* counting are kept as separate concerns.
|
||||
*
|
||||
* @param poseEnabled Whether pose detection is currently on at all.
|
||||
* @param phase The bowler's current delivery phase, or null if none currently validates.
|
||||
*/
|
||||
private fun renderPosePhase(poseEnabled: Boolean, phase: BowlingPhase?) {
|
||||
if (!poseEnabled) {
|
||||
binding.textPoseFeedback.visibility = View.GONE
|
||||
return
|
||||
}
|
||||
binding.textPoseFeedback.visibility = View.VISIBLE
|
||||
// Every other BowlingPhase falls back to the "waiting" message too --
|
||||
// see PosePhaseDetector's class doc, only STARTING_STANCE is detected today.
|
||||
if (phase == BowlingPhase.STARTING_STANCE) {
|
||||
binding.textPoseFeedback.text = getString(R.string.pose_phase_starting_stance)
|
||||
binding.textPoseFeedback.setBackgroundColor(ContextCompat.getColor(this, R.color.pose_feedback_ready))
|
||||
} else {
|
||||
binding.textPoseFeedback.text = getString(R.string.pose_phase_waiting)
|
||||
binding.textPoseFeedback.setBackgroundColor(ContextCompat.getColor(this, R.color.pose_feedback_waiting))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Shows or hides the raw torso/knee/elbow angle readout backing [renderPosePhase]'s label.
|
||||
* @param metrics This frame's angle readings, or null to hide the readout (pose detection off).
|
||||
*/
|
||||
private fun renderPoseMetrics(metrics: PosePhaseDetector.Metrics?) {
|
||||
if (metrics == null) {
|
||||
binding.textPoseMetrics.visibility = View.GONE
|
||||
return
|
||||
}
|
||||
binding.textPoseMetrics.visibility = View.VISIBLE
|
||||
binding.textPoseMetrics.text = getString(
|
||||
R.string.pose_metrics_format,
|
||||
angleText(metrics.torsoTiltDegrees),
|
||||
angleText(metrics.leftKneeAngleDegrees),
|
||||
angleText(metrics.rightKneeAngleDegrees),
|
||||
angleText(metrics.leftElbowAngleDegrees),
|
||||
angleText(metrics.rightElbowAngleDegrees)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Formats one angle reading for display.
|
||||
* @param degrees The angle in degrees, or null if that landmark wasn't confidently detected this frame.
|
||||
* @return e.g. "12°", or "--" if [degrees] is null.
|
||||
*/
|
||||
private fun angleText(degrees: Float?): String =
|
||||
if (degrees == null) "--" else "${degrees.toInt()}°"
|
||||
|
||||
/** @brief Hides the permission-rationale screen, revealing the camera UI underneath. */
|
||||
private fun showCameraUi() {
|
||||
binding.layoutPermissionRationale.visibility = View.GONE
|
||||
|
||||
Reference in New Issue
Block a user