import base64
import io

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

import numpy as np
import tensorflow as tf
from PIL import Image

from constants import CLASSES

import yaml 
with open('app.yaml') as yaml_data :
    params = yaml.safe_load(yaml_data)
    
IMAGE_WIDTH = params['IMAGE_WIDTH']
IMAGE_HEIGHT = params['IMAGE_HEIGHT']
PATH_MODEL = params['PATH_MODEL']


# Load DNN model
classifier = tf.keras.models.load_model(PATH_MODEL)

def classify_image(image, model, image_box=None):
  """Classify image by model

  Parameters
  ----------
  content: image content
  model: tf/keras classifier

  Returns
  -------
  class id returned by model classifier
  """
  images_list = []
  image = image.resize((IMAGE_WIDTH, IMAGE_HEIGHT), box=image_box)
                                        # box argument clips image to (x1, y1, x2, y2)
  image = np.array(image)
  images_list.append(image)
  
  return model.predict_classes(np.array(images_list))


app = dash.Dash('Traffic Signs Recognition', external_stylesheets=[dbc.themes.BOOTSTRAP])


pre_style = {
    'whiteSpace': 'pre-wrap',
    'wordBreak': 'break-all',
    'whiteSpace': 'normal'
}


# Define application layout


navbar = dbc.NavbarSimple(
    children=[
        dbc.DropdownMenu(
            children=[
                dbc.DropdownMenuItem('Réseau de Neurones', header=True),
                dbc.DropdownMenuItem('SVM', href="#"),
            ],
            nav=True,
            in_navbar=True,
            label='Modèle',
        ),
    ],
    brand="Menu",
    brand_href="#",
    color= "#d90054",
    dark=True
)

cards = html.Div(  
    [
        dbc.Card(
            dbc.CardBody(
                [
                    html.H5("Présentation", className="card-title"),
                    html.P(
                        [
                            'Cette application à pour but de réaliser des modèles capables de classer des panneaux de signalisation allemand à partir d\'une image. L\'application fonctionne de la manière suivante : vous déposer une image à l\'emplacement indiqué et la prédiction du modèle apparait immédiatement en dessous. En haut à droite vous pouvez sélectionner le modèle que vous voulez tester.',
                        ],
                        className='card-text',
                    ),
                ]
            ),
            className='w-75 mb-3', 
            color='#f1cbd1', 
            outline='Black',  
            style={
                'margin-top': '75px',  
                'margin-left': '185px'}, 
        ),
    ]
)

app.layout = html.Div([
    html.Div([navbar]),
    
    html.Div(cards),
    dcc.Upload(
        id='bouton-chargement',
        children=html.Div([
            'Cliquer-déposer ou ',
                    html.A('sélectionner une image')
        ]),
        style={
            'width': '50%',
            'height': '60px',
            'lineHeight': '60px',
            'borderWidth': '1px',
            'borderStyle': 'dashed',
            'borderRadius': '5px',
            'textAlign': 'center',
            'margin-top': '75px',
            'margin-left': '370px', 
        }
    ),
    html.Div(id='mon-image'),
    html.Div(id='ma-zone-resultat')
])

@app.callback(Output('mon-image', 'children'),
              [Input('bouton-chargement', 'contents')])
def update_output(contents):
    if contents is not None:
        content_type, content_string = contents.split(',')
        if 'image' in content_type:
            image = Image.open(io.BytesIO(base64.b64decode(content_string)))
            predicted_class = classify_image(image, classifier)[0]
            return html.Div([
                html.Hr(style={'margin-top': '75px'}),
                html.Img(src=contents, style={'margin-left': '750px'}),
                html.H4('Classe prédite : {}'.format(CLASSES[predicted_class]), style={'textAlign': 'center'}),
                html.Hr(),
                #html.Div('Raw Content'),
                #html.Pre(contents, style=pre_style)
            ])
        else:
            try:
                # Décodage de l'image transmise en base 64 (cas des fichiers ppm)
                # fichier base 64 --> image PIL
                image = Image.open(io.BytesIO(base64.b64decode(content_string)))
                # image PIL --> conversion PNG --> buffer mémoire 
                buffer = io.BytesIO()
                image.save(buffer, format='PNG')
                # buffer mémoire --> image base 64
                buffer.seek(0)
                img_bytes = buffer.read()
                content_string = base64.b64encode(img_bytes).decode('ascii')
                # Appel du modèle de classification
                predicted_class = classify_image(image, classifier)[0]
                # Affichage de l'image
                return html.Div([
                    html.Hr(style={'margin-top': '75px'}),
                    html.Img(src='data:image/png;base64,' + content_string, style={'margin-left': '750px'}),
                    html.H4('Classe prédite : {}'.format(CLASSES[predicted_class]), style={'textAlign': 'center'}),
                    html.Hr(),
                ])
            except:
                return html.Div([
                    html.Hr(),
                    html.Div('Uniquement des images svp : {}'.format(content_type)),
                    html.Hr(),                
                    html.Div('Raw Content'),
                    html.Pre(contents, style=pre_style)
                ])
            

