├── .gitignore ├── LICENSE ├── README.md ├── app.py ├── digit.png ├── mnist_nn.py ├── model ├── mnistModel.h5 └── mnistModel.json ├── static ├── index.js ├── starter-template.css └── uploads │ ├── 9.png │ ├── nine.png │ └── output.png └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ian Burke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Digit Recognition Web Application 2 | #### *Emerging Technologies Module - Lecturer: [Dr Ian McLoughlin](ianmcloughlin.github.io) - 4th Year Software Development* 3 | For my project in [Emerging Technologies](https://emerging-technologies.github.io/), I am required to create a web application in [Python](https://www.python.org/) to recognise digits in images. Users will be able to visit the web application through their browser, submit (or draw) an image containing a single digit, and the web application will respond with the digit contained in the image. [Flask](http://flask.pocoo.org/) will be used to run the web application and [Keras](https://keras.io/) will be used to help with the digit recognition. 4 | 5 | **_For more: [Project Instructions](https://emerging-technologies.github.io/problems/project.html)_** 6 | 7 | ## How to run 8 | 1. Click [here](https://github.com/ianburkeixiv/Python-TensorFlow-WebApp/archive/master.zip) to download the zip of the project. 9 | 2. Unzip the project. 10 | 3. Open a command terminal and cd into project directory 11 | 4. Enter the following to run the app: 12 | 13 | ```python 14 | 15 | python app.py 16 | 17 | ``` 18 | ## Architecture 19 | 20 | ## Python 21 | [![PyPI](https://img.shields.io/pypi/pyversions/Django.svg)]() 22 | 23 | The main programming language used in this problem sheet is [Python](https://www.python.org/) 24 | 25 | ## Flask 26 | [Flask](http://flask.pocoo.org/) is a Python micro web framework that provides tools, libraries and technologies that allow us to build a web application. 27 | 28 | ## Numpy 29 | [NumPy](http://www.numpy.org/) is a library for the [Python](https://www.python.org/) programming language, adding support for large, multi-dimensional arrays and matrices, along with a large collection of high-level mathematical functions to operate on these arrays. It is the fundamental package for scientific computing with Python. 30 | 31 | ## TensorFlow 32 | ![](https://user-images.githubusercontent.com/22341150/33095338-3573a9cc-cefb-11e7-9030-42e3f298e0b7.png) 33 | 34 | [TensorFlow](https://www.tensorflow.org/) is a Python open source library for fast numerical computing created and released by Google. It is used for machine learning applications such as [neural networks](https://en.wikipedia.org/wiki/Artificial_neural_network). It allows users to express arbitary computation as a graph of data flows. Nodes in this graph represent mathematical operations and the edges represent data that is communicated from one node to another. Data in TensorFlow are represented as tensors which are multidimensional arrays. 35 | 36 | ## Keras 37 | ![](https://user-images.githubusercontent.com/22341150/33095362-4cf67246-cefb-11e7-87e5-cad404557eec.png) 38 | 39 | [Keras](https://keras.io/) is as high level neural network API running on top of TensorFlow. Designed to enable fast and easy experimentation with deep neural networks. Keras is more minimal than TensorFlow as it runs seamlessly on CPU and GPU. It allows for fast prototyping due to its user friendliness. 40 | 41 | ## MNIST Dataset 42 | [MNIST]( http://yann.lecun.com/exdb/mnist/) is a famous dataset that consists of handwritten digits commonly used for training various image processing systems and also used in machine learning. The dataset contains 60,000 training images and 10,000 testing images. Each image is a 28x28 pixel square (784 pixels in total). A standard split of the dataset is used to evaluate and compare models. Excellent results achieve a prediction error/loss of less than 1% and accuracy of up to 0.99%. 43 | 44 | ![](https://www.tensorflow.org/images/mnist_digits.png) 45 | 46 | ### Neural Networks 47 | In each hemisphere of our brain, humans have a primary visual cortex, also known as V1, containing 140 million neurons, with tens of billions of connections between them. And yet human vision involves not just V1, but an entire series of visual cortices - V2, V3, V4, and V5 - doing progressively more complex image processing. We carry in our heads a supercomputer, tuned by evolution over hundreds of millions of years, and superbly adapted to understand the visual world. Recognizing handwritten digits isn't easy. We humans are astoundingly good at making sense of what our eyes show us. But nearly all that work is done unconsciously. And so we don't usually appreciate how tough a problem our visual systems solve. Adapted from: http://neuralnetworksanddeeplearning.com/chap1.html 48 | 49 | ![](http://neuralnetworksanddeeplearning.com/images/tikz11.png) 50 | 51 | #### Artificial Neural Networks 52 | *_[Artificial Neural Network](https://en.wikipedia.org/wiki/Artificial_neural_network)_* is a computational model that is inspired by the way biological neural networks in the human brain process information. Artificial Neural Networks have generated a lot of excitement in Machine Learning research and industry. The basic unit of computation in a neural network is the neuron, often called a node or unit. It receives input from some other nodes, or from an external source and computes an output. Each input has an associated weight (w), which is assigned on the basis of its relative importance to other inputs. 53 | 54 | ![](https://ujwlkarn.files.wordpress.com/2016/08/screen-shot-2016-08-09-at-3-42-21-am.png?w=768&h=410) 55 | 56 | ### Convolutional Neural Network (CNN) 57 | [Convolutional Neural Networks (CNN)](http://cs231n.github.io/convolutional-networks/) are multi-layer neural networks that have successfully been applied to analyzing visual imagery. The main feature of CNN's is that it can drastically reduce the number of parameters that need to be tuned. This means that CNN's can efficiently handle the high dimensionality of raw images. 58 | 59 | Convolutional neural networks are more complex than standard multi-layer perceptrons. In this project, we use 8 layers. 60 | 1. The first hidden layer is a convolutional layer called a Convolution2D. In our model, the layer has 32 filters, which with the size of 3×3 and a rectified linear unit activation function. This is the input layer, expecting images with the structure outline (pixels, width, height). 61 | 2. We add another covolutional layer with 64 filters. 62 | 3. Next we define a pooling layer called MaxPooling2D which is a way to reduce the number of parameters in our model. It is configured by sliding a 2x2 pooling filter across the previous layer and taking the max of the 4 values in the 2x2 filter. 63 | 4. The next layer is a regularization layer using dropout called Dropout. It is configured to randomly exclude 25% of neurons in the layer in order to reduce overfitting. 64 | 5. Next is a layer that converts the 2D matrix data to a vector called Flatten. It allows the output to be processed by standard fully connected layers. 65 | 6. Next a fully connected layer with 128 neurons and rectified linear unit activation function. 66 | 8. Finally, the output layer has 10 neurons for the 10 classes and a softmax activation function to output probability-like predictions for each class. 67 | 68 | ## Conclusion 69 | Using flask, we have published a web app that displays a canvas where a user can draw a digit inside the canvas with their mouse. The canvas data is then converted into an appropiate image to pass into our loaded model which predicts the digit and returns the result to the user. I have used a convolutional neural network which is renowned as the most accurate with a low loss rate. 70 | 71 | Video: [![Neural Network 3D Simulation](https://img.youtube.com/vi/https://www.youtube.com/watch?v=3JQ3hYko51Y/0.jpg)](https://www.youtube.com/watch?v=3JQ3hYko51Y) 72 | 73 | 74 | ## References 75 | [The MNIST Database](http://yann.lecun.com/exdb/mnist/) 76 | 77 | [CNN Tutorial](https://machinelearningmastery.com/handwritten-digit-recognition-using-convolutional-neural-networks-python-keras/) 78 | 79 | [Keras tutorial](https://elitedatascience.com/keras-tutorial-deep-learning-in-python) 80 | 81 | [Deep learning](http://neuralnetworksanddeeplearning.com/chap1.html) 82 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, flash, render_template, request, redirect, url_for, send_from_directory 2 | from werkzeug.utils import secure_filename 3 | import os 4 | from skimage import color # change colour of image 5 | from scipy.misc import imread, imresize, imsave # For images 6 | import numpy as np # martix math 7 | import re #regular expression used for canvas img string data 8 | import base64 # Encode canvas data bytes 9 | import keras.models as km 10 | from keras.models import model_from_json 11 | 12 | # Upload files code adapted from: http://flask.pocoo.org/docs/0.12/patterns/fileuploads/ 13 | # Canvas image code adapted from: https://github.com/llSourcell/how_to_deploy_a_keras_model_to_production/blob/master/app.py 14 | 15 | # =========== Initialisation ============================================================================================================ 16 | 17 | # UPLOAD_FOLDER is where we will store the uploaded image files 18 | UPLOAD_FOLDER = './static/uploads' 19 | # set of allowed file extensions. 20 | ALLOWED_EXTENSIONS = set(['pdf', 'png', 'jpg', 'jpeg', 'gif']) 21 | 22 | # Pass in __name__ to help flask determine root path 23 | app = Flask(__name__) # Initialising flask app 24 | app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER # configure upload folder 25 | 26 | # =============== Routing/Mapping ======================================================================================================= 27 | 28 | # @ signifies a decorator which is a way to wrap a function and modify its behaviour 29 | @app.route('/') #connect a webpage. '/' is a root directory. 30 | def main(): 31 | return render_template("index.html") # return rendered template 32 | 33 | # ============== Upload image ==================== 34 | 35 | # function that checks if file extension is allowed 36 | def allowed_file(filename): 37 | return '.' in filename and \ 38 | filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS 39 | 40 | # Upload Image file 41 | @app.route("/upload", methods=['GET', 'POST']) 42 | def upload_file(): 43 | if request.method == 'POST': 44 | # check if the post request has the file part 45 | if 'file' not in request.files: 46 | flash('No file part') 47 | return redirect(request.url) 48 | file = request.files['file'] 49 | # if user does not select file, browser also 50 | # submit a empty part without filename 51 | if file.filename == '': 52 | flash('No selected file') 53 | return redirect(request.url) 54 | # if theres a file with allowed extension then.. 55 | if file and allowed_file(file.filename): 56 | # secure a filename before storing it directly 57 | filename = secure_filename(file.filename) 58 | # Save file to upload_folder 59 | file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) 60 | 61 | return redirect(url_for('uploaded_file', filename=filename)) 62 | 63 | # return render_template('index.html') 64 | 65 | #@app.route('/uploads/') 66 | #def uploaded_file(filename): 67 | # return send_from_directory(app.config['UPLOAD_FOLDER'],filename) 68 | 69 | # ============== Canvas Image ======================== 70 | 71 | # Canvas Digit Image 72 | @app.route('/predict',methods=['GET','POST']) 73 | def predict(): 74 | # Get data from canvas 75 | imgData = request.get_data() 76 | # base64 is used to take binary data and turn it into text to easily transmit from html, 77 | # using regular expression 78 | # Adapted from: https://github.com/llSourcell/how_to_deploy_a_keras_model_to_production/blob/master/app.py 79 | imgstr = re.search(b'base64,(.*)', imgData).group(1) 80 | # Create/open file and write in the encoded data then decode it to form the image 81 | with open('digit.png','wb') as output: 82 | output.write(base64.decodebytes(imgstr)) 83 | 84 | # read parsed image back in mode L = 8-bit pixels, black and white. 85 | img = imread('digit.png',mode='L') 86 | # compute a bit-wise inversion 87 | img = np.invert(img) 88 | # make it 28x28 89 | img = imresize(img,(28,28)) 90 | 91 | #convert to a 4D tensor to feed into our neural network model 92 | img = img.reshape(1,28,28,1) 93 | 94 | # call our pre loaded graph from load.py 95 | #with graph.as_default(): 96 | json_file = open('./model/mnistModel.json','r') # open json file 97 | model_json = json_file.read() # read the model structure 98 | json_file.close() # close when done 99 | # 100 | model = model_from_json(model_json) 101 | # predict the digit using our model 102 | model.load_weights('./model/mnistModel.h5') 103 | #model = km.load_model() 104 | # feed the image into the model and get our prediction 105 | prediction = model.predict(img) 106 | #print(prediction) 107 | #print(np.argmax(prediction,axis=1)) 108 | #convert the response to a string 109 | response = np.array_str(np.argmax(prediction,axis=1)) 110 | return response 111 | 112 | 113 | 114 | 115 | if __name__ == "__main__": 116 | app.run(debug=True) # Start the web server. debug=True means to auto refresh page after code changes -------------------------------------------------------------------------------- /digit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IanBurke1/Python-TensorFlow-WebApp/4dd52bc7d46309297d906793fb5c419f5152375d/digit.png -------------------------------------------------------------------------------- /mnist_nn.py: -------------------------------------------------------------------------------- 1 | import keras as kr 2 | from keras.datasets import mnist # automatically downloaded once function is called. 3 | from keras.models import Sequential 4 | from keras.layers.core import Dense, Dropout, Activation 5 | from keras.utils import np_utils 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | from keras.layers import Dense, Dropout, Flatten 9 | from keras.layers import Conv2D, MaxPooling2D 10 | from keras import backend as K 11 | 12 | # MNIST convolutional neural network (CNN) 13 | 14 | # Adapted from: https://github.com/wxs/keras-mnist-tutorial/blob/master/MNIST%20in%20Keras.ipynb 15 | 16 | # input image dimensions 17 | # 28x28 pixel images. 18 | img_rows, img_cols = 28, 28 19 | 20 | # Load the pre-shuffled MNIST dataset from keras.datasets -> Dataset of 60,000 28x28 grayscale images of 10 digits & test set of 10,000 images 21 | # 2 tuples ( immutable data structure/lists consisting of mulitple parts): 22 | # x_train, x_test = Uint8 array of grayscale image data with shape (num_samples, 28,28) 23 | # y_train, y_test = Uint8 array of digit labels (integers in range 0-9) with shape(num_samples) 24 | # Adapted from: https://keras.io/datasets/ 25 | (x_train, y_train), (x_test, y_test) = mnist.load_data() 26 | 27 | 28 | # this assumes our data format 29 | # depending on format...Arrange the data in a certain way 30 | # For 3D data, "channels_last" assumes (conv_dim1, conv_dim2, conv_dim3, channels) while 31 | # "channels_first" assumes (channels, conv_dim1, conv_dim2, conv_dim3). 32 | if K.image_data_format() == 'channels_first': 33 | 34 | # We must declare a dimension of depth of the input image 35 | # a full-color image with all 3 RGB channels will have a depth of 3 36 | # MNIST images only have a depth of 1 (greyscale) but we need to declare that.. 37 | # we want to transform our dataset from having shape (n, width, height) to (n, depth, width, height). 38 | x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols) 39 | x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols) 40 | input_shape = (1, img_rows, img_cols) 41 | 42 | else: 43 | x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1) 44 | x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1) 45 | 46 | input_shape = (img_rows, img_cols, 1) 47 | 48 | # Format/reshape the data for training 49 | x_train = x_train.astype('float32') 50 | x_test = x_test.astype('float32') 51 | # normalize inputs from 0-255 to 0-1 52 | x_train /= 255 53 | x_test /= 255 54 | 55 | # there are 10 image classes 56 | num_classes = 10 57 | 58 | # Convert 1-dimensional class arrays to 10-dimensional class matrices 59 | y_train = kr.utils.to_categorical(y_train, num_classes) 60 | y_test = kr.utils.to_categorical(y_test, num_classes) 61 | # This means that if the array is: 62 | # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] then digit = 0 63 | # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] then digit = 1 64 | # [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] then digit = 2 65 | # [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] then digit = 3 66 | # [0, 0, 0, 0, 1, 0, 0, 0, 0, 0] then digit = 4 and so on..to 9 67 | 68 | # ================== CREATE MODEL ============================================================ 69 | # A model is understood as a sequence or a graph of standalone, 70 | # ..fully-configurable modules that can be plugged together with as little restrictions as possible. 71 | # For more: https://keras.io/ 72 | # Creating model and a neural network 73 | # model is used to organise layers 74 | # Create our model using sequential model which is a linear stack of layers 75 | model = Sequential() 76 | 77 | # Using 2D convolution layer. Ref: https://keras.io/layers/convolutional/ 78 | # Declare input layer 79 | # 32 = the number output of filters in the convolution 80 | # kernel_size = list of 2 integers, specifying the width and height of the 2D convolution window 81 | # activation function 'relu' which means rectified linear unit 82 | # input_shape = (1,28,28) = (depth, width, height) 83 | model.add(Conv2D(32, kernel_size=(3, 3), 84 | activation='relu', 85 | kernel_initializer='he_normal', 86 | input_shape=input_shape)) 87 | 88 | # Dropout method for regularizing our model in order to prevent overfitting 89 | # Add another convolution layer 90 | model.add(Conv2D(64, (3, 3), activation='relu')) 91 | # MaxPooling2D is a way to reduce the number of parameters in our model.. 92 | # by sliding a 2x2 pooling filter across the previous layer and taking the max of the 4 values in the 2x2 filter 93 | model.add(MaxPooling2D(pool_size=(2, 2))) 94 | # Dropout method for regularizing our model in order to prevent overfitting 95 | model.add(Dropout(0.25)) # float between 0 and 1. Fraction of the input units to drop. 96 | # Flatten the weights to 1 dimensional before passing to dense layer 97 | model.add(Flatten()) 98 | # Add dense layer 99 | # 128 = output size 100 | model.add(Dense(128, activation='relu')) 101 | model.add(Dropout(0.5)) 102 | model.add(Dense(num_classes, activation='softmax')) 103 | 104 | 105 | # ================ TRAIN THE MODEL ====================================================== 106 | # Configure and compile the model for training. 107 | # Uses the adam optimizer and categorical cross entropy as the loss function. 108 | # using adam optimizer algorithm for gradient descent 109 | # Loss function is the objective that the model will try to minimize 110 | # Add in some extra metrics - accuracy being the only one 111 | model.compile(optimizer="adam", 112 | loss="categorical_crossentropy", 113 | metrics=["accuracy"]) 114 | 115 | 116 | # Fit the model using our training data. 117 | # epochs is the number of times the training algorithm will iterate over the entire training set before terminating 118 | # batch_size is the number of training examples being used simultaneously during a single iteration 119 | # verbose is used to log the model being trained 120 | # verbose=1 means verbose mode 1 which is a progress bar 121 | # Verbosity mode: 0 = silent, 1 = progress bar, 2 = one line per epoch. 122 | model.fit(x_train, y_train, batch_size=128, epochs=4, verbose=1) 123 | 124 | # ================== TEST MODEL ========================================================== 125 | # Evaluate the model using the test data set. 126 | # model.evaluate compare answers 127 | # Verbose mode: 0 = silent 128 | loss, accuracy = model.evaluate(x_test, y_test, verbose=0) 129 | 130 | # Output the accuracy of the model. 131 | print("\n\nLoss: %6.4f\tAccuracy: %6.4f" % (loss, accuracy)) 132 | 133 | # ================== Prediction =========================================================== 134 | # Predict the digit 135 | # using model.predict 136 | # numpy.around to evenly round the given data 137 | # numpy.expand_dims to expand the shape of an array 138 | prediction = np.around(model.predict(np.expand_dims(x_test[0], axis=0))).astype(np.int)[0] 139 | 140 | # print out the actual digit and the prediction 141 | print("Actual: %s\tEstimated: %s" % (y_test[0].astype(np.int), prediction)) 142 | 143 | # Json file used to save structure of model 144 | # faster way to have the model loaded when called from the app 145 | # parse/serialise model to json format 146 | model_json = model.to_json() 147 | with open("mnistModel.json", "w") as json_file: 148 | json_file.write(model_json) 149 | 150 | # Save the model weights (learned values) 151 | # h5 is the file format for keras to save the weights of model 152 | model.save("mnistModel.h5") -------------------------------------------------------------------------------- /model/mnistModel.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IanBurke1/Python-TensorFlow-WebApp/4dd52bc7d46309297d906793fb5c419f5152375d/model/mnistModel.h5 -------------------------------------------------------------------------------- /model/mnistModel.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "batch_input_shape": [null, 28, 28, 1], "dtype": "float32", "filters": 32, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 2.0, "mode": "fan_in", "distribution": "normal", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_1", "trainable": true, "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}}, {"class_name": "Dropout", "config": {"name": "dropout_1", "trainable": true, "rate": 0.25, "noise_shape": null, "seed": null}}, {"class_name": "Flatten", "config": {"name": "flatten_1", "trainable": true}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 128, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dropout", "config": {"name": "dropout_2", "trainable": true, "rate": 0.5, "noise_shape": null, "seed": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "units": 10, "activation": "softmax", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}], "keras_version": "2.1.1", "backend": "tensorflow"} -------------------------------------------------------------------------------- /static/index.js: -------------------------------------------------------------------------------- 1 | // Adapted from: https://github.com/sleepokay/mnist-flask-app/blob/master/static/index.js 2 | (function() { 3 | var canvas = document.querySelector("#canvas"); //get canvas from DOM 4 | var context = canvas.getContext("2d"); // as 2 dimensional 5 | canvas.width = 280; // set the width 6 | canvas.height = 280; // set the height 7 | 8 | var Mouse = {x:0, y:0}; 9 | var lastMouse = {x:0, y:0}; 10 | context.fillStyle = "white"; // fill colour for canvas = white 11 | context.fillRect(0, 0, canvas.width, canvas.height); // fill the entire canvas 12 | context.color = "black"; // brush colour 13 | context.lineWidth = 7; // width of brush line 14 | context.lineJoin = context.lineCap = 'round'; // round top on brush 15 | 16 | debug(); //call debug function 17 | 18 | //Listening for user mouse movement in the canvas 19 | canvas.addEventListener("mousemove", function(e) { 20 | lastMouse.x = Mouse.x; 21 | lastMouse.y = Mouse.y; 22 | 23 | Mouse.x = e.pageX - this.offsetLeft-15; 24 | Mouse.y = e.pageY - this.offsetTop-15; 25 | }, false); 26 | 27 | // Once the user clicks on the canvas and move around the mouse, 28 | //the function will call eventListener mousemove to start drawing on canvas 29 | canvas.addEventListener("mousedown", function(e) { 30 | canvas.addEventListener("mousemove", onPaint, false); 31 | }, false); 32 | 33 | // Once the user unclicks the mouse while drawing on canvas, 34 | // then remove the eventListener mousemove to stop drawing. 35 | canvas.addEventListener("mouseup", function() { 36 | canvas.removeEventListener("mousemove", onPaint, false); 37 | }, false); 38 | 39 | // function that enables drawing 40 | var onPaint = function() { 41 | context.lineWidth = context.lineWidth; 42 | context.lineJoin = "round"; 43 | context.lineCap = "round"; 44 | context.strokeStyle = context.color; 45 | 46 | context.beginPath(); //begin to draw the path 47 | context.moveTo(lastMouse.x, lastMouse.y); //move to wherever co-ordinates the mouse moves 48 | context.lineTo(Mouse.x,Mouse.y ); // creates a line from new point from last point 49 | context.closePath(); // creates a path from current point back to starting point 50 | context.stroke(); // draw the path 51 | }; 52 | 53 | // Clear canvas function 54 | function debug() { 55 | $("#clearButton").on("click", function() { //Once clear button is clicked, init function 56 | context.clearRect( 0, 0, 280, 280 ); //clears rectangle 57 | context.fillStyle="white"; // fill colour = white 58 | context.fillRect(0,0,canvas.width,canvas.height); //fill the canvas white 59 | }); 60 | } 61 | }()); -------------------------------------------------------------------------------- /static/starter-template.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | .left { 9 | text-align: left; 10 | } 11 | 12 | .right { 13 | float: right; 14 | text-align: right; 15 | } 16 | .navbar-center { 17 | 18 | width: 100%; 19 | text-align: center; 20 | } 21 | 22 | #canvas { 23 | position: relative; 24 | border: 8px solid; 25 | margin: auto; 26 | margin-top: 160px; 27 | border-radius: 5px; 28 | cursor: pointer; 29 | text-align: center; 30 | } 31 | 32 | .centered { 33 | position: relative; 34 | text-align: center; 35 | } -------------------------------------------------------------------------------- /static/uploads/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IanBurke1/Python-TensorFlow-WebApp/4dd52bc7d46309297d906793fb5c419f5152375d/static/uploads/9.png -------------------------------------------------------------------------------- /static/uploads/nine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IanBurke1/Python-TensorFlow-WebApp/4dd52bc7d46309297d906793fb5c419f5152375d/static/uploads/nine.png -------------------------------------------------------------------------------- /static/uploads/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IanBurke1/Python-TensorFlow-WebApp/4dd52bc7d46309297d906793fb5c419f5152375d/static/uploads/output.png -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Digit Recognition App 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 |
26 |
27 | 28 |
29 |

Draw a digit

30 |
31 |
32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 |
41 |

42 |
43 |
44 | 45 | 46 | 47 | 48 | 60 | 61 | 62 | 63 |
64 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 91 | 92 | 93 | --------------------------------------------------------------------------------