-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.py
More file actions
157 lines (136 loc) · 5.76 KB
/
Copy pathapi.py
File metadata and controls
157 lines (136 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
from flask import Flask, request, jsonify
from PIL import Image
import numpy as np
import tensorflow as tf
import json, os, random
from business_logic import load_risk_dictionary, assess_prediction, init_db, save_detection, get_recent
app = Flask(__name__)
@app.after_request
def add_cors_headers(response):
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
response.headers['Access-Control-Allow-Methods'] = 'GET,POST,OPTIONS'
response.headers['Access-Control-Max-Age'] = '86400'
return response
@app.route('/health', methods=['GET'])
def health():
return jsonify({'status': 'ok', 'model_loaded': model is not None})
# load model and classes
MODEL_PATH = 'animal_classifier.h5'
CLASS_PATH = 'class_indices.json'
SAMPLES = {
"Tiger": "Farm Harmful Animal Dataset/train/Tiger",
"Elephant": "Farm Harmful Animal Dataset/train/Elephant",
"Leopard": "Farm Harmful Animal Dataset/train/Leopard",
"Cow": "Farm Harmful Animal Dataset/train/Cow",
"Monkey": "Farm Harmful Animal Dataset/train/Monkey",
"Rabbit": "Farm Harmful Animal Dataset/train/Rabbit",
}
print('Loading model...')
model = None
idx_to_class = {}
try:
model = tf.keras.models.load_model(MODEL_PATH)
with open(CLASS_PATH) as f:
idx = json.load(f)
idx_to_class = {v: k for k, v in idx.items()}
print('Model loaded')
except Exception as e:
print('Failed to load model:', e)
# load risk dict and db
RISK_DICT = load_risk_dictionary()
DB = init_db()
def predict_image(img: Image.Image):
arr = np.expand_dims(np.array(img.resize((96,96)))/255.0, 0)
p = model.predict(arr, verbose=0)[0]
top = np.argsort(p)[::-1][:3]
results = [(idx_to_class[i], float(p[i])) for i in top]
return results
@app.route('/predict', methods=['OPTIONS','POST','GET'])
def predict():
if request.method == 'OPTIONS':
return jsonify({'message': 'ok'}), 200
sample_query = request.args.get('sample')
if request.method == 'GET':
if not sample_query:
return jsonify({'error': 'sample query parameter required'}), 400
if sample_query.lower() == 'true':
sample_name, folder = random.choice(list(SAMPLES.items()))
else:
sample_name = sample_query
folder = SAMPLES.get(sample_name)
if folder is None:
return jsonify({'error': f'unknown sample species: {sample_name}'}), 400
files = [f for f in os.listdir(folder) if f.lower().endswith(('.jpg','.jpeg','.png'))]
if not files:
return jsonify({'error': 'no sample images available'}), 500
path = os.path.join(folder, random.choice(files))
try:
img = Image.open(path).convert('RGB')
except Exception:
return jsonify({'error': 'invalid sample image file'}), 500
else:
if 'file' not in request.files:
return jsonify({'error':'file missing'}), 400
f = request.files['file']
try:
img = Image.open(f.stream).convert('RGB')
except Exception:
return jsonify({'error': 'invalid image upload'}), 400
if model is None:
return jsonify({'error':'model not loaded'}), 500
preds = predict_image(img)
top_name, top_conf = preds[0]
# confidence threshold — below 0.5 means model is uncertain
low_confidence = top_conf < 0.50
# spread check — if top 2 predictions are close, scene is ambiguous
ambiguous = len(preds) > 1 and (top_conf - preds[1][1]) < 0.20
recent = 0
try:
recs = get_recent(DB, limit=200)
recent = sum(1 for r in recs if r['species'].replace(' ','_')==top_name)
except Exception:
recent = 0
assessment = assess_prediction(top_name, top_conf, RISK_DICT, recent_sightings=recent, site_context=None)
# build warning message for low confidence or ambiguous scenes
warning = None
if low_confidence:
warning = f"Low confidence ({round(top_conf*100)}%) — image may contain an animal not in the training dataset, or multiple animals. Results may be inaccurate."
elif ambiguous:
warning = f"Multiple animals may be present. Showing most dominant detection. Check alternative predictions below."
detection = {
'id': str(int(random.random()*1e9)),
'species': top_name.replace('_',' '),
'emoji': (RISK_DICT.get(top_name,{}).get('emoji') if isinstance(RISK_DICT.get(top_name), dict) else None) or '🦝',
'confidence': top_conf,
'low_confidence': low_confidence,
'ambiguous': ambiguous,
'warning': warning,
'human': assessment['human_risk'],
'crop': assessment['crop_risk'],
'livestock': assessment['livestock_risk'],
'desc': RISK_DICT.get(top_name,{}).get('desc',''),
'recommendations': assessment.get('recommendations',[]),
'insight': assessment.get('insight',''),
'timestamp': assessment.get('evaluated_at')
}
# persist detection
try:
save_detection(DB, detection['species'], detection['confidence'], detection['human'], detection['crop'], detection['livestock'], detection['recommendations'], detection['insight'], image_path=None, timestamp=detection['timestamp'])
except Exception:
pass
# alternative predictions
alt = [{'species':n.replace('_',' '),'confidence':c} for n,c in preds]
resp = {'detection':detection,'alternatives':alt}
return jsonify(resp)
@app.route('/recent', methods=['GET'])
def recent():
try:
recs = get_recent(DB, limit=50)
return jsonify({'recent':recs})
except Exception as e:
return jsonify({'error':str(e)}),500
if __name__=='__main__':
import os
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)