Hussein El-Hadidy commited on
Commit
a9142c5
Β·
1 Parent(s): 53952ec

Deploy latest version to Hugging Face Space

Browse files
Files changed (2) hide show
  1. app.py +310 -196
  2. main.py +36 -54
app.py CHANGED
@@ -23,16 +23,29 @@ from fastapi import WebSocket, WebSocketDisconnect
23
  import base64
24
  import cv2
25
  import time
26
- from CPR.CPRAnalyzer import CPRAnalyzer
27
  import tempfile
28
  import matplotlib.pyplot as plt
29
-
30
-
31
-
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
  app = FastAPI()
35
 
 
 
36
  UPLOAD_DIR = "uploads"
37
  os.makedirs(UPLOAD_DIR, exist_ok=True)
38
 
@@ -44,18 +57,7 @@ except Exception as e:
44
  print(f"❌ Model loading failed: {str(e)}")
45
  model = None
46
 
47
- # βœ… MongoDB connection
48
- mongo_uri = "mongodb://husseinelhadidy03:W8ByXdBS4EFkZmd5@ac-lqfhgnk-shard-00-00.ycuagnj.mongodb.net:27017,ac-lqfhgnk-shard-00-01.ycuagnj.mongodb.net:27017,ac-lqfhgnk-shard-00-02.ycuagnj.mongodb.net:27017/?replicaSet=atlas-az5d0x-shard-0&ssl=true&authSource=admin&retryWrites=true&w=majority&appName=Cluster0"
49
- client = MongoClient(mongo_uri, server_api=ServerApi('1'))
50
 
51
- try:
52
- client.admin.command('ping')
53
- print("βœ… Successfully connected to MongoDB!")
54
- except Exception as e:
55
- print("❌ MongoDB connection failed:", e)
56
-
57
- # βœ… Use a shared database instance
58
- db = client["El7a2ny"]
59
 
60
  # βœ… Cloudinary config
