Unlimitedlevel19 commited on
Commit
f4971d3
·
verified ·
1 Parent(s): ef71d06

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -385
app.py CHANGED
@@ -1,301 +1,66 @@
1
  import gradio as gr
2
- import torch
3
- try:
4
- import cv2
5
- CV2_AVAILABLE = True
6
- except ImportError:
7
- CV2_AVAILABLE = False
8
  import numpy as np
9
  from PIL import Image, ImageDraw, ImageFont
10
  import io
11
  import base64
12
- import os
13
  from datetime import datetime
14
  import json
 
15
 
16
- # Model dan kelas deteksi (dalam implementasi nyata, akan menggunakan model Hugging Face)
17
- PPE_CLASSES = ["helmet", "vest", "harness", "gloves", "boots", "goggles", "mask"]
18
- HEAD_PROTECTION = ["helmet"]
19
- BODY_PROTECTION = ["vest", "harness"]
20
- HAND_PROTECTION = ["gloves"]
21
- FOOT_PROTECTION = ["boots", "safety_shoes"]
22
- EYE_PROTECTION = ["goggles"]
23
- RESPIRATORY_PROTECTION = ["mask"]
24
-
25
- # Tentukan jenis pekerjaan dan APD yang diperlukan
26
- WORK_TYPES = {
27
- "ketinggian": {
28
- "required": ["head_protection", "body_protection", "foot_protection"],
29
- "recommended": ["hand_protection"]
30
- },
31
- "konstruksi_umum": {
32
- "required": ["head_protection", "foot_protection"],
33
- "recommended": ["body_protection", "hand_protection"]
34
- },
35
- "listrik": {
36
- "required": ["head_protection", "hand_protection", "foot_protection"],
37
- "recommended": ["eye_protection"]
38
- }
39
- }
40
-
41
- def preprocess_image(image):
42
- """Preproses gambar untuk analisis"""
43
- if isinstance(image, np.ndarray):
44
- # Konversi dari numpy array ke PIL Image
45
- if CV2_AVAILABLE and image.shape[2] == 3: # RGB
46
- return Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
47
- else:
48
- return Image.fromarray(image)
49
- return image
50
-
51
- def analyze_image(image):
52
- """
53
- Fungsi analisis gambar simulasi
54
- Dalam implementasi nyata, ini akan menggunakan model Hugging Face
55
- """
56
- # Dapatkan dimensi gambar
57
- if isinstance(image, np.ndarray):
58
- height, width = image.shape[:2]
59
- else:
60
- width, height = image.size
61
-
62
- # Deteksi orang (simulasi)
63
- # Dalam kasus nyata, model deteksi akan mengembalikan kotak untuk semua orang
64
- persons = [
65
- {"box": [int(width*0.3), int(height*0.1), int(width*0.7), int(height*0.9)], "confidence": 0.95}
66
- ]
67
-
68
- # Deteksi APD (simulasi untuk demo)
69
- ppe_detections = []
70
-
71
- # Simulasikan harness tetapi tidak ada helm, sarung tangan, dan sepatu safety
72
- ppe_detections.append({
73
- "item": "harness",
74
- "box": [int(width*0.35), int(height*0.3), int(width*0.65), int(height*0.7)],
75
- "confidence": 0.88,
76
- "ppe_type": "body_protection"
77
- })
78
-
79
- # Mengembalikan orang + APD yang terdeteksi
80
- results = []
81
-
82
- # Tambahkan deteksi orang
83
- for person in persons:
84
- results.append({
85
- "item": "person",
86
- "box": person["box"],
87
- "confidence": person["confidence"],
88
- "ppe_type": None
89
- })
90
-
91
- # Tambahkan deteksi APD
92
- results.extend(ppe_detections)
93
-
94
- return results
95
-
96
- def evaluate_compliance(detections, work_type="ketinggian"):
97
- """Evaluasi kepatuhan APD berdasarkan deteksi dan jenis pekerjaan"""
98
- has_person = any(d["item"] == "person" for d in detections)
99
-
100
- if not has_person:
101
- return "Tidak ada orang terdeteksi", []
102
-
103
- # Periksa perlindungan yang ada
104
- protections = {
105
- "head_protection": any(d["ppe_type"] == "head_protection" for d in detections),
106
- "body_protection": any(d["ppe_type"] == "body_protection" for d in detections),
107
- "hand_protection": any(d["ppe_type"] == "hand_protection" for d in detections),
108
- "foot_protection": any(d["ppe_type"] == "foot_protection" for d in detections),
109
- "eye_protection": any(d["ppe_type"] == "eye_protection" for d in detections),
110
- "respiratory_protection": any(d["ppe_type"] == "respiratory_protection" for d in detections)
111
- }
112
-
113
- # Tentukan persyaratan APD berdasarkan jenis pekerjaan
114
- if work_type in WORK_TYPES:
115
- required = WORK_TYPES[work_type]["required"]
116
- recommended = WORK_TYPES[work_type]["recommended"]
117
- else:
118
- # Default untuk konstruksi umum jika jenis pekerjaan tidak dikenali
119
- required = WORK_TYPES["konstruksi_umum"]["required"]
120
- recommended = WORK_TYPES["konstruksi_umum"]["recommended"]
121
-
122
- # Periksa perlindungan wajib
123
- critical_issues = []
124
- recommended_issues = []
125
-
126
- for protection in required:
127
- if not protections[protection]:
128
- if protection == "head_protection":
129
- critical_issues.append("Tidak ada pelindung kepala (helm)")
130
- elif protection == "body_protection":
131
- critical_issues.append("Tidak ada pelindung tubuh (harness/rompi)")
132
- elif protection == "hand_protection":
133
- critical_issues.append("Tidak ada pelindung tangan (sarung tangan)")
134
- elif protection == "foot_protection":
135
- critical_issues.append("Tidak ada pelindung kaki (sepatu safety)")
136
- elif protection == "eye_protection":
137
- critical_issues.append("Tidak ada pelindung mata (kacamata safety)")
138
- elif protection == "respiratory_protection":
139
- critical_issues.append("Tidak ada pelindung pernapasan (masker)")
140
-
141
- # Periksa perlindungan yang direkomendasikan
142
- for protection in recommended:
143
- if not protections[protection]:
144
- if protection == "head_protection":
145
- recommended_issues.append("Sebaiknya menggunakan pelindung kepala (helm)")
146
- elif protection == "body_protection":
147
- recommended_issues.append("Sebaiknya menggunakan pelindung tubuh (harness/rompi)")
148
- elif protection == "hand_protection":
149
- recommended_issues.append("Sebaiknya menggunakan pelindung tangan (sarung tangan)")
150
- elif protection == "foot_protection":
151
- recommended_issues.append("Sebaiknya menggunakan pelindung kaki (sepatu safety)")
152
- elif protection == "eye_protection":
153
- recommended_issues.append("Sebaiknya menggunakan pelindung mata (kacamata safety)")
154
- elif protection == "respiratory_protection":
155
- recommended_issues.append("Sebaiknya menggunakan pelindung pernapasan (masker)")
156
-
157
- # Menentukan status kepatuhan
158
- all_issues = critical_issues + recommended_issues
159
- if len(critical_issues) == 0:
160
- if len(recommended_issues) == 0:
161
- return "Patuh (Compliant)", []
162
- else:
163
- return "Patuh dengan Catatan", all_issues
164
- else:
165
- return "Tidak Patuh (Non-compliant)", all_issues
166
-
167
- def draw_detections(image, detections):
168
- """Menggambar kotak deteksi pada gambar"""
169
- # Buat salinan gambar
170
- if isinstance(image, np.ndarray):
171
- img_copy = Image.fromarray(image.copy())
172
- else:
173
- img_copy = image.copy()
174
-
175
- draw = ImageDraw.Draw(img_copy)
176
-
177
- # Coba dapatkan font yang tersedia
178
- try:
179
- font = ImageFont.truetype("arial.ttf", 15)
180
- except IOError:
181
- try:
182
- font = ImageFont.truetype("DejaVuSans.ttf", 15)
183
- except IOError:
184
- font = ImageFont.load_default()
185
-
186
- # Gambar kotak deteksi
187
- for detection in detections:
188
- box = detection["box"]
189
- label = detection["item"]
190
- confidence = detection["confidence"]
191
- ppe_type = detection["ppe_type"]
192
-
193
- # Pilih warna berdasarkan tipe
194
- if label == "person":
195
- color = "red"
196
- elif ppe_type == "head_protection":
197
- color = "blue"
198
- elif ppe_type == "body_protection":
199
- color = "green"
200
- elif ppe_type:
201
- color = "purple"
202
- else:
203
- color = "yellow"
204
-
205
- # Gambar box and label
206
- draw.rectangle([(box[0], box[1]), (box[2], box[3])], outline=color, width=3)
207
- text = f"{label}: {confidence:.2f}"
208
-
209
- # Estimasi ukuran teks jika draw.textsize tidak tersedia
210
- text_w, text_h = (100, 15) # Default
211
- if hasattr(draw, 'textsize'):
212
- text_w, text_h = draw.textsize(text, font=font)
213
-
214
- # Background untuk teks
215
- draw.rectangle([(box[0], box[1]-text_h-4), (box[0]+text_w, box[1])], fill=color)
216
- draw.text((box[0], box[1]-text_h-2), text, fill="white", font=font)
217
-
218
- return img_copy
219
 
220
  def detect_ppe(image, work_type="ketinggian"):
221
- """
222
- Fungsi utama untuk mendeteksi APD dalam gambar
223
- """
224
  if image is None:
225
  return None, "Tidak ada gambar", {"error": "Tidak ada gambar yang diberikan"}
226
 
227
  try:
228
- # Preproses gambar
229
- if isinstance(image, np.ndarray) and image.shape[2] == 4 and CV2_AVAILABLE: # RGBA
230
- image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
231
-
232
- # 1. Analisis gambar
233
- detections = analyze_image(image)
234
 
235
- # 2. Evaluasi kepatuhan berdasarkan jenis pekerjaan
236
- compliance_status, compliance_issues = evaluate_compliance(detections, work_type)
237
 
238
- # 3. Gambar hasil deteksi
239
- result_image = draw_detections(image, detections)
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- # 4. Identifikasi APD yang hilang
242
- missing_ppe = []
243
- for issue in compliance_issues:
244
- if "Tidak ada" in issue:
245
- missing_ppe.append(issue.replace("Tidak ada ", ""))
246
 
247
- # 5. Siapkan respons - format data array dengan indeks dimulai dari 1 bukan 0
248
- # Proses detections agar terformat dengan baik
249
- formatted_detections = {}
250
- for idx, detection in enumerate(detections, 1):
251
- # Format box array agar lebih rapi
252
- box_values = detection["box"]
253
- formatted_box = {}
254
- for i, coord in enumerate(box_values):
255
- formatted_box[str(i+1)] = coord
256
-
257
- # Buat entry detection dengan format yang rapi
258
- formatted_detections[str(idx)] = {
259
- "item": detection["item"],
260
- "box": formatted_box,
261
- "confidence": detection["confidence"],
262
- "ppe_type": detection["ppe_type"]
263
- }
264
-
265
- # Format issues dengan indeks dimulai dari 1
266
- formatted_issues = {}
267
- for idx, issue in enumerate(compliance_issues, 1):
268
- formatted_issues[str(idx)] = issue
269
-
270
- # Format missing_ppe dengan indeks dimulai dari 1
271
- formatted_missing = {}
272
- for idx, item in enumerate(missing_ppe, 1):
273
- formatted_missing[str(idx)] = item
274
-
275
- # Format recommendations dengan indeks dimulai dari 1
276
- recommendations = []
277
- for issue in compliance_issues:
278
- if "Tidak ada" in issue:
279
- recommendations.append(f"Pastikan pekerja menggunakan {issue.replace('Tidak ada ', '')}")
280
- elif "Sebaiknya menggunakan" in issue:
281
- recommendations.append(issue)
282
-
283
- formatted_recommendations = {}
284
- for idx, rec in enumerate(recommendations, 1):
285
- formatted_recommendations[str(idx)] = rec
286
 
