dispaly ocr
parent
d23b4f472c
commit
5a3f4031d2
|
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue