codewithRiz commited on
Commit
f98ae68
·
1 Parent(s): 436322f

time analytics added

Browse files
Files changed (3) hide show
  1. api/camera.py +0 -15
  2. api/config.py +1 -0
  3. api/utils.py +58 -3
api/camera.py CHANGED
@@ -15,7 +15,6 @@ class CameraData(BaseModel):
15
  user_id: str = Field(..., min_length=1)
16
  camera_name: str = Field(..., min_length=1)
17
  camera_loc: Optional[List[float]] = None
18
-
19
  @validator("camera_loc")
20
  def validate_loc(cls, loc):
21
  if loc is None:
@@ -35,7 +34,6 @@ class EditCameraData(BaseModel):
35
  old_camera_name: str
36
  new_camera_name: str
37
  new_camera_loc: List[float]
38
-
39
  @validator("new_camera_loc")
40
  def validate_loc(cls, loc):
41
  if len(loc) != 2:
@@ -61,13 +59,11 @@ def home():
61
  ]
62
  }
63
 
64
-
65
  # ---------- GET CAMERAS ----------
66
  @router.get("/get_cameras")
67
  def get_cameras(user_id: str = Query(...)):
68
  if not user_exists(user_id):
69
  raise HTTPException(status_code=404, detail="User not found")
70
-
71
  cameras = load_cameras(user_id)
72
  return {"success": True, "user_id": user_id, "cameras": cameras, "count": len(cameras)}
73
 
@@ -76,30 +72,23 @@ def get_cameras(user_id: str = Query(...)):
76
  @router.post("/add_camera")
77
  def add_camera(data: CameraData):
78
  cameras = load_cameras(data.user_id)
79
-
80
  if len(cameras) >= 2:
81
  raise HTTPException(status_code=400, detail="Only 2 cameras allowed")
82
-
83
  for cam in cameras:
84
  if cam["camera_name"].lower() == data.camera_name.lower():
85
  raise HTTPException(status_code=400, detail="Camera already exists")
86
-
87
  cameras.append({"camera_name": data.camera_name, "camera_loc": data.camera_loc})
88
  save_cameras(data.user_id, cameras)
89
-
90
  return {"success": True, "camera": data.camera_name}
91
 
92
  @router.put("/edit_camera")
93
  def edit_camera(data: EditCameraData):
94
  if not user_exists(data.user_id):
95
  raise HTTPException(status_code=404, detail="User not found")
96
-
97
  cameras = load_cameras(data.user_id)
98
-
99
  # Check if new camera name already exists (case-insensitive)
100
  if any(cam["camera_name"].lower() == data.new_camera_name.lower() for cam in cameras):
101
  raise HTTPException(status_code=400, detail=f"Camera name '{data.new_camera_name}' already exists")
102
-
103
  camera_found = False
104
  for cam in cameras:
105
  if cam["camera_name"].lower() == data.old_camera_name.lower():
@@ -107,7 +96,6 @@ def edit_camera(data: EditCameraData):
107
  cam["camera_name"] = data.new_camera_name
108
  cam["camera_loc"] = data.new_camera_loc
109
  camera_found = True
110
-
111
  # Rename camera folder
112
  old_folder = os.path.join(UPLOAD_DIR, data.user_id, old_name)
113
  new_folder = os.path.join(UPLOAD_DIR, data.user_id, data.new_camera_name)
@@ -116,7 +104,6 @@ def edit_camera(data: EditCameraData):
116
  os.rename(old_folder, new_folder)
117
  except Exception as e:
118
  raise HTTPException(status_code=500, detail=f"Failed to rename camera folder: {str(e)}")
119
-
120
  # Rename detection JSON
121
  for f in os.listdir(new_folder):
122
  if f.endswith("_detections.json") and f.lower().startswith(old_name.lower()):
@@ -128,10 +115,8 @@ def edit_camera(data: EditCameraData):
128
  except Exception as e:
129
  raise HTTPException(status_code=500, detail=f"Failed to rename detection JSON: {str(e)}")
130
  break # Only one detection JSON per camera
131
-
132
  save_cameras(data.user_id, cameras)
133
  return {"success": True, "updated": cam}
134
-
135
  if not camera_found:
136
  raise HTTPException(status_code=404, detail="Camera not found")
137
 
 
15
  user_id: str = Field(..., min_length=1)
16
  camera_name: str = Field(..., min_length=1)
17
  camera_loc: Optional[List[float]] = None
 
18
  @validator("camera_loc")
19
  def validate_loc(cls, loc):
20
  if loc is None:
 
34
  old_camera_name: str
35
  new_camera_name: str
36
  new_camera_loc: List[float]
 
37
  @validator("new_camera_loc")
38
  def validate_loc(cls, loc):
39
  if len(loc) != 2:
 
59
  ]
60
  }
61
 
 
62
  # ---------- GET CAMERAS ----------
63
  @router.get("/get_cameras")
64
  def get_cameras(user_id: str = Query(...)):
65
  if not user_exists(user_id):