287
- # Buat hasil analisis yang terformat dengan baik
288
  result = {
289
- "detections": formatted_detections,
290
  "compliance": {
291
  "status": compliance_status,
292
- "issues": formatted_issues
293
  },
294
- "missing_ppe": formatted_missing,
295
  "work_type": work_type,
296
  "summary": f"Deteksi: {len(detections)} objek. Status: {compliance_status}",
297
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
298
- "recommendations": formatted_recommendations
 
 
 
299
  }
300
 
301
  return result_image, compliance_status, result
@@ -303,67 +68,78 @@ def detect_ppe(image, work_type="ketinggian"):
303
  except Exception as e:
304
  return None, f"Error: {str(e)}", {"error": str(e)}
305
 
306
- # Fungsi untuk menerima dan mengolah base64 image
307
- def process_base64_image(base64_string, work_type="ketinggian"):
 
 
 
308
  try:
309
- # Cek apakah base64 datang dengan header data URL
310
- if "data:" in base64_string and ";base64," in base64_string:
311
- base64_string = base64_string.split(";base64,")[1]
312
-
313
- # Decode base64 ke bytes
314
- image_bytes = base64.b64decode(base64_string)
 
 
315
 
316
- # Buka gambar dari bytes
317
- image = Image.open(io.BytesIO(image_bytes))
318
 
