VIDEO NOT IMAGES + history4all
This commit is contained in:
1 parent
c716d23151
commit
4201a3cbb1
6 files changed
+66
-13
No files matched your search
@@ -14,6 +14,8 @@ wheels/
|
|||||||
|
|
||||||
# artifacts/logs i forgor what are the paths uhhhhhhh
|
# artifacts/logs i forgor what are the paths uhhhhhhh
|
||||||
alerts.jsonl
|
alerts.jsonl
|
||||||
|
output.jsonl
|
||||||
|
output.mp4
|
||||||
static/alerts/
|
static/alerts/
|
||||||
screenshots/
|
screenshots/
|
||||||
events/
|
events/
|
||||||
|
|||||||
+1
-1
Submodule SmokeDetectionApp updated: 339616b827...7ace087582.
+5
-2
@@ -122,10 +122,13 @@ class MySQLRepository:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return [DetectionRecord(**r) for r in rows]
|
return [DetectionRecord(**r) for r in rows]
|
||||||
|
|
||||||
def clear_detections(self, user_id: int) -> int:
|
def clear_detections(self, user_id: Optional[int] = None) -> int:
|
||||||
conn = self._connect()
|
conn = self._connect()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("DELETE FROM detections WHERE user_id = %s", (user_id,))
|
if user_id is None:
|
||||||
|
cursor.execute("DELETE FROM detections")
|
||||||
|
else:
|
||||||
|
cursor.execute("DELETE FROM detections WHERE user_id = %s", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
count = cursor.rowcount
|
count = cursor.rowcount
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
+2
-2
@@ -59,8 +59,8 @@ class Repository(Protocol):
|
|||||||
"""Get detection history, optionally filtered by user. Newest first."""
|
"""Get detection history, optionally filtered by user. Newest first."""
|
||||||
...
|
...
|
||||||
|
|
||||||
def clear_detections(self, user_id: int) -> int:
|
def clear_detections(self, user_id: Optional[int] = None) -> int:
|
||||||
"""Delete all detections for a user. Returns number of rows deleted."""
|
"""Delete detections, optionally filtered by user. Returns rows deleted."""
|
||||||
...
|
...
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
|
|||||||
+7
-2
@@ -109,9 +109,14 @@ class SQLiteRepository:
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
return [DetectionRecord(**dict(r)) for r in rows]
|
return [DetectionRecord(**dict(r)) for r in rows]
|
||||||
|
|
||||||
def clear_detections(self, user_id: int) -> int:
|
def clear_detections(self, user_id: Optional[int] = None) -> int:
|
||||||
conn = self._get_conn()
|
conn = self._get_conn()
|
||||||
cursor = conn.execute("DELETE FROM detections WHERE user_id = ?", (user_id,))
|
if user_id is None:
|
||||||
|
cursor = conn.execute("DELETE FROM detections")
|
||||||
|
else:
|
||||||
|
cursor = conn.execute(
|
||||||
|
"DELETE FROM detections WHERE user_id = ?", (user_id,)
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return cursor.rowcount
|
return cursor.rowcount
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from pipeline.config import PipelineConfig
|
|||||||
from pipeline.core import SmokingDetectionPipeline
|
from pipeline.core import SmokingDetectionPipeline
|
||||||
from pipeline.models import YoloCigaretteDetector, YoloClassifier, YoloPersonDetector
|
from pipeline.models import YoloCigaretteDetector, YoloClassifier, YoloPersonDetector
|
||||||
from pipeline.protocols import Sink
|
from pipeline.protocols import Sink
|
||||||
from pipeline.sinks import DatabaseSink, FileAlertStore, MetricsSink
|
from pipeline.sinks import FileAlertStore, MetricsSink, SmartEventSink
|
||||||
from pipeline.sources.stream import ThreadedStreamSource
|
from pipeline.sources.stream import ThreadedStreamSource
|
||||||
from pipeline.staged_core import StageBasedPipeline
|
from pipeline.staged_core import StageBasedPipeline
|
||||||
from pipeline.types import Frame
|
from pipeline.types import Frame
|
||||||
@@ -62,6 +62,48 @@ class LiveFrameSink(Sink):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseSmartEventSink(SmartEventSink):
|
||||||
|
"""Records event clips and stores clip path in DB as alert evidence."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
db_repo: Repository,
|
||||||
|
fps: float,
|
||||||
|
user_id: int = 1,
|
||||||
|
camera_source: str = "Pipeline",
|
||||||
|
output_dir: str = "static/events",
|
||||||
|
):
|
||||||
|
super().__init__(output_dir=output_dir, fps=fps)
|
||||||
|
self.db_repo = db_repo
|
||||||
|
self.user_id = user_id
|
||||||
|
self.camera_source = camera_source
|
||||||
|
|
||||||
|
def _trigger_event(self, trigger_frame: Frame):
|
||||||
|
was_recording = self.recording
|
||||||
|
super()._trigger_event(trigger_frame)
|
||||||
|
|
||||||
|
# Save one DB row per newly started event clip
|
||||||
|
if was_recording or not self.recording or self.current_event_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
ticket = trigger_frame.ticket
|
||||||
|
if not ticket:
|
||||||
|
return
|
||||||
|
|
||||||
|
filename = f"event_{self.current_event_id}.mp4"
|
||||||
|
web_path = f"/static/events/{filename}"
|
||||||
|
try:
|
||||||
|
self.db_repo.save_detection(
|
||||||
|
user_id=self.user_id,
|
||||||
|
image_path=web_path,
|
||||||
|
confidence=ticket.confidence,
|
||||||
|
camera_source=self.camera_source,
|
||||||
|
)
|
||||||
|
logger.info("Saved event alert to DB: %s", web_path)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to save event alert to DB: %s", e)
|
||||||
|
|
||||||
|
|
||||||
def _get_lan_address() -> tuple[str, str]:
|
def _get_lan_address() -> tuple[str, str]:
|
||||||
"""Return (ip, hostname) for the primary LAN interface."""
|
"""Return (ip, hostname) for the primary LAN interface."""
|
||||||
ip = "127.0.0.1"
|
ip = "127.0.0.1"
|
||||||
@@ -160,7 +202,7 @@ async def app_lifespan(_app: FastAPI):
|
|||||||
# 4. Sinks
|
# 4. Sinks
|
||||||
sinks: list[Sink] = [
|
sinks: list[Sink] = [
|
||||||
LiveFrameSink(),
|
LiveFrameSink(),
|
||||||
DatabaseSink(db, image_dir="static/alerts"),
|
DatabaseSmartEventSink(db, fps=source.fps, output_dir="static/events"),
|
||||||
MetricsSink(log_interval=200),
|
MetricsSink(log_interval=200),
|
||||||
FileAlertStore("alerts.jsonl"),
|
FileAlertStore("alerts.jsonl"),
|
||||||
]
|
]
|
||||||
@@ -231,6 +273,7 @@ app = FastAPI(title="Smoking Detection", lifespan=app_lifespan) # type: ignore
|
|||||||
|
|
||||||
# Serve static assets (alert images, CSS/JS, etc.)
|
# Serve static assets (alert images, CSS/JS, etc.)
|
||||||
Path("static/alerts").mkdir(parents=True, exist_ok=True)
|
Path("static/alerts").mkdir(parents=True, exist_ok=True)
|
||||||
|
Path("static/events").mkdir(parents=True, exist_ok=True)
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
|
||||||
@@ -269,7 +312,7 @@ def login(username: str = Form(...), password: str = Form(...)):
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/history")
|
@app.get("/history")
|
||||||
def get_history(user_id: int):
|
def get_history(user_id: int | None = None):
|
||||||
try:
|
try:
|
||||||
records = db.get_detections(user_id)
|
records = db.get_detections(user_id)
|
||||||
return [
|
return [
|
||||||
@@ -288,10 +331,10 @@ def get_history(user_id: int):
|
|||||||
|
|
||||||
|
|
||||||
@app.post("/clear_history")
|
@app.post("/clear_history")
|
||||||
def clear_history(user_id: int = Form(...)):
|
def clear_history():
|
||||||
try:
|
try:
|
||||||
deleted = db.clear_detections(user_id)
|
deleted = db.clear_detections()
|
||||||
logger.info("Cleared %d records for user %d", deleted, user_id)
|
logger.info("Cleared %d records", deleted)
|
||||||
return {"status": "success", "message": "History cleared"}
|
return {"status": "success", "message": "History cleared"}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "message": str(e)}
|
return {"status": "error", "message": str(e)}
|
||||||
|
|||||||
Reference in new issue
Block a user