
# image classification using OpenImageR (image processing toolkit) and KernelKnn (kernel k-nearest neighbors)


library(OpenImageR)

library(KernelKnn)


# path to the folder of the train-images
path = '../input/train/'


# secondary function for augmentation
augment_function = function(FILE) {
  
  im = readImage(FILE)
  
  im = rgb_2gray(im)
  
  im = Augmentation(im, crop_height = round(ncol(im) * 0.025):round(ncol(im) * 0.975), 
                    
                    crop_width = round(nrow(im) * 0.025):round(nrow(im) * 0.975),
                    
                    resiz_height = 96, resiz_width = 96, resiz_method = "bilinear")
  return(im)
}



# reads files from folders and performs augmentation
lst_files_train = function(path_train, threads = 1) {
  
  start = Sys.time()
  
  setwd(path_train)
  
  lst_f = list.files(path_train)
  
  if (threads > 1) {
    
    out = parallel::mclapply(1:length(lst_f), function(x) augment_function(lst_f[x]), mc.cores = threads)}
  
  else {
    
    out = lapply(1:length(lst_f), function(x) augment_function(lst_f[x]))
  }
  
  end = Sys.time()
  
  t = end - start
  
  cat('\n'); cat('time to complete :', t, attributes(t)$units, '\n'); cat('\n');
  
  return(list(out = out, imag_nams = lst_f))
}


# iterate over train sub-folders

train_out = image_nams = list()

for (i in 0:(length(list.files(path)) - 1)) {
  
  cat(paste('subfolder c', i, sep = ""), '\n')
  
  sub_path_train = paste0(paste0(path, 'c'), i)
  
  tmp_tr = lst_files_train(sub_path_train, threads = 4)
  
  train_out[[i + 1]] = tmp_tr$out
  
  image_nams[[i + 1]] = tmp_tr$imag_nams
  
  gc()
}


# unlist train data 
unl_tr = unlist(train_out, recursive = F)
length(unl_tr)

# unlist image names
unl_nams = unlist(image_nams)
length(unl_nams); head(unl_nams)

# convert list of images to array
lst2_array_train = List_2_Array(unl_tr)
class(lst2_array_train); dim(lst2_array_train)


# hog-features
hog = HOG_apply(lst2_array_train, cells = 7, orientations = 10, threads = 4)
dim(hog)


# extract numbers from name - files
lap_nums = unlist(lapply(unl_nams, function(x) as.numeric(strsplit(strsplit(x, '[.]')[[1]][1], '_')[[1]][2])))
head(lap_nums)


# cbind nums with hog features
dat = cbind(id = lap_nums, hog)
dim(dat)


# get numbers from driver - files
drv <- read.csv("../input/driver_imgs_list.csv", stringsAsFactors = F)
head(drv)

# extract numbers from drivers
lap_drv = unlist(lapply(drv$img, function(x) as.numeric(strsplit(strsplit(x, '[.]')[[1]][1], '_')[[1]][2])))
head(lap_drv)


# add lap_drv to driver files
drv$id = lap_drv


# merge hog features with the drivers data
merg = merge(drv, dat, by.x = 'id', by.y = 'id')
dim(merg)


# drivers
drivers = as.numeric(as.factor(merg$subject))
table(drivers)

# response in numeric form
y = as.numeric(as.factor(merg$classname))
table(y)

# end train data
x = merg[, 5:ncol(merg)]
dim(x)

gc()

#------------------------------------------------------------

# 4-fold cross-validation, split by driver

cv_folds = split(1:length(unique(drivers)), 1:4)
str(cv_folds)

folds = lapply(cv_folds, function(x) which( c(drivers) %in% x))
length(unlist(folds)); length(unique(unlist(folds))); str(folds)


#------------------------------------------------------------

# Evaluation metrics

# accuracy function
acc = function (y_true, preds) {
  
  out = table(y_true, max.col(preds, ties.method = "random"))
  
  acc = sum(diag(out))/sum(out)
  
  acc
}


# multiclass-log-loss function
MultiLogLoss = function (y_true, y_pred) {
  
  if (is.factor(y_true)) {
    
    y_true_mat <- matrix(0, nrow = length(y_true), ncol = length(levels(y_true)))
    
    sample_levels <- as.integer(y_true)
    
    for (i in 1:length(y_true)) y_true_mat[i, sample_levels[i]] <- 1
    
    y_true <- y_true_mat
  }
  
  eps <- 1e-15
  
  N <- dim(y_pred)[1]
  
  y_pred <- pmax(pmin(y_pred, 1 - eps), eps)
  
  MultiLogLoss <- (-1/N) * sum(y_true * log(y_pred))
  
  return(MultiLogLoss)
}

#-----------------------------------------------------------------------



vec_err = rep(NA, length(cv_folds))

for (i in 1:length(cv_folds)) {
  
  cat('fold', i, '\n')
  
  fit_diff = KernelKnn(x[unlist(folds[-i]), ], TEST_data = x[unlist(folds[i]), ], y[unlist(folds[-i])], k = 1250, method = 'braycurtis', 
                       
                       weights_function = 'biweight_triweight_gaussian_triangular_MULT', regression = F, threads = 4, Levels = unique(y))
  
  vec_err[i] = MultiLogLoss(as.factor(y[unlist(folds[i])]), fit_diff)
  
  cat('accuracy :', acc(y[unlist(folds[i])], fit_diff), '---', 'mclass-logloss', MultiLogLoss(as.factor(y[unlist(folds[i])]), fit_diff), '\n'); cat('\n')
}


cat('mean multi-class log-loss is:', mean(vec_err), '\n')

