dispaly ocr

3
Timo Bryant 2024-01-04 12:59:47 +01:00
parent d23b4f472c
commit 5a3f4031d2
5 changed files with 45 additions and 59 deletions

View File

@ -1,8 +1,6 @@
package de.itkl.documentViewer package de.itkl.documentViewer
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
@ -10,31 +8,22 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.loadImageBitmap import androidx.compose.ui.res.loadImageBitmap
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import androidx.compose.ui.zIndex
import com.github.panpf.zoomimage.ZoomImage import com.github.panpf.zoomimage.ZoomImage
import com.github.panpf.zoomimage.compose.ZoomState import com.github.panpf.zoomimage.compose.ZoomState
import com.github.panpf.zoomimage.compose.rememberZoomState import com.github.panpf.zoomimage.compose.rememberZoomState
@ -45,6 +34,7 @@ import de.itkl.httpClient.clients.MsOcr
import de.itkl.httpClient.httpClientModule import de.itkl.httpClient.httpClientModule
import de.itkl.textprocessing.CorpusFactory import de.itkl.textprocessing.CorpusFactory
import de.itkl.textprocessing.Document import de.itkl.textprocessing.Document
import de.itkl.textprocessing.OcrPage
import de.itkl.textprocessing.textProcessingModule import de.itkl.textprocessing.textProcessingModule
import de.itkl.tui.tuiModule import de.itkl.tui.tuiModule
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -67,11 +57,12 @@ private val Log = KotlinLogging.logger { }
class DocumentViewer : KoinComponent { class DocumentViewer : KoinComponent {
suspend fun loadTestDocument() { suspend fun loadTestDocument(): Document {
val corpus = CorpusFactory().load("assets/xs-reg") val corpus = CorpusFactory().load("assets/xs-reg")
val document = corpus.document("00001.jpg") val document = corpus.document("00001.jpg")
val ocrExtractor: MsOcr by inject() val ocrExtractor: MsOcr by inject()
document.process(ocrExtractor) document.process(ocrExtractor)
return document
} }
} }
@ -85,7 +76,7 @@ fun main() = auroraApplication {
httpClientModule) httpClientModule)
} }
runBlocking { val document = runBlocking {
DocumentViewer().loadTestDocument() DocumentViewer().loadTestDocument()
} }
@ -101,13 +92,14 @@ fun main() = auroraApplication {
windowTitlePaneConfiguration = AuroraWindowTitlePaneConfigurations.AuroraPlain(), windowTitlePaneConfiguration = AuroraWindowTitlePaneConfigurations.AuroraPlain(),
onCloseRequest = ::exitApplication onCloseRequest = ::exitApplication
) { ) {
viewImage() viewImage(document)
} }
} }
@Composable @Composable
fun viewImage() { fun viewImage(document: Document) {
val ocr = remember { runBlocking { document.retrieveOcrPages().first() } }
Column ( Column (
modifier = Modifier.fillMaxSize().auroraBackground() modifier = Modifier.fillMaxSize().auroraBackground()
) { ) {
@ -123,7 +115,7 @@ fun viewImage() {
contentDescription = "Sample", contentDescription = "Sample",
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) )
canvas(state.zoomable) canvas(state.zoomable, ocr)
// shapes(state.zoomable) // shapes(state.zoomable)
} }
} }
@ -214,7 +206,7 @@ fun shapes(zoomableState: ZoomableState) {
} }
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
fun canvas(zoomableState: ZoomableState) { fun canvas(zoomableState: ZoomableState, first: OcrPage) {
Canvas(modifier = Modifier Canvas(modifier = Modifier
.fillMaxSize() .fillMaxSize()
// .onPointerEvent(PointerEventType.Move) { // .onPointerEvent(PointerEventType.Move) {
@ -229,29 +221,15 @@ fun canvas(zoomableState: ZoomableState) {
canvasWidth = this.size.width, canvasWidth = this.size.width,
canvasHeight = this.size.height canvasHeight = this.size.height
) )
// val width = 2481
// val height = 3507
// val x =
// "width": 2481,
// "height": 3507,
// "boundingBox": [
// 288,
// 697,
// 793,
// 700,
// 793,
// 744,
// 288,
// 741
// ],
first.words.forEach { word ->
val rect = word.rectangle
drawRect( drawRect(
Color.Blue, Color.Blue,
topLeft = zoomableState.transform.offset + (Offset(288 * zoomableState.transform.scaleX,697 * zoomableState.transform.scaleY)), topLeft = zoomableState.transform.offset + (Offset(rect.x.toFloat() * zoomableState.transform.scaleX,rect.y.toFloat() * zoomableState.transform.scaleY)),
size = Size( (793 - 288)* zoomableState.transform.scaleX, (741 - 697) * zoomableState.transform.scaleY), size = Size(rect.width.toFloat() * zoomableState.transform.scaleX, rect.height.toFloat() * zoomableState.transform.scaleY),
style = Stroke(width = 5f) style = Stroke(width = 5f)
) )
}
} }
} }

View File

