11package org.commons.ml.demo
22
3+ import android.app.AlertDialog
34import org.commons.ml.common.DetectionOptions
45import org.commons.ml.common.DetectionResult
56import org.commons.ml.common.DetectionType
@@ -13,7 +14,10 @@ import android.graphics.RectF
1314import android.graphics.drawable.ColorDrawable
1415import android.net.Uri
1516import android.os.Bundle
17+ import android.view.LayoutInflater
1618import android.widget.ImageView
19+ import android.widget.ProgressBar
20+ import android.widget.TextView
1721import androidx.activity.ComponentActivity
1822import androidx.activity.compose.setContent
1923import androidx.activity.result.contract.ActivityResultContracts
@@ -40,9 +44,11 @@ import androidx.compose.ui.unit.dp
4044import androidx.compose.ui.viewinterop.AndroidView
4145import androidx.lifecycle.lifecycleScope
4246import kotlinx.coroutines.Dispatchers
47+ import kotlinx.coroutines.delay
4348import kotlinx.coroutines.launch
4449import kotlinx.coroutines.withContext
4550import java.util.Locale
51+ import kotlin.time.Duration.Companion.milliseconds
4652
4753/* * Standalone demo for local face and license-plate detection. */
4854class MainActivity : ComponentActivity () {
@@ -54,41 +60,11 @@ class MainActivity : ComponentActivity() {
5460 private var thresholdState by mutableFloatStateOf(0.5f )
5561 private var statusMessage by mutableStateOf(" " )
5662
57- private val createRedactedImage =
58- registerForActivityResult(ActivityResultContracts .CreateDocument (" image/jpeg" )) { uri ->
59- val source = sourceUri
60- val regions = overlay.getDetections()
61- if (uri == null || source == null || regions.isEmpty()) return @registerForActivityResult
62- lifecycleScope.launch {
63- val result = withContext(Dispatchers .IO ) {
64- runCatching {
65- Ajpegtran .pixelize(
66- this @MainActivity,
67- source,
68- uri,
69- regions.map {
70- val bounds = RectF (it.bounds).apply {
71- inset(- width() * 0.12f , - height() * 0.12f )
72- }
73- Ajpegtran .PixelizeRegion (
74- bounds.left.toInt().coerceAtLeast(0 ),
75- bounds.top.toInt().coerceAtLeast(0 ),
76- bounds.width().toInt().coerceAtLeast(1 ),
77- bounds.height().toInt().coerceAtLeast(1 )
78- )
79- }
80- ).getOrThrow()
81- }
82- }
83- result.onSuccess { setStatus(" Saved ajpegtran-redacted JPEG." ) }
84- .onFailure { setStatus(" ajpegtran failed: ${diagnosticMessage(it)} " ) }
85- }
63+ private val openImage =
64+ registerForActivityResult(ActivityResultContracts .OpenDocument ()) { uri ->
65+ uri?.let { loadImage(it) }
8666 }
8767
88- private val openImage = registerForActivityResult(ActivityResultContracts .OpenDocument ()) { uri ->
89- uri?.let { loadImage(it) }
90- }
91-
9268 override fun onCreate (savedInstanceState : Bundle ? ) {
9369 super .onCreate(savedInstanceState)
9470 buildUi()
@@ -159,14 +135,44 @@ class MainActivity : ComponentActivity() {
159135 }
160136 setStatus(" Running ONNX Runtime locally…" )
161137 lifecycleScope.launch {
138+ val dialogView = LayoutInflater .from(this @MainActivity)
139+ .inflate(R .layout.dialog, null )
140+ val titleView = dialogView.findViewById<TextView >(R .id.autodetect_title)
141+ val descView = dialogView.findViewById<TextView >(R .id.autodetect_description)
142+ val percentView = dialogView.findViewById<TextView >(R .id.autodetect_percent)
143+ val progressBar = dialogView.findViewById<ProgressBar >(R .id.autodetect_progress)
144+
145+ titleView?.text = " Auto-blurring faces and license plates."
146+ descView?.text = " You can discard false positives using the ❌ button of each blurring area."
147+ percentView?.text = " 0%"
148+ progressBar?.progress = 0
149+
150+ val progressDialog = AlertDialog .Builder (this @MainActivity)
151+ .setView(dialogView)
152+ .setCancelable(false )
153+ .create()
154+ progressDialog.show()
155+
162156 runCatching {
163157 withContext(Dispatchers .Default ) {
164158 val started = System .nanoTime()
165159 val options = DetectionOptions (confidenceThreshold = thresholdState)
166- val result = getDetector().detect(source, options)
160+ val result = getDetector().detect(source, options) { progress ->
161+ launch(Dispatchers .Main ) {
162+ val percent = (progress * 100 ).toInt()
163+ progressBar?.progress = percent
164+ percentView?.text = " $percent %"
165+ }
166+ }
167167 Pair (result, (System .nanoTime() - started) / 1_000_000 )
168168 }
169169 }.onSuccess { result ->
170+ // Ensure 100% is displayed and give a brief delay so the user can comfortably read it
171+ progressBar?.progress = 100
172+ percentView?.text = " 100%"
173+ delay(700 .milliseconds)
174+ progressDialog.dismiss()
175+
170176 val detections = when (val value = result.first) {
171177 is DetectionResult .Success -> value.detections
172178 is DetectionResult .Partial -> value.detections
@@ -177,15 +183,18 @@ class MainActivity : ComponentActivity() {
177183 }
178184 }
179185 overlay.setDetections(detections)
180- setStatus(String .format(
181- Locale .US ,
182- " Detected %d regions (%d faces, %d plates) in %d ms. Tap/drag boxes; delete false positives." ,
183- detections.size,
184- detections.count { it.type == DetectionType .FACE },
185- detections.count { it.type == DetectionType .LICENSE_PLATE },
186- result.second
187- ))
186+ setStatus(
187+ String .format(
188+ Locale .US ,
189+ " Detected %d regions (%d faces, %d plates) in %d ms. Tap/drag boxes; delete false positives." ,
190+ detections.size,
191+ detections.count { it.type == DetectionType .FACE },
192+ detections.count { it.type == DetectionType .LICENSE_PLATE },
193+ result.second
194+ )
195+ )
188196 }.onFailure { error ->
197+ progressDialog.dismiss()
189198 setStatus(" Detection failed: ${diagnosticMessage(error)} " )
190199 }
191200 }
0 commit comments