# Manage interactions with callbacks
@app.callback(
    Output(component_id='ma-zone-resultat', component_property='children'),
    [Input(component_id='mon-champ-texte', component_property='value')]
)
def update_output_div(input_value):
    return html.H3('Valeur saisie ici "{}"'.format(input_value))


# Start the application
if __name__ == '__main__':
    app.run_server(debug=True)
[((524, 562), 'tensorflow.keras.models.load_model', 'tf.keras.models.load_model', (['PATH_MODEL'], {}), '(PATH_MODEL)\n', (550, 562), True, 'import tensorflow as tf\n'), ((1083, 1171), 'dash.Dash', 'dash.Dash', (['"""Traffic Signs Recognition"""'], {'external_stylesheets': '[dbc.themes.BOOTSTRAP]'}), "('Traffic Signs Recognition', external_stylesheets=[dbc.themes.\n    BOOTSTRAP])\n", (1092, 1171), False, 'import dash\n'), ((353, 378), 'yaml.safe_load', 'yaml.safe_load', (['yaml_data'], {}), '(yaml_data)\n', (367, 378), False, 'import yaml\n'), ((974, 989), 'numpy.array', 'np.array', (['image'], {}), '(image)\n', (982, 989), True, 'import numpy as np\n'), ((3345, 3376), 'dash.dependencies.Output', 'Output', (['"""mon-image"""', '"""children"""'], {}), "('mon-image', 'children')\n", (3351, 3376), False, 'from dash.dependencies import Input, Output\n'), ((5671, 5741), 'dash.dependencies.Output', 'Output', ([], {'component_id': '"""ma-zone-resultat"""', 'component_property': '"""children"""'}), "(component_id='ma-zone-resultat', component_property='children')\n", (5677, 5741), False, 'from dash.dependencies import Input, Output\n'), ((1052, 1073), 'numpy.array', 'np.array', (['images_list'], {}), '(images_list)\n', (1060, 1073), True, 'import numpy as np\n'), ((2702, 2720), 'dash_html_components.Div', 'html.Div', (['[navbar]'], {}), '([navbar])\n', (2710, 2720), True, 'import dash_html_components as html\n'), ((2731, 2746), 'dash_html_components.Div', 'html.Div', (['cards'], {}), '(cards)\n', (2739, 2746), True, 'import dash_html_components as html\n'), ((3265, 3289), 'dash_html_components.Div', 'html.Div', ([], {'id': '"""mon-image"""'}), "(id='mon-image')\n", (3273, 3289), True, 'import dash_html_components as html\n'), ((3295, 3326), 'dash_html_components.Div', 'html.Div', ([], {'id': '"""ma-zone-resultat"""'}), "(id='ma-zone-resultat')\n", (3303, 3326), True, 'import dash_html_components as html\n'), ((3393, 3431), 'dash.dependencies.Input', 'Input', (['"""bouton-chargement"""', '"""contents"""'], {}), "('bouton-chargement', 'contents')\n", (3398, 3431), False, 'from dash.dependencies import Input, Output\n'), ((5748, 5813), 'dash.dependencies.Input', 'Input', ([], {'component_id': '"""mon-champ-texte"""', 'component_property': '"""value"""'}), "(component_id='mon-champ-texte', component_property='value')\n", (5753, 5813), False, 'from dash.dependencies import Input, Output\n'), ((4471, 4483), 'io.BytesIO', 'io.BytesIO', ([], {}), '()\n', (4481, 4483), False, 'import io\n'), ((1819, 1866), 'dash_html_components.H5', 'html.H5', (['"""Présentation"""'], {'className': '"""card-title"""'}), "('Présentation', className='card-title')\n", (1826, 1866), True, 'import dash_html_components as html\n'), ((1888, 2301), 'dash_html_components.P', 'html.P', (['["Cette application à pour but de réaliser des modèles capables de classer des panneaux de signalisation allemand à partir d\'une image. L\'application fonctionne de la manière suivante : vous déposer une image à l\'emplacement indiqué et la prédiction du modèle apparait immédiatement en dessous. En haut à droite vous pouvez sélectionner le modèle que vous voulez tester."\n    ]'], {'className': '"""card-text"""'}), '([\n    "Cette application à pour but de réaliser des modèles capables de classer des panneaux de signalisation allemand à partir d\'une image. L\'application fonctionne de la manière suivante : vous déposer une image à l\'emplacement indiqué et la prédiction du modèle apparait immédiatement en dessous. En haut à droite vous pouvez sélectionner le modèle que vous voulez tester."\n    ], className=\'card-text\')\n', (1894, 2301), True, 'import dash_html_components as html\n'), ((3629, 3661), 'base64.b64decode', 'base64.b64decode', (['content_string'], {}), '(content_string)\n', (3645, 3661), False, 'import base64\n'), ((3777, 3814), 'dash_html_components.Hr', 'html.Hr', ([], {'style': "{'margin-top': '75px'}"}), "(style={'margin-top': '75px'})\n", (3784, 3814), True, 'import dash_html_components as html\n'), ((3832, 3886), 'dash_html_components.Img', 'html.Img', ([], {'src': 'contents', 'style': "{'margin-left': '750px'}"}), "(src=contents, style={'margin-left': '750px'})\n", (3840, 3886), True, 'import dash_html_components as html\n'), ((4016, 4025), 'dash_html_components.Hr', 'html.Hr', ([], {}), '()\n', (4023, 4025), True, 'import dash_html_components as html\n'), ((1411, 1466), 'dash_bootstrap_components.DropdownMenuItem', 'dbc.DropdownMenuItem', (['"""Réseau de Neurones"""'], {'header': '(True)'}), "('Réseau de Neurones', header=True)\n", (1431, 1466), True, 'import dash_bootstrap_components as dbc\n'), ((1484, 1521), 'dash_bootstrap_components.DropdownMenuItem', 'dbc.DropdownMenuItem', (['"""SVM"""'], {'href': '"""#"""'}), "('SVM', href='#')\n", (1504, 1521), True, 'import dash_bootstrap_components as dbc\n'), ((2879, 2911), 'dash_html_components.A', 'html.A', (['"""sélectionner une image"""'], {}), "('sélectionner une image')\n", (2885, 2911), True, 'import dash_html_components as html\n'), ((4344, 4376), 'base64.b64decode', 'base64.b64decode', (['content_string'], {}), '(content_string)\n', (4360, 4376), False, 'import base64\n'), ((4690, 4717), 'base64.b64encode', 'base64.b64encode', (['img_bytes'], {}), '(img_bytes)\n', (4706, 4717), False, 'import base64\n'), ((4950, 4987), 'dash_html_components.Hr', 'html.Hr', ([], {'style': "{'margin-top': '75px'}"}), "(style={'margin-top': '75px'})\n", (4957, 4987), True, 'import dash_html_components as html\n'), ((5009, 5101), 'dash_html_components.Img', 'html.Img', ([], {'src': "('data:image/png;base64,' + content_string)", 'style': "{'margin-left': '750px'}"}), "(src='data:image/png;base64,' + content_string, style={\n    'margin-left': '750px'})\n", (5017, 5101), True, 'import dash_html_components as html\n'), ((5234, 5243), 'dash_html_components.Hr', 'html.Hr', ([], {}), '()\n', (5241, 5243), True, 'import dash_html_components as html\n'), ((5338, 5347), 'dash_html_components.Hr', 'html.Hr', ([], {}), '()\n', (5345, 5347), True, 'import dash_html_components as html\n'), ((5454, 5463), 'dash_html_components.Hr', 'html.Hr', ([], {}), '()\n', (5461, 5463), True, 'import dash_html_components as html\n'), ((5501, 5524), 'dash_html_components.Div', 'html.Div', (['"""Raw Content"""'], {}), "('Raw Content')\n", (5509, 5524), True, 'import dash_html_components as html\n'), ((5546, 5581), 'dash_html_components.Pre', 'html.Pre', (['contents'], {'style': 'pre_style'}), '(contents, style=pre_style)\n', (5554, 5581), True, 'import dash_html_components as html\n')]