mirror of
https://github.com/pendragonnn/PalmGuard-App-Thesis.git
synced 2025-08-12 18:32:21 +00:00
feat: [detection] update ui & change newest model
This commit is contained in:
@ -4,45 +4,6 @@ import com.example.palmguardapp.data.local.entity.DiseaseDiagnose
|
|||||||
import com.example.palmguardapp.data.local.room.DiseaseDiagnoseDao
|
import com.example.palmguardapp.data.local.room.DiseaseDiagnoseDao
|
||||||
import kotlinx.coroutines.flow.Flow
|
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(
|
class DiseaseRepository private constructor(
|
||||||
private val diseaseDiagnoseDao: DiseaseDiagnoseDao
|
private val diseaseDiagnoseDao: DiseaseDiagnoseDao
|
||||||
) {
|
) {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// Import library yang dibutuhkan untuk context, bitmap, logging, dan TensorFlow Lite
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.util.Log
|
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.image.ops.ResizeOp
|
||||||
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer
|
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer
|
||||||
|
|
||||||
|
// Kelas utama untuk klasifikasi gambar menggunakan model ML TFLite
|
||||||
class ImageClassifier(private val context: Context) {
|
class ImageClassifier(private val context: Context) {
|
||||||
|
|
||||||
|
// Ukuran input gambar yang dibutuhkan model
|
||||||
private val imageSize = 224
|
private val imageSize = 224
|
||||||
|
|
||||||
|
// Daftar label kelas dari model
|
||||||
private val classes = arrayOf("Brown Spots", "Healthy", "Unknown")
|
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()
|
private val imageProcessor = ImageProcessor.Builder()
|
||||||
.add(ResizeOp(imageSize, imageSize, ResizeOp.ResizeMethod.BILINEAR))
|
.add(ResizeOp(imageSize, imageSize, ResizeOp.ResizeMethod.BILINEAR)) // Resize gambar ke 224x224
|
||||||
.add(NormalizeOp(0f, 255f))
|
.add(NormalizeOp(0f, 255f)) // Normalisasi piksel gambar ke range 0–1
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
// Fungsi utama untuk mengklasifikasikan gambar
|
||||||
fun classifyImage(image: Bitmap): Pair<String, Float>? {
|
fun classifyImage(image: Bitmap): Pair<String, Float>? {
|
||||||
|
// Membuat instance model
|
||||||
val model = Model.newInstance(context)
|
val model = Model.newInstance(context)
|
||||||
|
|
||||||
|
// Mengonversi gambar bitmap ke TensorImage dengan tipe data FLOAT32
|
||||||
val tensorImage = TensorImage(DataType.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)
|
tensorImage.load(convertedBitmap)
|
||||||
|
|
||||||
|
// Proses preprocessing gambar: resize + normalisasi
|
||||||
val processedImage = imageProcessor.process(tensorImage)
|
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)
|
val inputFeature0 = TensorBuffer.createFixedSize(intArrayOf(1, imageSize, imageSize, 3), DataType.FLOAT32)
|
||||||
inputFeature0.loadBuffer(processedImage.buffer)
|
inputFeature0.loadBuffer(processedImage.buffer)
|
||||||
|
|
||||||
|
// Memproses input melalui model dan mendapatkan output
|
||||||
val outputs = model.process(inputFeature0)
|
val outputs = model.process(inputFeature0)
|
||||||
val outputFeature0 = outputs.outputFeature0AsTensorBuffer
|
val outputFeature0 = outputs.outputFeature0AsTensorBuffer
|
||||||
|
|
||||||
|
// Mengambil array probabilitas prediksi dari hasil output
|
||||||
val confidences = outputFeature0.floatArray
|
val confidences = outputFeature0.floatArray
|
||||||
Log.d("ImageClassifier", "Confidence: ${confidences.size}")
|
Log.d("ImageClassifier", "Confidence: ${confidences.size}")
|
||||||
|
|
||||||
|
// Logging setiap confidence untuk tiap kelas
|
||||||
for (i in confidences.indices) {
|
for (i in confidences.indices) {
|
||||||
Log.d("ImageClassifier", "Class $i (${classes[i]}): ${confidences[i]}")
|
Log.d("ImageClassifier", "Class $i (${classes[i]}): ${confidences[i]}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mencari indeks prediksi dengan confidence tertinggi
|
||||||
val maxPos = confidences.indices.maxByOrNull { confidences[it] } ?: -1
|
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")
|
Log.d("ImageClassifier", "Max Position: $maxPos, Max Confidence: $maxConfidence")
|
||||||
|
|
||||||
|
// Menutup instance model untuk membebaskan resource
|
||||||
model.close()
|
model.close()
|
||||||
|
|
||||||
|
// Mengembalikan hasil klasifikasi jika confidence mencukupi dan bukan kelas Unknown
|
||||||
return if (maxPos >= 0 && maxConfidence > THRESHOLD_CONFIDENCE && classes[maxPos] != "Unknown") {
|
return if (maxPos >= 0 && maxConfidence > THRESHOLD_CONFIDENCE && classes[maxPos] != "Unknown") {
|
||||||
Pair(classes[maxPos], maxConfidence)
|
Pair(classes[maxPos], maxConfidence)
|
||||||
} else {
|
} else {
|
||||||
null
|
null // Tidak ada hasil yang cukup meyakinkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// Batas minimum confidence yang dianggap valid (dalam persen)
|
||||||
private const val THRESHOLD_CONFIDENCE = 0.5f
|
private const val THRESHOLD_CONFIDENCE = 0.5f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ class DetectionViewModel(private val historyDiagnoseRepository: HistoryDiagnoseR
|
|||||||
fun deleteHistory(historyDiagnose: HistoryDiagnose) {
|
fun deleteHistory(historyDiagnose: HistoryDiagnose) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
historyDiagnoseRepository.delete(historyDiagnose)
|
historyDiagnoseRepository.delete(historyDiagnose)
|
||||||
// Refresh the list after deletion
|
|
||||||
getDetectionList()
|
getDetectionList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,12 +49,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
|
|||||||
private val diseaseToId = mapOf(
|
private val diseaseToId = mapOf(
|
||||||
"Brown Spots" to "D-001",
|
"Brown Spots" to "D-001",
|
||||||
"Healthy" to "D-002",
|
"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>
|
private lateinit var cameraLauncher: ActivityResultLauncher<Intent>
|
||||||
@ -219,38 +213,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
|
|||||||
Log.d("saveImageAndFetchData", "Date: $date")
|
Log.d("saveImageAndFetchData", "Date: $date")
|
||||||
val dateNow = formatter.format(date)
|
val dateNow = formatter.format(date)
|
||||||
val diseaseId = diseaseToId[diagnosis]
|
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 ->
|
diseaseId?.let { id ->
|
||||||
viewModel.getDiseaseById(id)
|
viewModel.getDiseaseById(id)
|
||||||
|
|
||||||
@ -317,7 +279,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
|
|||||||
binding.tvHsHistoryEmpty.visibility = View.GONE
|
binding.tvHsHistoryEmpty.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
_binding = null
|
_binding = null
|
||||||
|
@ -18,15 +18,6 @@ class DiseaseViewModel(private val diseaseRepository: DiseaseRepository) : ViewM
|
|||||||
private val _diseaseById = MutableSharedFlow<Result<DiseaseDiagnose?>>()
|
private val _diseaseById = MutableSharedFlow<Result<DiseaseDiagnose?>>()
|
||||||
val diseaseById: Flow<Result<DiseaseDiagnose?>> = _diseaseById.asSharedFlow()
|
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) {
|
fun getDiseaseById(id: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_diseaseById.emit(Result.Loading)
|
_diseaseById.emit(Result.Loading)
|
||||||
|
Binary file not shown.
@ -124,7 +124,7 @@
|
|||||||
android:id="@+id/tv_hs_phone_analyze"
|
android:id="@+id/tv_hs_phone_analyze"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Ambil Gambar"
|
android:text="Analisis Hasil"
|
||||||
android:textColor="@color/greenGeneral"
|
android:textColor="@color/greenGeneral"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/detection"
|
android:id="@+id/detection"
|
||||||
android:icon="@drawable/hs_trace_black_img"
|
android:icon="@drawable/hs_trace_black_img"
|
||||||
android:title="Deteksi" />
|
android:title="Riwayat" />
|
||||||
</menu>
|
</menu>
|
Reference in New Issue
Block a user