alpha
Login
or
Join now
nateholland.bsky.social
/
PoseDetection
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
fix: do not recreate model constantly
author
nate
date
4 months ago
(Feb 5, 2026, 1:16 PM +0200)
commit
9c154d04
9c154d04fbe17afc7baf773f65a21710282d5291
parent
28c66ab9
28c66ab92d49833634829a2e2a651da206d7b923
+50
-48
3 changed files
Expand all
Collapse all
Unified
Split
posedetection
src
androidMain
kotlin
com
performancecoachlab
posedetection
camera
Utils.android.kt
commonMain
kotlin
com
performancecoachlab
posedetection
custom
CustomObjectModel.kt
sample
composeApp
src
commonMain
kotlin
com
nate
posedetection
App.kt
+1
-3
posedetection/src/androidMain/kotlin/com/performancecoachlab/posedetection/camera/Utils.android.kt
Reviewed
···
332
332
runCatching {
333
333
val pose = Tasks.await(poseDetector.process(mlKitImage))
334
334
val landmarks = pose.allPoseLandmarks.size
335
335
-
Logger.d { "MLKit pose landmarks=$landmarks" }
336
335
skeleton(pose, timestamp, width, height)
337
337
-
}.onFailure { t ->
338
338
-
Logger.e(t) { "MLKit poseDetector.process failed" }
336
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
Reviewed
···
1
1
package com.performancecoachlab.posedetection.custom
2
2
3
3
import androidx.compose.runtime.Composable
4
4
+
import kotlinx.coroutines.InternalCoroutinesApi
5
5
+
import kotlinx.coroutines.internal.SynchronizedObject
6
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
21
+
object ObjectModelProvider {
22
22
+
@Volatile private var cached: ObjectModel? = null
23
23
+
24
24
+
// Kotlin/Native requires a SynchronizedObject as the lock
25
25
+
@OptIn(InternalCoroutinesApi::class)
26
26
+
private val lock = SynchronizedObject()
27
27
+
28
28
+
@OptIn(InternalCoroutinesApi::class)
29
29
+
@Composable
30
30
+
fun get(modelPath: ModelPath): ObjectModel {
31
31
+
val fast = cached
32
32
+
if (fast != null) return fast
33
33
+
34
34
+
return synchronized(lock) {
35
35
+
cached ?: initialiseObjectModel(modelPath).also { cached = it }
36
36
+
}
37
37
+
}
38
38
+
39
39
+
@OptIn(InternalCoroutinesApi::class)
40
40
+
fun clear() {
41
41
+
synchronized(lock) { cached = null }
42
42
+
}
43
43
+
}
+23
-45
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
Reviewed
···
4
4
import androidx.compose.foundation.Image
5
5
import androidx.compose.foundation.layout.Box
6
6
import androidx.compose.foundation.layout.Column
7
7
-
import androidx.compose.foundation.layout.Row
8
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
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
17
-
import androidx.compose.foundation.layout.wrapContentHeight
18
14
import androidx.compose.material3.Button
19
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
35
-
import androidx.compose.ui.geometry.Rect
36
30
import androidx.compose.ui.graphics.Color
37
31
import androidx.compose.ui.graphics.ImageBitmap
32
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
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
55
-
import com.performancecoachlab.posedetection.custom.initialiseObjectModel
49
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
59
-
import com.performancecoachlab.posedetection.recording.AnalysisObject
60
53
import com.performancecoachlab.posedetection.recording.FrameAnalyser
61
61
-
import com.performancecoachlab.posedetection.recording.FrameSize
62
54
import com.performancecoachlab.posedetection.recording.InputFrame
63
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
83
-
val androidPath = "yolov10n_float16.tflite"
74
74
+
val androidPath = "basketballs_n1.tflite"
84
75
val iosPath = "basketballs_n1"
76
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
92
-
modifier = Modifier
93
93
-
.fillMaxSize()
94
94
-
.windowInsetsPadding(WindowInsets.safeDrawing)
84
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
102
-
text = { Text(title) }
103
103
-
)
92
92
+
text = { Text(title) })
104
93
}
105
94
}
106
95
107
96
// Content always gets the remaining space.
108
97
Box(
109
109
-
modifier = Modifier
110
110
-
.weight(1f)
111
111
-
.fillMaxWidth()
98
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
172
-
val generalModel = initialiseObjectModel(
173
173
-
ModelPath(
174
174
-
androidPath,
175
175
-
iosPath
176
176
-
)
159
159
+
val generalModel = ObjectModelProvider.get(
160
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
221
-
val frameMap: MutableMap<String,List<Long>> = mutableMapOf()
205
205
+
val frameMap: MutableMap<String, List<Long>> = mutableMapOf()
222
206
223
223
-
LaunchedEffect(url){
207
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
254
-
frameTimeStamps.firstOrNull { it > frame}
255
255
-
}?:(
256
256
-
// Fallback:
257
257
-
// If no timestamp is found (e.g., end of list or metadata missing),
258
258
-
// assume a 50 FPS video and increment by 20ms.
259
259
-
// This keeps playback and processing moving forward smoothly.
260
260
-
frame + 20L
261
261
-
)
238
238
+
frameTimeStamps.firstOrNull { it > frame }
239
239
+
} ?: (
240
240
+
// Fallback:
241
241
+
// If no timestamp is found (e.g., end of list or metadata missing),
242
242
+
// assume a 50 FPS video and increment by 20ms.
243
243
+
// This keeps playback and processing moving forward smoothly.
244
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
330
-
var recordingId : String? by remember { mutableStateOf(null) }
313
313
+
var recordingId: String? by remember { mutableStateOf(null) }
331
314
var path by remember { mutableStateOf("") }
332
332
-
val generalModel = initialiseObjectModel(
333
333
-
ModelPath(
334
334
-
androidPath,
335
335
-
iosPath
336
336
-
)
315
315
+
val generalModel = ObjectModelProvider.get(
316
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
387
-
),
388
388
-
DrawableObject(
367
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
410
-
},
411
411
-
modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart)
389
389
+
}, modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart)
412
390
) {
413
391
Text("Start Recording")
414
392
}