319
- # Konversi ke numpy array jika model Anda memerlukan format ini
320
- image_np = np.array(image)
 
321
 
322
- # Proses gambar dengan fungsi deteksi APD yang ada
323
- return detect_ppe(image_np, work_type)
324
- except Exception as e:
325
- return None, f"Error processing base64 image: {str(e)}", {"error": str(e)}
 
326
 
327
- # API handler untuk base64 images
328
- def api_predict(data):
 
 
329
  try:
 
 
 
 
 
 
 
330
  # Extract data
331
- if len(data) == 0:
332
- return [None, "Error: No data provided", {"error": "No data provided"}]
 
 
 
 
333
 
334
- # Data[0] adalah gambar base64, data[1] adalah jenis pekerjaan (opsional)
335
- base64_image = data[0]
336
- work_type = data[1] if len(data) > 1 else "ketinggian"
 
 
337
 
338
- # Process the image
339
- result_image, status, analysis_result = process_base64_image(base64_image, work_type)
340
 
341
- # Convert result image to base64 for API response
342
  if result_image is not None:
343
  buffered = io.BytesIO()
344
  result_image.save(buffered, format="JPEG")
345
  img_str = "data:image/jpeg;base64," + base64.b64encode(buffered.getvalue()).decode()
346
  return [img_str, status, analysis_result]
347
- else:
348
- return [None, status, analysis_result]
349
  except Exception as e:
350
- return [None, f"API Error: {str(e)}", {"error": str(e)}]
351
 
352
- # Fungsi untuk menguji API secara langsung
353
- def test_api(test_image, test_work_type):
354
- # Konversi gambar ke base64
355
- if test_image is not None:
356
- buffered = io.BytesIO()
357
- test_image.save(buffered, format="JPEG")
358
- img_base64 = "data:image/jpeg;base64," + base64.b64encode(buffered.getvalue()).decode()
359
-
360
- # Panggil API dengan data yang diformat dengan benar
361
- result = api_predict([img_base64, test_work_type])
362
- return result
363
- return [None, "Error: No test image provided", {"error": "No test image provided"}]
364
 
365
- # Buat interface Gradio untuk UI
366
- with gr.Blocks(title="Sistem Deteksi APD untuk K3L Platform") as demo:
367
  gr.Markdown("# Sistem Deteksi APD untuk K3L Platform")
368
  gr.Markdown("Upload gambar untuk analisis kepatuhan APD di lokasi konstruksi")
369
 
@@ -395,76 +171,24 @@ with gr.Blocks(title="Sistem Deteksi APD untuk K3L Platform") as demo:
395
  3. Klik tombol "Analisis Gambar"
396
  4. Lihat hasil analisis dengan kotak deteksi, status kepatuhan, dan rekomendasi
397
 