66
  raise HTTPException(status_code=404, detail="User not found")
 
67
  cameras = load_cameras(user_id)
68
  return {"success": True, "user_id": user_id, "cameras": cameras, "count": len(cameras)}
69
 
 
72
  @router.post("/add_camera")
73
  def add_camera(data: CameraData):
74
  cameras = load_cameras(data.user_id)
 
75
  if len(cameras) >= 2:
76
  raise HTTPException(status_code=400, detail="Only 2 cameras allowed")
 
77
  for cam in cameras:
78
  if cam["camera_name"].lower() == data.camera_name.lower():
79
  raise HTTPException(status_code=400, detail="Camera already exists")
 
80
  cameras.append({"camera_name": data.camera_name, "camera_loc": data.camera_loc})
81
  save_cameras(data.user_id, cameras)
 
82
  return {"success": True, "camera": data.camera_name}
83
 
84
  @router.put("/edit_camera")
85
  def edit_camera(data: EditCameraData):
86
  if not user_exists(data.user_id):
87
  raise HTTPException(status_code=404, detail="User not found")
 
88
  cameras = load_cameras(data.user_id)
 
89
  # Check if new camera name already exists (case-insensitive)
90
  if any(cam["camera_name"].lower() == data.new_camera_name.lower() for cam in cameras):
91
  raise HTTPException(status_code=400, detail=f"Camera name '{data.new_camera_name}' already exists")
 
92
  camera_found = False
93
  for cam in cameras:
94
  if cam["camera_name"].lower() == data.old_camera_name.lower():
 
96
  cam["camera_name"] = data.new_camera_name
97
  cam["camera_loc"] = data.new_camera_loc
98
  camera_found = True
 
99
  # Rename camera folder
100
  old_folder = os.path.join(UPLOAD_DIR, data.user_id, old_name)
101
  new_folder = os.path.join(UPLOAD_DIR, data.user_id, data.new_camera_name)
 
104
  os.rename(old_folder, new_folder)
105
  except Exception as e:
106
  raise HTTPException(status_code=500, detail=f"Failed to rename camera folder: {str(e)}")
 
107
  # Rename detection JSON
108
  for f in os.listdir(new_folder):
109
  if f.endswith("_detections.json") and f.lower().startswith(old_name.lower()):
 
115
  except Exception as e:
116
  raise HTTPException(status_code=500, detail=f"Failed to rename detection JSON: {str(e)}")
117
  break # Only one detection JSON per camera
 
118
  save_cameras(data.user_id, cameras)
119
  return {"success": True, "updated": cam}
 
120
  if not camera_found:
121
  raise HTTPException(status_code=404, detail="Camera not found")
122
 
api/config.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import logging
 
3
  from dotenv import load_dotenv
4
  from ultralytics import YOLO
5
  from google.cloud import storage
 
1
  import os
2
  import logging
3
+ from zipfile import Path
4
  from dotenv import load_dotenv
5
  from ultralytics import YOLO
6
  from google.cloud import storage
api/utils.py CHANGED
@@ -189,7 +189,6 @@ def save_cameras(user_id: str, cameras: list):
189
  json.dump(cameras, f, indent=2)
190
 
191
 
192
-
193
  #>>>>>>>>dashboard>>>>>>>>>>>>
194
  def get_user_dashboard(user_id: str, camera_name: str = None) -> dict:
195
  """Return analytics for a user or a specific camera"""
@@ -197,31 +196,47 @@ def get_user_dashboard(user_id: str, camera_name: str = None) -> dict:
197
  cameras_file = user_folder / "cameras.json"
198
  if not cameras_file.exists():
199
  raise HTTPException(404, f"User {user_id} not found")
 
200
  try:
201
  with open(cameras_file, "r") as f:
202
  cameras = json.load(f)
203
  except json.JSONDecodeError:
204
  cameras = []
 
205
  total_cameras = len(cameras)
206
  total_images = 0
207
  total_detections = 0
208
  buck_type_distribution = {}
209
  buck_doe_distribution = {"Buck": 0, "Doe": 0}
 
 
 
 
 
 
 
 
 
 
 
210
  for cam in cameras:
211
  cam_name = cam["camera_name"]
212
- # Skip cameras if a specific one is selected
213
  if camera_name and cam_name != camera_name:
214
  continue
 
215
  raw_folder = user_folder / cam_name / "raw"
216
  detections_file = user_folder / cam_name / f"{cam_name}_detections.json"
 
217
  # Count images
218
  if raw_folder.exists():
219
  total_images += len(list(raw_folder.glob("*.*")))
 
220
  # Count detections and distributions
221
  if detections_file.exists():
222
  try:
223
  dets = json.load(open(detections_file, "r"))
224
  for rec in dets:
 
225
  for d in rec.get("detections", []):
226
  total_detections += 1
227
  label = d.get("label", "")
@@ -232,8 +247,38 @@ def get_user_dashboard(user_id: str, camera_name: str = None) -> dict:
232
  buck_type_distribution[parts[2]] = buck_type_distribution.get(parts[2], 0) + 1
