Skip to content

Commit

Permalink
Fix bugs found on Milestone #1 (#11)
Browse files Browse the repository at this point in the history
* + Material3 Switch

* + Fix crash when model doesn't exists
Also removed requireWifi since the model isn't that big (500kb+)

* + DateTime formatting
I thought Google's documentation is bad, but omg java's datetime documentation
somehow worse.

* + Result list max lines

* + Sort results

* + Disable input on loading

* + Bumps version
  • Loading branch information
null2264 authored Jun 6, 2022
1 parent adac29a commit e175a2e
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 119 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

### TODO
- [ ] Fix ML
- [ ] Wait for ML model to download on scan
- [x] Wait for ML model to download on scan
- [x] Polishing Auth
- [x] Add alert for registration fail
- [x] Add input validation
Expand All @@ -34,7 +34,7 @@
- [x] Move photo automatically to data/Files/results
- [ ] Delete data when photo no longer exists
- [ ] Delete photo when data being deleted
- [ ] Reverse current list (scannedAt DESC)
- [x] Reverse current list (scannedAt DESC)
- [ ] Landscape/Desktop support (Low priority)

### Mobile Team
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import data.Result
import io.github.skincanorg.skincan.databinding.ItemRowResultBinding
import io.github.skincanorg.skincan.lib.Util
import io.github.skincanorg.skincan.ui.result.ResultActivity
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter

