Step counter test1
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* @file DebugSessionLogger.kt
|
||||
* @brief Per-frame diagnostic trace written to a text file for the duration of a recording.
|
||||
*/
|
||||
package com.example.jnicpp.bowling
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import com.google.mlkit.vision.pose.PoseLandmark
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* @brief Writes one line per analyzed frame -- landmark confidence,
|
||||
* position, and derived torso scale -- to a plain-text file in the
|
||||
* public Downloads/bowling folder, for the duration of a single
|
||||
* recording.
|
||||
*
|
||||
* Exists purely as a step-counting troubleshooting aid: it's a much richer,
|
||||
* un-throttled trace than the 1/sec Logcat line in
|
||||
* [BowlingCameraActivity.onPoseResult], and lands somewhere retrievable
|
||||
* without needing adb or the Logcat panel -- the file shows up like any
|
||||
* other downloaded file, so it can be opened, shared, or copied off the
|
||||
* device by whatever means is convenient.
|
||||
*
|
||||
* One instance is meant to be reused across the Activity's lifetime; call
|
||||
* [start] when a recording begins and [stop] when it ends. Calling [log]
|
||||
* while not started is a harmless no-op.
|
||||
*/
|
||||
class DebugSessionLogger(private val appContext: Context) {
|
||||
|
||||
private var writer: BufferedWriter? = null
|
||||
private var lastLoggedMs: Long? = null
|
||||
|
||||
/**
|
||||
* @brief Opens a new timestamped file in Downloads/bowling and writes a header line.
|
||||
*
|
||||
* Uses `MediaStore.Downloads` on API 29+ (scoped storage, no extra
|
||||
* permission needed) and a direct file write into the public Downloads
|
||||
* directory below that, mirroring [CameraXController.startRecording]'s
|
||||
* own API-level branching for saving the video file.
|
||||
*/
|
||||
fun start() {
|
||||
stop()
|
||||
val fileName = "bowling_debug_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(java.util.Date())}.txt"
|
||||
val outputStream = try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Downloads.MIME_TYPE, "text/plain")
|
||||
put(MediaStore.Downloads.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/bowling")
|
||||
}
|
||||
val uri = appContext.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||
uri?.let { appContext.contentResolver.openOutputStream(it) }
|
||||
} else {
|
||||
val dir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "bowling")
|
||||
dir.mkdirs()
|
||||
FileOutputStream(File(dir, fileName))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
writer = outputStream?.let { BufferedWriter(OutputStreamWriter(it)) }
|
||||
lastLoggedMs = null
|
||||
writer?.let {
|
||||
it.write("timestampMs dtMs ankleL(y,lik) ankleR(y,lik) hipL(y,lik) hipR(y,lik) shoulderL(lik) shoulderR(lik) torsoScalePx stepCount")
|
||||
it.newLine()
|
||||
it.flush()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends one frame's diagnostic data as a line, if a session is currently open.
|
||||
* @param landmarks EMA-smoothed landmarks for this frame, keyed by ML Kit's `PoseLandmark` type constant.
|
||||
* @param timestampMs Wall-clock time this frame was analyzed, in milliseconds.
|
||||
* @param stepCount Current cumulative step count at the time of this frame.
|
||||
*/
|
||||
fun log(landmarks: Map<Int, SmoothedLandmark>, timestampMs: Long, stepCount: Int) {
|
||||
val out = writer ?: return
|
||||
|
||||
val ankleL = landmarks[PoseLandmark.LEFT_ANKLE]
|
||||
val ankleR = landmarks[PoseLandmark.RIGHT_ANKLE]
|
||||
val hipL = landmarks[PoseLandmark.LEFT_HIP]
|
||||
val hipR = landmarks[PoseLandmark.RIGHT_HIP]
|
||||
val shoulderL = landmarks[PoseLandmark.LEFT_SHOULDER]
|
||||
val shoulderR = landmarks[PoseLandmark.RIGHT_SHOULDER]
|
||||
|
||||
val torsoScale = torsoScale(shoulderL, shoulderR, hipL, hipR)
|
||||
val dtMs = lastLoggedMs?.let { timestampMs - it }
|
||||
lastLoggedMs = timestampMs
|
||||
|
||||
val line = "$timestampMs " +
|
||||
"${dtMs ?: "-"} " +
|
||||
"${format(ankleL)} ${format(ankleR)} " +
|
||||
"${format(hipL)} ${format(hipR)} " +
|
||||
"${formatLikelihoodOnly(shoulderL)} ${formatLikelihoodOnly(shoulderR)} " +
|
||||
"${torsoScale?.let { "%.1f".format(it) } ?: "-"} " +
|
||||
stepCount
|
||||
|
||||
try {
|
||||
out.write(line)
|
||||
out.newLine()
|
||||
// Flush every line, not just on stop() -- if the app is force-
|
||||
// stopped mid-recording the file should still have everything
|
||||
// logged up to that point rather than losing a buffered tail.
|
||||
out.flush()
|
||||
} catch (e: Exception) {
|
||||
// A failed debug write shouldn't disrupt the actual recording.
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Closes the current file, if one is open. Safe to call even if nothing is open. */
|
||||
fun stop() {
|
||||
try {
|
||||
writer?.close()
|
||||
} catch (e: Exception) {
|
||||
// Nothing useful to do about a failed close on a debug file.
|
||||
}
|
||||
writer = null
|
||||
}
|
||||
|
||||
private fun format(landmark: SmoothedLandmark?): String =
|
||||
if (landmark == null) "-" else "(%.1f,%.2f)".format(landmark.y, landmark.inFrameLikelihood)
|
||||
|
||||
private fun formatLikelihoodOnly(landmark: SmoothedLandmark?): String =
|
||||
if (landmark == null) "-" else "%.2f".format(landmark.inFrameLikelihood)
|
||||
|
||||
/** @brief Shoulder-to-hip pixel distance, matching [LiveStepDetector]'s own torso-scale definition. */
|
||||
private fun torsoScale(
|
||||
shoulderL: SmoothedLandmark?,
|
||||
shoulderR: SmoothedLandmark?,
|
||||
hipL: SmoothedLandmark?,
|
||||
hipR: SmoothedLandmark?
|
||||
): Float? {
|
||||
val shoulder = shoulderL ?: shoulderR ?: return null
|
||||
val hip = hipL ?: hipR ?: return null
|
||||
val dx = shoulder.x - hip.x
|
||||
val dy = shoulder.y - hip.y
|
||||
return kotlin.math.sqrt(dx * dx + dy * dy).takeIf { it > 0f }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user