This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: do not recreate model constantly

+50 -48
+1 -3
posedetection/src/androidMain/kotlin/com/performancecoachlab/posedetection/camera/Utils.android.kt
··· 332 332 runCatching { 333 333 val pose = Tasks.await(poseDetector.process(mlKitImage)) 334 334 val landmarks = pose.allPoseLandmarks.size 335 - Logger.d { "MLKit pose landmarks=$landmarks" } 336 335 skeleton(pose, timestamp, width, height) 337 - }.onFailure { t -> 338 - Logger.e(t) { "MLKit poseDetector.process failed" } 336 + }.onFailure { t -> Logger.e(t) { "MLKit poseDetector.process failed" } 339 337 }.getOrNull() 340 338 } else null 341 339
+26
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/custom/CustomObjectModel.kt
··· 1 1 package com.performancecoachlab.posedetection.custom 2 2 3 3 import androidx.compose.runtime.Composable 4 + import kotlinx.coroutines.InternalCoroutinesApi 5 + import kotlinx.coroutines.internal.SynchronizedObject 6 + import kotlinx.coroutines.internal.synchronized 4 7 import kotlin.concurrent.Volatile 5 8 6 9 data class ModelPath( ··· 15 18 expect class ObjectModel { 16 19 } 17 20 21 + object ObjectModelProvider { 22 + @Volatile private var cached: ObjectModel? = null 23 + 24 + // Kotlin/Native requires a SynchronizedObject as the lock 25 + @OptIn(InternalCoroutinesApi::class) 26 + private val lock = SynchronizedObject() 27 + 28 + @OptIn(InternalCoroutinesApi::class) 29 + @Composable 30 + fun get(modelPath: ModelPath): ObjectModel { 31 + val fast = cached 32 + if (fast != null) return fast 33 + 34 + return synchronized(lock) { 35 + cached ?: initialiseObjectModel(modelPath).also { cached = it } 36 + } 37 + } 38 + 39 + @OptIn(InternalCoroutinesApi::class) 40 + fun clear() { 41 + synchronized(lock) { cached = null } 42 + } 43 + }
+23 -45
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
··· 4 4 import androidx.compose.foundation.Image 5 5 import androidx.compose.foundation.layout.Box 6 6 import androidx.compose.foundation.layout.Column 7 - import androidx.compose.foundation.layout.Row 8 - import androidx.compose.foundation.layout.Spacer 9 7 import androidx.compose.foundation.layout.WindowInsets 10 8 import androidx.compose.foundation.layout.fillMaxSize 11 9 import androidx.compose.foundation.layout.fillMaxWidth 12 10 import androidx.compose.foundation.layout.imePadding 13 - import androidx.compose.foundation.layout.navigationBarsPadding 14 11 import androidx.compose.foundation.layout.padding 15 12 import androidx.compose.foundation.layout.safeDrawing 16 13 import androidx.compose.foundation.layout.windowInsetsPadding 17 - import androidx.compose.foundation.layout.wrapContentHeight 18 14 import androidx.compose.material3.Button 19 - import androidx.compose.ui.graphics.drawscope.Stroke 20 15 import androidx.compose.material3.CircularProgressIndicator 21 16 import androidx.compose.material3.Tab 22 17 import androidx.compose.material3.TabRow ··· 32 27 import androidx.compose.runtime.setValue 33 28 import androidx.compose.ui.Alignment 34 29 import androidx.compose.ui.Modifier 35 - import androidx.compose.ui.geometry.Rect 36 30 import androidx.compose.ui.graphics.Color 37 31 import androidx.compose.ui.graphics.ImageBitmap 32 + import androidx.compose.ui.graphics.drawscope.Stroke 38 33 import androidx.compose.ui.layout.ContentScale 39 34 import androidx.compose.ui.unit.dp 40 35 import androidx.compose.ui.unit.sp ··· 43 38 import chaintech.videoplayer.model.VideoPlayerConfig 44 39 import chaintech.videoplayer.ui.video.VideoPlayerComposable 45 40 import chaintech.videoplayer.util.RetrieveMediaDuration 46 - import co.touchlab.kermit.Logger 47 41 import com.nate.posedetection.theme.AppTheme 48 42 import com.performancecoachlab.posedetection.camera.CameraView 49 43 import com.performancecoachlab.posedetection.camera.CameraViewControllerImpl ··· 52 46 import com.performancecoachlab.posedetection.camera.DrawableShape 53 47 import com.performancecoachlab.posedetection.custom.CustomObjectRespository 54 48 import com.performancecoachlab.posedetection.custom.ModelPath 55 - import com.performancecoachlab.posedetection.custom.initialiseObjectModel 49 + import com.performancecoachlab.posedetection.custom.ObjectModelProvider 56 50 import com.performancecoachlab.posedetection.encoding.VideoBuilder 57 51 import com.performancecoachlab.posedetection.encoding.createVideoBuilder 58 52 import com.performancecoachlab.posedetection.permissions.PermissionProvider 59 - import com.performancecoachlab.posedetection.recording.AnalysisObject 60 53 import com.performancecoachlab.posedetection.recording.FrameAnalyser 61 - import com.performancecoachlab.posedetection.recording.FrameSize 62 54 import com.performancecoachlab.posedetection.recording.InputFrame 63 - import com.performancecoachlab.posedetection.recording.Label 64 55 import com.performancecoachlab.posedetection.recording.extractFrame 65 56 import com.performancecoachlab.posedetection.recording.listVideoFrameTimestamps 66 57 import com.performancecoachlab.posedetection.skeleton.Pose ··· 80 71 import kotlin.time.Clock 81 72 import kotlin.time.ExperimentalTime 82 73 83 - val androidPath = "yolov10n_float16.tflite" 74 + val androidPath = "basketballs_n1.tflite" 84 75 val iosPath = "basketballs_n1" 76 + 85 77 @Composable 86 78 internal fun App() = AppTheme { 87 79 var selectedTabIndex by remember { mutableStateOf(0) } ··· 89 81 90 82 // Apply safe-area insets once to the whole screen. 91 83 Column( 92 - modifier = Modifier 93 - .fillMaxSize() 94 - .windowInsetsPadding(WindowInsets.safeDrawing) 84 + modifier = Modifier.fillMaxSize().windowInsetsPadding(WindowInsets.safeDrawing) 95 85 ) { 96 86 // Tab row should be only as tall as needed. 97 87 TabRow(selectedTabIndex = selectedTabIndex, modifier = Modifier.fillMaxWidth()) { ··· 99 89 Tab( 100 90 selected = selectedTabIndex == index, 101 91 onClick = { selectedTabIndex = index }, 102 - text = { Text(title) } 103 - ) 92 + text = { Text(title) }) 104 93 } 105 94 } 106 95 107 96 // Content always gets the remaining space. 108 97 Box( 109 - modifier = Modifier 110 - .weight(1f) 111 - .fillMaxWidth() 98 + modifier = Modifier.weight(1f).fillMaxWidth() 112 99 ) { 113 100 when (selectedTabIndex) { 114 101 0 -> CameraSample() ··· 169 156 var image by remember { mutableStateOf<InputFrame?>(null) } 170 157 val timeRange = Pair(0L, duration) 171 158 var frame by remember { mutableStateOf(timeRange.first) } 172 - val generalModel = initialiseObjectModel( 173 - ModelPath( 174 - androidPath, 175 - iosPath 176 - ) 159 + val generalModel = ObjectModelProvider.get( 160 + ModelPath(androidPath, iosPath) 177 161 ) 178 162 val frameAnalyser by remember { mutableStateOf(FrameAnalyser(generalModel)) } 179 163 var bitmap by remember { mutableStateOf<ImageBitmap?>(null) } ··· 218 202 } 219 203 } 220 204 221 - val frameMap: MutableMap<String,List<Long>> = mutableMapOf() 205 + val frameMap: MutableMap<String, List<Long>> = mutableMapOf() 222 206 223 - LaunchedEffect(url){ 207 + LaunchedEffect(url) { 224 208 val timestamps = listVideoFrameTimestamps(url) 225 209 frameMap[url] = timestamps 226 210 } ··· 251 235 e.printStackTrace() 252 236 } finally { 253 237 val nextFrame = frameMap[url]?.let { frameTimeStamps -> 254 - frameTimeStamps.firstOrNull { it > frame} 255 - }?:( 256 - // Fallback: 257 - // If no timestamp is found (e.g., end of list or metadata missing), 258 - // assume a 50 FPS video and increment by 20ms. 259 - // This keeps playback and processing moving forward smoothly. 260 - frame + 20L 261 - ) 238 + frameTimeStamps.firstOrNull { it > frame } 239 + } ?: ( 240 + // Fallback: 241 + // If no timestamp is found (e.g., end of list or metadata missing), 242 + // assume a 50 FPS video and increment by 20ms. 243 + // This keeps playback and processing moving forward smoothly. 244 + frame + 20L) 262 245 nextFrame.also { 263 246 if (it < timeRange.second) { 264 247 frame = it ··· 327 310 val skeleton by skeletonRepository.skeletonFlow.collectAsState() 328 311 val customObjects by customObjectRespository.customObjectFlow.collectAsState() 329 312 var permissionGranted by remember { mutableStateOf(false) } 330 - var recordingId : String? by remember { mutableStateOf(null) } 313 + var recordingId: String? by remember { mutableStateOf(null) } 331 314 var path by remember { mutableStateOf("") } 332 - val generalModel = initialiseObjectModel( 333 - ModelPath( 334 - androidPath, 335 - iosPath 336 - ) 315 + val generalModel = ObjectModelProvider.get( 316 + ModelPath(androidPath, iosPath) 337 317 ) 338 318 var frontCamera by remember { mutableStateOf(false) } 339 319 val controller = remember { CameraViewControllerImpl() } ··· 384 364 shape = DrawableShape.LABEL, 385 365 colour = Color.Yellow, 386 366 style = Stroke(it.boundingBox.width * 0.1f) 387 - ), 388 - DrawableObject( 367 + ), DrawableObject( 389 368 obj = it, 390 369 shape = DrawableShape.RECTANGLE, 391 370 colour = Color.Green, ··· 407 386 Button( 408 387 onClick = { 409 388 recordingId = "${Clock.System.now().epochSeconds}" 410 - }, 411 - modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart) 389 + }, modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart) 412 390 ) { 413 391 Text("Start Recording") 414 392 }