feat: [detection] update ui & change newest model

This commit is contained in:
Wisnu Andika
2025-07-10 21:22:09 +07:00
parent 11babcaf29
commit e8f4c370a1
8 changed files with 28 additions and 97 deletions

View File

@ -4,45 +4,6 @@ import com.example.palmguardapp.data.local.entity.DiseaseDiagnose
import com.example.palmguardapp.data.local.room.DiseaseDiagnoseDao
import kotlinx.coroutines.flow.Flow
//class DiseaseRepository private constructor(
// private val apiService: ApiService
//) {
//
// suspend fun getDiseaseById(id: String): Flow<Result<DiseaseByIdResponse>> = flow {
// emit(Result.Loading)
// val response = apiService.getDiseaseById(id)
// emit(Result.Success(response))
// }.catch { e ->
// emit(Result.Error(e.message.toString()))
// }
//
// suspend fun getAllDiseaseDetail(): Flow<Result<DiseaseDetailResponse>> = flow {
// emit(Result.Loading)
// val response = apiService.getAllDiseaseDetail()
// emit(Result.Success(response))
// }.catch { e ->
// emit(Result.Error(e.message.toString()))
// }
//
// suspend fun getDiseaseDetailById(id: String): Flow<Result<DiseaseDetailByIdResponse>> = flow {
// emit(Result.Loading)
// val response = apiService.getDiseaseDetailById(id)
// emit(Result.Success(response))
// }.catch { e ->
// emit(Result.Error(e.message.toString()))
// }
//
// companion object {
// @Volatile
// private var instance: DiseaseRepository? = null
//
// fun getInstance(apiService: ApiService): DiseaseRepository =
// instance ?: synchronized(this) {
// instance ?: DiseaseRepository(apiService).also { instance = it }
// }
// }
//}
class DiseaseRepository private constructor(
private val diseaseDiagnoseDao: DiseaseDiagnoseDao
) {

View File

@ -1,3 +1,4 @@
// Import library yang dibutuhkan untuk context, bitmap, logging, dan TensorFlow Lite
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
@ -9,51 +10,69 @@ import org.tensorflow.lite.support.image.TensorImage
import org.tensorflow.lite.support.image.ops.ResizeOp
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer
// Kelas utama untuk klasifikasi gambar menggunakan model ML TFLite
class ImageClassifier(private val context: Context) {
// Ukuran input gambar yang dibutuhkan model
private val imageSize = 224
// Daftar label kelas dari model
private val classes = arrayOf("Brown Spots", "Healthy", "Unknown")
// ImageProcessor untuk mengubah ukuran dan menormalisasi gambar agar sesuai dengan input model
private val imageProcessor = ImageProcessor.Builder()
.add(ResizeOp(imageSize, imageSize, ResizeOp.ResizeMethod.BILINEAR))
.add(NormalizeOp(0f, 255f))
.add(ResizeOp(imageSize, imageSize, ResizeOp.ResizeMethod.BILINEAR)) // Resize gambar ke 224x224
.add(NormalizeOp(0f, 255f)) // Normalisasi piksel gambar ke range 01
.build()
// Fungsi utama untuk mengklasifikasikan gambar
fun classifyImage(image: Bitmap): Pair<String, Float>? {
// Membuat instance model
val model = Model.newInstance(context)
// Mengonversi gambar bitmap ke TensorImage dengan tipe data FLOAT32
val tensorImage = TensorImage(DataType.FLOAT32)
val convertedBitmap = image.copy(Bitmap.Config.ARGB_8888, true)
val convertedBitmap = image.copy(Bitmap.Config.ARGB_8888, true) // Salin bitmap agar bisa diolah
tensorImage.load(convertedBitmap)
// Proses preprocessing gambar: resize + normalisasi
val processedImage = imageProcessor.process(tensorImage)
// Menyiapkan buffer input model dengan shape dan tipe data yang sesuai
val inputFeature0 = TensorBuffer.createFixedSize(intArrayOf(1, imageSize, imageSize, 3), DataType.FLOAT32)
inputFeature0.loadBuffer(processedImage.buffer)
// Memproses input melalui model dan mendapatkan output
val outputs = model.process(inputFeature0)
val outputFeature0 = outputs.outputFeature0AsTensorBuffer
// Mengambil array probabilitas prediksi dari hasil output
val confidences = outputFeature0.floatArray
Log.d("ImageClassifier", "Confidence: ${confidences.size}")
// Logging setiap confidence untuk tiap kelas
for (i in confidences.indices) {
Log.d("ImageClassifier", "Class $i (${classes[i]}): ${confidences[i]}")
}
// Mencari indeks prediksi dengan confidence tertinggi
val maxPos = confidences.indices.maxByOrNull { confidences[it] } ?: -1
val maxConfidence = (maxOf(confidences[maxPos]) * 100).toFloat()
val maxConfidence = (maxOf(confidences[maxPos]) * 100).toFloat() // Konversi ke persen
Log.d("ImageClassifier", "Max Position: $maxPos, Max Confidence: $maxConfidence")
// Menutup instance model untuk membebaskan resource
model.close()
// Mengembalikan hasil klasifikasi jika confidence mencukupi dan bukan kelas Unknown
return if (maxPos >= 0 && maxConfidence > THRESHOLD_CONFIDENCE && classes[maxPos] != "Unknown") {
Pair(classes[maxPos], maxConfidence)
} else {
null
null // Tidak ada hasil yang cukup meyakinkan
}
}
companion object {
// Batas minimum confidence yang dianggap valid (dalam persen)
private const val THRESHOLD_CONFIDENCE = 0.5f
}
}
}

