Skip to content

Commit

Permalink
Add a text recognition screen
Browse files Browse the repository at this point in the history
  • Loading branch information
plweegie committed Jun 28, 2020
1 parent 0f99810 commit 8ad023f
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 6 deletions.
19 changes: 15 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@ dependencies {

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.6'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.7'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.7'

implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
Expand All @@ -79,6 +81,15 @@ dependencies {
implementation 'org.openscience.cdk:cdk-io:2.3'
implementation 'org.openscience.cdk:cdk-builder3d:2.3'

def camerax_version = "1.0.0-beta06"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:1.0.0-alpha13"

implementation 'com.google.android.gms:play-services-mlkit-text-recognition:16.0.0'
implementation 'com.google.guava:guava:27.0.1-android'

testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ar.MagMolActivity" />

<activity
android:screenOrientation="portrait"
android:name=".ar.MagMolActivity" />

<activity
android:screenOrientation="portrait"
android:name=".ocr.TextRecognitionActivity" />
</application>

</manifest>
6 changes: 6 additions & 0 deletions app/src/main/java/com/plweegie/magmolecular/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.plweegie.magmolecular

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import com.plweegie.magmolecular.ar.MagMolActivity
import com.plweegie.magmolecular.ocr.TextRecognitionActivity
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.activity_main.*

Expand All @@ -21,6 +23,10 @@ class MainActivity : AppCompatActivity() {
viewModel.getSmilesForName(smiles_field?.text.toString())
}

get_atoms_from_camera_btn?.setOnClickListener {
Intent(this, TextRecognitionActivity::class.java).also { startActivity(it) }
}

viewModel.smiles.observe(this, Observer { smiles ->
if (smiles.isNotEmpty()) {
val intent = MagMolActivity.newIntent(this, smiles)
Expand Down
115 changes: 115 additions & 0 deletions app/src/main/java/com/plweegie/magmolecular/ocr/TextAnalyzer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.plweegie.magmolecular.ocr

import android.content.Context
import android.graphics.Rect
import android.util.Log
import android.widget.Toast
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.coroutineScope
import com.google.mlkit.common.MlKitException
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.text.Text
import com.google.mlkit.vision.text.TextRecognition
import com.plweegie.magmolecular.utils.ImageUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext


class TextAnalyzer(
private val context: Context,
private val result: MutableLiveData<String>,
private val imageCropPercentages: MutableLiveData<Pair<Int, Int>>,
lifecycle: Lifecycle
) : ImageAnalysis.Analyzer {

private companion object {
const val TAG = "TextAnalyzer"
}

private val detector = TextRecognition.getClient()
private val textAnalyzerScope = lifecycle.coroutineScope + Dispatchers.Default

init {
lifecycle.addObserver(detector)
}

@androidx.camera.core.ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image ?: return

val rotationDegrees = imageProxy.imageInfo.rotationDegrees

// We requested a setTargetAspectRatio, but it's not guaranteed that's what the camera
// stack is able to support, so we calculate the actual ratio from the first frame to
// know how to appropriately crop the image we want to analyze.
val imageHeight = mediaImage.height
val imageWidth = mediaImage.width

val actualAspectRatio = imageWidth / imageHeight

val convertImageToBitmap = ImageUtils.convertYuv420888ImageToBitmap(mediaImage)
val cropRect = Rect(0, 0, imageWidth, imageHeight)

// If the image has a way wider aspect ratio than expected, crop less of the height so we
// don't end up cropping too much of the image. If the image has a way taller aspect ratio
// than expected, we don't have to make any changes to our cropping so we don't handle it
// here.
val currentCropPercentages = imageCropPercentages.value ?: return
if (actualAspectRatio > 3) {
val originalHeightCropPercentage = currentCropPercentages.first
val originalWidthCropPercentage = currentCropPercentages.second
imageCropPercentages.value =
Pair(originalHeightCropPercentage / 2, originalWidthCropPercentage)
}

// If the image is rotated by 90 (or 270) degrees, swap height and width when calculating
// the crop.
val cropPercentages = imageCropPercentages.value ?: return
val heightCropPercent = cropPercentages.first
val widthCropPercent = cropPercentages.second
val (widthCrop, heightCrop) = when (rotationDegrees) {
90, 270 -> Pair(heightCropPercent / 100f, widthCropPercent / 100f)
else -> Pair(widthCropPercent / 100f, heightCropPercent / 100f)
}

cropRect.inset(
(imageWidth * widthCrop / 2).toInt(),
(imageHeight * heightCrop / 2).toInt()
)
val croppedBitmap =
ImageUtils.rotateAndCrop(convertImageToBitmap, rotationDegrees, cropRect)

textAnalyzerScope.launch {
recognizeText(InputImage.fromBitmap(croppedBitmap, 0))
imageProxy.close()
}
}

private suspend fun recognizeText(image: InputImage): Text? =
try {
detector.process(image).await().also {
result.postValue(it.text)
}
} catch (e: Exception) {
Log.e(TAG, "Text recognition error", e)
getErrorMessage(e)?.let {
withContext(Dispatchers.Main) {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
}
}
null
}

private fun getErrorMessage(exception: Exception): String? {
val mlKitException = exception as? MlKitException ?: return exception.message
return if (mlKitException.errorCode == MlKitException.UNAVAILABLE) {
"Waiting for text recognition model to be downloaded"
} else exception.message
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.plweegie.magmolecular.ocr

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.plweegie.magmolecular.R
import dagger.hilt.android.AndroidEntryPoint


@AndroidEntryPoint
class TextRecognitionActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_text_recognition)

if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, TextRecognitionFragment.newInstance())
.commitNow()
}
}
}
Loading

0 comments on commit 8ad023f

Please sign in to comment.