61
  cloudinary.config(
@@ -65,35 +67,11 @@ cloudinary.config(
65
  secure=True
66
  )
67
 
68
- # βœ… Basic Hello route
69
  @app.get("/")
70
  def greet_json():
71
  return {"Hello": "World!"}
72
 
73
- # βœ… MongoDB document count route for Images collection
74
- @app.get("/count")
75
- def count_docs():
76
- collection = db["Images"]
77
- count = collection.count_documents({})
78
- return {"document_count": count}
79
-
80
- # βœ… Upload image to Cloudinary and save URL to MongoDB
81
- @app.post("/cloudinary/upload")
82
- async def upload_sample(file: UploadFile = File(...)):
83
- try:
84
- # Upload the file to Cloudinary
85
- result = cloudinary.uploader.upload(file.file, public_id=file.filename)
86
- uploaded_url = result["secure_url"]
87
-
88
- # Save image URL to MongoDB
89
- collection = db["Images"]
90
- doc = {"filename": file.filename, "url": uploaded_url}
91
- collection.insert_one(doc)
92
-
93
- return {"uploaded_url": uploaded_url}
94
- except Exception as e:
95
- return {"error": str(e)}
96
-
97
  @app.post("/predict_burn")
98
  async def predict_burn(file: UploadFile = File(...)):
99
  try:
@@ -102,10 +80,6 @@ async def predict_burn(file: UploadFile = File(...)):
102
  with open(temp_file_path, "wb") as temp_file:
103
  temp_file.write(await file.read())
104
 
105
- # Upload the file to Cloudinary
106
- #upload_result = cloudinary.uploader.upload(temp_file_path, public_id=f"predict_{file.filename}")
107
- #cloudinary_url = upload_result["secure_url"]
108
- cloudinary_url = "https:facebook.com"
109
 
110
  # Load the saved SVM model
111
  with open('svm_model.pkl', 'rb') as model_file:
@@ -133,21 +107,14 @@ async def predict_burn(file: UploadFile = File(...)):
133
  prediction_label = "Second Class"
134
  else:
135
  prediction_label = "Zero Class"
136
-
137
- # Save result to MongoDB
138
- #collection = db["Predictions"]
139
- #doc = {"filename": file.filename, "url": cloudinary_url, "prediction": prediction_label}
140
- #collection.insert_one(doc)
141
 
142
  return {
143
- "prediction": prediction_label,
144
- "image_url": cloudinary_url
145
  }
146
 
147
  except Exception as e:
148
  return JSONResponse(content={"error": str(e)}, status_code=500)
149
 
150
-
151
  @app.post("/segment_burn")
152
  async def segment_burn_endpoint(reference: UploadFile = File(...), patient: UploadFile = File(...)):
153
  try:
@@ -184,7 +151,7 @@ async def segment_burn_endpoint(reference: UploadFile = File(...), patient: Uplo
184
 
185
  os.remove(burn_crop_clean_path)
186
  os.remove(burn_crop_debug_path)
187
-
188
 
189
  return {
190
  "crop_clean_url": crop_clean_url,
@@ -195,19 +162,6 @@ async def segment_burn_endpoint(reference: UploadFile = File(...), patient: Uplo
195
  return JSONResponse(content={"error": str(e)}, status_code=500)
196
 
197
 
198
- # βœ… Optimize and transform image URL
199
- @app.get("/cloudinary/transform")
200
- def transform_image():
201
- try:
202
- optimized_url, _ = cloudinary_url("shoes", fetch_format="auto", quality="auto")
203
- auto_crop_url, _ = cloudinary_url("shoes", width=500, height=500, crop="auto", gravity="auto")
204
- return {
205
- "optimized_url": optimized_url,
206
- "auto_crop_url": auto_crop_url
207
- }
208
- except Exception as e:
209
- return {"error": str(e)}
210
-
211
  @app.post("/classify-ecg")
212
  async def classify_ecg_endpoint(file: UploadFile = File(...)):
213
  model = joblib.load('voting_classifier.pkl')
@@ -229,8 +183,7 @@ async def classify_ecg_endpoint(file: UploadFile = File(...)):
229
 
230
  except Exception as e:
231
  return JSONResponse(content={"error": str(e)}, status_code=500)
232
-
233
-
234
  @app.post("/diagnose-ecg")
235
  async def diagnose_ecg(file: UploadFile = File(...)):
236
  try:
@@ -266,6 +219,17 @@ async def diagnose_ecg(file: UploadFile = File(...)):
266
  return JSONResponse(content={"error": str(e)}, status_code=500)
267
 
268
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
  @app.post("/process_video")
271
  async def process_video(file: UploadFile = File(...)):
@@ -275,159 +239,309 @@ async def process_video(file: UploadFile = File(...)):
275
  print("File content type:", file.content_type)
276
  print("File filename:", file.filename)
277
 
278
- # Save uploaded file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  video_path = os.path.join(UPLOAD_DIR, file.filename)
280
  with open(video_path, "wb") as buffer:
281
  shutil.copyfileobj(file.file, buffer)
282
 
283
- print("[START] CPR Analysis started")
284
- start_time = time.time()
285
-
286
- cap = cv2.VideoCapture(video_path)
287
- fps = cap.get(cv2.CAP_PROP_FPS)
288
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
289
- duration_seconds = total_frames / fps
290
- chunk_duration = 10 # seconds
291
- frames_per_chunk = int(fps * chunk_duration)
292
-
293
- chunks = []
294
- chunk_index = 0
295
- current_frame = 0
296
-
297
- while current_frame < total_frames:
298
- # Read the chunk into memory
299
- frames = []
300
- for _ in range(frames_per_chunk):
301
- ret, frame = cap.read()
302
- if not ret:
303
- break
304
- frames.append(frame)
305
- current_frame += 1
306
-
307
- if not frames:
308
- break
309
-
310
- # Save chunk to temp video
311
- temp_chunk_path = os.path.join(tempfile.gettempdir(), f"chunk_{chunk_index}.mp4")
312
- height, width = frames[0].shape[:2]
313
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
314
- out = cv2.VideoWriter(temp_chunk_path, fourcc, fps, (width, height))
315
- for f in frames:
316
- out.write(f)
317
- out.release()
318
-
319
- # Analyze chunk
320
- print(f"[CHUNK {chunk_index}] Processing chunk at {temp_chunk_path}")
321
- analyzer = CPRAnalyzer(temp_chunk_path)
322
- analyzer.run_analysis()
323
 
324
- # Gather results
325
- metrics = analyzer.get_compression_metrics()
326
- warnings = analyzer.get_posture_warning_results()
327
 
328
- # Upload each warning to cloudinary
329
- for w in warnings:
330
- local_path = w['image_url']
331
- upload_result = cloudinary.uploader.upload(local_path, folder="posture_warnings")
332
- w['image_url'] = upload_result['secure_url']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
- # Estimate score (adjust this logic as needed)
335
- penalty = len(warnings) * 0.1
336
- rate_score = min(metrics["average_compression_rate"] / 120, 1.0)
337
- depth_score = min(metrics["average_compression_depth"] / 5.0, 1.0)
338
- average_score = max(0.0, (rate_score + depth_score)/2 - penalty)
339
 
340
- chunks.append({
341
- "average_score": round(average_score, 2),
342
- "average_rate": round(metrics["average_compression_rate"], 1),
343
- "average_depth": round(metrics["average_compression_depth"], 1),
344
- "posture_warnings": warnings
345
- })
346
 
347
- chunk_index += 1
 
 
 
 
 
348
 
349
- cap.release()
350
- print(f"[END] Total processing time: {time.time() - start_time:.2f}s")
351
 
352
- return JSONResponse(content={"chunks": chunks})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
 
355
- @app.post("/process_image")
356
- async def process_image(file: UploadFile = File(...)):
357
- if not file.content_type.startswith("image/"):
358
- raise HTTPException(status_code=400, detail="File must be an image.")
359
 
360
- print("File content type:", file.content_type)
361
- print("File filename:", file.filename)
362
 
363
- # Save uploaded image
364
- image_path = os.path.join(UPLOAD_DIR, file.filename)
365
- with open(image_path, "wb") as buffer:
366
- shutil.copyfileobj(file.file, buffer)
367
 
368
-
 
369
 
370
- # Run YOLO detection on the image
371
- try:
372
- results = model(
373
- source=image_path,
374
- show=False,
375
- save=False
376
- )
377
 
378
- if not results or len(results) == 0 or results[0].keypoints is None or results[0].keypoints.xy is None:
379
- return JSONResponse(content={"message": "No keypoints detected"}, status_code=200)
 
 
380
 
381
- keypoints = results[0].keypoints.xy
382
- confidences = results[0].boxes.conf if results[0].boxes is not None else []
383
 
384
- except Exception as e:
385
- raise HTTPException(status_code=500, detail=f"YOLO processing error: {str(e)}")
 
 
 
 
386
 
387
- return JSONResponse(content={
388
- "message": "Image processed successfully",
389
- "KeypointsXY": keypoints.tolist(),
390
- "confidences": confidences.tolist()
391
- })
392
 
 
 
 
393
 
394
- # WebSocket endpoint to handle image processing
395
- @app.websocket("/ws/image")
396
- async def websocket_endpoint(websocket: WebSocket):
397
- model = YOLO("yolo11n-pose_float16.tflite")
398
- print("Model loaded successfully")
399
  await websocket.accept()
 
 
 
400
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  while True:
402
- data = await websocket.receive_text() # Receive base64-encoded image data
403
- image_data = base64.b64decode(data) # Decode the image data
404
 
405
- # Convert image bytes to numpy array and decode with OpenCV
406
- np_arr = np.frombuffer(image_data, np.uint8)
407
- frame = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
408
-
409
- # Run YOLO pose estimation
410
- if model is not None:
411
- try:
412
- results = model.predict(frame, save=False, conf=0.3)
413
-
414
- if results and results[0].keypoints:
415
- keypoints = results[0].keypoints.xy.cpu().numpy().tolist() # Extract keypoints
416
- confidences = results[0].boxes.conf.cpu().numpy().tolist() if results[0].boxes else []
417
-
418
- # Send the results back to the client
419
- response = {
420
- "message": "Pose detected",
421
- "KeypointsXY": keypoints[:5], # Limit to first 5 keypoints for brevity
422
- "Confidences": confidences[:5], # Limit to first 5 confidences
423
- }
424
- await websocket.send_text(str(response))
425
- else:
426
- await websocket.send_text("❌ No keypoints detected.")
427
- except Exception as e:
428
- await websocket.send_text(f"⚠️ Error processing image: {str(e)}")
429
- else:
430
- await websocket.send_text("⚠️ Model not loaded.")
431
-
432
  except WebSocketDisconnect:
433
- print("πŸ”Œ Client disconnected")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  import base64
24
  import cv2
25
  import time
26
+ from CPR.CPRAnalyzer import CPRAnalyzer as OfflineAnalyzer
27
  import tempfile
28
  import matplotlib.pyplot as plt
29
+ import json
30
+ import asyncio
31
+ import concurrent.futures
32
+ from CPRRealTime.main import CPRAnalyzer as RealtimeAnalyzer
33
+ from threading import Thread
34
+ from starlette.responses import StreamingResponse
35
+ import threading
36
+ import queue
37
+ from CPRRealTime.analysis_socket_server import AnalysisSocketServer # adjust if needed
38
+ from CPRRealTime.logging_config import cpr_logger
39
+ import logging
40
+ import sys
41
+ import re
42
+ import signal
43
 
44
 
45
  app = FastAPI()
46
 
47
+ SCREENSHOTS_DIR = "screenshots" # Folder containing screenshots to upload
48
+ OUTPUT_DIR = "Output" # Folder containing the .mp4 video and graph .png
49
  UPLOAD_DIR = "uploads"
50
  os.makedirs(UPLOAD_DIR, exist_ok=True)
51
 
 
57
  print(f"❌ Model loading failed: {str(e)}")
58
  model = None
59
 
 
 
 
60
 
 
 
 
 
 
 
 
 
61
 
62
  # βœ… Cloudinary config
63
  cloudinary.config(
 
67
  secure=True
68
  )
69
 
70
+ # Basic Hello route
71
  @app.get("/")
72
  def greet_json():
73
  return {"Hello": "World!"}
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  @app.post("/predict_burn")
76
  async def predict_burn(file: UploadFile = File(...)):
77
  try:
 
80
  with open(temp_file_path, "wb") as temp_file:
81
  temp_file.write(await file.read())
82
 
 
 
 
 
83
 
84
  # Load the saved SVM model
85
  with open('svm_model.pkl', 'rb') as model_file:
 
107
  prediction_label = "Second Class"
108
  else:
109
  prediction_label = "Zero Class"
 
 
 
 
 
110
 
111
  return {
112
+ "prediction": prediction_label
 
113
  }
114
 
115
  except Exception as e:
116
  return JSONResponse(content={"error": str(e)}, status_code=500)
117
 
 
118
  @app.post("/segment_burn")
119
  async def segment_burn_endpoint(reference: UploadFile = File(...), patient: UploadFile = File(...)):
120
  try:
 
151
 
152
  os.remove(burn_crop_clean_path)
153
  os.remove(burn_crop_debug_path)
154
+
155
 
156
  return {
157
  "crop_clean_url": crop_clean_url,
 
162
  return JSONResponse(content={"error": str(e)}, status_code=500)
163
 
164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  @app.post("/classify-ecg")
166
  async def classify_ecg_endpoint(file: UploadFile = File(...)):
167
  model = joblib.load('voting_classifier.pkl')
 
183
 
184
  except Exception as e:
185
  return JSONResponse(content={"error": str(e)}, status_code=500)
186
+
 
187
  @app.post("/diagnose-ecg")
188
  async def diagnose_ecg(file: UploadFile = File(...)):
189
  try:
 
219
  return JSONResponse(content={"error": str(e)}, status_code=500)
220
 
221
 
222
+ def clean_warning_name(filename: str) -> str:
223
+ """
224
+ Remove frame index and underscores from filename base
225
+ E.g. "posture_001.png" -> "posture"
226
+ """
227
+ name, _ = os.path.splitext(filename)
228
+ # Remove trailing underscore + digits
229
+ cleaned = re.sub(r'_\d+$', '', name)
230
+ # Remove all underscores in the name for description
231
+ cleaned_desc = cleaned.replace('_', ' ')
232
+ return cleaned, cleaned_desc
233
 
234
  @app.post("/process_video")
235
  async def process_video(file: UploadFile = File(...)):
 
239
  print("File content type:", file.content_type)
240
  print("File filename:", file.filename)
241
 
242
+ # Prepare directories
243
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
244
+ os.makedirs(SCREENSHOTS_DIR, exist_ok=True)
245
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
246
+
247
+ folders = ["screenshots", "uploads", "Output"]
248
+
249
+ for folder in folders:
250
+ if os.path.exists(folder):
251
+ for filename in os.listdir(folder):
252
+ file_path = os.path.join(folder, filename)
253
+ if os.path.isfile(file_path):
254
+ os.remove(file_path)
255
+
256
+ # Save uploaded video file
257
  video_path = os.path.join(UPLOAD_DIR, file.filename)
258
  with open(video_path, "wb") as buffer:
259
  shutil.copyfileobj(file.file, buffer)
260
 
261
+ print(f"\n[API] CPR Analysis Started on {video_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
 
263
+ # Prepare output paths for the analyzer
264
+ video_output_path = os.path.join(OUTPUT_DIR, "Myoutput.mp4")
265
+ plot_output_path = os.path.join(OUTPUT_DIR, "Myoutput.png")
266
 
267
+ # Initialize analyzer with input video and output paths
268
+ start_time = time.time()
269
+ analyzer = OfflineAnalyzer(video_path, video_output_path, plot_output_path, requested_fps=30)
270
+
271
+ # Run the analysis (choose your method)
272
+ chunks = analyzer.run_analysis_video()
273
+
274
+ warnings = [] # Start empty list
275
+
276
+ # Upload screenshots and build warnings list with descriptions and URLs
277
+ if os.path.exists(SCREENSHOTS_DIR):
278
+ for filename in os.listdir(SCREENSHOTS_DIR):
279
+ if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
280
+ local_path = os.path.join(SCREENSHOTS_DIR, filename)
281
+ cleaned_name, description = clean_warning_name(filename)
282
+
283
+ upload_result = cloudinary.uploader.upload(
284
+ local_path,
285
+ folder="posture_warnings",
286
+ public_id=cleaned_name,
287
+ overwrite=True
288
+ )
289
+
290
+ # Add new warning with image_url and description
291
+ warnings.append({
292
+ "image_url": upload_result['secure_url'],
293
+ "description": description
294
+ })
295
+
296
+ video_path = "Output/Myoutput_final.mp4"
297
+
298
+ if os.path.isfile(video_path):
299
+ upload_result = cloudinary.uploader.upload_large(
300
+ video_path,
301
+ resource_type="video",
302
+ folder="output_videos",
303
+ public_id="Myoutput_final",
304
+ overwrite=True
305
+ )
306
+ wholevideoURL = upload_result['secure_url']
307
+ else:
308
+ wholevideoURL = None
309
+
310
+ # Upload graph output
311
+ graphURL = None
312
+ if os.path.isfile(plot_output_path):
313
+ upload_graph_result = cloudinary.uploader.upload(
314
+ plot_output_path,
315
+ folder="output_graphs",
316
+ public_id=os.path.splitext(os.path.basename(plot_output_path))[0],
317
+ overwrite=True
318
+ )
319
+ graphURL = upload_graph_result['secure_url']
320
 
321
+ print(f"[API] CPR Analysis Completed on {video_path}")
322
+ analysis_time = time.time() - start_time
323
+ print(f"[TIMING] Analysis time: {analysis_time:.2f}s")
 
 
324
 
325
+ if wholevideoURL is None:
326
+ raise HTTPException(status_code=500, detail="No chunk data was generated from the video.")
 
 
 
 
327
 
328
+ return JSONResponse(content={
329
+ "videoURL": wholevideoURL,
330
+ "graphURL": graphURL,
331
+ "warnings": warnings,
332
+ "chunks": chunks,
333
+ })
334
 
 
 
335
 
336
+ # @app.websocket("/ws/process_video")
337
+ # async def websocket_process_video(websocket: WebSocket):
338
+
339
+ # await websocket.accept()
340
+
341
+ # frame_buffer = []
342
+ # frame_limit = 50
343
+ # frame_size = (640, 480) # Adjust if needed
344
+ # fps = 30 # Adjust if needed
345
+ # loop = asyncio.get_event_loop()
346
+
347
+ # # Progress reporting during analysis
348
+ # async def progress_callback(data):
349
+ # await websocket.send_text(json.dumps(data))
350
+
351
+ # def sync_callback(data):
352
+ # asyncio.run_coroutine_threadsafe(progress_callback(data), loop)
353
+
354
+ # def save_frames_to_video(frames, path):
355
+ # out = cv2.VideoWriter(path, cv2.VideoWriter_fourcc(*'mp4v'), fps, frame_size)
356
+ # for frame in frames:
357
+ # resized = cv2.resize(frame, frame_size)
358
+ # out.write(resized)
359
+ # out.release()
360
+
361
+ # def run_analysis_on_buffer(frames):
362
+ # try:
363
+ # tmp_path = "temp_video.mp4"
364
+ # save_frames_to_video(frames, tmp_path)
365
+
366
+ # # Notify: video saved
367
+ # asyncio.run_coroutine_threadsafe(
368
+ # websocket.send_text(json.dumps({
369
+ # "status": "info",
370
+ # "message": "Video saved. Starting CPR analysis..."
371
+ # })),
372
+ # loop
373
+ # )
374
+
375
+ # # Run analysis
376
+ # analyzer = CPRAnalyzer(video_path=tmp_path)
377
+ # analyzer.run_analysis(progress_callback=sync_callback)
378
+
379
+ # except Exception as e:
380
+ # asyncio.run_coroutine_threadsafe(
381
+ # websocket.send_text(json.dumps({"error": str(e)})),
382
+ # loop
383
+ # )
384
+
385
+ # try:
386
+ # while True:
387
+ # data: bytes = await websocket.receive_bytes()
388
+ # np_arr = np.frombuffer(data, np.uint8)
389
+ # frame = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
390
+ # if frame is None:
391
+ # continue
392
+
393
+ # frame_buffer.append(frame)
394
+ # print(f"Frame added to buffer: {len(frame_buffer)}")
395
+
396
+ # if len(frame_buffer) == frame_limit:
397
+ # # Notify Flutter that we're switching to processing
398
+ # await websocket.send_text(json.dumps({
399
+ # "status": "ready",
400
+ # "message": "Prepare Right CPR: First 150 frames received. Starting processing."
401
+ # }))
402
+
403
+ # # Copy and clear buffer
404
+ # buffer_copy = frame_buffer[:]
405
+ # frame_buffer.clear()
406
+
407
+ # # Launch background processing
408
+ # executor = concurrent.futures.ThreadPoolExecutor()
409
+ # loop.run_in_executor(executor, run_analysis_on_buffer, buffer_copy)
410
+ # else:
411
+ # # Tell Flutter to send the next frame
412
+ # await websocket.send_text(json.dumps({
413
+ # "status": "continue",
414
+ # "message": f"Frame {len(frame_buffer)} received. Send next."
415
+ # }))
416
+
417
+ # except WebSocketDisconnect:
418
+ # print("Client disconnected")
419
+
420
+ # except Exception as e:
421
+ # await websocket.send_text(json.dumps({"error": str(e)}))
422
+
423
+ # finally:
424
+ # cv2.destroyAllWindows()
425
+
426
+
427
+ logger = logging.getLogger("cpr_logger")
428
+ clients = set()
429
+ analyzer_thread = None
430
+ analysis_started = False
431
+ analyzer_lock = threading.Lock()
432
+ socket_server: AnalysisSocketServer = None # Global reference
433
+
434
+
435
+ async def forward_results_from_queue(websocket: WebSocket, warning_queue):
436
+ try:
437
+ while True:
438
+ warnings = await asyncio.to_thread(warning_queue.get)
439
+ serialized = json.dumps(warnings)
440
+ await websocket.send_text(serialized)
441
+ except asyncio.CancelledError:
442
+ logger.info("[WebSocket] Forwarding task cancelled")
443
+ except Exception as e:
444
+ logger.error(f"[WebSocket] Error forwarding data: {e}")
445
 
446
 
447
+ def run_cpr_analysis(source, requested_fps, output_path):
448
+ global socket_server
449
+ logger.info(f"[MAIN] CPR Analysis Started")
 
450
 
451
+ requested_fps = 30
452
+ input_video = source
453
 
454
+ output_dir = r"D:\BackendGp\Deploy_El7a2ny_Application\CPRRealTime\outputs"
455
+ os.makedirs(output_dir, exist_ok=True)
 
 
456
 
457
+ video_output_path = os.path.join(output_dir, "output.mp4")
458
+ plot_output_path = os.path.join(output_dir, "output.png")
459
 
460
+ logger.info(f"[CONFIG] Input video: {input_video}")
461
+ logger.info(f"[CONFIG] Video output: {video_output_path}")
462
+ logger.info(f"[CONFIG] Plot output: {plot_output_path}")
 
 
 
 
463
 
464
+ initialization_start_time = time.time()
465
+ analyzer = RealtimeAnalyzer(input_video, video_output_path, plot_output_path, requested_fps)
466
+ socket_server = analyzer.socket_server
467
+ analyzer.plot_output_path = plot_output_path
468
 
469
+ elapsed_time = time.time() - initialization_start_time
470
+ logger.info(f"[TIMING] Initialization time: {elapsed_time:.2f}s")
471
 
472
+ try:
473
+ analyzer.run_analysis()
474
+ finally:
475
+ if analyzer.socket_server:
476
+ analyzer.socket_server.stop_server()
477
+ logger.info("[MAIN] Analyzer stopped")
478
 
 
 
 
 
 
479
 
480
+ @app.websocket("/ws/real")
481
+ async def websocket_analysis(websocket: WebSocket):
482
+ global analyzer_thread, analysis_started, socket_server
483
 
 
 
 
 
 
484
  await websocket.accept()
485
+ clients.add(websocket)
486
+ logger.info("[WebSocket] Flutter connected")
487
+
488
  try:
489
+ # Wait for the client to send the stream URL as first message
490
+ source = await websocket.receive_text()
491
+ logger.info(f"[WebSocket] Received stream URL: {source}")
492
+
493
+ # Ensure analyzer starts only once using a thread-safe lock
494
+ with analyzer_lock:
495
+ if not analysis_started:
496
+ requested_fps = 30
497
+ output_path = r"D:\CPR\End to End\Code Refactor\output\output.mp4"
498
+
499
+ analyzer_thread = threading.Thread(
500
+ target=run_cpr_analysis,
501
+ args=(source, requested_fps, output_path),
502
+ daemon=True
503
+ )
504
+ analyzer_thread.start()
505
+ analysis_started = True
506
+ logger.info("[WebSocket] Analysis thread started")
507
+
508
+ # Rest of your existing code remains exactly the same...
509
+ while socket_server is None or socket_server.warning_queue is None:
510
+ await asyncio.sleep(0.1)
511
+
512
+ forward_task = asyncio.create_task(
513
+ forward_results_from_queue(websocket, socket_server.warning_queue)
514
+ )
515
+
516
  while True:
517
+ await asyncio.sleep(1) # Keep alive
 
518
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  except WebSocketDisconnect:
520
+ logger.warning("[WebSocket] Client disconnected")
521
+ if 'forward_task' in locals():
522
+ forward_task.cancel()
523
+ except Exception as e:
524
+ logger.error(f"[WebSocket] Error receiving stream URL: {str(e)}")
525
+ await websocket.close(code=1011) # 1011 = Internal Error
526
+ finally:
527
+ clients.discard(websocket)
528
+ logger.info(f"[WebSocket] Active clients: {len(clients)}")
529
+
530
+ if not clients and socket_server:
531
+ logger.info("[WebSocket] No clients left. Stopping analyzer.")
532
+ socket_server.stop_server()
533
+ analysis_started = False
534
+ socket_server = None
535
+
536
+
537
+ def shutdown_handler(signum, frame):
538
+ logger.info("Received shutdown signal")
539
+ if socket_server:
540
+ try:
541
+ socket_server.stop_server()
542
+ except Exception as e:
543
+ logger.warning(f"Error during socket server shutdown: {e}")
544
+ os._exit(0)
545
+
546
+ signal.signal(signal.SIGINT, shutdown_handler)
547
+ signal.signal(signal.SIGTERM, shutdown_handler)
main.py CHANGED
@@ -39,6 +39,7 @@ from CPRRealTime.logging_config import cpr_logger
39
  import logging
40
  import sys
41
  import re
 
42
 
43
 
44
  app = FastAPI()
@@ -56,18 +57,7 @@ except Exception as e:
56
  print(f"❌ Model loading failed: {str(e)}")
57
  model = None
58
 
59
- # βœ… MongoDB connection
60
- mongo_uri = "mongodb://husseinelhadidy03:W8ByXdBS4EFkZmd5@ac-lqfhgnk-shard-00-00.ycuagnj.mongodb.net:27017,ac-lqfhgnk-shard-00-01.ycuagnj.mongodb.net:27017,ac-lqfhgnk-shard-00-02.ycuagnj.mongodb.net:27017/?replicaSet=atlas-az5d0x-shard-0&ssl=true&authSource=admin&retryWrites=true&w=majority&appName=Cluster0"
61
- client = MongoClient(mongo_uri, server_api=ServerApi('1'))
62
 
63
- try:
64
- client.admin.command('ping')
65
- print("βœ… Successfully connected to MongoDB!")
66
- except Exception as e:
67
- print("❌ MongoDB connection failed:", e)
68
-
69
- # βœ… Use a shared database instance
70
- db = client["El7a2ny"]
71
 
72
  # βœ… Cloudinary config
73
  cloudinary.config(
@@ -77,7 +67,7 @@ cloudinary.config(
77
  secure=True
78
  )
79
 
80
- # βœ… Basic Hello route
81
  @app.get("/")
82
  def greet_json():
83
  return {"Hello": "World!"}
@@ -90,10 +80,6 @@ async def predict_burn(file: UploadFile = File(...)):
90
  with open(temp_file_path, "wb") as temp_file:
91
  temp_file.write(await file.read())
92
 
93
- # Upload the file to Cloudinary
94
- #upload_result = cloudinary.uploader.upload(temp_file_path, public_id=f"predict_{file.filename}")
95
- #cloudinary_url = upload_result["secure_url"]
96
- cloudinary_url = "https:facebook.com"
97
 
98
  # Load the saved SVM model
99
  with open('svm_model.pkl', 'rb') as model_file:
@@ -121,15 +107,9 @@ async def predict_burn(file: UploadFile = File(...)):
121
  prediction_label = "Second Class"
122
  else:
123
  prediction_label = "Zero Class"
124
-
125
- # Save result to MongoDB
126
- #collection = db["Predictions"]
127
- #doc = {"filename": file.filename, "url": cloudinary_url, "prediction": prediction_label}
128
- #collection.insert_one(doc)
129
 
130
  return {
131
- "prediction": prediction_label,
132
- "image_url": cloudinary_url
133
  }
134
 
135
  except Exception as e:
@@ -444,8 +424,6 @@ async def process_video(file: UploadFile = File(...)):
444
  # cv2.destroyAllWindows()
445
 
446
 
447
-
448
-
449
  logger = logging.getLogger("cpr_logger")
450
  clients = set()
451
  analyzer_thread = None
@@ -507,42 +485,48 @@ async def websocket_analysis(websocket: WebSocket):
507
  clients.add(websocket)
508
  logger.info("[WebSocket] Flutter connected")
509
 
510
- # Ensure analyzer starts only once using a thread-safe lock
511
- with analyzer_lock:
512
- if not analysis_started:
513
- source = "http://192.168.137.244:8080/video"
514
- requested_fps = 30
515
- output_path = r"D:\CPR\End to End\Code Refactor\output\output.mp4"
516
-
517
- analyzer_thread = threading.Thread(
518
- target=run_cpr_analysis,
519
- args=(source, requested_fps, output_path),
520
- daemon=True
521
- )
522
- analyzer_thread.start()
523
- analysis_started = True
524
- logger.info("[WebSocket] Analysis thread started")
525
-
526
- # Wait until the socket server and queue are initialized
527
- while socket_server is None or socket_server.warning_queue is None:
528
- await asyncio.sleep(0.1)
529
-
530
- # Start async task to stream data to client
531
- forward_task = asyncio.create_task(
532
- forward_results_from_queue(websocket, socket_server.warning_queue)
533
- )
534
-
535
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  while True:
537
  await asyncio.sleep(1) # Keep alive
 
538
  except WebSocketDisconnect:
539
  logger.warning("[WebSocket] Client disconnected")
540
- forward_task.cancel()
 
 
 
 
541
  finally:
542
  clients.discard(websocket)
543
  logger.info(f"[WebSocket] Active clients: {len(clients)}")
544
 
545
- # Optional: stop analysis if no clients remain
546
  if not clients and socket_server:
547
  logger.info("[WebSocket] No clients left. Stopping analyzer.")
548
  socket_server.stop_server()
@@ -550,8 +534,6 @@ async def websocket_analysis(websocket: WebSocket):
550
  socket_server = None
551
 
552
 
553
- import signal
554
-
555
  def shutdown_handler(signum, frame):
556
  logger.info("Received shutdown signal")
557
  if socket_server:
 
39
  import logging
40
  import sys
41
  import re
42
+ import signal
43
 
44
 
45
  app = FastAPI()
 
57
  print(f"❌ Model loading failed: {str(e)}")
58
  model = None
59
 
 
 
 
60
 
 
 
 
 
 
 
 
 
61
 
62
  # βœ… Cloudinary config
63
  cloudinary.config(
 
67
  secure=True
68
  )
69
 
70
+ # Basic Hello route
71
  @app.get("/")
72
  def greet_json():
73
  return {"Hello": "World!"}
 
80
  with open(temp_file_path, "wb") as temp_file:
81
  temp_file.write(await file.read())
82
 
 
 
 
 
83
 
84
  # Load the saved SVM model
85
  with open('svm_model.pkl', 'rb') as model_file:
 
107
  prediction_label = "Second Class"
108
  else:
109
  prediction_label = "Zero Class"
 
 
 
 
 
110
 
111
  return {
112
+ "prediction": prediction_label
 
113
  }
114
 
115
  except Exception as e:
 
424
  # cv2.destroyAllWindows()
425
 
426
 
 
 
427
  logger = logging.getLogger("cpr_logger")
428
  clients = set()
429
  analyzer_thread = None
 
485
  clients.add(websocket)
486
  logger.info("[WebSocket] Flutter connected")
487
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  try:
489
+ # Wait for the client to send the stream URL as first message
490
+ source = await websocket.receive_text()
491
+ logger.info(f"[WebSocket] Received stream URL: {source}")
492
+
493
+ # Ensure analyzer starts only once using a thread-safe lock
494
+ with analyzer_lock:
495
+ if not analysis_started:
496
+ requested_fps = 30
497
+ output_path = r"D:\CPR\End to End\Code Refactor\output\output.mp4"
498
+
499
+ analyzer_thread = threading.Thread(
500
+ target=run_cpr_analysis,
501
+ args=(source, requested_fps, output_path),
502
+ daemon=True
503
+ )
504
+ analyzer_thread.start()
505
+ analysis_started = True
506
+ logger.info("[WebSocket] Analysis thread started")
507
+
508
+ # Rest of your existing code remains exactly the same...
509
+ while socket_server is None or socket_server.warning_queue is None:
510
+ await asyncio.sleep(0.1)
511
+
512
+ forward_task = asyncio.create_task(
513
+ forward_results_from_queue(websocket, socket_server.warning_queue)
514
+ )
515
+
516
  while True:
517
  await asyncio.sleep(1) # Keep alive
518
+
519
  except WebSocketDisconnect:
520
  logger.warning("[WebSocket] Client disconnected")
521
+ if 'forward_task' in locals():
522
+ forward_task.cancel()
523
+ except Exception as e:
524
+ logger.error(f"[WebSocket] Error receiving stream URL: {str(e)}")
525
+ await websocket.close(code=1011) # 1011 = Internal Error
526
  finally:
527
  clients.discard(websocket)
528
  logger.info(f"[WebSocket] Active clients: {len(clients)}")
529
 
 
530
  if not clients and socket_server:
531
  logger.info("[WebSocket] No clients left. Stopping analyzer.")
532
  socket_server.stop_server()
 
534
  socket_server = None
535
 
536
 
 
 
537
  def shutdown_handler(signum, frame):
538
  logger.info("Received shutdown signal")
539
  if socket_server: