class FoodClassifier:
    #Class Attributes:
    #model - the underlying keras model
    #labels - the labels to be associated with the activation of each output neuron. 
    #Labels must be the same size as the output layer of the neural network.    
    
    
    def __init__(self, modelpath, labels, min_confidence = 0.6):
        from keras.models import load_model
        from keras.applications.resnet50 import ResNet50
        self.resnet = ResNet50(include_top=False,weights='imagenet',pooling='max',input_shape=(224,224,3))
        self.extModel = load_model(modelpath)
        
        if(isinstance(labels,str)):
            #its a file path
            from os.path import exists
            if(exists(labels)):
                f = open(labels,'r')
                x = f.readlines()
                y = []
                for i in x:
                    y.append(i.split('\n')[0])
                self.labels = y
        else:
            self.labels = labels
	
        self.num_classes = len(labels)
        self.min_confidence=min_confidence
    
    
    def predict(self,img):
        import os
        from PIL import Image
        from keras.preprocessing.image import img_to_array
        import numpy as np 
        #check if image is a filepath
        if(isinstance(img,str)):
            if(not os.path.exists(img)):
                print("Error: Invalid File Path")
                return ""
            else:
                #if its a filepath, convert to PIL image
                img = Image.open(img)
        
        #resize image
        #shape from model input
        shape = self.resnet.input_shape
        imgr = img.resize(shape[1:3])
        
        x = img_to_array(imgr).reshape((1,shape[1],shape[2],shape[3]))
        
        
        #predict
        features = self.resnet.predict(x)
        prediction = self.extModel.predict(features)
        
        #get max of predictions and return label(s)
        predIdx = np.argmax(prediction)
        if(prediction[0,predIdx]<self.min_confidence):
            return ""
        else:
            return self.labels[predIdx]
    
    
    
    def set_extModel(self,model):
        self.extModel = model
    
    def get_extModel(self):
        return self.extModel
    
    
    def set_labels(self,labels):
        self.labels = labels
        
    def get_labels(self):
        return self.labels
    
    
    def set_min_confidence(self,conf):
        self.min_confidence=conf
    
    def get_min_confidence(self):
        return self.min_confidence
    
        
def generate_features_from_directory(location,target_image_count,model=None):
    #generates feature maps from the convolutional layers of ResNet50 using all
    #images from the directory
    #INPUT:
        #directory containing NESTED DIRECTORIES  of images. (Very Important)
        #the number of feature maps to generate for each image class
    #OUTPUT:
        #a npy file containing the 2048-dimensional feature vector 
        #produced by ResNet50's convolutional layers
    
    #data is generated in batches of 32
    
    import numpy as np
    
    from keras.preprocessing.image import ImageDataGenerator
    from keras.applications.resnet50 import ResNet50
    from os import listdir
    from os.path import isdir 
    
    #create the model, if not defined
    if model==None:
        model = ResNet50(weights='imagenet',include_top=False,pooling='max')
    
    #create the data generation
    
    datagen = ImageDataGenerator()
    
    #for each directory in 
    if(not isdir(location)):
        print("could not find location: " + location)
        return
    for label in listdir(location):
        #first check that its a directory
        label_path = location+'/'+label
        if(not isdir(label_path)):
            continue
        
        #create the data generator
        #Output size is 256x256 to fit the ResNet50
        print("Generating feature maps for " + label + "...")
        generator = datagen.flow_from_directory(
                 label_path,
                 target_size = (224,224),
                 batch_size = 32, 
                 class_mode=None)
        
        #use ResNet50 to create the features
        features = model.predict_generator(generator,target_image_count/32)
        #features = np.reshape(features,(features.shape[0],features.shape[3]))
        
        #save the features in a numpy binary
        np.save(location+'/'+label+'.npy', features)
        
    
        
        
def create_data_set(data_path,output_folder,save_to_file=True):
    #combines all npy files into one large file with their respective labels
    #INPUTS:
        #a directory containing npy fils of all different classes
    #Outputs:
        #training array and training labels
        #label array is returned as a one hot encoding
        #label names
        
    from os.path import isdir
    from os import listdir
    import numpy as np
    
    #find out how many classes
    num_classes = 0
    label_names = []
    
    if(not isdir(data_path)):
        print("Could not find directory: "+ data_path)
        return
    
    data_contents = listdir(data_path)
    for f in data_contents:
        if(f.endswith('.npy')):
            num_classes +=1
            label_names.append(f.split('.')[0])
    
    if(num_classes==0):
        print("Could not find any data files in directory: "+data_path)
        return
    
    #generate one-hot label vectors
    labels = np.zeros([num_classes,num_classes])
    for i in range(0,num_classes):
        labels[i][i]=1
    
    #load all arrays into memory. 
    #In the future, might need to do this on either a high ram machine
    #or find another way to concatenate data
    arrays = []
    sizes = []
    for f in data_contents:
        if(f.endswith('.npy')):
            arr = np.load(data_path+'/'+f)
            sizes.append(arr.shape[0])
            arrays.append(arr)
    
    X = np.vstack([arr for arr in arrays])
    
    #load the labels into memory
    labelcodes = []
    for i in range(0,num_classes):
        labelcodes.append(np.vstack([labels[i]]*sizes[i]))
    y = np.vstack([l for l in labelcodes])
    
    if(save_to_file):
        np.save(output_folder+'/data_set.npy',X)
        np.save(output_folder+'/label_codes.npy',y)
        with open(output_folder+"/labels.txt","w") as output:
            output.write("".join([label + '\n' for label in label_names]))
    
    return X,y,label_names
        


def train_classifier_from_images(train_dir,train_size,val_dir,val_size,output_dir):
    #INPUTS:
        #train_dir is the directory containig the training images
        #test_dir is the directory containing the validation images
        #output_dir is the directory to save the trained model
        #train_size is the number of images to generate for each training class
        #val_size is the number of images to generate for each validation class
    #OUTPUTS
    #A model that takes as input a 2048-vector of feature maps and outputs
    #a prediction of what an image with those features might be.
    #The labels file is also placed in this directory
    
    #The model created is an SVM with softmax activation.
    from time import time
    from keras.applications.resnet50 import ResNet50
    from keras.models import Sequential
    from keras.optimizers import SGD
    from keras.regularizers import l2
    from keras.layers import Dense
    from sklearn.utils import shuffle
    from keras.callbacks import EarlyStopping, ModelCheckpoint
    
    #import ResNet50 without top layer
    print("Loading the ResNet50 Network...")
    resnet = ResNet50(weights='imagenet',include_top=False,pooling='max')
    
    #create the training and validation datasets for each class
    print("Generating Training Set...")
    generate_features_from_directory(train_dir,train_size,model=resnet)
    print("Generating Testing Set...")
    generate_features_from_directory(val_dir,val_size,model=resnet)
    
    #create the combined dataset
    print("Combining datasets...")
    X_train,y_train,labels = create_data_set(train_dir,output_dir+"/train",save_to_file=True)
    X_val,y_val,labels = create_data_set(val_dir,output_dir+"/validation",save_to_file=True)
    
    #shuffle the train data
    X_train,y_train = shuffle(X_train,y_train)
    
    num_classes = len(labels)
    
    #create the extension model
    print("Creating extension model...")
    extModel = Sequential()
    extModel.add(Dense(num_classes,input_shape=(2048,), activation='softmax', W_regularizer=l2(0.01)))
    extModel.compile(loss='hinge',optimizer=SGD(lr=0.01,momentum=0.9),metrics=["accuracy"])
    
    #callbacks
    checkpoint = ModelCheckpoint(output_dir + "/extModel"+str(int(time()))+".h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)
    early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')
    
    
    with open(output_dir+"/labels.txt","w") as output:
        output.write("".join([label + '\n' for label in labels]))
    
    #train model
    print("Training...")
    extModel.fit(X_train,y_train,
                 batch_size=32,
                 epochs=50,
                 validation_data=(X_val,y_val),
                 callbacks = [checkpoint,early])
    
    return extModel

def add_to_train(train_dir,image,label, resnet):
    #INPUTS
        #Train_dir - the directory that all npy files are contained
        #image - the path to the image being added
        #resnet - the resnet model to be used for feature determination
        #label - the name of the item
    #Appends the features of the new item to the training set data for that label
    from PIL import Image
    from os.path import exists
    from keras.preprocessing.image import img_to_array 
    
    if(isinstance(image,str)):
        if(not exists(image)):
            print("Error: Invalid File Path")
            return ""
        else:
            #if its a filepath, convert to PIL image
            img = Image.open(image)
    
    shape = resnet.input_shape
    imgr = img.resize(shape[1:3])
    
    x = img_to_array(imgr).reshape((1,shape[1],shape[2],shape[3]))
    
    #predict
    features = resnet.predict(x)
    
    
    

    import numpy as np
    
    npyname = train_dir+'/'+label+'.npy'
    if(not exists(npyname)):
        np.save(npyname,features)    
    else:
        fullset = np.load(npyname)
        newset = np.append(fullset,features,axis=0)
        np.save(npyname,newset)

[((112, 14, 112, 34), 'keras.preprocessing.image.ImageDataGenerator', 'ImageDataGenerator', ({}, {}), '()', False, 'from keras.preprocessing.image import ImageDataGenerator\n'), ((118, 17, 118, 34), 'os.listdir', 'listdir', ({(118, 25, 118, 33): 'location'}, {}), '(location)', False, 'from os import listdir\n'), ((164, 20, 164, 38), 'os.listdir', 'listdir', ({(164, 28, 164, 37): 'data_path'}, {}), '(data_path)', False, 'from os import listdir\n'), ((175, 13, 175, 48), 'numpy.zeros', 'np.zeros', ({(175, 22, 175, 47): '[num_classes, num_classes]'}, {}), '([num_classes, num_classes])', True, 'import numpy as np\n'), ((190, 8, 190, 42), 'numpy.vstack', 'np.vstack', ({(190, 18, 190, 41): '[arr for arr in arrays]'}, {}), '([arr for arr in arrays])', True, 'import numpy as np\n'), ((196, 8, 196, 42), 'numpy.vstack', 'np.vstack', ({(196, 18, 196, 41): '[l for l in labelcodes]'}, {}), '([l for l in labelcodes])', True, 'import numpy as np\n'), ((232, 13, 232, 73), 'keras.applications.resnet50.ResNet50', 'ResNet50', (), '', False, 'from keras.applications.resnet50 import ResNet50\n'), ((246, 22, 246, 46), 'sklearn.utils.shuffle', 'shuffle', ({(246, 30, 246, 37): 'X_train', (246, 38, 246, 45): 'y_train'}, {}), '(X_train, y_train)', False, 'from sklearn.utils import shuffle\n'), ((252, 15, 252, 27), 'keras.models.Sequential', 'Sequential', ({}, {}), '()', False, 'from keras.models import Sequential\n'), ((258, 12, 258, 94), 'keras.callbacks.EarlyStopping', 'EarlyStopping', (), '', False, 'from keras.callbacks import EarlyStopping, ModelCheckpoint\n'), ((11, 22, 11, 106), 'keras.applications.resnet50.ResNet50', 'ResNet50', (), '', False, 'from keras.applications.resnet50 import ResNet50\n'), ((12, 24, 12, 45), 'keras.models.load_model', 'load_model', ({(12, 35, 12, 44): 'modelpath'}, {}), '(modelpath)', False, 'from keras.models import load_model\n'), ((58, 18, 58, 39), 'numpy.argmax', 'np.argmax', ({(58, 28, 58, 38): 'prediction'}, {}), '(prediction)', True, 'import numpy as np\n'), ((108, 16, 108, 76), 'keras.applications.resnet50.ResNet50', 'ResNet50', (), '', False, 'from keras.applications.resnet50 import ResNet50\n'), ((115, 11, 115, 26), 'os.path.isdir', 'isdir', ({(115, 17, 115, 25): 'location'}, {}), '(location)', False, 'from os.path import isdir\n'), ((138, 8, 138, 52), 'numpy.save', 'np.save', ({(138, 16, 138, 41): "(location + '/' + label + '.npy')", (138, 43, 138, 51): 'features'}, {}), "(location + '/' + label + '.npy', features)", True, 'import numpy as np\n'), ((160, 11, 160, 27), 'os.path.isdir', 'isdir', ({(160, 17, 160, 26): 'data_path'}, {}), '(data_path)', False, 'from os.path import isdir\n'), ((199, 8, 199, 48), 'numpy.save', 'np.save', ({(199, 16, 199, 45): "(output_folder + '/data_set.npy')", (199, 46, 199, 47): 'X'}, {}), "(output_folder + '/data_set.npy', X)", True, 'import numpy as np\n'), ((200, 8, 200, 51), 'numpy.save', 'np.save', ({(200, 16, 200, 48): "(output_folder + '/label_codes.npy')", (200, 49, 200, 50): 'y'}, {}), "(output_folder + '/label_codes.npy', y)", True, 'import numpy as np\n'), ((307, 11, 307, 26), 'os.path.exists', 'exists', ({(307, 18, 307, 25): 'npyname'}, {}), '(npyname)', False, 'from os.path import exists\n'), ((308, 8, 308, 33), 'numpy.save', 'np.save', ({(308, 16, 308, 23): 'npyname', (308, 24, 308, 32): 'features'}, {}), '(npyname, features)', True, 'import numpy as np\n'), ((310, 18, 310, 34), 'numpy.load', 'np.load', ({(310, 26, 310, 33): 'npyname'}, {}), '(npyname)', True, 'import numpy as np\n'), ((311, 17, 311, 51), 'numpy.append', 'np.append', (), '', True, 'import numpy as np\n'), ((312, 8, 312, 31), 'numpy.save', 'np.save', ({(312, 16, 312, 23): 'npyname', (312, 24, 312, 30): 'newset'}, {}), '(npyname, newset)', True, 'import numpy as np\n'), ((17, 15, 17, 29), 'os.path.exists', 'exists', ({(17, 22, 17, 28): 'labels'}, {}), '(labels)', False, 'from os.path import exists\n'), ((121, 15, 121, 32), 'os.path.isdir', 'isdir', ({(121, 21, 121, 31): 'label_path'}, {}), '(label_path)', False, 'from os.path import isdir\n'), ((186, 18, 186, 42), 'numpy.load', 'np.load', ({(186, 26, 186, 41): "data_path + '/' + f"}, {}), "(data_path + '/' + f)", True, 'import numpy as np\n'), ((195, 26, 195, 57), 'numpy.vstack', 'np.vstack', ({(195, 36, 195, 56): '([labels[i]] * sizes[i])'}, {}), '([labels[i]] * sizes[i])', True, 'import numpy as np\n'), ((254, 44, 254, 69), 'keras.optimizers.SGD', 'SGD', (), '', False, 'from keras.optimizers import SGD\n'), ((286, 15, 286, 28), 'os.path.exists', 'exists', ({(286, 22, 286, 27): 'image'}, {}), '(image)', False, 'from os.path import exists\n'), ((291, 18, 291, 35), 'PIL.Image.open', 'Image.open', ({(291, 29, 291, 34): 'image'}, {}), '(image)', False, 'from PIL import Image\n'), ((296, 8, 296, 26), 'keras.preprocessing.image.img_to_array', 'img_to_array', ({(296, 21, 296, 25): 'imgr'}, {}), '(imgr)', False, 'from keras.preprocessing.image import img_to_array\n'), ((38, 19, 38, 38), 'os.path.exists', 'os.path.exists', ({(38, 34, 38, 37): 'img'}, {}), '(img)', False, 'import os\n'), ((43, 22, 43, 37), 'PIL.Image.open', 'Image.open', ({(43, 33, 43, 36): 'img'}, {}), '(img)', False, 'from PIL import Image\n'), ((50, 12, 50, 30), 'keras.preprocessing.image.img_to_array', 'img_to_array', ({(50, 25, 50, 29): 'imgr'}, {}), '(imgr)', False, 'from keras.preprocessing.image import img_to_array\n'), ((253, 92, 253, 100), 'keras.regularizers.l2', 'l2', ({(253, 95, 253, 99): '(0.01)'}, {}), '(0.01)', False, 'from keras.regularizers import l2\n'), ((257, 66, 257, 72), 'time.time', 'time', ({}, {}), '()', False, 'from time import time\n')]