Skip to content

Commit 0487421

Browse files
committed
test fix + version choosing implemented
1 parent 962133d commit 0487421

6 files changed

Lines changed: 137 additions & 29 deletions

File tree

ai/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/build
1+
/build
2+
/src/androidTest/assets/bus.jpg

ai/build.gradle.kts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,34 @@ android {
5858
sourceCompatibility = JavaVersion.VERSION_11
5959
targetCompatibility = JavaVersion.VERSION_11
6060
}
61+
val downloadTestImage by tasks.register<Exec>("downloadTestImage") {
62+
val outputDir = layout.projectDirectory.dir("src/androidTest/assets")
63+
val imageUrl = "https://github.com/ultralytics/yolov5/raw/master/data/images/bus.jpg"
64+
val outputFile = outputDir.file("bus.jpg")
65+
66+
commandLine(
67+
if (System.getProperty("os.name").lowercase().contains("windows")) {
68+
listOf(
69+
"powershell",
70+
"-Command",
71+
"Invoke-WebRequest",
72+
"-Uri",
73+
imageUrl,
74+
"-OutFile",
75+
outputFile.asFile.absolutePath,
76+
)
77+
} else {
78+
listOf("curl", "-L", "-o", outputFile.asFile.absolutePath, imageUrl)
79+
},
80+
)
81+
82+
outputs.file(outputFile)
83+
}
84+
85+
// Добавь зависимость, чтобы таск выполнялся перед тестами
86+
tasks.matching { it.name == "preBuild" }.configureEach {
87+
dependsOn(downloadTestImage)
88+
}
6189
}
6290

6391
val prepareYolo26Model =

ai/src/androidTest/assets/.gitkeep

Whitespace-only changes.

ai/src/androidTest/java/com/itlab/ai/OpenVinoAiLayerInstrumentedTest.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,56 @@ class OpenVinoAiLayerInstrumentedTest {
8787
assertTrue(initialized)
8888
assertTrue(engine.isReady())
8989
}
90+
91+
private fun copyTestImage(context: android.content.Context): String {
92+
val testImageFile = File(context.filesDir, "bus.jpg")
93+
if (!testImageFile.exists()) {
94+
context.assets.open("bus.jpg").use { input ->
95+
testImageFile.outputStream().use { output ->
96+
input.copyTo(output)
97+
}
98+
}
99+
}
100+
return testImageFile.absolutePath
101+
}
102+
103+
@Test
104+
fun yolo26n_detectsBus_onTestImage() =
105+
runBlocking {
106+
val context = InstrumentationRegistry.getInstrumentation().targetContext
107+
val modelPath = File(context.filesDir, "models/yolo26n_openvino_model/yolo26n.xml").absolutePath
108+
val engine = createEngine(modelPath)
109+
110+
withContext(Dispatchers.Default) {
111+
engine.initialize()
112+
}
113+
assertTrue("Engine should be ready", engine.isReady())
114+
115+
val imagePath = copyTestImage(context)
116+
val result = engine.runYoloTagging(imagePath)
117+
118+
assertTrue("Should detect objects", result.isNotEmpty())
119+
assertTrue("Should detect bus", result.contains("bus"))
120+
}
121+
122+
@Test
123+
fun yoloV10n_detectsBus_onTestImage() =
124+
runBlocking {
125+
val context = InstrumentationRegistry.getInstrumentation().targetContext
126+
val modelPath = File(context.filesDir, "models/yolov10n_openvino_model/yolov10n.xml").absolutePath
127+
val engine = createEngine(modelPath)
128+
129+
val initialized =
130+
withContext(Dispatchers.Default) {
131+
engine.initialize()
132+
}
133+
assertTrue("Engine should initialize with yoloV10", initialized)
134+
assertTrue("Engine should be ready", engine.isReady())
135+
136+
val imagePath = copyTestImage(context)
137+
val result = engine.runYoloTagging(imagePath)
138+
139+
assertTrue("Should detect objects", result.isNotEmpty())
140+
assertTrue("Should detect bus", result.contains("bus"))
141+
}
90142
}

