# Univerzitet u Novom Sadu, Fakultet tehničkih nauka, Novi Sad, Srbija
# Studijski program OAS Informacioni inženjering
# Predmet Metode i tehnike nauke o podacima

# Pomoćni sadržaj

# Korišćeni podaci
#
# Skup podataka "Grapevine Leaves Image Dataset"
# - Internet sajt Murata Koklua (Turska)
# - skup podataka koji sadrži slike listova od pet različitih vrsta vinove loze
#   - autor podataka: Murat Koklu i saradnici (Turska)
#   - vremenska odrednica za podatke: 2022.
#   - format podataka: PNG
#
# Internet strana:
# https://www.muratkoklu.com/datasets/
#
# Studija:
# Koklu M, Unlersen MF, Ozkan IA, Aslan MF, Sabanci K. A CNN-SVM Study
# Based on Selected Deep Features for Grapevine Leaves Classification.
# Measurement. 2022 January;188; 110425.


# %% Biblioteke

import random as rand
import os
import math
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Flatten
from keras.layers import Input
from keras.layers import Dense
from keras.layers import Dropout
from keras.optimizers import Adam
from keras.utils import to_categorical
from tensorflow.math import confusion_matrix


# %% Podešavanje generatora vrednosti

rand.seed(89)


# %% Učitavanje i sređivanje podataka

direktorijum = "Grapevine_Leaves_Image_Dataset"
vrste = ["Ak", "Ala_Idris", "Buzgulu", "Dimnit", "Nazli"]
vrste_preslikavanje_tb = {"Ak": 0, 
                          "Ala_Idris": 1, 
                          "Buzgulu": 2, 
                          "Dimnit": 3, 
                          "Nazli": 4}
vrste_preslikavanje_bt = {0: "Ak", 
                          1: "Ala_Idris", 
                          2: "Buzgulu", 
                          3: "Dimnit", 
                          4: "Nazli"}
vrste_kard = len(vrste)

skup = []
oznake = []

for vrsta in vrste:
    poddirektorijum = os.path.join(".", direktorijum, vrsta)
    for datoteka in os.listdir(poddirektorijum):
        putanja = os.path.join(poddirektorijum, datoteka)
        slika = cv.resize(cv.imread(putanja, cv.IMREAD_GRAYSCALE), 
                          (256, 256), interpolation=cv.INTER_AREA)
        skup.append(slika / 255)
        oznake.append(vrsta)

oznake_brojčano = [vrste_preslikavanje_tb[oznaka] for oznaka in oznake]
oznake_matrično = to_categorical(oznake_brojčano, vrste_kard)


# %% Pripremanje podataka za postupke obučavanja i validacije neuronske mreže

indeksi_obučavanje = rand.sample(range(len(skup)), math.floor(0.8 * len(skup)))
indeksi_validacija = list(set(range(len(skup))) - set(indeksi_obučavanje))

indeksi_obučavanje.sort()
indeksi_validacija.sort()

skup_obučavanje = np.array([skup[i] for i in indeksi_obučavanje]) 
skup_obučavanje_oznake = oznake_matrično[np.array(indeksi_obučavanje)]

skup_validacija = np.array([skup[i] for i in indeksi_validacija])
skup_validacija_oznake = oznake_matrično[np.array(indeksi_validacija)]


# %% Formiranje, obučavanje i validacija neuronske mreže

veličina_grupe = 1
broj_epoha = 50

knm = Sequential()

knm.add(Input(shape=(256, 256, 1)))

knm.add(Conv2D(filters=4, kernel_size=(3, 3), strides=(2, 2), 
               padding="valid", activation="relu", use_bias=True))

knm.add(MaxPooling2D(pool_size=(2, 2)))

knm.add(Conv2D(filters=4, kernel_size=(2, 2), strides=(1, 1), 
               padding="valid", activation="relu", use_bias=True))

knm.add(MaxPooling2D(pool_size=(2, 2)))

knm.add(Conv2D(filters=3, kernel_size=(2, 2), strides=(1, 1), 
               padding="valid", activation="relu", use_bias=True))

knm.add(MaxPooling2D(pool_size=(2, 2)))

knm.add(Flatten())

knm.add(Dropout(0.5))

knm.add(Dense(15, activation="relu"))

knm.add(Dropout(0.5))

knm.add(Dense(vrste_kard, activation="softmax"))

knm.compile(loss="categorical_crossentropy",
            optimizer=Adam(learning_rate=0.001),
            metrics=["categorical_accuracy"])

knm.summary()

knm_rezultati = knm.fit(skup_obučavanje, skup_obučavanje_oznake, 
                        batch_size=veličina_grupe, epochs=broj_epoha,
                        validation_data=(skup_validacija, 
                                         skup_validacija_oznake))


# %% Analiza performansi neuronske mreže

plt.figure(figsize=(8, 6))
plt.plot(range(1, broj_epoha + 1), 
         knm_rezultati.history["categorical_accuracy"], 
         "o--", label="obučavanje")
plt.plot(range(1, broj_epoha + 1), 
         knm_rezultati.history["val_categorical_accuracy"],
         "o--", label="validacija")
plt.xlim(0)
plt.ylim(0)
plt.title("Performanse neuronske mreže")
plt.xticks([i for i in range(1, broj_epoha + 1) if i % 5 == 0])
plt.xlabel("Epoha")
plt.ylabel("Kategorijska tačnost")
plt.legend(title="Faza")

oznake_validacija_procenjene = np.argmax(
    knm.predict(skup_validacija, verbose=0), axis=1)

oznake_validacija_stvarne = np.array(
    [oznake_brojčano[i] for i in indeksi_validacija])

matrica_konfuzije = confusion_matrix(oznake_validacija_stvarne, 
                                     oznake_validacija_procenjene)
print("matrica konfuzije \n", matrica_konfuzije)

maksimalni_broj_prikaza = 5
broj_prikaza = 0

for indeks, stvarno in enumerate(oznake_validacija_stvarne):
    procenjeno = oznake_validacija_procenjene[indeks]
    if stvarno != procenjeno:
        if broj_prikaza < maksimalni_broj_prikaza:
            broj_prikaza += 1            
            cv.imshow(
                "({}) stvarno={}, procenjeno={}".format(
                    broj_prikaza, vrste_preslikavanje_bt[stvarno], 
                    vrste_preslikavanje_bt[procenjeno]),
                skup_validacija[indeks]
            )
            cv.waitKey(3000)
        else:
            break

if broj_prikaza > 0:
    cv.destroyAllWindows()

