# Instructions to build a quick skeleton Flask app for Cat/Dog Classifier
This guide will help you to create a simple skeleton of flask app which allow you to upload a image file and call tensorflow to predict the image for you. Then result will be displayed on classify HTML page.
Remember, there are a lot of potential improvement ideas for your flask apps:
- Change POST request to Ajax so the page will be never reloaded
- Style up the website (specially the upload form)
- Add more pages about yourself and model
- Add options to use multiple different models
- Go further, make one-page web application (everything handled by Ajax)
## Project structure
* simple_flask
* static
* images
* script.js
* style.css
* templates
* classify.html
* home.html
* uploads
* app.py
### Make the main flask directory called simple_flask
Ubuntu/Mac
```bash
mkdir simple_flask
```
Windows
```bash
mkdir "simple_flask"
```
### Change directory
```bash
cd simple_flask
```
### Create the following folders and files
Ubuntu/Mac
```bash
mkdir static templates uploads static/images
touch app.py static/script.js static/style.css templates/classify.html templates/home.html
```
Windows
```bash
mkdir "static" "templates" "uploads" "static/images"
copy nul "app.py"
copy nul "static/script.js"
copy nul "static/style.css"
copy nul "templates/classify.html"
copy nul "templates/home.html"
```
### Add this routing and prediction code in **flask_app/app.py**
```python
import os
from flask import Flask, render_template, request
from flask import send_from_directory
import numpy as np
import tensorflow as tf
app = Flask(__name__)
dir_path = os.path.dirname(os.path.realpath(__file__))
UPLOAD_FOLDER = 'uploads'
STATIC_FOLDER = 'static'
# Load model
cnn_model = tf.keras.models.load_model(STATIC_FOLDER + '/' + 'catdog_classifier_Xception.h5')
IMAGE_SIZE = 224
# Preprocess an image
def preprocess_image(image):
image = tf.image.decode_jpeg(image, channels=3)
image = tf.image.resize(image, [IMAGE_SIZE, IMAGE_SIZE])
image /= 255.0 # normalize to [0,1] range
return image
# Read the image from path and preprocess
def load_and_preprocess_image(path):
image = tf.io.read_file(path)
return preprocess_image(image)
# Predict & classify image
def classify(model, image_path):
preprocessed_imgage = load_and_preprocess_image(image_path)
preprocessed_imgage = tf.reshape(preprocessed_imgage, (1,IMAGE_SIZE ,IMAGE_SIZE ,3))
prob = cnn_model.predict(preprocessed_imgage)
label = "Cat" if prob >= 0.5 else "Dog"
classified_prob = prob if prob >= 0.5 else 1 - prob
return label, classified_prob
# home page
@app.route('/')
def home():
return render_template('home.html')
@app.route('/classify', methods=['POST','GET'])
def upload_file():
if request.method == 'GET':
return render_template('home.html')
else:
file = request.files["image"]
upload_image_path = os.path.join(UPLOAD_FOLDER, file.filename)
print(upload_image_path)
file.save(upload_image_path)
label, prob = classify(cnn_model, upload_image_path)
prob = round((prob[0][0] * 100), 2)
return render_template('classify.html', image_file_name = file.filename, label = label, prob = prob)
@app.route('/classify/<filename>')
def send_file(filename):
return send_from_directory(UPLOAD_FOLDER, filename)
if __name__ == '__main__':
app.debug = True
app.run(debug=True)
app.debug = True
```
### Add some css styles in **flask_app/static/style.css**
```css
h2 {
letter-spacing: 2px;
font-size: 1.5rem
}
form {
padding-top:20px;
font-size: 24px;
}
.classify{
text-align:center;
margin:auto;
margin-top:200px;
}
.image-prediction,
.text-prediction{
margin-top: 100px;
text-align:center;
}
.pred_img {
width: 400px;
height: 400px;
}
#pred {
font-size: 60px;
text-transform: uppercase;
}
#label {
color: red;
font-size: 4rem;
text-transform: uppercase;
}
#classify_button {
font-size: 2rem;
font-weight: bold;
}
```
## HTML Template
### Create template for our home page at **flask_app/templates/home.html**
> *Hints: most of this code can be automaticaly generated with a VSCode Extension called flask-snippets by only writting "fapp"*
> 
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Catto & Doggo Classifier</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Simple Catto Doggo</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
</li>
</ul>
<span class="navbar-text">
Simple Flask With Upload Functionality
</span>
</div>
</nav>
<div class="container-fluid">
<div class="classify">
<div>
<h2>
Hi, Please upload your dog or cat image here!
</h2>
</div>
<div>
<form action="/classify" method="post" enctype="multipart/form-data">
<div class="file has-name is-centered">
<label class="file-label">
<input class="file-input" type="file" id="image" name="image">
</label>
</div>
<br>
<input type="submit" value="Classify" class="btn btn-danger" name="image" id="classify_button">
</form>
</div>
</div>
</div>
<script src="/static/script.js"></script>
</body>
</html>
```
> *Hints: most of this code can be automaticaly generated with VSCode Extensions called Bootstrap 4, Font awesome 4, Font Awesome 5 Free & Pro snippets by only writting "b4-$"*
### Create template for our classify/prediction page at **flask_app/templates/classify.html**
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Catto & Doggo Classifier</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="/static/style.css" type="text/css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Simple Catto Doggo</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText"
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
</li>
</ul>
<span class="navbar-text">
Simple Flask With Upload Functionality
</span>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col image-prediction">
<img class="pred_img" src="{{ url_for('send_file', filename=image_file_name)}}" />
</div>
<div class="col text-prediction">
<h1 id="pred">
This is a
</h1>
<h1 class="box" id="label">
{{ label }}
</h1>
<h2>
Confidence: <span>{{prob}}%</span>
</h2>
<br>
<a href="/" class="btn btn-primary">Back to Home</a>
</div>
</div>
</div>
<script src="/static/script.js"></script>
</body>
</html>
```
### Add trained Xception model of Mina have built before:
Download the saved model in the link: https://drive.google.com/file/d/1W4uEBbCLFTsdYk6dWeEkzte1jk0X95XP/view?usp=sharing
And copy that catdog_classifier_Xception.h5 into our **simple_flask/static/** folder
**For fun, you should train your own model and replace this model with your model.**
### Run the flask app
Change directory
```bash
flask run
```
On Docker, you need to expose that IP address to public so run:
```bash
flask run --host=0.0.0.0
```
:confetti_ball: Congratulation, you have just created your Flask app. Here is the link of your Flask app: http://127.0.0.1:5000
Or
http://localhost:5000