using System.Collections;
using UnityEngine;
using Unity.Barracuda;
using TMPro;
using UnityEngine.UI; // Untuk komponen Image
public class OrganikClassifier : MonoBehaviour
{
[Header("Model Barracuda")]
public NNModel onnxModel;
private Model runtimeModel;
private IWorker worker;
[Header("UI Text")]
public TextMeshProUGUI outputText;
public TextMeshProUGUI scoreText;
[Header("UI Image")]
public Image displayImage; // Menampilkan foto hasil kamera
[Header("Feedback Images")]
public Image feedbackCorrectImage; // Image checklist
public Image feedbackWrongImage; // Image silang
public float feedbackDelay = 1.5f; // Delay sebelum muncul canvas (dalam detik)
[Header("Canvas")]
public GameObject canvasBerhasil;
public GameObject canvasGagal;
[Header("Audio")]
[SerializeField] private AudioSource audioSource;
[SerializeField] private AudioClip correctClip;
[SerializeField] private AudioClip wrongClip;
[SerializeField] private AudioClip awardClip; // Pastikan penamaan konsisten (gunakan huruf kecil)
// Urutan kelas (sesuai training)
private string[] classes = { "anorganik", "bukansampah", "organik" };
private int inputWidth = 224;
private int inputHeight = 224;
private int currentScore = 0;
private int maxScore = 5;
void Start()
{
// Muat model dari asset
runtimeModel = ModelLoader.Load(onnxModel);
// Buat worker untuk melakukan inferensi (auto memilih antara CPU/GPU)
worker = WorkerFactory.CreateWorker(WorkerFactory.Type.Auto, runtimeModel);
// Pastikan feedback image tersembunyi awalnya
if (feedbackCorrectImage != null)
feedbackCorrectImage.gameObject.SetActive(false);
if (feedbackWrongImage != null)
feedbackWrongImage.gameObject.SetActive(false);
UpdateScoreText();
}
///
/// Fungsi yang dipanggil oleh tombol untuk membuka kamera bawaan dan mengambil gambar.
///
public void TakePicture()
{
NativeCamera.Permission permission = NativeCamera.CheckPermission(true);
if (permission == NativeCamera.Permission.Denied || permission == NativeCamera.Permission.ShouldAsk)
{
NativeCamera.RequestPermission(true);
}
NativeCamera.TakePicture((path) =>
{
if (!string.IsNullOrEmpty(path))
{
// Load gambar dengan ukuran target
Texture2D texture = NativeCamera.LoadImageAtPath(path, inputWidth);
if (texture == null)
{
Debug.LogError("Gagal memuat gambar dari path: " + path);
return;
}
// Pastikan texture bisa diakses (readable)
Texture2D readableTexture = MakeTextureReadable(texture);
// Koreksi orientasi gambar dari kamera (sesuaikan rotasinya jika perlu)
Texture2D correctedTexture = CorrectCameraImage(readableTexture);
// Tampilkan gambar di UI Image (opsional)
if (displayImage != null)
{
Sprite newSprite = Sprite.Create(correctedTexture,
new Rect(0, 0, correctedTexture.width, correctedTexture.height),
new Vector2(0.5f, 0.5f));
displayImage.sprite = newSprite;
}
// Proses gambar yang diambil dengan inferensi
RunInference(correctedTexture);
}
}, maxSize: inputWidth);
}
///
/// Melakukan inferensi pada gambar yang diberikan.
///
public void RunInference(Texture2D inputTexture)
{
if (inputTexture == null)
{
Debug.LogError("Gambar tidak tersedia untuk inferensi!");
return;
}
// Resize texture ke ukuran input model
Texture2D resized = ResizeTexture(inputTexture, inputWidth, inputHeight);
// Dapatkan data piksel dan lakukan normalisasi ke rentang [-1, 1]
Color32[] pixels = resized.GetPixels32();
float[] floatValues = new float[inputWidth * inputHeight * 3];
for (int i = 0; i < pixels.Length; i++)
{
floatValues[i * 3 + 0] = (pixels[i].r / 127.5f) - 1.0f;
floatValues[i * 3 + 1] = (pixels[i].g / 127.5f) - 1.0f;
floatValues[i * 3 + 2] = (pixels[i].b / 127.5f) - 1.0f;
}
// Buat tensor input dengan dimensi [1, inputHeight, inputWidth, 3]
Tensor inputTensor = new Tensor(1, inputHeight, inputWidth, 3, floatValues);
worker.Execute(inputTensor);
Tensor outputTensor = worker.PeekOutput();
float[] scores = outputTensor.ToReadOnlyArray();
int predictedIndex = ArgMax(scores);
float confidence = scores[predictedIndex];
string predictedClass = classes[predictedIndex];
Debug.Log($"Predicted Class: {predictedClass}\nConfidence: {confidence:F2}");
if (outputText != null)
{
outputText.text = $"Predicted Class: {predictedClass}\nConfidence: {confidence:F2}";
}
// Logika skor dan feedback
if (predictedClass == "organik")
{
currentScore++;
UpdateScoreText();
if (audioSource != null && correctClip != null)
{
audioSource.PlayOneShot(correctClip);
}
// Jika sudah mencapai target (5 kali)
if (currentScore >= maxScore)
{
StartCoroutine(ShowFeedback(true, true)); // benar dan final
}
else
{
StartCoroutine(ShowFeedback(true, false)); // benar tapi belum final
}
}
else
{
if (audioSource != null && wrongClip != null)
{
audioSource.PlayOneShot(wrongClip);
}
// Jika salah, tampilkan feedback dan canvas gagal (final)
StartCoroutine(ShowFeedback(false, true));
}
inputTensor.Dispose();
outputTensor.Dispose();
}
///
/// Coroutine untuk menampilkan image feedback (checklist atau silang) selama delay tertentu,
/// kemudian jika isFinal true, akan menampilkan canvas hasil dan (jika benar) memutar awardClip.
///
private IEnumerator ShowFeedback(bool isCorrect, bool isFinal)
{
// Tampilkan image feedback
if (isCorrect)
{
if (feedbackCorrectImage != null)
feedbackCorrectImage.gameObject.SetActive(true);
}
else
{
if (feedbackWrongImage != null)
feedbackWrongImage.gameObject.SetActive(true);
}
// Tunggu selama feedbackDelay
yield return new WaitForSeconds(feedbackDelay);
// Sembunyikan image feedback
if (isCorrect)
{
if (feedbackCorrectImage != null)
feedbackCorrectImage.gameObject.SetActive(false);
}
else
{
if (feedbackWrongImage != null)
feedbackWrongImage.gameObject.SetActive(false);
}
// Jika isFinal true, tampilkan canvas hasil dan putar awardClip (jika benar)
if (isFinal)
{
if (isCorrect)
{
if (audioSource != null && awardClip != null)
{
audioSource.PlayOneShot(awardClip);
}
if (canvasBerhasil != null)
canvasBerhasil.SetActive(true);
}
else
{
if (canvasGagal != null)
canvasGagal.SetActive(true);
}
}
}
///
/// Mengembalikan indeks elemen dengan nilai maksimum pada array.
///
private int ArgMax(float[] array)
{
int index = 0;
float max = array[0];
for (int i = 1; i < array.Length; i++)
{
if (array[i] > max)
{
max = array[i];
index = i;
}
}
return index;
}
///
/// Meresize Texture2D ke ukuran yang diinginkan.
///
private Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight)
{
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);
RenderTexture.active = rt;
Graphics.Blit(source, rt);
Texture2D newTexture = new Texture2D(newWidth, newHeight);
newTexture.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
newTexture.Apply();
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
return newTexture;
}
///
/// Memperbarui tampilan skor pada UI.
///
private void UpdateScoreText()
{
if (scoreText != null)
{
scoreText.text = $"{currentScore}/{maxScore}";
}
}
///
/// Membuat salinan texture yang dapat diakses (readable) dengan RenderTexture.
///
private Texture2D MakeTextureReadable(Texture2D tex)
{
RenderTexture tmp = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
Graphics.Blit(tex, tmp);
RenderTexture previous = RenderTexture.active;
RenderTexture.active = tmp;
Texture2D readableTex = new Texture2D(tex.width, tex.height);
readableTex.ReadPixels(new Rect(0, 0, tmp.width, tmp.height), 0, 0);
readableTex.Apply();
RenderTexture.active = previous;
RenderTexture.ReleaseTemporary(tmp);
return readableTex;
}
///
/// Mengoreksi orientasi gambar yang diambil dari NativeCamera.
/// Contoh: memutar gambar 90° searah jarum jam.
/// (Jika perlu, sesuaikan rotasinya sesuai kondisi perangkat.)
///
private Texture2D CorrectCameraImage(Texture2D original)
{
int width = original.width;
int height = original.height;
Texture2D rotated = new Texture2D(height, width, original.format, false);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
rotated.SetPixel(j, width - i - 1, original.GetPixel(i, j));
}
}
rotated.Apply();
return rotated;
}
private void OnDestroy()
{
worker?.Dispose();
}
}