233
  else: # Doe
234
  buck_doe_distribution["Doe"] += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  except json.JSONDecodeError:
236
  continue
 
 
 
 
 
 
 
237
  return {
238
  "user_id": user_id,
239
  "selected_camera": camera_name,
@@ -241,5 +286,15 @@ def get_user_dashboard(user_id: str, camera_name: str = None) -> dict:
241
  "images_uploaded": total_images,
242
  "total_detections": total_detections,
243
  "buck_type_distribution": buck_type_distribution,
244
- "buck_doe_distribution": buck_doe_distribution
 
 
 
 
 
 
 
 
 
245
  }
 
 
189
  json.dump(cameras, f, indent=2)
190
 
191
 
 
192
  #>>>>>>>>dashboard>>>>>>>>>>>>
193
  def get_user_dashboard(user_id: str, camera_name: str = None) -> dict:
194
  """Return analytics for a user or a specific camera"""
 
196
  cameras_file = user_folder / "cameras.json"
197
  if not cameras_file.exists():
198
  raise HTTPException(404, f"User {user_id} not found")
199
+
200
  try:
201
  with open(cameras_file, "r") as f:
202
  cameras = json.load(f)
203
  except json.JSONDecodeError:
204
  cameras = []
205
+
206
  total_cameras = len(cameras)
207
  total_images = 0
208
  total_detections = 0
209
  buck_type_distribution = {}
210
  buck_doe_distribution = {"Buck": 0, "Doe": 0}
211
+
212
+ # New dashboard analytics
213
+ from collections import defaultdict, Counter
214
+ from datetime import datetime
215
+
216
+ heatmap = defaultdict(lambda: [0]*24) # day -> 24 hours
217
+ deer_per_day = Counter()
218
+ bucks_per_day = Counter()
219
+ does_per_day = Counter()
220
+ hour_activity = [0]*24 # 0-23 hours
221
+
222
  for cam in cameras:
223
  cam_name = cam["camera_name"]
 
224
  if camera_name and cam_name != camera_name:
225
  continue
226
+
227
  raw_folder = user_folder / cam_name / "raw"
228
  detections_file = user_folder / cam_name / f"{cam_name}_detections.json"
229
+
230
  # Count images
231
  if raw_folder.exists():
232
  total_images += len(list(raw_folder.glob("*.*")))
233
+
234
  # Count detections and distributions
235
  if detections_file.exists():
236
  try:
237
  dets = json.load(open(detections_file, "r"))
238
  for rec in dets:
239
+ # --- Existing Buck/Doe counts ---
240
  for d in rec.get("detections", []):
241
  total_detections += 1
242
  label = d.get("label", "")
 
247
  buck_type_distribution[parts[2]] = buck_type_distribution.get(parts[2], 0) + 1
248
  else: # Doe
249
  buck_doe_distribution["Doe"] += 1
250
+
251
+ # --- New analytics using datetime_original ---
252
+ dt_str = rec.get("metadata", {}).get("exif", {}).get("datetime_original")
253
+ if dt_str:
254
+ dt = datetime.strptime(dt_str, "%Y:%m:%d %H:%M:%S")
255
+ day = dt.date()
256
+ hour = dt.hour
257
+
258
+ # Heatmap count
259
+ heatmap[day][hour] += len(rec.get("detections", []))
260
+
261
+ # Count deer, bucks, does per day
262
+ for d in rec.get("detections", []):
263
+ label = d.get("label", "")
264
+ if "Deer" in label:
265
+ deer_per_day[day] += 1
266
+ if "Buck" in label:
267
+ bucks_per_day[day] += 1
268
+ if "Doe" in label:
269
+ does_per_day[day] += 1
270
+
271
+ # Hourly aggregated activity
272
+ hour_activity[hour] += len(rec.get("detections", []))
273
  except json.JSONDecodeError:
274
  continue
275
+
276
+ # Average activity by hour (morning/night)
277
+ morning_hours = range(6, 18)
278
+ night_hours = list(range(0,6)) + list(range(18,24))
279
+ morning_activity = sum(hour_activity[h] for h in morning_hours) / len(morning_hours)
280
+ night_activity = sum(hour_activity[h] for h in night_hours) / len(night_hours)
281
+
282
  return {
283
  "user_id": user_id,
284
  "selected_camera": camera_name,
 
286
  "images_uploaded": total_images,
287
  "total_detections": total_detections,
288
  "buck_type_distribution": buck_type_distribution,
289
+ "buck_doe_distribution": buck_doe_distribution,
290
+ # --- New analytics ---
291
+ "activity_heatmap": dict(heatmap),
292
+ "deer_per_day": dict(deer_per_day),
293
+ "bucks_per_day": dict(bucks_per_day),
294
+ "does_per_day": dict(does_per_day),
295
+ "average_activity": {
296
+ "morning": round(morning_activity,2),
297
+ "night": round(night_activity,2)
298
+ }
299
  }
300
+