aarushpixel commited on
Commit
abf94bb
·
verified ·
1 Parent(s): d29d23c

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile.txt +7 -0
  2. cargo_app.py +332 -0
  3. requirements.txt +8 -0
Dockerfile.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+ WORKDIR /app
3
+ COPY requirements.txt .
4
+ RUN pip install --no-cache-dir -r requirements.txt
5
+ COPY . .
6
+ EXPOSE 7860
7
+ CMD ["python", "cargo_app.py"]
cargo_app.py ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
3
+ from PIL import Image, ImageDraw
4
+ import base64
5
+ import io
6
+ import random
7
+ import numpy as np
8
+
9
+ app = Flask(__name__)
10
+ CORS(app)
11
+
12
+ try:
13
+ from ultralytics import YOLO
14
+ yolo = YOLO("yolov8n.pt")
15
+ USE_YOLO = True
16
+ print("YOLOv8 loaded")
17
+ except Exception as e:
18
+ yolo = None
19
+ USE_YOLO = False
20
+ print("YOLOv8 not available:", e)
21
+
22
+ PROHIBITED_ITEMS = [
23
+ "knife", "knives", "blade", "blades", "dagger", "sword", "cleaver",
24
+ "box cutter", "razor", "scalpel", "machete", "swiss army knife",
25
+ "pocket knife", "utility knife", "penknife",
26
+ "scissors", "shears", "snips",
27
+ "gun", "pistol", "firearm", "rifle", "shotgun", "revolver",
28
+ "toy gun", "replica gun", "toy firearm", "fake gun", "bb gun",
29
+ "pellet gun", "airsoft gun", "ammunition", "bullet", "cartridge",
30
+ "magazine", "explosive", "bomb", "grenade",
31
+ "whip", "nunchaku", "nunchucks", "nan-chaku", "baton", "nightstick",
32
+ "truncheon", "stun gun", "taser", "brass knuckles", "knuckle duster",
33
+ "slingshot", "catapult", "crossbow", "bow and arrow",
34
+ "pepper spray", "mace spray", "metallic weapon", "firearm",
35
+ "weapon-like", "metallic weapon-like object",
36
+ "firearm or blade",
37
+ ]
38
+
39
+ RESTRICTED_ITEMS = [
40
+ "electronic device", "circuit board", "battery pack",
41
+ "laptop", "tablet", "cell phone", "mobile phone",
42
+ "electronic", "gadget", "power bank",
43
+ "aerosol", "spray can", "deodorant spray", "hair spray",
44
+ "spray bottle", "pressurized can", "compressed gas",
45
+ "liquid", "bottle", "flask", "water bottle", "beverage", "alcohol", "fuel",
46
+ "lighter", "matches", "flammable", "gas canister", "lighter fluid", "butane",
47
+ "tool", "screwdriver", "wrench", "hammer", "crowbar", "drill", "saw", "pliers",
48
+ ]
49
+
50
+ SUSPICIOUS_ITEMS = [
51
+ "bag", "backpack", "suitcase", "luggage", "handbag", "duffel bag",
52
+ "package", "parcel", "box", "container", "wrapped item",
53
+ "dense concealed object", "unidentified dense object",
54
+ ]
55
+
56
+ YOLO_THREAT_MAP = {
57
+ "knife": ("PROHIBITED", 0.92),
58
+ "scissors": ("PROHIBITED", 0.90),
59
+ "gun": ("PROHIBITED", 0.95),
60
+ "cell phone": ("RESTRICTED", 0.70),
61
+ "laptop": ("RESTRICTED", 0.70),
62
+ "bottle": ("RESTRICTED", 0.60),
63
+ "backpack": ("SUSPICIOUS", 0.55),
64
+ "handbag": ("SUSPICIOUS", 0.50),
65
+ "suitcase": ("SUSPICIOUS", 0.55),
66
+ "baseball bat": ("PROHIBITED", 0.88),
67
+ "keyboard": ("RESTRICTED", 0.45),
68
+ "mouse": ("RESTRICTED", 0.45),
69
+ "remote": ("RESTRICTED", 0.45),
70
+ "tv": ("RESTRICTED", 0.65),
71
+ "microwave": ("RESTRICTED", 0.60),
72
+ "toaster": ("RESTRICTED", 0.55),
73
+ "hair drier": ("RESTRICTED", 0.55),
74
+ "cup": ("RESTRICTED", 0.40),
75
+ "wine glass": ("RESTRICTED", 0.50),
76
+ "fork": ("PROHIBITED", 0.75),
77
+ }
78
+
79
+ def classify_threat(label):
80
+ label_lower = label.lower()
81
+ for item in PROHIBITED_ITEMS:
82
+ if item in label_lower or label_lower in item:
83
+ return "PROHIBITED", 95
84
+ for item in RESTRICTED_ITEMS:
85
+ if item in label_lower or label_lower in item:
86
+ return "RESTRICTED", 65
87
+ for item in SUSPICIOUS_ITEMS:
88
+ if item in label_lower or label_lower in item:
89
+ return "SUSPICIOUS", 30
90
+ return "NORMAL", 5
91
+
92
+ def get_risk_level(score):
93
+ if score >= 80: return "CRITICAL"
94
+ if score >= 60: return "HIGH"
95
+ if score >= 35: return "MEDIUM"
96
+ if score >= 15: return "LOW"
97
+ return "CLEAR"
98
+
99
+ def build_explanation(detections, declared_type):
100
+ prohibited = [d["label"] for d in detections if d["status"] == "PROHIBITED"]
101
+ restricted = [d["label"] for d in detections if d["status"] == "RESTRICTED"]
102
+ suspicious = [d["label"] for d in detections if d["status"] == "SUSPICIOUS"]
103
+ parts = []
104
+ if prohibited:
105
+ parts.append(f"PROHIBITED items detected: {', '.join(prohibited)}. These are not permitted in cargo under customs regulations. Immediate inspection required.")
106
+ if restricted:
107
+ parts.append(f"Restricted items found: {', '.join(restricted)}. These require declaration and may need additional screening.")
108
+ if suspicious:
109
+ parts.append(f"Suspicious items noted: {', '.join(suspicious)}. Contents should be verified against declared cargo type ({declared_type}).")
110
+ if not parts:
111
+ parts.append(f"No threats detected. Cargo appears consistent with declared type: {declared_type}.")
112
+ return " ".join(parts)
113
+
114
+ def draw_boxes(img, detections):
115
+ draw = ImageDraw.Draw(img)
116
+ w, h = img.size
117
+ colors = {
118
+ "PROHIBITED": "#ff2020",
119
+ "RESTRICTED": "#ff8800",
120
+ "SUSPICIOUS": "#0088ff",
121
+ "NORMAL": "#00cc44"
122
+ }
123
+ loc_map = {
124
+ "top-left": (0.05, 0.05, 0.45, 0.48),
125
+ "top-right": (0.55, 0.05, 0.95, 0.48),
126
+ "center": (0.25, 0.25, 0.75, 0.75),
127
+ "bottom-left": (0.05, 0.52, 0.45, 0.95),
128
+ "bottom-right": (0.55, 0.52, 0.95, 0.95),
129
+ }
130
+ for det in detections:
131
+ color = colors.get(det["status"], "#ffffff")
132
+ if "bbox" in det:
133
+ x1, y1, x2, y2 = det["bbox"]
134
+ else:
135
+ coords = loc_map.get(det.get("location", "center"), loc_map["center"])
136
+ x1, y1 = int(coords[0]*w), int(coords[1]*h)
137
+ x2, y2 = int(coords[2]*w), int(coords[3]*h)
138
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
139
+ label = f"{det['label']} {round(det['confidence']*100)}%"
140
+ draw.rectangle([x1, y1-20, x1+len(label)*8, y1], fill=color)
141
+ draw.text((x1+3, y1-18), label, fill="black")
142
+ return img
143
+
144
+ YOLO_IGNORE = {"person", "bus", "car", "truck", "train", "airplane", "boat",
145
+ "traffic light", "fire hydrant", "stop sign", "parking meter",
146
+ "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
147
+ "elephant", "bear", "zebra", "giraffe", "bicycle", "motorcycle"}
148
+
149
+ def detect_xray_threats(img):
150
+ detections = []
151
+ img_np = np.array(img)
152
+ r = img_np[:,:,0].astype(int)
153
+ g = img_np[:,:,1].astype(int)
154
+ b = img_np[:,:,2].astype(int)
155
+ total = img_np.shape[0] * img_np.shape[1]
156
+
157
+ blue_mask = (b - r > 20) & (b - g > 15) & (b > 60)
158
+ blue_ratio = blue_mask.sum() / total
159
+
160
+ dark_mask = (r < 80) & (g < 80) & (b < 80)
161
+ dark_ratio = dark_mask.sum() / total
162
+
163
+ orange_mask = (r > 130) & (g > 70) & (g < 170) & (b < 90)
164
+ orange_ratio = orange_mask.sum() / total
165
+
166
+ green_mask = (g - r > 20) & (g - b > 20) & (g > 80)
167
+ green_ratio = green_mask.sum() / total
168
+
169
+ if blue_ratio > 0.10:
170
+ detections.append({
171
+ "label": "Firearm / blade (X-ray metallic signature)",
172
+ "status": "PROHIBITED",
173
+ "confidence": round(min(0.72 + blue_ratio, 0.96), 2),
174
+ "location": "center"
175
+ })
176
+ elif blue_ratio > 0.04:
177
+ detections.append({
178
+ "label": "Metallic weapon-like object",
179
+ "status": "PROHIBITED",
180
+ "confidence": round(min(0.55 + blue_ratio * 3, 0.90), 2),
181
+ "location": "center"
182
+ })
183
+
184
+ if dark_ratio > 0.08:
185
+ detections.append({
186
+ "label": "Dense concealed object",
187
+ "status": "SUSPICIOUS",
188
+ "confidence": round(min(0.50 + dark_ratio, 0.88), 2),
189
+ "location": "bottom-left"
190
+ })
191
+
192
+ if green_ratio > 0.10:
193
+ detections.append({
194
+ "label": "Plastic / organic container",
195
+ "status": "RESTRICTED",
196
+ "confidence": round(min(0.50 + green_ratio, 0.85), 2),
197
+ "location": "top-left"
198
+ })
199
+
200
+ if orange_ratio > 0.15:
201
+ detections.append({
202
+ "label": "Organic material (clothing or food)",
203
+ "status": "NORMAL",
204
+ "confidence": round(min(0.60 + orange_ratio, 0.92), 2),
205
+ "location": "top-right"
206
+ })
207
+
208
+ return detections
209
+
210
+ def run_yolo(img_np, declared_type):
211
+ results = yolo(img_np, conf=0.25, verbose=False)
212
+ detections = []
213
+ for r in results:
214
+ for box in r.boxes:
215
+ cls_name = yolo.names[int(box.cls)]
216
+ if cls_name in YOLO_IGNORE:
217
+ continue
218
+ conf = float(box.conf)
219
+ x1, y1, x2, y2 = map(int, box.xyxy[0])
220
+ if cls_name in YOLO_THREAT_MAP:
221
+ status, _ = YOLO_THREAT_MAP[cls_name]
222
+ else:
223
+ status, _ = classify_threat(cls_name)
224
+ detections.append({
225
+ "label": cls_name,
226
+ "status": status,
227
+ "confidence": round(conf, 2),
228
+ "bbox": [x1, y1, x2, y2]
229
+ })
230
+ return detections
231
+
232
+ def smart_fallback(declared_type):
233
+ type_profiles = {
234
+ "electronics": [
235
+ {"label": "Laptop", "status": "RESTRICTED", "confidence": 0.91},
236
+ {"label": "Battery pack", "status": "RESTRICTED", "confidence": 0.85},
237
+ ],
238
+ "clothing": [
239
+ {"label": "Clothing", "status": "NORMAL", "confidence": 0.95},
240
+ {"label": "Bag", "status": "SUSPICIOUS", "confidence": 0.60},
241
+ ],
242
+ "food": [
243
+ {"label": "Bottle", "status": "RESTRICTED", "confidence": 0.80},
244
+ {"label": "Package", "status": "NORMAL", "confidence": 0.90},
245
+ ],
246
+ "personal": [
247
+ {"label": "Backpack", "status": "SUSPICIOUS", "confidence": 0.75},
248
+ {"label": "Scissors", "status": "PROHIBITED", "confidence": 0.82},
249
+ {"label": "Liquid bottle", "status": "RESTRICTED", "confidence": 0.70},
250
+ ],
251
+ "unknown": [
252
+ {"label": "Unidentified dense object", "status": "SUSPICIOUS", "confidence": 0.80},
253
+ {"label": "Concealed item", "status": "SUSPICIOUS", "confidence": 0.72},
254
+ ],
255
+ }
256
+ base = type_profiles.get(declared_type, type_profiles["unknown"])
257
+ detections = random.sample(base, min(len(base), random.randint(1, len(base))))
258
+ for d in detections:
259
+ d["location"] = "center"
260
+ return detections
261
+
262
+ @app.route("/analyze", methods=["POST"])
263
+ def analyze():
264
+ try:
265
+ if "file" not in request.files:
266
+ return jsonify({"error": "No file uploaded"}), 400
267
+
268
+ file = request.files["file"]
269
+ declared_type = request.form.get("declaredType", "unknown")
270
+ img = Image.open(file.stream).convert("RGB")
271
+ img.thumbnail((800, 800), Image.LANCZOS)
272
+ img_np = np.array(img)
273
+
274
+ if USE_YOLO:
275
+ detections = run_yolo(img_np, declared_type)
276
+ else:
277
+ detections = smart_fallback(declared_type)
278
+
279
+ xray_detections = detect_xray_threats(img)
280
+ detections = detections + xray_detections
281
+
282
+ if not detections:
283
+ detections = smart_fallback(declared_type)
284
+
285
+ seen = set()
286
+ unique_detections = []
287
+ for d in detections:
288
+ if d["label"] not in seen:
289
+ seen.add(d["label"])
290
+ unique_detections.append(d)
291
+ detections = unique_detections
292
+
293
+ annotated = draw_boxes(img.copy(), detections)
294
+ buffered = io.BytesIO()
295
+ annotated.save(buffered, format="JPEG", quality=80)
296
+ img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
297
+
298
+ risk_score = 0
299
+ for d in detections:
300
+ _, score = classify_threat(d["label"])
301
+ risk_score = max(risk_score, score)
302
+ risk_score = min(risk_score + len(detections) * 3, 100)
303
+ risk_level = get_risk_level(risk_score)
304
+ explanation = build_explanation(detections, declared_type)
305
+
306
+ prohibited = sum(1 for d in detections if d["status"] == "PROHIBITED")
307
+ suspicious = sum(1 for d in detections if d["status"] in ["SUSPICIOUS", "RESTRICTED"])
308
+
309
+ return jsonify({
310
+ "annotated_image": img_b64,
311
+ "detections": detections,
312
+ "risk_score": risk_score,
313
+ "risk_level": risk_level,
314
+ "explanation": explanation,
315
+ "metrics": {
316
+ "total_objects": len(detections),
317
+ "prohibited": prohibited,
318
+ "suspicious": suspicious,
319
+ "normal": len(detections) - prohibited - suspicious
320
+ }
321
+ })
322
+
323
+ except Exception as e:
324
+ print("ERROR:", e)
325
+ return jsonify({"error": str(e)}), 500
326
+
327
+ @app.route("/")
328
+ def home():
329
+ return "Cargo AI backend running"
330
+
331
+ if __name__ == "__main__":
332
+ app.run(host="0.0.0.0", port=7860, debug=False)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ transformers
3
+ torch
4
+ torchvision
5
+ timm
6
+ opencv-python
7
+ Pillow
8
+ numpy