Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.appdevforall.codeonthego.computervision.data.repository

import android.graphics.Bitmap
import com.google.mlkit.vision.text.Text
import org.appdevforall.codeonthego.computervision.domain.model.DetectionResult

/**
* Abstract contract for computer vision data sources.
* Handles raw interactions with machine learning models (YOLO, MLKit)
* without leaking domain logic.
*/
interface VisionRepository {
suspend fun initModel(): Result<Unit>
suspend fun detectWidgets(bitmap: Bitmap): Result<List<DetectionResult>>
suspend fun recognizeText(bitmap: Bitmap): Result<List<Text.TextBlock>>
fun isInitialized(): Boolean
fun release()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.appdevforall.codeonthego.computervision.data.repository

import android.content.res.AssetManager
import android.graphics.Bitmap
import com.google.mlkit.vision.text.Text
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.appdevforall.codeonthego.computervision.data.source.OcrSource
import org.appdevforall.codeonthego.computervision.data.source.YoloModelSource
import org.appdevforall.codeonthego.computervision.domain.model.DetectionResult

class VisionRepositoryImpl(
private val assetManager: AssetManager,
private val yoloModelSource: YoloModelSource,
private val ocrSource: OcrSource
) : VisionRepository {

override suspend fun initModel(): Result<Unit> = withContext(Dispatchers.IO) {
runCatching {
yoloModelSource.initialize(assetManager)
}
}

override suspend fun detectWidgets(bitmap: Bitmap): Result<List<DetectionResult>> =
withContext(Dispatchers.Default) {
runCatching { yoloModelSource.runInference(bitmap) }
}

override suspend fun recognizeText(bitmap: Bitmap): Result<List<Text.TextBlock>> =
withContext(Dispatchers.Default) {
ocrSource.recognizeText(bitmap)
}

override fun isInitialized(): Boolean = yoloModelSource.isInitialized()

override fun release() {
yoloModelSource.release()
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package org.appdevforall.codeonthego.computervision.di

import org.appdevforall.codeonthego.computervision.data.repository.ComputerVisionRepository
import org.appdevforall.codeonthego.computervision.data.repository.ComputerVisionRepositoryImpl
import org.appdevforall.codeonthego.computervision.data.repository.DrawableImportHelper
import org.appdevforall.codeonthego.computervision.data.repository.VisionRepository
import org.appdevforall.codeonthego.computervision.data.repository.VisionRepositoryImpl
import org.appdevforall.codeonthego.computervision.data.source.OcrSource
import org.appdevforall.codeonthego.computervision.data.source.YoloModelSource
import org.appdevforall.codeonthego.computervision.domain.GenericBoxResolver
import org.appdevforall.codeonthego.computervision.domain.RegionOcrProcessor
import org.appdevforall.codeonthego.computervision.domain.usecase.GenerateXmlUC
import org.appdevforall.codeonthego.computervision.domain.usecase.ImportPlaceholderImageUC
import org.appdevforall.codeonthego.computervision.domain.usecase.PrepareImageUC
import org.appdevforall.codeonthego.computervision.domain.usecase.RemovePlaceholderImageUC
import org.appdevforall.codeonthego.computervision.domain.usecase.RunVisionUC
import org.appdevforall.codeonthego.computervision.ui.viewmodel.ComputerVisionViewModel
import org.koin.android.ext.koin.androidContext
import org.koin.core.module.dsl.viewModel
Expand All @@ -14,30 +20,33 @@ import org.koin.dsl.module
val computerVisionModule = module {

single { YoloModelSource() }

single { OcrSource() }

single { RegionOcrProcessor(ocrSource = get()) }
single { GenericBoxResolver() }

single<ComputerVisionRepository> {
ComputerVisionRepositoryImpl(
single<VisionRepository> {
VisionRepositoryImpl(
assetManager = androidContext().assets,
yoloModelSource = get(),
regionOcrProcessor = get()
ocrSource = get()
)
}

single {
DrawableImportHelper(
contentResolver = androidContext().contentResolver
)
}
single { DrawableImportHelper(contentResolver = androidContext().contentResolver) }
single { GenerateXmlUC() }
single { ImportPlaceholderImageUC(drawableImportHelper = get()) }
single { PrepareImageUC(contentResolver = androidContext().contentResolver) }
single { RemovePlaceholderImageUC(drawableImportHelper = get()) }
single { RunVisionUC(repository = get(), boxResolver = get(), regionOcrProcessor = get()) }

viewModel { (layoutFilePath: String?, layoutFileName: String?) ->
ComputerVisionViewModel(
repository = get(),
drawableImportHelper = get(),
contentResolver = androidContext().contentResolver,
prepareImageUC = get(),
runVisionUC = get(),
generateXmlUC = get(),
importPlaceholderImageUC = get(),
removePlaceholderImageUC = get(),
layoutFilePath = layoutFilePath,
layoutFileName = layoutFileName
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.appdevforall.codeonthego.computervision.domain

import android.graphics.Rect
import org.appdevforall.codeonthego.computervision.domain.model.DetectionResult
import org.appdevforall.codeonthego.computervision.domain.model.ScaledBox
import kotlin.math.max
import kotlin.math.roundToInt

/**
* Scales the normalized YOLO coordinates (0.0 to 1.0) to the target dimensions
* in DP (e.g., 360x640) of the Android screen.
*/
object DetectionScaler {
private const val MIN_W_ANY = 8
private const val MIN_H_ANY = 8

fun scale(
detection: DetectionResult, sourceWidth: Int, sourceHeight: Int, targetW: Int, targetH: Int
): ScaledBox {
if (sourceWidth == 0 || sourceHeight == 0) {
return ScaledBox(detection.label, detection.text, 0, 0, MIN_W_ANY, MIN_H_ANY, MIN_W_ANY / 2, MIN_H_ANY / 2, Rect(0, 0, MIN_W_ANY, MIN_H_ANY))
}
val rect = detection.boundingBox
val normCx = ((rect.left + rect.right) / 2f) / sourceWidth.toFloat()
val normCy = ((rect.top + rect.bottom) / 2f) / sourceHeight.toFloat()
val normW = (rect.right - rect.left) / sourceWidth.toFloat()
val normH = (rect.bottom - rect.top) / sourceHeight.toFloat()

val x = max(0, ((normCx - normW / 2f) * targetW).roundToInt())
val y = max(0, ((normCy - normH / 2f) * targetH).roundToInt())
val w = max(MIN_W_ANY, (normW * targetW).roundToInt())
val h = max(MIN_H_ANY, (normH * targetH).roundToInt())

return ScaledBox(
detection.label, detection.text, x, y, w, h, x + w / 2, y + h / 2, Rect(x, y, x + w, y + h)
)
}
}
Loading
Loading