@ -61,6 +61,7 @@ class FilesystemAssets(private val baseDir: Path) : Assets, KoinComponent {
if (!destination.exists()) { if (!destination.exists()) {
return null return null
} }
Log.debug { "Loading file at $destination" }
val resource = resourceFactory.file(destination) val resource = resourceFactory.file(destination)
return resource return resource
} }

View File

@ -21,7 +21,8 @@ interface Resource {
fun read(): InputStream fun read(): InputStream
fun <T: Any> json(deserializer: DeserializationStrategy<T>): T { fun <T: Any> json(deserializer: DeserializationStrategy<T>): T {
return Json.decodeFromString(deserializer, read().readAllBytes().contentToString()) val string = String(read().readAllBytes())
return Json.decodeFromString(deserializer, string)
} }
} }

View File

@ -3,6 +3,7 @@ dependencies {
api("org.apache.lucene:lucene-analysis-common:9.9.0") api("org.apache.lucene:lucene-analysis-common:9.9.0")
api("io.github.piruin:geok:1.2.2") api("io.github.piruin:geok:1.2.2")
api(project(":libraries:assetmanager")) api(project(":libraries:assetmanager"))
api("com.soywiz.korge:korge-foundation:5.1.0")
implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.9.2") implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.9.2")
implementation("com.google.guava:guava:32.1.3-jre") implementation("com.google.guava:guava:32.1.3-jre")
} }

View File

@ -8,6 +8,7 @@ import de.itkl.core_api.interfaces.Resource
import de.itkl.core_api.interfaces.assets.Assets import de.itkl.core_api.interfaces.assets.Assets
import de.itkl.core_api.interfaces.assets.FileProcessorBackend import de.itkl.core_api.interfaces.assets.FileProcessorBackend
import de.itkl.core_api.interfaces.data.Processable import de.itkl.core_api.interfaces.data.Processable
import korlibs.math.geom.Rectangle
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@ -33,11 +34,10 @@ class Document(
suspend fun retrieveOcrPages(): List<OcrPage> { suspend fun retrieveOcrPages(): List<OcrPage> {
// TODO: How to identify the assets independently from their name? // TODO: How to identify the assets independently from their name?
val resource = checkNotNull(assets() val resource = checkNotNull(assets()
.retrieve("ms-ocr")) { .retrieve("ms-ocr.json")) {
"Ocr for $name is not yet created" "Ocr for $name is not yet created"
} }
val msOcrResponse = resource.json(MsOcrResponse.serializer()) val msOcrResponse = resource.json(MsOcrResponse.serializer())
return msOcrResponse.analyzeResult.readResults.map { toOcrPage(it) } return msOcrResponse.analyzeResult.readResults.map { toOcrPage(it) }
} }
override suspend fun process(fileProcessor: FileProcessor2) { override suspend fun process(fileProcessor: FileProcessor2) {
@ -59,12 +59,17 @@ class Document(
private fun toOcrWord(word: MsOcrResponse.AnalyzeResult.ReadResult.Line.Word): OcrPage.OcrWord { private fun toOcrWord(word: MsOcrResponse.AnalyzeResult.ReadResult.Line.Word): OcrPage.OcrWord {
val box = word.boundingBox val box = word.boundingBox
return OcrPage.OcrWord( return OcrPage.OcrWord(
polygon = Polygon(listOf( Rectangle(
LatLng(box[0].toDouble(), box[1].toDouble()), x = box[0],
LatLng(box[2].toDouble(), box[3].toDouble()), y = box[1],
LatLng(box[4].toDouble(), box[5].toDouble()), width = box[2] - box[0],
LatLng(box[6].toDouble(), box[7].toDouble()), height = box[7] - box[1]),
)), // polygon = Polygon(listOf(
// LatLng(box[0].toDouble(), box[1].toDouble()),
// LatLng(box[2].toDouble(), box[3].toDouble()),
// LatLng(box[4].toDouble(), box[5].toDouble()),
// LatLng(box[6].toDouble(), box[7].toDouble()),
// )),
text = word.text text = word.text
) )
} }
@ -75,24 +80,24 @@ class OcrPage(
val height: Int, val height: Int,
val pageNumber: Int, val pageNumber: Int,
val words: List<OcrWord>, val words: List<OcrWord>,
val regions: List<DocumentRegion> = emptyList() // val regions: List<DocumentRegion> = emptyList()
) { ) {
inner class DocumentRegion( // inner class DocumentRegion(
private val polygon: Polygon, // private val polygon: Polygon,
private val type: String, // private val type: String,
) { // ) {
fun words(): Flow<OcrWord> { // fun words(): Flow<OcrWord> {
return words // return words
.asFlow() // .asFlow()
.filter { word -> word.polygon.intersectionWith(polygon) != null } // .filter { word -> word.polygon.intersectionWith(polygon) != null }
} // }
} // }
fun addOcrWord(polygon: Polygon, text: String): OcrWord { fun addOcrWord(rectangle: Rectangle, text: String): OcrWord {
return OcrWord(polygon, text) return OcrWord(rectangle, text)
} }
class OcrWord( class OcrWord(
val polygon: Polygon, val rectangle: Rectangle,
val text: String val text: String
) )
} }