View File

@ -29,7 +29,6 @@ class DetectionViewModel(private val historyDiagnoseRepository: HistoryDiagnoseR
fun deleteHistory(historyDiagnose: HistoryDiagnose) {
viewModelScope.launch {
historyDiagnoseRepository.delete(historyDiagnose)
// Refresh the list after deletion
getDetectionList()
}
}

View File

@ -49,12 +49,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
private val diseaseToId = mapOf(
"Brown Spots" to "D-001",
"Healthy" to "D-002",
// "Bird Eye Spot" to "D-003",
// "Brown Blight" to "D-004",
// "Gray Light" to "D-005",
// "Red Leaf Spot" to "D-006",
// "White Spot" to "D-007",
// "Healthy" to "D-008"
)
private lateinit var cameraLauncher: ActivityResultLauncher<Intent>
@ -219,38 +213,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
Log.d("saveImageAndFetchData", "Date: $date")
val dateNow = formatter.format(date)
val diseaseId = diseaseToId[diagnosis]
// diseaseId?.let { id ->
// viewModel.getDiseaseById(id)
// viewModel.dataDisease.collect { result ->
// when (result) {
// is Result.Success -> {
// val diseaseData = result.data
// Log.d("HomeFragment", "Disease Data: $diseaseData")
// val historyDiagnose = HistoryDiagnose(
// name = diagnosis,
// imageUri = imageUri.toString(),
// diagnosis = diseaseData.data?.diseaseExplanation ?: "",
// recommendation = diseaseData.data?.diseaseRecommendation ?: "",
// confidence = confidence.toString(),
// date = dateNow
// )
//
// viewModel.saveDiagnose(historyDiagnose)
// lastDiagnosis = historyDiagnose
// Log.d("HomeFragment", "History Diagnose: $historyDiagnose")
// binding.progressResult.visibility = View.GONE
// restartFragment()
// navigateToDiagnoseDetail(historyDiagnose)
// }
// is Result.Error -> {
// // Handle error state
// }
// Result.Loading -> {
// binding.progressResult.visibility = View.VISIBLE
// }
// }
// }
// }
diseaseId?.let { id ->
viewModel.getDiseaseById(id)
@ -317,7 +279,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
binding.tvHsHistoryEmpty.visibility = View.GONE
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null

View File

@ -18,15 +18,6 @@ class DiseaseViewModel(private val diseaseRepository: DiseaseRepository) : ViewM
private val _diseaseById = MutableSharedFlow<Result<DiseaseDiagnose?>>()
val diseaseById: Flow<Result<DiseaseDiagnose?>> = _diseaseById.asSharedFlow()
// fun getAllDisease() {
// viewModelScope.launch {
// _listDisease.emit(Result.Loading)
// diseaseRepository.getAllDisease().collect { diseases ->
// _listDisease.emit(Result.Success(diseases))
// }
// }
// }
fun getDiseaseById(id: String) {
viewModelScope.launch {
_diseaseById.emit(Result.Loading)

Binary file not shown.

View File

@ -124,7 +124,7 @@
android:id="@+id/tv_hs_phone_analyze"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ambil Gambar"
android:text="Analisis Hasil"
android:textColor="@color/greenGeneral"
android:textSize="14sp"
android:textStyle="bold"

View File

@ -7,5 +7,5 @@
<item
android:id="@+id/detection"
android:icon="@drawable/hs_trace_black_img"
android:title="Deteksi" />
android:title="Riwayat" />
</menu>