Gigishot commited on
Commit
5c9ce3f
·
verified ·
1 Parent(s): ab36530

Upload 23 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ 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
+ static/bgpo.jpeg filter=lfs diff=lfs merge=lfs -text
37
+ static/cat.jpg filter=lfs diff=lfs merge=lfs -text
38
+ static/fish.jpg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
app.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request
2
+ from tensorflow.keras.models import load_model
3
+ from tensorflow.keras.preprocessing import image
4
+ import numpy as np
5
+ import os
6
+ import uuid
7
+ import tensorflow as tf
8
+ import random
9
+
10
+ # Fix randomness for reproducibility
11
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
12
+ tf.random.set_seed(42)
13
+ np.random.seed(42)
14
+ random.seed(42)
15
+
16
+ app = Flask(__name__)
17
+
18
+ # Load the model (only one model now)
19
+ model = load_model("model/cat_dog_neither_classifier_new.h5", compile=False)
20
+ # <-- your model file
21
+
22
+ class_names = ['cat', 'dog', 'neither']
23
+ UPLOAD_FOLDER = 'static/uploads'
24
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
25
+
26
+ def preprocess_image(img_path):
27
+ img = image.load_img(img_path, target_size=(224, 224)) # Ensure matches model input
28
+ img_array = image.img_to_array(img) / 255.0
29
+ img_array = np.expand_dims(img_array, axis=0)
30
+ return img_array
31
+
32
+ @app.route('/', methods=['GET'])
33
+ def index():
34
+ return render_template('upload.html')
35
+
36
+ @app.route('/predict', methods=['POST'])
37
+ def predict():
38
+ if 'file' not in request.files:
39
+ return "No file part", 400
40
+
41
+ file = request.files['file']
42
+ if file.filename == '':
43
+ return "No selected file", 400
44
+
45
+ filename = str(uuid.uuid4()) + os.path.splitext(file.filename)[1]
46
+ img_path = os.path.join(UPLOAD_FOLDER, filename)
47
+ file.save(img_path)
48
+
49
+ # Preprocess image
50
+ processed = preprocess_image(img_path)
51
+
52
+ # Predict
53
+ prediction = model.predict(processed)[0]
54
+ prediction /= np.sum(prediction) # normalize
55
+
56
+ class_index = int(np.argmax(prediction))
57
+ confidence = round(float(np.max(prediction)) * 100, 2)
58
+ final_class = class_names[class_index]
59
+
60
+ return render_template(
61
+ 'result.html',
62
+ prediction=final_class,
63
+ confidence=confidence,
64
+ img_path='/' + img_path
65
+ )
66
+
67
+ if __name__ == '__main__':
68
+ import os
69
+ port = int(os.environ.get("PORT", 5000)) # Render sets PORT
70
+ app.run(host='0.0.0.0', port=port, debug=False)
71
+
main.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tensorflow as tf
2
+ from tensorflow.keras.models import Model
3
+ from tensorflow.keras.layers import Dense, Dropout, GlobalAveragePooling2D
4
+ from tensorflow.keras.preprocessing import image_dataset_from_directory
5
+ from tensorflow.keras.applications import MobileNetV2
6
+ from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
7
+ from tensorflow.keras.layers import RandomFlip, RandomRotation, RandomZoom
8
+ from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
9
+ import os
10
+
11
+ # Constants
12
+ img_size = 224
13
+ batch_size = 32
14
+ epochs = 30 # Increased epochs for deeper training
15
+
16
+ # Callbacks
17
+ early_stop = EarlyStopping(monitor='val_accuracy', patience=5, restore_best_weights=True)
18
+ lr_reduce = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, verbose=1, min_lr=1e-6)
19
+
20
+ # Load datasets
21
+ train_dataset = image_dataset_from_directory(
22
+ 'dataset/training_set',
23
+ labels='inferred',
24
+ label_mode='categorical',
25
+ image_size=(img_size, img_size),
26
+ batch_size=batch_size,
27
+ shuffle=True
28
+ )
29
+
30
+ test_dataset = image_dataset_from_directory(
31
+ 'dataset/test_set',
32
+ labels='inferred',
33
+ label_mode='categorical',
34
+ image_size=(img_size, img_size),
35
+ batch_size=batch_size
36
+ )
37
+
38
+ class_names = train_dataset.class_names
39
+ print("Class indices:", class_names)
40
+
41
+ # Preprocessing
42
+ train_dataset = train_dataset.map(lambda x, y: (preprocess_input(x), y)).prefetch(tf.data.AUTOTUNE)
43
+ test_dataset = test_dataset.map(lambda x, y: (preprocess_input(x), y)).prefetch(tf.data.AUTOTUNE)
44
+
45
+ # Data Augmentation
46
+ data_augmentation = tf.keras.Sequential([
47
+ RandomFlip('horizontal'),
48
+ RandomRotation(0.2),
49
+ RandomZoom(0.2),
50
+ ])
51
+
52
+ # Load pretrained base model
53
+ base_model = MobileNetV2(input_shape=(img_size, img_size, 3), include_top=False, weights='imagenet')
54
+ base_model.trainable = False # Freeze base layers
55
+
56
+ # Build model
57
+ inputs = tf.keras.Input(shape=(img_size, img_size, 3))
58
+ x = data_augmentation(inputs)
59
+ x = base_model(x, training=False)
60
+ x = GlobalAveragePooling2D()(x)
61
+ x = Dropout(0.3)(x)
62
+ outputs = Dense(3, activation='softmax')(x) # 3 classes: cat, dog, neither
63
+
64
+ model = Model(inputs, outputs)
65
+
66
+ # Compile
67
+ model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
68
+ model.summary()
69
+
70
+ # Train
71
+ history = model.fit(
72
+ train_dataset,
73
+ validation_data=test_dataset,
74
+ epochs=epochs,
75
+ callbacks=[early_stop, lr_reduce]
76
+ )
77
+
78
+ model.save('cat_dog_neither_classifier_new.h5', save_format='h5')
79
+ print("✅ Training complete and model saved as .h5.")
80
+
model/cat_dog_neither_classifier_new.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67926b9d15446d349fbfc3f9150e3a71ba7c59b62f3532ab6dbd36cc7b51df90
3
+ size 9392928
render.yaml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+
2
+ services:
3
+ - type: web
4
+ name: cat-dog-classifier
5
+ env: python
6
+ buildCommand: pip install -r requirements.txt
7
+ startCommand: python app.py
8
+ pythonVersion: 3.10.13
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Flask==2.3.3
2
+ tensorflow==2.15.0
3
+ numpy==1.24.3
4
+ Pillow==10.0.1
5
+
6
+
runtime.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python-3.10.13
static/bgpo.jpeg ADDED

Git LFS Details

  • SHA256: c4da173b3068c21f3f768c0802ad49718e0c241a39ce317c3ac6e2500d029d33
  • Pointer size: 131 Bytes
  • Size of remote file: 624 kB
static/cat.jpg ADDED

Git LFS Details

  • SHA256: 1c6871dc972d108194cfb30eeedcfa52cc93d3bc2952d28ed57ac45f5b3af19c
  • Pointer size: 131 Bytes
  • Size of remote file: 610 kB
static/dog.webp ADDED
static/fish.jpg ADDED

Git LFS Details

  • SHA256: d4174ea27cb0de4aa11d7a93f87b1fe9070f740d41ebb40891d75553ca9352b4
  • Pointer size: 132 Bytes
  • Size of remote file: 1.58 MB
static/ham.webp ADDED
static/horse.webp ADDED
static/style.css ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ background: url("/static/bgpo.jpeg") no-repeat center center fixed;
3
+ background-size: cover;
4
+ color: #060721;
5
+ font-family: 'Segoe UI', sans-serif;
6
+ display: flex;
7
+ justify-content: center;
8
+ align-items: center;
9
+ height: 100vh;
10
+ flex-direction: column;
11
+ margin: 0;
12
+ }
13
+
14
+ h1{
15
+ font-size: 32px;
16
+ color: #0d333f;
17
+ font-family: "Segoe UI", sans-serif;
18
+ text-align: center;
19
+ margin-bottom: 3px;
20
+ }
21
+ h2 {
22
+ font-size: 20px;
23
+ color: #33a6cc;
24
+ font-family: "Segoe UI", sans-serif;
25
+ margin-bottom: 20px;
26
+ }
27
+
28
+ .upload-box {
29
+ background-color: #dafffc2a;
30
+ border-radius: 15px;
31
+ padding: 40px 30px;
32
+ backdrop-filter: blur(3px);
33
+ -webkit-backdrop-filter: blur(3px);
34
+ display: inline-block;
35
+ box-shadow: 0 0 20px rgba(90, 90, 255, 0.3);
36
+ max-width: 500px;
37
+ width: 100%;
38
+ border: none;
39
+ border-radius: 25px;
40
+ border-color: 2px, #e2fdff;
41
+ }
42
+
43
+ .drop-area {
44
+ border: 2px dashed #61ddff;
45
+ padding: 40px 20px;
46
+ border-radius: 12px;
47
+ display: block;
48
+ cursor: pointer;
49
+ transition: 0.3s;
50
+ text-align: center;
51
+ box-shadow: 0 0 10px #a0ccff88;
52
+ }
53
+
54
+ .drop-area:hover {
55
+ background: #ffe8b62f;
56
+ box-shadow: 0 0 10px #58a6ff88;
57
+ }
58
+
59
+ .drop-area p {
60
+ font-size: 16px;
61
+ margin-bottom: 20px;
62
+ color: rgba(46, 46, 46, 0.927);
63
+ font-weight: 600;
64
+ }
65
+
66
+ input[type="file"] {
67
+ display: none;
68
+ }
69
+
70
+ .upload-btn {
71
+ padding: 12px 25px;
72
+ border: none;
73
+ border-radius: 25px;
74
+ background: linear-gradient(to right, #3b82f6, #06b6d4);
75
+ color: white;
76
+ font-weight: bold;
77
+ cursor: pointer;
78
+ font-size: 16px;
79
+ transition: 0.3s ease;
80
+ box-shadow: 0 0 15px #3b82f688;
81
+ margin-top: 20px;
82
+ }
83
+
84
+ .upload-btn:hover {
85
+ transform: scale(1.05);
86
+ box-shadow: 0 0 25px #3b82f6aa;
87
+ }
88
+
89
+ .note {
90
+ font-size: 14px;
91
+ margin-top: 25px;
92
+ color: #575757;
93
+ text-align: center;
94
+ }
95
+
96
+ .note span {
97
+ background: #21262d;
98
+ padding: 5px 10px;
99
+ border-radius: 8px;
100
+ margin: 0 5px;
101
+ color: #58a6ff;
102
+ font-weight: bold;
103
+ font-size: 13px;
104
+ }
105
+
106
+ .sample-images {
107
+ display: flex;
108
+ justify-content: center;
109
+ gap: 15px;
110
+ margin-top: 15px;
111
+ }
112
+
113
+ .sample-images img {
114
+ width: 80px;
115
+ height: 80px;
116
+ object-fit: cover;
117
+ border-radius: 10px;
118
+ box-shadow: 0 0 10px rgba(0,0,0,0.2);
119
+ cursor: pointer;
120
+ border: 1px #d9efff solid;
121
+ box-shadow: #00395d88;
122
+ }
123
+
124
+ /* Reset + Base */
125
+ .result-body {
126
+ margin: 0;
127
+ padding: 0;
128
+ font-family: 'Segoe UI', sans-serif;
129
+ background: #ffffff;
130
+ color: #0d1b2a;
131
+ display: flex;
132
+ justify-content: center;
133
+ align-items: center;
134
+ min-height: 100vh;
135
+ }
136
+
137
+ /* Main Container */
138
+ .container {
139
+ text-align: center;
140
+ max-width: 500px;
141
+ width: 90%;
142
+ padding: 30px 25px;
143
+ background: rgba(255, 255, 255, 0.85);
144
+ border-radius: 20px;
145
+ box-shadow: 0 8px 30px rgba(0, 170, 255, 0.15);
146
+ backdrop-filter: blur(8px);
147
+ }
148
+
149
+ /* Glowing Heading */
150
+ .glow-heading {
151
+ font-size: 1.9rem;
152
+ color: #00395d;
153
+ text-shadow: 0 0 5px rgba(0, 170, 255, 0.3);
154
+ margin-bottom: 25px;
155
+ }
156
+
157
+ /* Image Preview */
158
+ .preview-img {
159
+ width: 100%;
160
+ max-height: 300px;
161
+ object-fit: contain;
162
+ border-radius: 12px;
163
+ border: 2px dashed #00bfff;
164
+ padding: 8px;
165
+ background: #f0faff;
166
+ box-shadow: 0 4px 20px rgba(0, 170, 255, 0.1);
167
+ }
168
+
169
+ /* Card Box */
170
+ .card-glow {
171
+ background: #f9fdff;
172
+ border-radius: 15px;
173
+ padding: 20px;
174
+ margin-top: 15px;
175
+ border: 1px solid #cceeff;
176
+ box-shadow: 0 0 10px rgba(0, 170, 255, 0.1);
177
+ }
178
+
179
+ /* Result Text */
180
+ .result-info {
181
+ margin-top: 15px;
182
+ font-size: 1rem;
183
+ color: #002b4a;
184
+ }
185
+
186
+ .highlight {
187
+ color: #ff4081;
188
+ font-weight: bold;
189
+ text-shadow: 0 0 3px rgba(255, 64, 129, 0.2);
190
+ }
191
+
192
+ .confidence {
193
+ color: #ffa500;
194
+ font-weight: bold;
195
+ text-shadow: 0 0 3px rgba(255, 165, 0, 0.2);
196
+ }
197
+
198
+ /* Glowing Button */
199
+ .glow-button {
200
+ display: inline-block;
201
+ margin-top: 30px;
202
+ padding: 12px 30px;
203
+ background: linear-gradient(145deg, #00cfff, #00aaff);
204
+ color: #fff;
205
+ border: none;
206
+ border-radius: 30px;
207
+ text-decoration: none;
208
+ font-weight: 600;
209
+ box-shadow: 0 5px 15px rgba(0, 170, 255, 0.3);
210
+ transition: all 0.3s ease-in-out;
211
+ }
212
+
213
+ .glow-button:hover {
214
+ background: #009fe3;
215
+ box-shadow: 0 8px 20px rgba(0, 170, 255, 0.5);
216
+ }
static/uploads/54b460ad-5cac-4ce8-9a90-1bc823c41311.webp ADDED
static/uploads/909e7831-9aac-46cc-b40b-251042ccd8ba.webp ADDED
static/uploads/a525852f-fc25-4152-a6ad-4e5280934525.webp ADDED
static/uploads/b8ef355f-d14e-47a1-97ca-7a15b77d2778.webp ADDED
static/uploads/c43acfd2-bf74-415b-8709-f0b326c56a85.webp ADDED
templates/result.html ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Prediction Result</title>
6
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
7
+ </head>
8
+ <body class="result-body">
9
+ <div class="container">
10
+ <h1 class="glow-heading">🐾 Prediction Result</h1>
11
+
12
+ <div class="card-glow">
13
+ <img src="{{ img_path }}" alt="Uploaded Image" class="preview-img">
14
+ <div class="result-info">
15
+ <p><strong>Prediction:</strong> <span class="highlight">{{ prediction }}</span></p>
16
+ <p><strong>Confidence:</strong> <span class="confidence">{{ confidence }}%</span></p>
17
+ </div>
18
+ </div>
19
+
20
+ <a href="/" class="glow-button">🔁 Classify Another Image</a>
21
+ </div>
22
+ </body>
23
+ </html>
templates/upload.html ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Upload Image</title>
6
+ <h1>Cat, Dog or Neither?</h1>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
+ </head>
9
+ <body>
10
+ <h2>Upload an Image</h2>
11
+ <div class="upload-box">
12
+
13
+ <form id="upload-form" method="POST" action="/predict" enctype="multipart/form-data">
14
+ <div class="drop-area" id="drop-area">
15
+ <p>Drag or drop images here or click to select</p>
16
+ <input type="file" name="file" id="file-input" accept="image/*">
17
+ </div>
18
+ <button class="upload-btn" type="submit">Upload</button>
19
+ </form>
20
+
21
+
22
+ </div>
23
+ <div class="note">Try these sample images:</div>
24
+ <div class="sample-images">
25
+ <img src="/static/cat.jpg" alt="Cat">
26
+ <img src="/static/dog.webp" alt="Dog">
27
+ <img src="/static/horse.webp" alt="Other">
28
+ <img src="/static/fish.jpg" alt="Other">
29
+ </div>
30
+ <script>
31
+ const dropArea = document.getElementById('drop-area');
32
+ const fileInput = document.getElementById('file-input');
33
+ const form = document.getElementById('upload-form');
34
+
35
+ // When file is selected via click
36
+ fileInput.addEventListener('change', () => {
37
+ if (fileInput.files.length > 0) {
38
+ form.submit();
39
+ }
40
+ });
41
+
42
+ // Click on drag area triggers file input
43
+ dropArea.addEventListener('click', () => {
44
+ fileInput.click();
45
+ });
46
+
47
+ // Handle drag-and-drop
48
+ dropArea.addEventListener('dragover', (e) => {
49
+ e.preventDefault();
50
+ dropArea.style.backgroundColor = "#dbf9ff3d";
51
+ });
52
+
53
+ dropArea.addEventListener('dragleave', () => {
54
+ dropArea.style.backgroundColor = "";
55
+ });
56
+
57
+ dropArea.addEventListener('drop', (e) => {
58
+ e.preventDefault();
59
+ dropArea.style.backgroundColor = "";
60
+ if (e.dataTransfer.files.length > 0) {
61
+ fileInput.files = e.dataTransfer.files;
62
+ form.submit(); // 🚀 Submit form automatically
63
+ }
64
+ });
65
+ </script>
66
+ </body>
67
+ </html>
utils/evaluate.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tensorflow.keras.models import load_model
2
+ from tensorflow.keras.preprocessing import image_dataset_from_directory
3
+ import tensorflow as tf
4
+
5
+ # Load model
6
+ model = load_model('model/cat_dog_neither_classifier_new.h5') # Updated filename
7
+
8
+ # Load test data
9
+ test_dataset = image_dataset_from_directory(
10
+ 'dataset/test_set',
11
+ labels='inferred',
12
+ label_mode='categorical',
13
+ image_size=(224, 224),
14
+ batch_size=32
15
+ )
16
+
17
+ # Normalize & prefetch
18
+ test_dataset = test_dataset.map(lambda x, y: (x / 255.0, y)).prefetch(tf.data.AUTOTUNE)
19
+
20
+ # Evaluate
21
+ loss, accuracy = model.evaluate(test_dataset)
22
+ print(f"Test Accuracy: {accuracy:.4f}")
utils/predict.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ from tensorflow.keras.models import load_model
4
+ from tensorflow.keras.preprocessing import image
5
+
6
+ from tensorflow.keras.models import load_model
7
+
8
+ # Load your old model
9
+ model = load_model("model/cat_dog_neither_classifier_new.h5", compile=False)
10
+
11
+ # Class names — must match the order used during training
12
+ class_names = ['cat', 'dog', 'neither']
13
+
14
+ def preprocess_image(image_path):
15
+ img = image.load_img(image_path, target_size=(224, 224)) # ✅ Match model input
16
+ img_array = image.img_to_array(img) / 255.0
17
+ img_array = np.expand_dims(img_array, axis=0)
18
+ return img_array
19
+
20
+ def predict_image(image_path):
21
+ if not os.path.exists(image_path):
22
+ raise FileNotFoundError(f"Image not found: {image_path}")
23
+
24
+ processed_img = preprocess_image(image_path)
25
+ prediction = model.predict(processed_img)[0]
26
+
27
+ prediction /= np.sum(prediction) # Normalize
28
+ class_index = np.argmax(prediction)
29
+ confidence = float(np.max(prediction))
30
+
31
+ return class_names[class_index], round(confidence * 100, 2)
32
+
33
+ if __name__ == "__main__":
34
+ image_path = "dog.webp" # You can replace this with a path from CLI
35
+ label, confidence = predict_image(image_path)
36
+ print(f"Prediction: {label} ({confidence}%)")