398
- ### Deteksi APD Wajib untuk Pekerjaan di Ketinggian
399
- - Helm (wajib)
400
- - Safety harness (wajib)
401
- - Sepatu safety (wajib)
402
- - Sarung tangan (direkomendasikan)
403
- """)
404
-
405
- status_msg = "✅ OpenCV tersedia" if CV2_AVAILABLE else "⚠️ OpenCV tidak tersedia - menggunakan mode fallback"
406
- gr.Markdown(f"*Status sistem: {status_msg}*")
407
-
408
- # Tambahkan tab untuk testing API
409
- with gr.Blocks(title="API Testing") as api_test_interface:
410
- gr.Markdown("## Test API Endpoint")
411
- gr.Markdown("Test the API endpoint with an example image")
412
-
413
- with gr.Row():
414
- with gr.Column():
415
- test_input_image = gr.Image(label="Test Image")
416
- test_work_type = gr.Radio(
417
- label="Work Type",
418
- choices=["ketinggian", "konstruksi_umum", "listrik"],
419
- value="ketinggian"
420
- )
421
- test_btn = gr.Button("Test API", variant="primary")
422
-
423
- with gr.Column():
424
- test_output_image = gr.Image(label="API Result Image")
425
- test_status = gr.Textbox(label="API Status")
426
- test_json = gr.JSON(label="API Response")
427
-
428
- test_btn.click(
429
- fn=test_api,
430
- inputs=[test_input_image, test_work_type],
431
- outputs=[test_output_image, test_status, test_json]
432
- )
433
-
434
- # Buat interface dengan tabs dan definisikan API endpoint di dalam konteks gr.Blocks
435
- with gr.Blocks() as combined_interface:
436
- with gr.Tabs():
437
- with gr.TabItem("K3L APD Detection"):
438
- demo.render()
439
- with gr.TabItem("API Testing"):
440
- api_test_interface.render()
441
 
442
- # PERBAIKAN: Daftarkan API endpoint DI DALAM konteks gr.Blocks
443
- gr.Interface(
444
- fn=api_predict,
445
- inputs=[gr.JSON(label="Data")],
446
- outputs=[
447
- gr.Image(label="Result Image"),
448
- gr.Textbox(label="Status"),
449
- gr.JSON(label="Analysis Result")
450
- ],
451
- title="K3L PPE Detection API",
452
- description="API for detecting PPE in construction sites",
453
- flagging_mode="never"
454
- ).queue().launch(prevent_thread_lock=True, show_api=True, share=False)
455
-
456
- # Simpan fungsi queue untuk berfungsi dengan baik dengan semua interface
457
- combined_interface.queue()
458
 
459
- # Launch aplikasi dengan semua konfigurasi yang diperlukan
460
- combined_interface.launch(
461
- share=True, # Mengaktifkan URL publik
462
- server_name="0.0.0.0", # Bind ke semua interfaces
463
- server_port=7860, # Port standar Gradio
464
- enable_queue=True, # Mengaktifkan antrian untuk permintaan API
465
- )
466
 
467
- # CATATAN: Endpoint API yang terdaftar akan tersedia di:
468
- # POST https://[your-space-url]/api/predict
469
- # dengan format payload:
470
- # {"data": ["base64_image_string", "work_type_string"]}
 
1
  import gradio as gr
 
 
 
 
 
 
2
  import numpy as np
3
  from PIL import Image, ImageDraw, ImageFont
4
  import io
5
  import base64
 
6
  from datetime import datetime
7
  import json
8
+ from fastapi import FastAPI, Request
9
 
10
+ # Setup FastAPI instance
11
+ app = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  def detect_ppe(image, work_type="ketinggian"):
14
+ """Fungsi utama untuk deteksi APD (simulasi)"""
 
 
15
  if image is None:
16
  return None, "Tidak ada gambar", {"error": "Tidak ada gambar yang diberikan"}
17
 
18
  try:
19
+ # Konversi ke PIL Image jika perlu
20
+ if isinstance(image, np.ndarray):
21
+ pil_image = Image.fromarray(image)
22
+ else:
23
+ pil_image = image
 
24
 
25
+ width, height = pil_image.size
 
26
 
27
+ # Simulasikan hasil deteksi
28
+ detections = [
29
+ {
30
+ "item": "person",
31
+ "box": [int(width*0.3), int(height*0.1), int(width*0.7), int(height*0.9)],
32
+ "confidence": 0.95,
33
+ "ppe_type": None
34
+ },
35
+ {
36
+ "item": "harness",
37
+ "box": [int(width*0.35), int(height*0.3), int(width*0.65), int(height*0.7)],
38
+ "confidence": 0.88,
39
+ "ppe_type": "body_protection"
40
+ }
41
+ ]
42
 
43
+ # Simulasikan hasil kepatuhan
44
+ compliance_status = "Tidak Patuh (Non-compliant)"
45
+ compliance_issues = ["Tidak ada pelindung kepala (helm)", "Tidak ada pelindung kaki (sepatu safety)"]
 
 
46
 
47
+ # Gambar hasil deteksi
48
+ result_image = draw_detections(pil_image, detections)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ # Format JSON untuk respons API
51
  result = {
52
+ "detections": {str(i+1): d for i, d in enumerate(detections)},
53
  "compliance": {
54
  "status": compliance_status,
55
+ "issues": {str(i+1): issue for i, issue in enumerate(compliance_issues)}
56
  },
 
57
  "work_type": work_type,
58
  "summary": f"Deteksi: {len(detections)} objek. Status: {compliance_status}",
59
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
60
+ "recommendations": {
61
+ "1": "Pastikan pekerja menggunakan pelindung kepala (helm)",
62
+ "2": "Pastikan pekerja menggunakan pelindung kaki (sepatu safety)"
63
+ }
64
  }
65
 
66
  return result_image, compliance_status, result
 
68
  except Exception as e:
69
  return None, f"Error: {str(e)}", {"error": str(e)}
70
 
71
+ def draw_detections(image, detections):
72
+ """Menggambar kotak deteksi pada gambar"""
73
+ img_copy = image.copy()
74
+ draw = ImageDraw.Draw(img_copy)
75
+
76
  try:
77
+ font = ImageFont.truetype("DejaVuSans.ttf", 15)
78
+ except IOError:
79
+ font = ImageFont.load_default()
80
+
81
+ for detection in detections:
82
+ box = detection["box"]
83
+ label = detection["item"]
84
+ confidence = detection["confidence"]
85
 
86
+ # Warna berdasarkan tipe
87
+ color = "red" if label == "person" else "blue"
88
 
89
+ # Gambar box
90
+ draw.rectangle([(box[0], box[1]), (box[2], box[3])], outline=color, width=3)
91
+ text = f"{label}: {confidence:.2f}"
92
 
93
+ # Label
94
+ draw.rectangle([(box[0], box[1]-20), (box[0]+100, box[1])], fill=color)
95
+ draw.text((box[0], box[1]-18), text, fill="white", font=font)
96
+
97
+ return img_copy
98
 
99
+ # API function for FastAPI
100
+ @app.post("/api/predict")
101
+ async def predict_api(request: Request):
102
+ """API endpoint untuk deteksi APD"""
103
  try:
104
+ # Baca JSON dari request
105
+ data = await request.json()
106
+
107
+ # Validasi data
108
+ if "data" not in data or not isinstance(data["data"], list) or len(data["data"]) == 0:
109
+ return {"error": "Invalid data format. Expected: {\"data\": [\"base64_image\", \"work_type\"]}"}
110
+
111
  # Extract data
112
+ base64_image = data["data"][0]
113
+ work_type = data["data"][1] if len(data["data"]) > 1 else "ketinggian"
114
+
115
+ # Hapus header data URL jika ada
116
+ if "data:" in base64_image and ";base64," in base64_image:
117
+ base64_image = base64_image.split(";base64,")[1]
118
 
119
+ # Decode base64 ke bytes
120
+ image_bytes = base64.b64decode(base64_image)
121
+
122
+ # Buka gambar dari bytes
123
+ image = Image.open(io.BytesIO(image_bytes))
124
 
125
+ # Proses gambar
126
+ result_image, status, analysis_result = detect_ppe(image, work_type)
127
 
128
+ # Konversi hasil gambar ke base64
129
  if result_image is not None:
130
  buffered = io.BytesIO()
131
  result_image.save(buffered, format="JPEG")
132
  img_str = "data:image/jpeg;base64," + base64.b64encode(buffered.getvalue()).decode()
133
  return [img_str, status, analysis_result]
134
+ return [None, status, analysis_result]
135
+
136
  except Exception as e:
137
+ return {"error": str(e)}
138
 
139
+ # Buat UI utama
140
+ demo = gr.Blocks(title="K3L APD Detection")
 
 
 
 
 
 
 
 
 
 
141
 
142
+ with demo:
 
143
  gr.Markdown("# Sistem Deteksi APD untuk K3L Platform")
144
  gr.Markdown("Upload gambar untuk analisis kepatuhan APD di lokasi konstruksi")
145
 
 
171
  3. Klik tombol "Analisis Gambar"
172
  4. Lihat hasil analisis dengan kotak deteksi, status kepatuhan, dan rekomendasi
173
 
174
+ ### API Endpoint
175
+ API endpoint tersedia di: `/api/predict`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
+ Format request (POST):
178
+ ```json
179
+ {
180
+ "data": [
181
+ "BASE64_IMAGE",
182
+ "JENIS_PEKERJAAN" (opsional)
183
+ ]
184
+ }
185
+ ```
186
+ """)
 
 
 
 
 
 
187
 
188
+ # Gabungkan Gradio dengan FastAPI
189
+ app = gr.mount_gradio_app(app, demo, path="/")
 
 
 
 
 
190
 
191
+ # Jalankan aplikasi dengan uvicorn
192
+ if __name__ == "__main__":
193
+ import uvicorn
194
+ uvicorn.run(app, host="0.0.0.0", port=7860)