ai/src/main/java/com/itlab/ai/OpenVinoEngine.kt

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,31 @@ class OpenVinoEngine(
3535
private const val CONF_THRESHOLD = 0.35f
3636
private const val IOU_THRESHOLD = 0.45f
3737
private const val MAX_DETECTIONS = 300
38+
39+
fun getOptimalModelPath(context: Context): String {
40+
// val forcedModel = System.getProperty("YOLO_MODEL_NUMBER")
41+
val forcedModel = "10"
42+
return when (forcedModel) {
43+
"26" -> "models/yolo26n_openvino_model/yolo26n.xml"
44+
"10" -> "models/yolov10n_openvino_model/yolov10n.xml"
45+
else -> {
46+
val coreCount = Runtime.getRuntime().availableProcessors()
47+
val totalRam = getTotalRamMB(context)
48+
if (coreCount >= 4 && totalRam >= 2048) {
49+
"models/yolo26n_openvino_model/yolo26n.xml"
50+
} else {
51+
"models/yolov10n_openvino_model/yolov10n.xml"
52+
}
53+
}
54+
}
55+
}
56+
57+
private fun getTotalRamMB(context1: Context): Long {
58+
val activityManager = context1.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
59+
val memInfo = ActivityManager.MemoryInfo()
60+
activityManager.getMemoryInfo(memInfo)
61+
return memInfo.totalMem / (1024 * 1024)
62+
}
3863
}
3964

4065
private var core: Core? = null
@@ -123,25 +148,6 @@ class OpenVinoEngine(
123148
}
124149
}
125150

126-
@Suppress("UnusedPrivateMember")
127-
private fun getOptimalModelPath(): String {
128-
val coreCount = Runtime.getRuntime().availableProcessors()
129-
val totalRam = getTotalRamMB()
130-
131-
return if (coreCount >= 4 && totalRam >= 2048) {
132-
"models/yolo26n_openvino_model/yolo26n.xml"
133-
} else {
134-
"models/yolov10n_openvino_model/yolov10n.xml"
135-
}
136-
}
137-
138-
private fun getTotalRamMB(): Long {
139-
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
140-
val memInfo = ActivityManager.MemoryInfo()
141-
activityManager.getMemoryInfo(memInfo)
142-
return memInfo.totalMem / (1024 * 1024)
143-
}
144-
145151
private fun loadClassNames(): List<String> =
146152
try {
147153
val names = mutableListOf<String>()
@@ -349,17 +355,34 @@ class OpenVinoEngine(
349355
}
350356

351357
private fun resolveModelXmlPath(): String? {
358+
// Если путь задан явно и существует — сразу возвращаем
359+
if (modelXmlPath.isNotBlank()) {
360+
return if (File(modelXmlPath).exists()) {
361+
modelXmlPath
362+
} else {
363+
// Пробуем скопировать из assets
364+
val relativePath = modelXmlPath.substringAfter("files/")
365+
val targetFile = File(context.filesDir, relativePath)
366+
val assetDir = relativePath.substringBeforeLast("/")
367+
368+
if (copyAssetDirectory(assetDir, targetFile.parentFile ?: context.filesDir)) {
369+
targetFile.absolutePath
370+
} else {
371+
Log.e(TAG, "Custom model not found and cannot be copied: $modelXmlPath")
372+
null
373+
}
374+
}
375+
}
376+
377+
// Дефолт — yolo26n
352378
val modelDir = File(context.filesDir, DEFAULT_MODEL_ASSET_DIR)
353379
val modelXml = File(modelDir, DEFAULT_MODEL_XML)
354380

355-
return when {
356-
modelXmlPath.isNotBlank() -> modelXmlPath
357-
modelXml.exists() -> modelXml.absolutePath
358-
copyAssetDirectory(DEFAULT_MODEL_ASSET_DIR, modelDir) -> modelXml.absolutePath
359-
else -> {
360-
Log.e(TAG, "Bundled model assets are missing: $DEFAULT_MODEL_ASSET_DIR")
361-
null
362-
}
381+
return if (modelXml.exists() || copyAssetDirectory(DEFAULT_MODEL_ASSET_DIR, modelDir)) {
382+
modelXml.absolutePath
383+
} else {
384+
Log.e(TAG, "Bundled model assets are missing: $DEFAULT_MODEL_ASSET_DIR")
385+
null
363386
}
364387
}
365388

ai/src/main/java/com/itlab/ai/di/AiModule.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import com.itlab.domain.ai.NoteAiService
77
import kotlinx.coroutines.runBlocking
88
import org.koin.android.ext.koin.androidContext
99
import org.koin.dsl.module
10+
import java.io.File
1011

1112
val aiModule =
1213
module {
1314
single {
14-
OpenVinoEngine(androidContext()).also { engine ->
15+
val context = androidContext()
16+
val optimalRelativePath = OpenVinoEngine.getOptimalModelPath(context)
17+
val optimalAbsolutePath = File(context.filesDir, optimalRelativePath).absolutePath
18+
OpenVinoEngine(context, optimalAbsolutePath).also { engine ->
1519
runBlocking { engine.initialize() }
1620
}
1721
}

0 commit comments

Comments
 (0)