This repository has no description
0

Configure Feed

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

feat: add options to control objects being drawn

+166 -57
+1 -1
posedetection/build.gradle.kts
··· 6 6 7 7 mavenPublishing { 8 8 publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) 9 - coordinates("com.performancecoachlab.posedetection", "posedetection-compose", "3.1.0") 9 + coordinates("com.performancecoachlab.posedetection", "posedetection-compose", "3.2.0") 10 10 11 11 pom { 12 12 name.set("Pose Detection")
+4 -3
posedetection/src/androidMain/kotlin/com.performancecoachlab/posedetection/camera/CameraView.android.kt
··· 62 62 customObjectRepository: CustomObjectRespository, 63 63 drawSkeleton: Boolean, 64 64 objectModel: ObjectModel?, 65 - drawObjects: Boolean, 65 + drawObjects: ((List<AnalysisObject>) -> List<DrawableObject>)?, 66 66 modifier: Modifier, 67 67 frontCamera: Boolean, 68 68 isRecording: Boolean, ··· 162 162 imageProxy.toBitmap().rotate(imageProxy.imageInfo.rotationDegrees.toFloat()) 163 163 .asImageBitmap().let { inbmp -> 164 164 addFrame(inbmp,timestamp) 165 - AnalysisResult( 165 + /*AnalysisResult( 166 166 skeleton = if(drawSkeleton) it.skeleton else null, 167 167 objects = if(drawObjects) it.objects else emptyList(), 168 168 ).let{ 169 169 inbmp.drawAnalysisResults(it) 170 - } 170 + }*/ 171 + inbmp.drawResults(if(drawSkeleton) it.skeleton else null, drawObjects?.invoke(it.objects)?: emptyList()) 171 172 } 172 173 imageProxy.close() 173 174 }
+19 -2
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/camera/CameraView.kt
··· 4 4 import androidx.compose.runtime.Composable 5 5 import androidx.compose.ui.Modifier 6 6 import androidx.compose.ui.geometry.Rect 7 + import androidx.compose.ui.graphics.Color 8 + import androidx.compose.ui.graphics.drawscope.DrawStyle 9 + import androidx.compose.ui.graphics.drawscope.Stroke 7 10 import com.performancecoachlab.posedetection.custom.CustomObjectRespository 8 11 import com.performancecoachlab.posedetection.custom.ObjectModel 12 + import com.performancecoachlab.posedetection.recording.AnalysisObject 9 13 import com.performancecoachlab.posedetection.skeleton.SkeletonRepository 10 14 11 15 @Composable ··· 14 18 customObjectRepository: CustomObjectRespository, 15 19 drawSkeleton: Boolean = true, 16 20 objectModel: ObjectModel? = null, 17 - drawObjects: Boolean = true, 21 + drawObjects: ((List<AnalysisObject>) -> List<DrawableObject>)? = { objs -> 22 + objs.map { DrawableObject(it) } 23 + }, 18 24 modifier: Modifier = Modifier.fillMaxSize(), 19 25 frontCamera: Boolean = true, 20 26 isRecording: Boolean = false, 21 27 focusArea: Rect? = null, 22 28 onRecordToggled: (Boolean) -> Unit = {}, 23 29 onVideoSaved: (String) -> Unit = {}, 24 - ) 30 + ) 31 + 32 + data class DrawableObject( 33 + val obj: AnalysisObject, 34 + val shape: DrawableShape = DrawableShape.RECTANGLE, 35 + val colour: Color = Color.Red, 36 + val style: DrawStyle = Stroke(5f), 37 + ) 38 + 39 + enum class DrawableShape { 40 + OVAL,RECTANGLE 41 + }
+117 -29
posedetection/src/commonMain/kotlin/com/performancecoachlab/posedetection/camera/Utils.kt
··· 14 14 import androidx.compose.ui.graphics.rotate 15 15 import androidx.compose.ui.unit.Density 16 16 import androidx.compose.ui.unit.LayoutDirection 17 - import androidx.compose.ui.unit.min 18 17 import com.performancecoachlab.posedetection.recording.AnalysisResult 19 18 import com.performancecoachlab.posedetection.skeleton.Skeleton 20 19 21 20 fun ImageBitmap.drawSkeleton( 22 - inskeleton: Skeleton?, 23 - rotation: Float = 0f, 24 - mirrored: Boolean = false 21 + inskeleton: Skeleton?, rotation: Float = 0f, mirrored: Boolean = false 25 22 ): ImageBitmap { 26 23 val skeleton = inskeleton?.let { 27 24 if (mirrored) { ··· 76 73 line.first.x, line.first.y 77 74 ), end = androidx.compose.ui.geometry.Offset( 78 75 line.second.x, line.second.y 79 - ), strokeWidth = paintWhite.strokeWidth, 80 - blendMode = BlendMode.Softlight 76 + ), strokeWidth = paintWhite.strokeWidth, blendMode = BlendMode.Softlight 81 77 ) 82 78 drawLine( 83 79 color = paintBlue.color, start = androidx.compose.ui.geometry.Offset( 84 80 line.first.x, line.first.y 85 81 ), end = androidx.compose.ui.geometry.Offset( 86 82 line.second.x, line.second.y 87 - ), strokeWidth = paintBlue.strokeWidth, 88 - blendMode = BlendMode.Color 83 + ), strokeWidth = paintBlue.strokeWidth, blendMode = BlendMode.Color 84 + ) 85 + } 86 + 87 + joints().forEach { joint -> 88 + drawCircle( 89 + brush = Brush.radialGradient( 90 + colors = listOf(Color.Blue, Color.Transparent), 91 + center = androidx.compose.ui.geometry.Offset(joint.x, joint.y), 92 + radius = 1.2f * scaledStrokeWidth 93 + ), 94 + radius = 1.2f * scaledStrokeWidth, 95 + center = androidx.compose.ui.geometry.Offset(joint.x, joint.y) 96 + ) 97 + } 98 + } 99 + } 100 + return bitmap 101 + } 102 + } 103 + 104 + fun ImageBitmap.drawResults( 105 + skeleton: Skeleton?, drawableObjects: List<DrawableObject> 106 + ): ImageBitmap { 107 + also { 108 + val bitmap = ImageBitmap(it.width, it.height) 109 + val canvas = Canvas(bitmap) 110 + val drawScope = CanvasDrawScope() 111 + val size = Size(it.width.toFloat(), it.height.toFloat()) 112 + 113 + // Calculate scaling factor based on skeleton size 114 + val skeletonSize = skeleton?.joints()?.let { joints -> 115 + val minX = joints.minOfOrNull { joint -> joint.x } ?: 0f 116 + val maxX = joints.maxOfOrNull { joint -> joint.x } ?: 0f 117 + val minY = joints.minOfOrNull { joint -> joint.y } ?: 0f 118 + val maxY = joints.maxOfOrNull { joint -> joint.y } ?: 0f 119 + kotlin.math.max(maxX - minX, maxY - minY) 120 + } ?: 1f 121 + val minDime = kotlin.math.min(it.width, it.height).toFloat() 122 + val scaleFactor = skeletonSize / minDime 123 + val baseStrokeWidth = 30f 124 + val scaledStrokeWidth = (baseStrokeWidth * scaleFactor).coerceIn(2f, minDime / 50f) 125 + 126 + drawScope.draw( 127 + Density(1f), 128 + LayoutDirection.Ltr, 129 + canvas, 130 + size, 131 + ) { 132 + drawImage(it) 133 + for (drawableObject in drawableObjects) { 134 + when (drawableObject.shape) { 135 + DrawableShape.OVAL -> drawOval( 136 + color = drawableObject.colour, 137 + topLeft = androidx.compose.ui.geometry.Offset( 138 + drawableObject.obj.boundingBox.left, drawableObject.obj.boundingBox.top 139 + ), 140 + size = Size( 141 + drawableObject.obj.boundingBox.width, 142 + drawableObject.obj.boundingBox.height 143 + ), 144 + style = drawableObject.style 145 + ) 146 + 147 + DrawableShape.RECTANGLE -> drawRect( 148 + color = drawableObject.colour, 149 + topLeft = androidx.compose.ui.geometry.Offset( 150 + drawableObject.obj.boundingBox.left, drawableObject.obj.boundingBox.top 151 + ), 152 + size = Size( 153 + drawableObject.obj.boundingBox.width, 154 + drawableObject.obj.boundingBox.height 155 + ), 156 + style = drawableObject.style 157 + ) 158 + } 159 + } 160 + skeleton?.apply { 161 + val paintWhite = Paint().apply { 162 + color = Color.White 163 + strokeWidth = scaledStrokeWidth 164 + style = Stroke 165 + } 166 + val paintBlue = Paint().apply { 167 + color = Color.Blue 168 + strokeWidth = 0.8f * scaledStrokeWidth 169 + style = Fill 170 + } 171 + bones().forEach { line -> 172 + drawLine( 173 + color = paintWhite.color, start = androidx.compose.ui.geometry.Offset( 174 + line.first.x, line.first.y 175 + ), end = androidx.compose.ui.geometry.Offset( 176 + line.second.x, line.second.y 177 + ), strokeWidth = paintWhite.strokeWidth, blendMode = BlendMode.Softlight 178 + ) 179 + drawLine( 180 + color = paintBlue.color, start = androidx.compose.ui.geometry.Offset( 181 + line.first.x, line.first.y 182 + ), end = androidx.compose.ui.geometry.Offset( 183 + line.second.x, line.second.y 184 + ), strokeWidth = paintBlue.strokeWidth, blendMode = BlendMode.Color 89 185 ) 90 186 } 91 187 ··· 107 203 } 108 204 109 205 fun ImageBitmap.drawAnalysisResults( 110 - analysisResults: AnalysisResult, 111 - rotation: Float = 0f, 112 - mirrored: Boolean = false 206 + analysisResults: AnalysisResult, rotation: Float = 0f, mirrored: Boolean = false 113 207 ): ImageBitmap { 114 208 val skeleton = analysisResults.skeleton?.let { 115 209 if (mirrored) { ··· 121 215 also { 122 216 val bitmap = ImageBitmap(it.width, it.height) 123 217 val canvas = Canvas(bitmap).apply { 124 - if (rotation != 0f) rotate(rotation, it.width.toFloat() / 2f, it.height.toFloat() / 2f) 218 + if (rotation != 0f) rotate( 219 + rotation, it.width.toFloat() / 2f, it.height.toFloat() / 2f 220 + ) 125 221 } 126 222 val drawScope = CanvasDrawScope() 127 223 val size = Size(it.width.toFloat(), it.height.toFloat()) ··· 137 233 val minDime = kotlin.math.min(it.width, it.height).toFloat() 138 234 val scaleFactor = skeletonSize / minDime 139 235 val baseStrokeWidth = 30f 140 - val scaledStrokeWidth = (baseStrokeWidth * scaleFactor).coerceIn(2f, minDime/50f) 236 + val scaledStrokeWidth = (baseStrokeWidth * scaleFactor).coerceIn(2f, minDime / 50f) 141 237 142 238 drawScope.draw( 143 239 Density(1f), ··· 148 244 drawImage(it) 149 245 analysisResults.objects.forEach { analysisObject -> 150 246 drawRect( 151 - color = Color.Red, 152 - topLeft = androidx.compose.ui.geometry.Offset( 153 - analysisObject.boundingBox.left, 154 - analysisObject.boundingBox.top 155 - ), 156 - size = Size( 157 - analysisObject.boundingBox.width, 158 - analysisObject.boundingBox.height 159 - ), 160 - style = Stroke(scaledStrokeWidth) 247 + color = Color.Red, topLeft = androidx.compose.ui.geometry.Offset( 248 + analysisObject.boundingBox.left, analysisObject.boundingBox.top 249 + ), size = Size( 250 + analysisObject.boundingBox.width, analysisObject.boundingBox.height 251 + ), style = Stroke(scaledStrokeWidth) 161 252 ) 162 253 } 163 254 skeleton?.apply { ··· 178 269 line.first.x, line.first.y 179 270 ), end = androidx.compose.ui.geometry.Offset( 180 271 line.second.x, line.second.y 181 - ), strokeWidth = paintWhite.strokeWidth, 182 - blendMode = BlendMode.Softlight 272 + ), strokeWidth = paintWhite.strokeWidth, blendMode = BlendMode.Softlight 183 273 ) 184 274 drawLine( 185 275 color = paintBlue.color, start = androidx.compose.ui.geometry.Offset( 186 276 line.first.x, line.first.y 187 277 ), end = androidx.compose.ui.geometry.Offset( 188 278 line.second.x, line.second.y 189 - ), strokeWidth = paintBlue.strokeWidth, 190 - blendMode = BlendMode.Color 279 + ), strokeWidth = paintBlue.strokeWidth, blendMode = BlendMode.Color 191 280 ) 192 281 } 193 282 ··· 209 298 } 210 299 211 300 expect enum class PlatformType { 212 - ANDROID, 213 - IOS; 301 + ANDROID, IOS; 214 302 215 303 companion object { 216 304 fun getCurrentPlatform(): PlatformType
+15 -17
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraEngine.kt
··· 13 13 import com.performancecoachlab.posedetection.custom.CustomObjectRespository 14 14 import com.performancecoachlab.posedetection.custom.ObjectModel 15 15 import com.performancecoachlab.posedetection.recording.AnalysisObject 16 - import com.performancecoachlab.posedetection.recording.AnalysisResult 17 16 import com.performancecoachlab.posedetection.skeleton.Skeleton 18 17 import com.performancecoachlab.posedetection.skeleton.SkeletonRepository 19 18 import kotlinx.cinterop.ExperimentalForeignApi ··· 86 85 import platform.darwin.dispatch_get_global_queue 87 86 import platform.darwin.dispatch_get_main_queue 88 87 import platform.darwin.dispatch_queue_create 89 - import platform.posix.abs 90 88 import platform.posix.memcpy 91 89 import kotlin.math.abs 92 90 import kotlin.native.runtime.NativeRuntimeApi ··· 219 217 cameraController.onVideoSaved = callback 220 218 } 221 219 222 - fun setDrawOptions(drawSkeleton: Boolean, drawObjects: Boolean) { 220 + fun setDrawOptions( 221 + drawSkeleton: Boolean, drawObjects: ((List<AnalysisObject>) -> List<DrawableObject>)? 222 + ) { 223 223 cameraController.drawSkeleton = drawSkeleton 224 224 cameraController.drawObjects = drawObjects 225 225 } ··· 228 228 cameraController.setFocusArea(focusArea) 229 229 } 230 230 231 - fun setObjectModel(objectModel: ObjectModel?){ 231 + fun setObjectModel(objectModel: ObjectModel?) { 232 232 cameraController.setObjectModel(objectModel) 233 233 } 234 234 } ··· 250 250 var startTime: Long? = null 251 251 var onVideoSaved: ((String) -> Unit)? = null 252 252 var drawSkeleton: Boolean = true 253 - var drawObjects: Boolean = true 253 + var drawObjects: ((List<AnalysisObject>) -> List<DrawableObject>)? = null 254 254 255 255 sealed class CameraException : Exception() { 256 256 class DeviceNotAvailable : CameraException() ··· 297 297 frameProcessor.setFocusArea(focusArea) 298 298 } 299 299 300 - fun setObjectModel(objectModel: ObjectModel?){ 300 + fun setObjectModel(objectModel: ObjectModel?) { 301 301 frameProcessor.setObjectModel(objectModel) 302 302 } 303 303 ··· 524 524 }.also { bo -> 525 525 ImageBitmap( 526 526 bo.first, bo.second 527 - ).drawAnalysisResults( 528 - analysisResults = AnalysisResult( 529 - skeleton = if (drawSkeleton) previewSkeleton else null, 530 - objects = if (drawObjects) previewObjects else emptyList() 531 - ) 527 + ).drawResults( 528 + if (drawSkeleton) previewSkeleton else null, 529 + drawObjects?.invoke(previewObjects) ?: emptyList() 532 530 ).also { drawn -> 533 531 frameListener?.updateFrame( 534 532 drawn ··· 615 613 if (point == null) return null 616 614 617 615 // Normalize the point 618 - val normalizedPoint = 619 - CGPointMake(point.x.toDouble() / width, point.y.toDouble() / height) 616 + val normalizedPoint = CGPointMake(point.x.toDouble() / width, point.y.toDouble() / height) 620 617 val screenPoint = previewLayer.pointForCaptureDevicePointOfInterest(normalizedPoint) 621 618 return Skeleton.SkeletonCoordinate( 622 619 screenPoint.useContents { x.toFloat() }, 623 620 screenPoint.useContents { y.toFloat() }) 624 621 } 625 622 626 - val minbounds = previewLayer.pointForCaptureDevicePointOfInterest(CGPointMake(0.0,0.0)).useContents { Pair(x.toFloat(), y.toFloat()) } 627 - val maxbounds = previewLayer.pointForCaptureDevicePointOfInterest(CGPointMake(1.0,1.0)).useContents { Pair(x.toFloat(), y.toFloat()) } 623 + val minbounds = previewLayer.pointForCaptureDevicePointOfInterest(CGPointMake(0.0, 0.0)) 624 + .useContents { Pair(x.toFloat(), y.toFloat()) } 625 + val maxbounds = previewLayer.pointForCaptureDevicePointOfInterest(CGPointMake(1.0, 1.0)) 626 + .useContents { Pair(x.toFloat(), y.toFloat()) } 628 627 val bounds = Pair( 629 - abs( maxbounds.first - minbounds.first), 630 - abs(maxbounds.second - minbounds.second) 628 + abs(maxbounds.first - minbounds.first), abs(maxbounds.second - minbounds.second) 631 629 ) 632 630 633 631 return Skeleton(
+3 -2
posedetection/src/iosMain/kotlin/com/performancecoachlab/posedetection/camera/CameraView.ios.kt
··· 16 16 import androidx.compose.ui.layout.ContentScale 17 17 import com.performancecoachlab.posedetection.custom.CustomObjectRespository 18 18 import com.performancecoachlab.posedetection.custom.ObjectModel 19 + import com.performancecoachlab.posedetection.recording.AnalysisObject 19 20 import com.performancecoachlab.posedetection.skeleton.SkeletonRepository 20 21 import kotlinx.cinterop.BetaInteropApi 21 22 import kotlinx.cinterop.autoreleasepool ··· 29 30 customObjectRepository:CustomObjectRespository, 30 31 drawSkeleton: Boolean, 31 32 objectModel: ObjectModel?, 32 - drawObjects: Boolean, 33 + drawObjects: ((List<AnalysisObject>) -> List<DrawableObject>)?, 33 34 modifier: Modifier, 34 35 frontCamera: Boolean, 35 36 isRecording: Boolean, ··· 73 74 modifier = Modifier.fillMaxSize(), onCameraControllerReady = { engine -> 74 75 cameraEngine.value = engine.also { if (!frontCamera) it.toggleCameraLens() } 75 76 },) 76 - if (drawSkeleton || drawObjects) { 77 + if (drawSkeleton || drawObjects!= null) { 77 78 kotlin.native.runtime.GC.collect() 78 79 frameBitmap?.also { 79 80 Image(
+7 -3
sample/composeApp/src/commonMain/kotlin/com/nate/posedetection/App.kt
··· 24 24 import androidx.compose.ui.Alignment 25 25 import androidx.compose.ui.Modifier 26 26 import androidx.compose.ui.geometry.Rect 27 + import androidx.compose.ui.graphics.Color 27 28 import androidx.compose.ui.graphics.ImageBitmap 28 29 import androidx.compose.ui.layout.ContentScale 29 30 import androidx.compose.ui.unit.dp ··· 35 36 import chaintech.videoplayer.util.RetrieveMediaDuration 36 37 import com.nate.posedetection.theme.AppTheme 37 38 import com.performancecoachlab.posedetection.camera.CameraView 39 + import com.performancecoachlab.posedetection.camera.DrawableObject 40 + import com.performancecoachlab.posedetection.camera.DrawableShape 38 41 import com.performancecoachlab.posedetection.custom.CustomObjectRespository 39 42 import com.performancecoachlab.posedetection.custom.ModelPath 40 43 import com.performancecoachlab.posedetection.custom.initialiseObjectModel 41 44 import com.performancecoachlab.posedetection.encoding.VideoBuilder 42 45 import com.performancecoachlab.posedetection.encoding.createVideoBuilder 43 46 import com.performancecoachlab.posedetection.permissions.PermissionProvider 47 + import com.performancecoachlab.posedetection.recording.AnalysisObject 44 48 import com.performancecoachlab.posedetection.recording.FrameAnalyser 45 49 import com.performancecoachlab.posedetection.recording.InputFrame 46 50 import com.performancecoachlab.posedetection.recording.extractFrame ··· 61 65 62 66 @Composable 63 67 internal fun App() = AppTheme { 64 - var selectedTabIndex by remember { mutableStateOf(1) } 68 + var selectedTabIndex by remember { mutableStateOf(0) } 65 69 val tabs = listOf("Camera Feed", "Recorded Video") 66 70 Column { 67 71 TabRow(selectedTabIndex = selectedTabIndex) { ··· 325 329 customObjectRepository = customObjectRespository, 326 330 drawSkeleton = true, 327 331 objectModel = generalModel, 328 - drawObjects = true, 332 + drawObjects = null, 329 333 modifier = Modifier.weight(1f), 330 - frontCamera = false, 334 + frontCamera = true, 331 335 isRecording = isRecording, 332 336 onRecordToggled = { isRecording = it }, 333 337 onVideoSaved = { path = it },