class ResultRecyclerAdapter :
PagingDataAdapter<Result, ResultRecyclerAdapter.ListViewHolder>(DIFF_CALLBACK) {
Expand All @@ -40,16 +43,22 @@ class ResultRecyclerAdapter :
.load(Util.processBitmap(result.imgPath))
.into(ivResultPict)

tvDatetime.text = result.scannedAt.toString()

tvDatetime.text =
DateTimeFormatter.ofPattern("d MMM YYYY, HH:mm")
.format(Instant.ofEpochSecond(result.scannedAt).atZone(ZoneId.systemDefault()))

when (result.result) {
"Clear" -> {
tvResultStatus.text = "Clear"
tvStatus.text = "No cancer found"
}

null -> {
tvResultStatus.text = "ERROR"
tvStatus.text = "Error: Failed to retrieve data"
}

else -> {
tvResultStatus.text = "Cancer"
tvStatus.text = "Potientional risk cancer found"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class LoginActivity : AppCompatActivity() {
}
else -> {}
}
validateList.forEach { it.isEnabled = state !is AppResult.Loading }
btnLogin.isLoading = state is AppResult.Loading
btnGotoRegisterContainer.isEnabled = state !is AppResult.Loading
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class RegisterActivity : AppCompatActivity() {
}
else -> {}
}
validateList.forEach { it.isEnabled = state !is AppResult.Loading }
btnRegister.isLoading = state is AppResult.Loading
btnGotoLoginContainer.isEnabled = state !is AppResult.Loading
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class ScannerActivity : AppCompatActivity() {
private var photoFile: File? = null
private var shouldLoop = false
private var looped = true
private lateinit var interpreter: Interpreter
private lateinit var image: Bitmap

@Inject
Expand All @@ -65,17 +64,6 @@ class ScannerActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(binding.root)

val conditions = CustomModelDownloadConditions.Builder()
.requireWifi()
.build()
FirebaseModelDownloader.getInstance()
.getModel("CancerDetector", DownloadType.LOCAL_MODEL_UPDATE_IN_BACKGROUND, conditions)
.addOnSuccessListener { model ->
val modelFile = model?.file
if (modelFile != null)
interpreter = Interpreter(modelFile)
}

binding.apply {
photoFile = intent.extras?.get("IMG_FILE") as File?
if (photoFile != null) {
Expand Down Expand Up @@ -138,95 +126,102 @@ class ScannerActivity : AppCompatActivity() {
}

private fun doPrediction(bitmap: Bitmap) {
val inputShape = interpreter.getInputTensor(0).shape()
val inputSize = Size(inputShape[2], inputShape[1])

val imgProcessor = ImageProcessor.Builder()
.add(
ResizeOp(
inputSize.height, inputSize.width, ResizeOp.ResizeMethod.BILINEAR,
),
)
.add(NormalizeOp(0f, 1f))
.build()
val tensorImage = imgProcessor.process(TensorImage(DataType.FLOAT32).apply { load(bitmap) })

val modelOutput = TensorBuffer.createFixedSize(interpreter.getOutputTensor(0).shape(), DataType.FLOAT32)

interpreter.run(tensorImage.buffer, modelOutput.buffer.rewind())

val probProcessor = TensorProcessor.Builder()
.add(NormalizeOp(0f, 1f))
.build()

val labels = TensorLabel(
BufferedReader(
InputStreamReader(assets.open("model_labels.txt")),
).readLines(),
probProcessor.process(modelOutput),
)

val resultMap = labels.mapWithFloatValue

var result = "Clear"
var lastHighest = .50f
resultMap.keys.forEach {
val value = resultMap[it] as Float
if (value >= lastHighest) {
if (value != lastHighest)
lastHighest = value
result = StringBuilder().apply {
append("$it ")
append(String.format("%.2f", value))
}.toString()
}
Log.d(
"ziML",
StringBuilder().apply {
append("$it ")
append(String.format("%.2f", value))
}.toString(),
)
}
FirebaseModelDownloader.getInstance()
.getModel("CancerDetector", DownloadType.LATEST_MODEL, CustomModelDownloadConditions.Builder().build())
.addOnSuccessListener { model ->
val modelFile = model?.file ?: return@addOnSuccessListener
val interpreter = Interpreter(modelFile)

val inputShape = interpreter.getInputTensor(0).shape()
val inputSize = Size(inputShape[2], inputShape[1])

val imgProcessor = ImageProcessor.Builder()
.add(
ResizeOp(
inputSize.height, inputSize.width, ResizeOp.ResizeMethod.BILINEAR,
),
)
.add(NormalizeOp(0f, 1f))
.build()
val tensorImage = imgProcessor.process(TensorImage(DataType.FLOAT32).apply { load(bitmap) })

val modelOutput = TensorBuffer.createFixedSize(interpreter.getOutputTensor(0).shape(), DataType.FLOAT32)

interpreter.run(tensorImage.buffer, modelOutput.buffer.rewind())

val probProcessor = TensorProcessor.Builder()
.add(NormalizeOp(0f, 1f))
.build()

val labels = TensorLabel(
BufferedReader(
InputStreamReader(assets.open("model_labels.txt")),
).readLines(),
probProcessor.process(modelOutput),
)

Log.d("ziML", result)

lifecycleScope.launch(Dispatchers.IO) {
delay(3000L)
val q = database.resultsQueries

val currentTime = System.currentTimeMillis() / 1000
val cacheDir = Util.getCacheDir(applicationContext, "results")

var outChan: FileChannel? = null
var inChan: FileChannel? = null
val newFile = File(cacheDir, currentTime.toString())
try {
outChan = FileOutputStream(newFile).channel
inChan = FileInputStream(photoFile).channel
inChan.transferTo(0, inChan.size(), outChan)
inChan.close()
photoFile!!.delete()
} finally {
inChan?.close()
outChan?.close()
}
val resultMap = labels.mapWithFloatValue

var result = "Clear"
var lastHighest = .50f
resultMap.keys.forEach {
val value = resultMap[it] as Float
if (value >= lastHighest) {
if (value != lastHighest)
lastHighest = value
result = StringBuilder().apply {
append("$it ")
append(String.format("%.2f", value))
}.toString()
}
Log.d(
"ziML",
StringBuilder().apply {
append("$it ")
append(String.format("%.2f", value))
}.toString(),
)
}

Log.d("ziML", result)

lifecycleScope.launch(Dispatchers.IO) {
delay(3000L)
val q = database.resultsQueries

val currentTime = System.currentTimeMillis() / 1000
val cacheDir = Util.getCacheDir(applicationContext, "results")

var outChan: FileChannel? = null
var inChan: FileChannel? = null
val newFile = File(cacheDir, currentTime.toString())
try {
outChan = FileOutputStream(newFile).channel
inChan = FileInputStream(photoFile).channel
inChan.transferTo(0, inChan.size(), outChan)
inChan.close()
photoFile!!.delete()
} finally {
inChan?.close()
outChan?.close()
}

val path = newFile.path
q.insert(path, result, currentTime)

withContext(Dispatchers.Main) {
Toast.makeText(this@ScannerActivity, "ML Result: $result", Toast.LENGTH_LONG).show()
startActivity(
Intent(this@ScannerActivity, ResultActivity::class.java).apply {
putExtra(ResultActivity.PHOTO_PATH, path)
putExtra(ResultActivity.RESULT, result)
putExtra(ResultActivity.FROM, 0)
},
)
shouldLoop = false
finish()
}
}
val path = newFile.path
q.insert(path, result, currentTime)

withContext(Dispatchers.Main) {
Toast.makeText(this@ScannerActivity, "ML Result: $result", Toast.LENGTH_LONG).show()
startActivity(
Intent(this@ScannerActivity, ResultActivity::class.java).apply {
putExtra(ResultActivity.PHOTO_PATH, path)
putExtra(ResultActivity.RESULT, result)
putExtra(ResultActivity.FROM, 0)
},
)
shouldLoop = false
finish()
}
}
}.addOnFailureListener { finish() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class PreferenceFragment : PreferenceFragmentCompat() {
key = PreferenceKeys.NIGHT_MODE
titleRes = R.string.dark_mode
isChecked = Util.isNightModeOn(context)
widgetLayoutResource = R.layout.preference_material_switch
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/res/layout/item_row_result.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@
android:id="@+id/tv_datetime" />

<TextView
android:ellipsize="end"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="8dp"
android:layout_width="0dp"
android:maxLines="2"
android:textColor="?attr/colorOnSurfaceVariant"
app:layout_constraintEnd_toStartOf="@id/tv_result_status"
app:layout_constraintStart_toStartOf="@id/tv_datetime"
app:layout_constraintTop_toBottomOf="@id/tv_datetime"
tools:text="No cancer found"
tools:text="No cancer found\nlol\nlol"
android:id="@+id/tv_status" />

<TextView
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/layout/preference_material_switch.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@null"
android:clickable="false"
android:focusable="false"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="@+id/switchWidget" />
3 changes: 2 additions & 1 deletion app/src/main/res/values-night/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
<item name="android:navigationBarColor">?android:attr/colorBackground</item>
<item name="android:windowLightNavigationBar" tools:targetApi="O_MR1">false</item>

<item name="switchStyle">@style/Widget.App.Switch</item>
<!-- Use MaterialSwitch to fix width issue -->
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
<item name="bottomNavigationStyle">@style/Widget.App.BottomNavigationView</item>
</style>
</resources>
12 changes: 2 additions & 10 deletions app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
<item name="android:navigationBarColor">?android:attr/colorBackground</item>
<item name="android:windowLightNavigationBar" tools:targetApi="O_MR1">true</item>

<item name="switchStyle">@style/Widget.App.Switch</item>
<!-- Use MaterialSwitch to fix width issue -->
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
<item name="bottomNavigationStyle">@style/Widget.App.BottomNavigationView</item>
</style>

Expand All @@ -57,13 +58,4 @@
<item name="elevationOverlayEnabled">false</item>
<item name="elevation">0dp</item>
</style>

<!-- https://github.com/patzly/doodle-android. Google needs to step up their game on updating their UI lib -->
<!-- FIXME: Fix width issue -->
<style name="Widget.App.Switch" parent="Widget.Material3.CompoundButton.Switch">
<item name="track">@drawable/ui_m3_switch_track</item>
<item name="trackTint">@color/sel_m3_switch_track</item>
<item name="android:thumb">@drawable/ui_m3_switch_thumb</item>
<item name="thumbTint">@color/sel_m3_switch_thumb</item>
</style>
</resources>
4 changes: 3 additions & 1 deletion app/src/main/sqldelight/data/results.sq
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ FROM result
WHERE _id = :id;

lastResult:
SELECT result FROM result;
SELECT result FROM result
ORDER BY scannedAt DESC;

results:
SELECT *
FROM result
ORDER BY scannedAt DESC
LIMIT :limit OFFSET :offset;

insert:
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/Android.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ object Android {
val compileSdk = 32
val minSdk = 23
val targetSdk = 32
val versionCode = 1
val versionName = "0.0.1"
val versionCode = 2
val versionName = "0.0.2"
}
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Library.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object Library {
const val paging = "androidx.paging:paging-runtime-ktx:3.1.1"
}

const val material = "com.google.android.material:material:1.6.1"
const val material = "com.google.android.material:material:1.7.0-alpha02"
const val hilt = "com.google.dagger:hilt-android:${Version.hilt}"
const val hiltCompiler = "com.google.dagger:hilt-compiler:${Version.hilt}"

Expand Down

0 comments on commit e175a2e

Please sign in to comment.