Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ The important field for the camera API is:

* `adapter` which selects the camera implementation

Other fields such as `type`, `azimuth`, `poses`, or `bbox_mask_url` are used by the engine and the API.
Other fields such as `type`, `pose_ids`, `poses`, or `bbox_mask_url` are used by the engine and the API.

`pose_ids` contains the pose IDs from the pyro-api database. For static cameras it is a list with a single element; for PTZ cameras each entry corresponds positionally to the matching physical preset in `poses`. If `bbox_mask_url` is set, occlusion mask files are fetched at `{bbox_mask_url}_{pose_id}.json`.

Below is one generic example for each adapter: `url`, `rtsp`, `reolink` static, `reolink` PTZ and `mock`.
```json
Expand All @@ -139,7 +141,7 @@ Below is one generic example for each adapter: `url`, `rtsp`, `reolink` static,
"name": "url_camera_1",
"adapter": "url",
"url": "http://user:password@camera-host:1234/cgi-bin/snapshot.cgi",
"azimuth": 0,
"pose_ids": [10],
"id": "10",
"bbox_mask_url": "",
"poses": [],
Expand All @@ -151,7 +153,7 @@ Below is one generic example for each adapter: `url`, `rtsp`, `reolink` static,
"name": "rtsp_camera_1",
"adapter": "rtsp",
"rtsp_url": "rtsp://user:password@camera-host:554/live/STREAM_ID",
"azimuth": 0,
"pose_ids": [11],
"id": "11",
"bbox_mask_url": "https://example.com/occlusion-masks/rtsp_camera_1",
"poses": [],
Expand All @@ -163,7 +165,7 @@ Below is one generic example for each adapter: `url`, `rtsp`, `reolink` static,
"name": "reolink_static_1",
"adapter": "reolink",
"type": "static",
"azimuth": 45,
"pose_ids": [12],
"id": "12",
"poses": [],
"bbox_mask_url": "https://example.com/occlusion-masks/reolink_static_1",
Expand All @@ -176,7 +178,7 @@ Below is one generic example for each adapter: `url`, `rtsp`, `reolink` static,
"type": "ptz",
"id": "13",
"poses": [0, 1, 2, 3],
"azimuths": [0, 90, 180, 270],
"pose_ids": [20, 21, 22, 23],
"bbox_mask_url": "https://example.com/occlusion-masks/reolink_ptz_1",
"token": "JWT_TOKEN_HERE"
},
Expand All @@ -186,7 +188,7 @@ Below is one generic example for each adapter: `url`, `rtsp`, `reolink` static,
"adapter": "linovision",
"type": "ptz",
"poses": [0, 1, 2, 3],
"azimuths": [0, 90, 180, 270],
"pose_ids": [30, 31, 32, 33],
"azimuth_offset_deg": 90,
"bbox_mask_url": "https://example.com/occlusion-masks/linovision_ptz_1",
"token": "JWT_TOKEN_HERE"
Expand All @@ -196,7 +198,7 @@ Below is one generic example for each adapter: `url`, `rtsp`, `reolink` static,
"name": "mock_camera_1",
"adapter": "mock",
"type": "static",
"azimuth": 0,
"pose_ids": [0],
"id": "14",
"poses": [],
"bbox_mask_url": "",
Expand Down
27 changes: 17 additions & 10 deletions pyroengine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ def __init__(

self.occlusion_masks: Dict[str, Tuple[Optional[str], Dict[Any, Any], int]] = {"-1": (None, {}, 0)}
if isinstance(cam_creds, dict):
for cam_id, (_, azimuth, bbox_mask_url) in cam_creds.items():
self.occlusion_masks[cam_id] = (bbox_mask_url, {}, int(azimuth))
for cam_id, (_, pose_id, bbox_mask_url) in cam_creds.items():
self.occlusion_masks[cam_id] = (bbox_mask_url, {}, pose_id)

# Restore pending alerts cache
self._alerts: deque = deque(maxlen=cache_size)
Expand Down Expand Up @@ -300,16 +300,17 @@ def predict(
):
logging.info(f"Update occlusion masks for cam {cam_key}")
self._states[cam_key]["last_bbox_mask_fetch"] = time.time()
bbox_mask_url, bbox_mask_dict, azimuth = self.occlusion_masks[cam_key]
bbox_mask_url, bbox_mask_dict, pose_id = self.occlusion_masks[cam_key]
if bbox_mask_url is not None:
full_url = f"{bbox_mask_url}_{azimuth}.json"
full_url = f"{bbox_mask_url}_{pose_id}.json"
try:
response = requests.get(full_url)
response.raise_for_status()
bbox_mask_dict = response.json()
self.occlusion_masks[cam_key] = (bbox_mask_url, bbox_mask_dict, azimuth)
self.occlusion_masks[cam_key] = (bbox_mask_url, bbox_mask_dict, pose_id)
logging.info(f"Downloaded occlusion masks for cam {cam_key} at {bbox_mask_url} :{bbox_mask_dict}")
except requests.exceptions.RequestException:
logging.info(f"No occluson available for: {cam_key}")
logging.info(f"No occlusion masks available for: {cam_key}")

# Inference with ONNX
if fake_pred is None:
Expand Down Expand Up @@ -396,20 +397,26 @@ def _process_alerts(self) -> None:

try:
# Detection creation
bboxes = self._alerts[0]["bboxes"]
if not bboxes:
logging.warning(f"Camera '{cam_id}' - skipping alert with empty bboxes")
self._alerts.popleft()
continue
stream = io.BytesIO()
frame_info["frame"].save(stream, format="JPEG", quality=self.jpeg_quality)
bboxes = self._alerts[0]["bboxes"]
bboxes = [tuple(bboxe) for bboxe in bboxes]
_, cam_azimuth, _ = self.cam_creds[cam_id]
_, pose_id, _ = self.cam_creds[cam_id]
ip = cam_id.split("_")[0]
response = self.api_client[ip].create_detection(stream.getvalue(), cam_azimuth, bboxes)
response = self.api_client[ip].create_detection(stream.getvalue(), bboxes, pose_id)

try:
# Force a KeyError if the request failed
response.json()["id"]
except ValueError:
logging.error(f"Camera '{cam_id}' - non-JSON response body: {response.text}")
raise
except KeyError:
logging.error(f"Camera '{cam_id}' - unexpected API response: {response.text}")
raise

# Clear
self._alerts.popleft()
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ portalocker==3.2.0 ; python_version >= "3.11" and python_version < "4.0"
protobuf==6.33.1 ; python_version >= "3.11" and python_version < "4.0"
pyreadline3==3.5.4 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "win32"
pyro-camera-api-client @ git+https://github.com/pyronear/pyro-engine.git@0f3ff6836d226334847af63e365e8849c2bced22#subdirectory=pyro_camera_api/client ; python_version >= "3.11" and python_version < "4.0"
pyroclient @ git+https://github.com/pyronear/pyro-api.git@9cba4afdf1d096436ca875bfde104f1a9bc1df24#subdirectory=client ; python_version >= "3.11" and python_version < "4.0"
pyroclient @ git+https://github.com/pyronear/pyro-api.git@119ff76266eee72ffeb06141a85d420506322fa8#subdirectory=client ; python_version >= "3.11" and python_version < "4.0"
python-dotenv==1.1.0 ; python_version >= "3.11" and python_version < "4.0"
pywin32==311 ; python_version >= "3.11" and python_version < "4.0" and platform_system == "Windows"
pyyaml==6.0.3 ; python_version >= "3.11" and python_version < "4.0"
Expand Down
10 changes: 4 additions & 6 deletions src/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,11 @@ def main(args):

if cam_data["type"] == "ptz":
cam_poses = cam_data["poses"]
cam_azimuths = cam_data["azimuths"]
for pos_id, cam_azimuth in zip(cam_poses, cam_azimuths, strict=False):
splitted_cam_creds[_ip + "_" + str(pos_id)] = (cam_data["token"], cam_azimuth, bbox_mask_url)
cam_pose_ids = cam_data["pose_ids"]
for pos_id, pose_id in zip(cam_poses, cam_pose_ids, strict=False):
splitted_cam_creds[_ip + "_" + str(pos_id)] = (cam_data["token"], pose_id, bbox_mask_url)
else:
cam_poses = []
cam_azimuths = [cam_data["azimuth"]]
splitted_cam_creds[_ip] = cam_data["token"], cam_data["azimuth"], bbox_mask_url
splitted_cam_creds[_ip] = cam_data["token"], cam_data["pose_ids"][0], bbox_mask_url

engine = Engine(
model_path=args.model_path,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def test_engine_online(tmpdir_factory, mock_wildfire_stream, mock_wildfire_image
# With API
load_dotenv(Path(__file__).parent.parent.joinpath(".env").absolute())
api_url = os.environ.get("API_URL")
cam_creds = {"dummy_cam": (os.environ.get("API_TOKEN"), 0, None)}
cam_creds = {"dummy_cam": (os.environ.get("API_TOKEN"), 1, None)}
# Skip the API-related tests if the URL is not specified

if isinstance(api_url, str):
Expand Down
Loading