arifa-batool commited on
Commit
5d83589
·
verified ·
1 Parent(s): ffd6bf4

Integrate trained DenseNet model with modular Flask application

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ model/model_dense121.keras filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask , render_template, request, redirect, url_for, flash
3
+ from tensorflow.keras.models import load_model
4
+ from tensorflow.keras.preprocessing.image import load_img, img_to_array
5
+ from tensorflow.keras.applications.densenet import preprocess_input
6
+ from utils.allowed_file import allowed_file
7
+ from utils.upload_file import upload_file
8
+
9
+ import numpy as np
10
+
11
+
12
+ app = Flask(__name__)
13
+
14
+ app.config['UPLOAD_FOLDER'] = 'static/uploads'
15
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
16
+
17
+
18
+ MODEL_PATH = os.path.join('model', 'model_dense121.keras')
19
+ model = load_model(MODEL_PATH)
20
+
21
+
22
+ CLASSES_NAME = [
23
+ 'Downdog',
24
+ 'Goddess',
25
+ 'Plank',
26
+ 'Tree',
27
+ 'Warrior2'
28
+ ]
29
+
30
+
31
+ # routes
32
+
33
+ @app.route('/', methods = ['GET', 'POST'])
34
+
35
+ def index():
36
+ if request.method == 'POST':
37
+ file = request.files.get('file')
38
+ if file and allowed_file(file.filename):
39
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
40
+ file.save(filepath)
41
+
42
+
43
+ # preprocess
44
+ img = load_img(filepath, target_size=(224, 224))
45
+ x = img_to_array(img)
46
+ x = np.expand_dims(x, axis=0)
47
+ x = preprocess_input(x)
48
+ # predict
49
+ preds = model.predict(x)
50
+ idx = np.argmax(preds[0])
51
+ label = CLASSES_NAME[idx]
52
+ confidence = preds[0][idx]
53
+
54
+
55
+ return render_template('index.html',
56
+ filename = file.filename,
57
+ label = label,
58
+ confidence = f"{confidence*100:.1f}%"
59
+
60
+ )
61
+
62
+ return redirect(request.url)
63
+
64
+ return render_template('index.html')
65
+
66
+
67
+ @app.route('/uploads/<filename>')
68
+
69
+ def uploaded_file(filename):
70
+ return upload_file(filename)
71
+
72
+
73
+
74
+ if __name__ == '__main__':
75
+ app.run(host='0.0.0.0', port = 8080, debug = True)
76
+
77
+
78
+
model/model_dense121.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a22c36fc84c250f407e5b711cd6a5dcb354d1814fa3da92a50ccd2919b6c83c1
3
+ size 68209499
notebook/yoga_pose_classificationipynb.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
requirement.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ tensorflow
2
+ Flask
3
+ numpy
4
+ pillow
static/css/style.css ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ========== GLOBAL ========== */
2
+ body {
3
+ font-family: 'Poppins', sans-serif;
4
+ background: radial-gradient(circle at top, #1a1a1a, #000);
5
+ color: #eee;
6
+ margin: 0;
7
+ padding: 0;
8
+ text-align: center;
9
+ }
10
+
11
+ html, body {
12
+ height: 100%;
13
+ margin: 0;
14
+ padding: 0;
15
+ }
16
+
17
+ /* Flex container for sticky footer */
18
+ .page-container {
19
+ display: flex;
20
+ flex-direction: column;
21
+ min-height: 100vh; /* full viewport height */
22
+ }
23
+
24
+ /* Content area grows and pushes footer down */
25
+ .content-wrap {
26
+ flex: 1;
27
+ }
28
+
29
+ /* ========== HEADER ========== */
30
+ header {
31
+ background: linear-gradient(90deg, #6a00f4, #b300ff);
32
+ color: white;
33
+ padding: 30px 10px;
34
+ box-shadow: 0 4px 10px rgba(0,0,0,0.5);
35
+ }
36
+
37
+ header h1 {
38
+ font-size: 2.2em;
39
+ margin-bottom: 5px;
40
+ }
41
+
42
+ header p {
43
+ font-size: 1.1em;
44
+ opacity: 0.9;
45
+ }
46
+
47
+ /* ========== UPLOAD SECTION ========== */
48
+ .upload-container {
49
+ margin-top: 40px;
50
+ }
51
+
52
+ .upload-label {
53
+ background-color: #2e2e2e;
54
+ border: 2px dashed #6a00f4;
55
+ border-radius: 10px;
56
+ padding: 20px;
57
+ display: inline-block;
58
+ cursor: pointer;
59
+ transition: all 0.3s ease;
60
+ color: #aaa;
61
+ }
62
+
63
+ .upload-label:hover {
64
+ background-color: #3b3b3b;
65
+ border-color: #b300ff;
66
+ color: white;
67
+ }
68
+
69
+ .upload-label span {
70
+ display: block;
71
+ margin-bottom: 10px;
72
+ font-size: 1.1em;
73
+ }
74
+
75
+ input[type="file"] {
76
+ display: none;
77
+ }
78
+
79
+ button {
80
+ margin-top: 20px;
81
+ background: linear-gradient(90deg, #6a00f4, #b300ff);
82
+ color: white;
83
+ border: none;
84
+ padding: 12px 30px;
85
+ border-radius: 30px;
86
+ font-size: 1em;
87
+ cursor: pointer;
88
+ transition: 0.3s ease;
89
+ box-shadow: 0 0 10px #6a00f4;
90
+ }
91
+
92
+ button:hover {
93
+ transform: scale(1.05);
94
+ box-shadow: 0 0 20px #b300ff;
95
+ }
96
+
97
+ /* ========== RESULT SECTION ========== */
98
+ .result-container {
99
+ margin-top: 50px;
100
+ padding: 20px;
101
+ background-color: #121212;
102
+ border-radius: 15px;
103
+ display: inline-block;
104
+ box-shadow: 0 0 20px rgba(106,0,244,0.4);
105
+ }
106
+
107
+ .uploaded-img {
108
+ max-width: 300px;
109
+ border-radius: 15px;
110
+ margin-top: 15px;
111
+ box-shadow: 0 0 15px rgba(179,0,255,0.6);
112
+ }
113
+
114
+ .result-text, .result-confidence {
115
+ margin-top: 15px;
116
+ font-size: 1.2em;
117
+ color: #c59fff;
118
+ }
119
+
120
+ /* ========== FOOTER ========== */
121
+ footer {
122
+ background: #111;
123
+ color: #888;
124
+ padding: 20px;
125
+ font-size: 0.9em;
126
+ text-align: center;
127
+ }
128
+
129
+ /* ========== TOAST NOTIFICATION ========== */
130
+ .toast {
131
+ visibility: hidden;
132
+ min-width: 250px;
133
+ background-color: #6a00f4;
134
+ color: white;
135
+ text-align: center;
136
+ border-radius: 8px;
137
+ padding: 15px 20px;
138
+ position: fixed;
139
+ top: 20px;
140
+ right: 20px;
141
+ z-index: 1000;
142
+ font-size: 1em;
143
+ box-shadow: 0 0 15px rgba(179, 0, 255, 0.6);
144
+ opacity: 0;
145
+ transform: translateY(-30px);
146
+ transition: opacity 0.5s, transform 0.5s;
147
+ }
148
+
149
+ .toast.show {
150
+ visibility: visible;
151
+ opacity: 1;
152
+ transform: translateY(0);
153
+ }
154
+
155
+
156
+ /* Clear button styles, explicitly matching Predict button */
157
+ #clearBtn {
158
+ margin-top: 20px;
159
+ background: #3e3e3e; /* Greyish background, only difference */
160
+ color: white;
161
+ border: none;
162
+ padding: 12px 30px;
163
+ border-radius: 30px;
164
+ font-size: 1em;
165
+ cursor: pointer;
166
+ transition: 0.3s ease;
167
+ box-shadow: 0 0 10px #6a00f4;
168
+ }
169
+
170
+
171
+ #clearBtn:hover {
172
+ transform: scale(1.05);
173
+ box-shadow: 0 0 20px #b300ff;
174
+ }
static/uploads/yoga10.jpg ADDED
static/uploads/yoga8.jpg ADDED
templates/index.html ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Yoga Pose Classifier</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
+ </head>
9
+ <body>
10
+ <div class="page-container">
11
+
12
+ <!-- Toast Notification -->
13
+ <div id="toast" class="toast">✅ Image selected successfully!</div>
14
+
15
+ <header>
16
+ <h1>🧘 Yoga Pose Classifier</h1>
17
+ <p>Upload your yoga pose image to identify its type.</p>
18
+ </header>
19
+
20
+
21
+ <main class="content-wrap">
22
+
23
+ <div class="upload-container">
24
+ <form method="POST" enctype="multipart/form-data" id="uploadForm">
25
+ <label class="upload-label">
26
+ <span id="choose-text">Choose an Image</span>
27
+ <input type="file" name="file" accept="image/*" required id="fileInput">
28
+ </label>
29
+ <p id="upload-msg" style="color: #6a00f4; margin-top: 10px; display: none;">
30
+ ✅ Image selected!
31
+ </p>
32
+ <button type="submit" style="margin-left: 5px;">Predict Pose</button>
33
+ </form>
34
+ </div>
35
+
36
+ {% if filename %}
37
+ <div class="result-container">
38
+ <h2>Prediction Result</h2>
39
+ <img src="{{ url_for('static', filename='uploads/' + filename) }}" alt="Uploaded Image" class="uploaded-img">
40
+ <p class="result-text"><strong>Pose:</strong> {{ label }}</p>
41
+ <p class="result-confidence"><strong>Confidence:</strong> {{ confidence }}</p>
42
+ </div>
43
+ {% endif %}
44
+ <p> </p>
45
+ <p> </p>
46
+ </main>
47
+
48
+ <footer>
49
+ <p>Made with 💜 using DenseNet121 | © 2025 Yoga Pose Classifier</p>
50
+ </footer>
51
+ </div>
52
+
53
+ <script>
54
+ const fileInput = document.getElementById('fileInput');
55
+ const toast = document.getElementById('toast');
56
+
57
+ fileInput.addEventListener('change', () => {
58
+ if (fileInput.files.length > 0) {
59
+ toast.classList.add('show');
60
+
61
+ // Hide after 3 seconds
62
+ setTimeout(() => {
63
+ toast.classList.remove('show');
64
+ }, 3000);
65
+ }
66
+ });
67
+ </script>
68
+ </body>
69
+ </html>
utils/allowed_file.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ def allowed_file(filename):
2
+ return '.' in filename and \
3
+ filename.rsplit('.',1)[1].lower() in {'png', 'jpg', 'jpeg', 'gif'}
4
+
utils/upload_file.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from flask import redirect, url_for
2
+
3
+ def upload_file(filename):
4
+ return redirect(url_for('static', filename= f'uploads/{filename}'))