diff --git a/containers/notebooks/app/send_real_alerts.ipynb b/containers/notebooks/app/send_real_alerts.ipynb
index ce63162..dd988c5 100644
--- a/containers/notebooks/app/send_real_alerts.ipynb
+++ b/containers/notebooks/app/send_real_alerts.ipynb
@@ -15,7 +15,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@@ -39,7 +39,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
@@ -49,14 +49,78 @@
"SUPERADMIN_PWD = os.environ.get(\"SUPERADMIN_PWD\")\n",
"\n",
"# Get access token\n",
- "admin_access_token = get_token(API_URL, SUPERADMIN_LOGIN, SUPERADMIN_PWD)\n"
+ "admin_access_token = get_token(API_URL, SUPERADMIN_LOGIN, SUPERADMIN_PWD)\n",
+ "admin_client = Client(admin_access_token, API_URL)"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 3,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " id | \n",
+ " organization_id | \n",
+ " password | \n",
+ " login | \n",
+ " role | \n",
+ " created_at | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 2 | \n",
+ " 2 | \n",
+ " test | \n",
+ " test77 | \n",
+ " agent | \n",
+ " 2024-02-23T08:18:45.447773 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 3 | \n",
+ " 3 | \n",
+ " test | \n",
+ " test07 | \n",
+ " agent | \n",
+ " 2024-02-23T08:18:45.447773 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " id organization_id password login role created_at\n",
+ "0 2 2 test test77 agent 2024-02-23T08:18:45.447773\n",
+ "1 3 3 test test07 agent 2024-02-23T08:18:45.447773"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"users = pd.read_csv(\"../data/csv/API_DATA_DEV - users.csv\")\n",
"cameras = pd.read_csv(\"../data/csv/API_DATA_DEV - cameras.csv\")\n",
@@ -65,9 +129,295 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 4,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " id | \n",
+ " organization_id | \n",
+ " name | \n",
+ " angle_of_view | \n",
+ " elevation | \n",
+ " lat | \n",
+ " lon | \n",
+ " is_trustable | \n",
+ " created_at | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 2 | \n",
+ " 2 | \n",
+ " videlles-01 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.478300 | \n",
+ " 2.424200 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 3 | \n",
+ " 2 | \n",
+ " videlles-02 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.478300 | \n",
+ " 2.424200 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 4 | \n",
+ " 2 | \n",
+ " croix-augas-01 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.426700 | \n",
+ " 2.710900 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 5 | \n",
+ " 2 | \n",
+ " croix-augas-02 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.426700 | \n",
+ " 2.710900 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 6 | \n",
+ " 2 | \n",
+ " moret-sur-loing-01 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.379200 | \n",
+ " 2.820800 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " 7 | \n",
+ " 2 | \n",
+ " moret-sur-loing-02 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.379200 | \n",
+ " 2.820800 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " 8 | \n",
+ " 2 | \n",
+ " nemours-01 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.260500 | \n",
+ " 2.706400 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " 9 | \n",
+ " 2 | \n",
+ " nemours-02 | \n",
+ " 54.2 | \n",
+ " 110.0 | \n",
+ " 48.260500 | \n",
+ " 2.706400 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " 10 | \n",
+ " 3 | \n",
+ " brison-01 | \n",
+ " 87.0 | \n",
+ " 110.0 | \n",
+ " 44.545100 | \n",
+ " 4.216500 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " 11 | \n",
+ " 3 | \n",
+ " brison-02 | \n",
+ " 87.0 | \n",
+ " 110.0 | \n",
+ " 44.545100 | \n",
+ " 4.216500 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 10 | \n",
+ " 12 | \n",
+ " 3 | \n",
+ " brison-03 | \n",
+ " 87.0 | \n",
+ " 110.0 | \n",
+ " 44.545100 | \n",
+ " 4.216500 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 11 | \n",
+ " 13 | \n",
+ " 3 | \n",
+ " brison-04 | \n",
+ " 87.0 | \n",
+ " 110.0 | \n",
+ " 44.545100 | \n",
+ " 4.216500 | \n",
+ " True | \n",
+ " 2023-11-07T15:07:19.226673 | \n",
+ "
\n",
+ " \n",
+ " | 12 | \n",
+ " 14 | \n",
+ " 3 | \n",
+ " serre-de-barre-01 | \n",
+ " 54.2 | \n",
+ " 881.0 | \n",
+ " 44.396900 | \n",
+ " 4.083700 | \n",
+ " True | \n",
+ " 2025-02-07T16:49:44.312941 | \n",
+ "
\n",
+ " \n",
+ " | 13 | \n",
+ " 15 | \n",
+ " 3 | \n",
+ " st-peray-01 | \n",
+ " 54.2 | \n",
+ " 200.0 | \n",
+ " 44.927570 | \n",
+ " 4.841640 | \n",
+ " True | \n",
+ " 2025-02-07T16:49:44.312941 | \n",
+ "
\n",
+ " \n",
+ " | 14 | \n",
+ " 16 | \n",
+ " 3 | \n",
+ " st-peray-02 | \n",
+ " 54.2 | \n",
+ " 200.0 | \n",
+ " 44.927570 | \n",
+ " 4.841640 | \n",
+ " True | \n",
+ " 2025-02-07T16:49:44.312941 | \n",
+ "
\n",
+ " \n",
+ " | 15 | \n",
+ " 17 | \n",
+ " 3 | \n",
+ " st-peray-03 | \n",
+ " 180.0 | \n",
+ " 200.0 | \n",
+ " 44.927570 | \n",
+ " 4.841640 | \n",
+ " True | \n",
+ " 2025-02-07T16:49:44.312941 | \n",
+ "
\n",
+ " \n",
+ " | 16 | \n",
+ " 18 | \n",
+ " 3 | \n",
+ " marguerite-01 | \n",
+ " 87.0 | \n",
+ " 987.0 | \n",
+ " 44.688492 | \n",
+ " 4.313501 | \n",
+ " True | \n",
+ " 2025-02-07T16:49:44.312941 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " id organization_id name angle_of_view elevation \\\n",
+ "0 2 2 videlles-01 54.2 110.0 \n",
+ "1 3 2 videlles-02 54.2 110.0 \n",
+ "2 4 2 croix-augas-01 54.2 110.0 \n",
+ "3 5 2 croix-augas-02 54.2 110.0 \n",
+ "4 6 2 moret-sur-loing-01 54.2 110.0 \n",
+ "5 7 2 moret-sur-loing-02 54.2 110.0 \n",
+ "6 8 2 nemours-01 54.2 110.0 \n",
+ "7 9 2 nemours-02 54.2 110.0 \n",
+ "8 10 3 brison-01 87.0 110.0 \n",
+ "9 11 3 brison-02 87.0 110.0 \n",
+ "10 12 3 brison-03 87.0 110.0 \n",
+ "11 13 3 brison-04 87.0 110.0 \n",
+ "12 14 3 serre-de-barre-01 54.2 881.0 \n",
+ "13 15 3 st-peray-01 54.2 200.0 \n",
+ "14 16 3 st-peray-02 54.2 200.0 \n",
+ "15 17 3 st-peray-03 180.0 200.0 \n",
+ "16 18 3 marguerite-01 87.0 987.0 \n",
+ "\n",
+ " lat lon is_trustable created_at \n",
+ "0 48.478300 2.424200 True 2023-11-07T15:07:19.226673 \n",
+ "1 48.478300 2.424200 True 2023-11-07T15:07:19.226673 \n",
+ "2 48.426700 2.710900 True 2023-11-07T15:07:19.226673 \n",
+ "3 48.426700 2.710900 True 2023-11-07T15:07:19.226673 \n",
+ "4 48.379200 2.820800 True 2023-11-07T15:07:19.226673 \n",
+ "5 48.379200 2.820800 True 2023-11-07T15:07:19.226673 \n",
+ "6 48.260500 2.706400 True 2023-11-07T15:07:19.226673 \n",
+ "7 48.260500 2.706400 True 2023-11-07T15:07:19.226673 \n",
+ "8 44.545100 4.216500 True 2023-11-07T15:07:19.226673 \n",
+ "9 44.545100 4.216500 True 2023-11-07T15:07:19.226673 \n",
+ "10 44.545100 4.216500 True 2023-11-07T15:07:19.226673 \n",
+ "11 44.545100 4.216500 True 2023-11-07T15:07:19.226673 \n",
+ "12 44.396900 4.083700 True 2025-02-07T16:49:44.312941 \n",
+ "13 44.927570 4.841640 True 2025-02-07T16:49:44.312941 \n",
+ "14 44.927570 4.841640 True 2025-02-07T16:49:44.312941 \n",
+ "15 44.927570 4.841640 True 2025-02-07T16:49:44.312941 \n",
+ "16 44.688492 4.313501 True 2025-02-07T16:49:44.312941 "
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"cameras"
]
@@ -81,9 +431,17 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 5,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SAMPLE_PATH alert_samples directory exists, assume alerts inside, no download\n"
+ ]
+ }
+ ],
"source": [
"BASE_DIRECTORY = \"../data\"\n",
"\n",
@@ -108,7 +466,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
@@ -117,9 +475,17 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 7,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SAMPLE_PATH selection-true-positives directory exists, assume alerts inside, no download\n"
+ ]
+ }
+ ],
"source": [
"SAMPLE_PATH = \"selection-true-positives\"\n",
"url = \"https://github.com/pyronear/pyro-envdev/releases/download/v0.0.1/selection-true-positives.zip\"\n",
@@ -135,38 +501,76 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 8,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Sending alerts from camera 2 at azimuth 94 (pose_id=1)\n",
+ "Sending alerts from camera 4 at azimuth 61 (pose_id=2)\n",
+ "Sending alerts from camera 5 at azimuth 259 (pose_id=3)\n",
+ "Sending alerts from camera 14 at azimuth 104 (pose_id=4)\n"
+ ]
+ }
+ ],
"source": [
+ "def get_or_create_pose_id_for_azimuth(camera_client, camera_id, azimuth, tol=0.1, patrol_id=None):\n",
+ " resp = camera_client.get_current_poses()\n",
+ " resp.raise_for_status()\n",
+ " poses = resp.json()\n",
+ " for pose in poses:\n",
+ " if abs(pose[\"azimuth\"] - azimuth) <= tol:\n",
+ " return pose[\"id\"]\n",
+ "\n",
+ " create_resp = camera_client.create_pose(camera_id=camera_id, azimuth=azimuth, patrol_id=patrol_id)\n",
+ " create_resp.raise_for_status()\n",
+ " return create_resp.json()[\"id\"]\n",
+ "\n",
+ "\n",
+ "send_alert_from_cam_ids = [2, 4, 5, 14] # select cameras\n",
+ "\n",
"sequances_folders = glob.glob(f\"{SAMPLE_PATH}/*\")\n",
"\n",
"for camera_id in send_alert_from_cam_ids:\n",
- " \n",
" camera_token = get_camera_token(API_URL, camera_id, admin_access_token)\n",
- " camera_client = Client(camera_token, API_URL)\n",
+ " camera_client = Client(camera_token, API_URL)\n",
"\n",
- " sequances_folder = sequances_folders[random.randint(0,len(sequances_folders)-1)]\n",
+ " sequances_folder = sequances_folders[random.randint(0, len(sequances_folders) - 1)]\n",
" imgs = glob.glob(f\"{sequances_folder}/images/*\")\n",
" imgs.sort()\n",
" preds = glob.glob(f\"{sequances_folder}/labels_predictions/*\")\n",
" preds.sort()\n",
- " \n",
- " cam_center_azimuth = random.randint(0,360)\n",
- " print(f\"Sending alerts from camera {camera_id} at azimuth {cam_center_azimuth}\")\n",
+ "\n",
+ " cam_center_azimuth = random.randint(0, 359)\n",
+ " pose_id = get_or_create_pose_id_for_azimuth(camera_client, camera_id, cam_center_azimuth)\n",
+ "\n",
+ " print(f\"Sending alerts from camera {camera_id} at azimuth {cam_center_azimuth} (pose_id={pose_id})\")\n",
" for img_file, pred_file in zip(imgs, preds):\n",
- " \n",
" stream = io.BytesIO()\n",
" im = Image.open(img_file)\n",
" im.save(stream, format=\"JPEG\", quality=80)\n",
"\n",
" bboxes = read_pred_file(pred_file)\n",
- " response = camera_client.create_detection(stream.getvalue(), cam_center_azimuth, bboxes)\n",
- " # Force a KeyError if the request failed\n",
- " \n",
- " response.json()[\"id\"]"
+ " response = camera_client.create_detection(stream.getvalue(), bboxes, pose_id=pose_id)\n",
+ " response.json()[\"id\"]\n"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -187,48 +591,74 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 9,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Sending alerts from camera 7 at azimuth 233 (pose_id=5)\n",
+ "Sending alerts from camera 5 at azimuth 153 (pose_id=6)\n",
+ "Sending alerts from camera 13 at azimuth 328 (pose_id=7)\n",
+ "Sending alerts from camera 7 at azimuth 354 (pose_id=8)\n",
+ "Sending alerts from camera 7 at azimuth 278 (pose_id=9)\n",
+ "Sending alerts from camera 11 at azimuth 194 (pose_id=10)\n",
+ "Sending alerts from camera 8 at azimuth 309 (pose_id=11)\n",
+ "Sending alerts from camera 7 at azimuth 212 (pose_id=12)\n",
+ "Sending alerts from camera 7 at azimuth 323 (pose_id=13)\n",
+ "Sending alerts from camera 7 at azimuth 329 (pose_id=14)\n"
+ ]
+ }
+ ],
"source": [
"SEQUENCES_DIR_PATH = \"../data/alert_samples/single_sequences/*\"\n",
"sequences_directories = glob.glob(SEQUENCES_DIR_PATH)\n",
- "CAM_MAPPING = {22:13, 42:7, 59:5, 14:11, 43:8, 79:14, 13:10, 11:15, 59:1, 60:2, 15:12, 12:9, 10:16, 41:2, 65:8}\n",
+ "CAM_MAPPING = {22: 13, 42: 7, 59: 5, 14: 11, 43: 8, 79: 14, 13: 10, 11: 15, 60: 2, 15: 12, 12: 9, 10: 16, 41: 2, 65: 8}\n",
+ "\n",
+ "def get_or_create_pose_id_for_azimuth(camera_client, camera_id, azimuth, tol=0.1, patrol_id=None):\n",
+ " resp = camera_client.get_current_poses()\n",
+ " resp.raise_for_status()\n",
+ " for pose in resp.json():\n",
+ " if abs(pose[\"azimuth\"] - azimuth) <= tol:\n",
+ " return pose[\"id\"]\n",
+ "\n",
+ " create_resp = camera_client.create_pose(camera_id=camera_id, azimuth=azimuth, patrol_id=patrol_id)\n",
+ " create_resp.raise_for_status()\n",
+ " return create_resp.json()[\"id\"]\n",
"\n",
"for seq_path in sequences_directories:\n",
- " \n",
" seq_prefix_num = int(seq_path.split(\"/\")[-1].split(\"_\")[0])\n",
"\n",
" if CAM_MAPPING.get(seq_prefix_num) is None:\n",
- " print(f\" === WARNING the prefix {seq_prefix_num} is not in CAM_MAPPING dict, hence no alerts will be generated (or from random camera but no implemented yet)\")\n",
- " else: \n",
- " camera_id = CAM_MAPPING[seq_prefix_num]\n",
- " camera_token = get_camera_token(API_URL, camera_id, admin_access_token)\n",
- " camera_client = Client(camera_token, API_URL)\n",
- " \n",
- " imgs = glob.glob(f\"{seq_path}/images/*\")\n",
- " imgs.sort()\n",
- " preds = glob.glob(f\"{seq_path}/labels_predictions/*\")\n",
- " preds.sort()\n",
- "\n",
- " cam_center_azimuth = random.randint(0,360)\n",
- " \n",
- " print(f\"Sending alerts from camera {camera_id} at azimuth {cam_center_azimuth}\")\n",
- " for img_file, pred_file in zip(imgs, preds):\n",
- " \n",
- " stream = io.BytesIO()\n",
- " im = Image.open(img_file)\n",
- " im.save(stream, format=\"JPEG\", quality=80)\n",
- " \n",
- " #bboxes = read_pred_file(pred_file)\n",
- " with open(pred_file, 'r', encoding='utf-8') as file:\n",
- " bboxes = ast.literal_eval(file.read())\n",
- " \n",
- " #bboxes = np.loadtxt(pred_file, ndmin=2)\n",
- " response = camera_client.create_detection(stream.getvalue(), cam_center_azimuth, bboxes)\n",
- " # Force a KeyError if the request failed\n",
- " time.sleep(0.5)\n",
- " response.json()[\"id\"]"
+ " print(\n",
+ " f\" === WARNING the prefix {seq_prefix_num} is not in CAM_MAPPING dict, \"\n",
+ " \"hence no alerts will be generated (or from random camera but no implemented yet)\"\n",
+ " )\n",
+ " continue\n",
+ "\n",
+ " camera_id = CAM_MAPPING[seq_prefix_num]\n",
+ " camera_token = get_camera_token(API_URL, camera_id, admin_access_token)\n",
+ " camera_client = Client(camera_token, API_URL)\n",
+ "\n",
+ " imgs = sorted(glob.glob(f\"{seq_path}/images/*\"))\n",
+ " preds = sorted(glob.glob(f\"{seq_path}/labels_predictions/*\"))\n",
+ "\n",
+ " cam_center_azimuth = random.randint(0, 359)\n",
+ " pose_id = get_or_create_pose_id_for_azimuth(camera_client, camera_id, cam_center_azimuth)\n",
+ "\n",
+ " print(f\"Sending alerts from camera {camera_id} at azimuth {cam_center_azimuth} (pose_id={pose_id})\")\n",
+ " for img_file, pred_file in zip(imgs, preds):\n",
+ " stream = io.BytesIO()\n",
+ " im = Image.open(img_file)\n",
+ " im.save(stream, format=\"JPEG\", quality=80)\n",
+ "\n",
+ " with open(pred_file, \"r\", encoding=\"utf-8\") as file:\n",
+ " bboxes = ast.literal_eval(file.read())\n",
+ "\n",
+ " response = camera_client.create_detection(stream.getvalue(), bboxes, pose_id=pose_id)\n",
+ " time.sleep(0.5)\n",
+ " response.json()[\"id\"]\n"
]
},
{
@@ -241,9 +671,32 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 10,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SAMPLE_PATH triangulated_sequences directory exists, assume alerts inside, no download\n",
+ "Cam 12: 10 images, 10 preds\n",
+ "Cam 13: 8 images, 8 preds\n",
+ "Send some entrelaced detections\n"
+ ]
+ },
+ {
+ "ename": "NameError",
+ "evalue": "name 'ast' is not defined",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
+ "\u001b[31mNameError\u001b[39m Traceback (most recent call last)",
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[10]\u001b[39m\u001b[32m, line 22\u001b[39m\n\u001b[32m 8\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mSAMPLE_PATH \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mSAMPLE_PATH\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m directory exists, assume alerts inside, no download\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 11\u001b[39m cam_triangulation = {\n\u001b[32m 12\u001b[39m \u001b[33m\"\u001b[39m\u001b[33m12\u001b[39m\u001b[33m\"\u001b[39m: {\n\u001b[32m 13\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mazimuth\u001b[39m\u001b[33m\"\u001b[39m: \u001b[32m226\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 19\u001b[39m }\n\u001b[32m 20\u001b[39m }\n\u001b[32m---> \u001b[39m\u001b[32m22\u001b[39m \u001b[43msend_triangulated_alerts\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcam_triangulation\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mAPI_URL\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mClient\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43madmin_access_token\u001b[49m\u001b[43m)\u001b[49m\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m/app/notebooks/utils.py:192\u001b[39m, in \u001b[36msend_triangulated_alerts\u001b[39m\u001b[34m(cam_triangulation, API_URL, Client, admin_access_token, sleep_seconds)\u001b[39m\n\u001b[32m 189\u001b[39m im.save(stream, \u001b[38;5;28mformat\u001b[39m=\u001b[33m\"\u001b[39m\u001b[33mJPEG\u001b[39m\u001b[33m\"\u001b[39m, quality=\u001b[32m80\u001b[39m)\n\u001b[32m 191\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(pred_file, \u001b[33m\"\u001b[39m\u001b[33mr\u001b[39m\u001b[33m\"\u001b[39m, encoding=\u001b[33m\"\u001b[39m\u001b[33mutf-8\u001b[39m\u001b[33m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m file:\n\u001b[32m--> \u001b[39m\u001b[32m192\u001b[39m bboxes = \u001b[43mast\u001b[49m.literal_eval(file.read())\n\u001b[32m 194\u001b[39m response = client.create_detection(stream.getvalue(), bboxes, pose_id=pose_id)\n\u001b[32m 195\u001b[39m time.sleep(sleep_seconds)\n",
+ "\u001b[31mNameError\u001b[39m: name 'ast' is not defined"
+ ]
+ }
+ ],
"source": [
"BASE_DIRECTORY = \"../data\"\n",
"SAMPLE_PATH = \"triangulated_sequences\"\n",
@@ -352,7 +805,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.13.7"
+ "version": "3.11.14"
}
},
"nbformat": 4,
diff --git a/containers/notebooks/app/test poses.ipynb b/containers/notebooks/app/test poses.ipynb
new file mode 100644
index 0000000..c801a17
--- /dev/null
+++ b/containers/notebooks/app/test poses.ipynb
@@ -0,0 +1,870 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "5b6797a0-8025-4e97-bf5e-5849f4623f69",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from dotenv import load_dotenv\n",
+ "import os\n",
+ "from api import get_token, get_camera_token\n",
+ "from pyroclient import Client\n",
+ "import requests\n",
+ "\n",
+ "\n",
+ "API_URL = \"http://api:5050\"\n",
+ "load_dotenv(\"../.env\")\n",
+ "SUPERADMIN_LOGIN = os.environ.get(\"SUPERADMIN_LOGIN\")\n",
+ "SUPERADMIN_PWD = os.environ.get(\"SUPERADMIN_PWD\")\n",
+ "\n",
+ "# Get access token\n",
+ "admin_access_token = get_token(API_URL, SUPERADMIN_LOGIN, SUPERADMIN_PWD)\n",
+ "\n",
+ "\n",
+ "api_client = Client(admin_access_token, API_URL)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "ec200386-bd8d-4716-9850-5948b99534c0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "api_client.create_pose(camera_id=1, azimuth=12, patrol_id=3)\n",
+ "api_client.create_pose(camera_id=1, azimuth=350, patrol_id=33333)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "861f2108-2d28-4103-a29e-cfcfcbb4a451",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'elevation': 110.0,\n",
+ " 'lat': 48.4783,\n",
+ " 'lon': 2.4242,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'videlles-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 1,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [{'azimuth': 12.0, 'patrol_id': 3, 'id': 2, 'camera_id': 1},\n",
+ " {'azimuth': 350.0, 'patrol_id': 33333, 'id': 3, 'camera_id': 1},\n",
+ " {'azimuth': 12.0, 'patrol_id': 3, 'id': 4, 'camera_id': 1},\n",
+ " {'azimuth': 350.0, 'patrol_id': 33333, 'id': 5, 'camera_id': 1}],\n",
+ " 'created_at': '2025-12-19T10:18:43.964412'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4783,\n",
+ " 'lon': 2.4242,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'videlles-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 2,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.974069'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4267,\n",
+ " 'lon': 2.7109,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'croix-augas-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 3,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.979926'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4267,\n",
+ " 'lon': 2.7109,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'croix-augas-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 4,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.985838'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.3792,\n",
+ " 'lon': 2.8208,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'moret-sur-loing-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 5,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.991312'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.3792,\n",
+ " 'lon': 2.8208,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'moret-sur-loing-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 6,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.997215'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.2605,\n",
+ " 'lon': 2.7064,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'nemours-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 7,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.002691'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.2605,\n",
+ " 'lon': 2.7064,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'nemours-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 8,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.008032'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-01',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 9,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.013729'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-02',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 10,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.019147'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-03',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 11,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.024241'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-04',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 12,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.029660'},\n",
+ " {'elevation': 881.0,\n",
+ " 'lat': 44.3969,\n",
+ " 'lon': 4.0837,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'serre-de-barre-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 13,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.035088'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 14,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.040337'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 15,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.045870'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-03',\n",
+ " 'angle_of_view': 180.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 16,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.051086'},\n",
+ " {'elevation': 987.0,\n",
+ " 'lat': 44.6884918213,\n",
+ " 'lon': 4.3135008812,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'marguerite-01',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 17,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.056639'}]"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "api_client.fetch_cameras().json()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "9968d36c-f37b-4949-9e21-92cf69b6697b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "api_client.patch_pose(pose_id=2, azimuth=3)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "c0231abf-acd5-4e19-91cc-78d7e3c3ef7f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'elevation': 110.0,\n",
+ " 'lat': 48.4783,\n",
+ " 'lon': 2.4242,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'videlles-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 1,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [{'azimuth': 4.0, 'patrol_id': 2222, 'id': 1, 'camera_id': 1}],\n",
+ " 'created_at': '2025-12-19T10:18:43.964412'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4783,\n",
+ " 'lon': 2.4242,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'videlles-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 2,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.974069'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4267,\n",
+ " 'lon': 2.7109,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'croix-augas-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 3,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.979926'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4267,\n",
+ " 'lon': 2.7109,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'croix-augas-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 4,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.985838'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.3792,\n",
+ " 'lon': 2.8208,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'moret-sur-loing-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 5,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.991312'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.3792,\n",
+ " 'lon': 2.8208,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'moret-sur-loing-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 6,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.997215'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.2605,\n",
+ " 'lon': 2.7064,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'nemours-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 7,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.002691'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.2605,\n",
+ " 'lon': 2.7064,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'nemours-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 8,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.008032'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-01',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 9,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.013729'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-02',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 10,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.019147'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-03',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 11,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.024241'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-04',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 12,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.029660'},\n",
+ " {'elevation': 881.0,\n",
+ " 'lat': 44.3969,\n",
+ " 'lon': 4.0837,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'serre-de-barre-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 13,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.035088'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 14,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.040337'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 15,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.045870'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-03',\n",
+ " 'angle_of_view': 180.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 16,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.051086'},\n",
+ " {'elevation': 987.0,\n",
+ " 'lat': 44.6884918213,\n",
+ " 'lon': 4.3135008812,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'marguerite-01',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 17,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.056639'}]"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "api_client.fetch_cameras().json()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "f7e15332-f8c6-49c8-95fe-c2b65feb689a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "api_client.delete_pose(pose_id=1)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "3467cbc6-3ed3-42e3-a9f6-7d5e3377e25b",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'elevation': 110.0,\n",
+ " 'lat': 48.4783,\n",
+ " 'lon': 2.4242,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'videlles-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 1,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.964412'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4783,\n",
+ " 'lon': 2.4242,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'videlles-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 2,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.974069'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4267,\n",
+ " 'lon': 2.7109,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'croix-augas-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 3,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.979926'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.4267,\n",
+ " 'lon': 2.7109,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'croix-augas-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 4,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.985838'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.3792,\n",
+ " 'lon': 2.8208,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'moret-sur-loing-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 5,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.991312'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.3792,\n",
+ " 'lon': 2.8208,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'moret-sur-loing-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 6,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:43.997215'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.2605,\n",
+ " 'lon': 2.7064,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'nemours-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 7,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.002691'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 48.2605,\n",
+ " 'lon': 2.7064,\n",
+ " 'organization_id': 2,\n",
+ " 'name': 'nemours-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 8,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.008032'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-01',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 9,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.013729'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-02',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 10,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.019147'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-03',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 11,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.024241'},\n",
+ " {'elevation': 110.0,\n",
+ " 'lat': 44.5451,\n",
+ " 'lon': 4.2165,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'brison-04',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 12,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.029660'},\n",
+ " {'elevation': 881.0,\n",
+ " 'lat': 44.3969,\n",
+ " 'lon': 4.0837,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'serre-de-barre-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 13,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.035088'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-01',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 14,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.040337'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-02',\n",
+ " 'angle_of_view': 54.2,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 15,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.045870'},\n",
+ " {'elevation': 200.0,\n",
+ " 'lat': 44.92757,\n",
+ " 'lon': 4.84164,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'st-peray-03',\n",
+ " 'angle_of_view': 180.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 16,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.051086'},\n",
+ " {'elevation': 987.0,\n",
+ " 'lat': 44.6884918213,\n",
+ " 'lon': 4.3135008812,\n",
+ " 'organization_id': 3,\n",
+ " 'name': 'marguerite-01',\n",
+ " 'angle_of_view': 87.0,\n",
+ " 'is_trustable': True,\n",
+ " 'id': 17,\n",
+ " 'last_active_at': None,\n",
+ " 'last_image': None,\n",
+ " 'last_image_url': None,\n",
+ " 'poses': [],\n",
+ " 'created_at': '2025-12-19T10:18:44.056639'}]"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "api_client.fetch_cameras().json()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "5c40b370-e846-4a39-a50d-66cfcdfc3304",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "api_client.create_pose(camera_id=1, azimuth=3, patrol_id=\"re\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5d6ba20c-ab93-4ac6-81b3-089e70618366",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.14"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/containers/notebooks/app/utils.py b/containers/notebooks/app/utils.py
index 6dc7a1c..a51f5b8 100644
--- a/containers/notebooks/app/utils.py
+++ b/containers/notebooks/app/utils.py
@@ -7,7 +7,7 @@
import time
from PIL import Image
import requests
-
+import ast
from api import get_camera_token
@@ -144,6 +144,17 @@ def dl_seqs_from_url(url, output_path):
def send_triangulated_alerts(
cam_triangulation, API_URL, Client, admin_access_token, sleep_seconds=1
):
+ def get_or_create_pose_id_for_azimuth(camera_client, camera_id, azimuth, tol=0.1, patrol_id=None):
+ resp = camera_client.get_current_poses()
+ resp.raise_for_status()
+ for pose in resp.json():
+ if abs(pose["azimuth"] - azimuth) <= tol:
+ return pose["id"]
+
+ create_resp = camera_client.create_pose(camera_id=camera_id, azimuth=azimuth, patrol_id=patrol_id)
+ create_resp.raise_for_status()
+ return create_resp.json()["id"]
+
for cam_id, info in cam_triangulation.items():
camera_token = get_camera_token(API_URL, cam_id, admin_access_token)
camera_client = Client(camera_token, API_URL)
@@ -156,7 +167,7 @@ def send_triangulated_alerts(
preds = glob.glob(f"{seq_folder}/labels_predictions/*")
preds.sort()
- print(f"Cam {cam_id}: {len(imgs)} images, {len(preds)} preds") # debug
+ print(f"Cam {cam_id}: {len(imgs)} images, {len(preds)} preds")
info["seq_data_pair"] = list(zip(imgs, preds))
@@ -166,20 +177,22 @@ def send_triangulated_alerts(
):
for (cam_id, info), pair in zip(cam_triangulation.items(), files):
if pair is None:
- continue # len of sequences might be different
+ continue
img_file, pred_file = pair
client = info["client"]
azimuth = info["azimuth"]
+ pose_id = get_or_create_pose_id_for_azimuth(client, cam_id, azimuth)
+
stream = io.BytesIO()
im = Image.open(img_file)
im.save(stream, format="JPEG", quality=80)
- with open(pred_file, "r") as file:
- bboxes = file.read()
+ with open(pred_file, "r", encoding="utf-8") as file:
+ bboxes = ast.literal_eval(file.read())
- response = client.create_detection(stream.getvalue(), azimuth, eval(bboxes))
+ response = client.create_detection(stream.getvalue(), bboxes, pose_id=pose_id)
time.sleep(sleep_seconds)
- response.json()["id"] # Force a KeyError if the request failed
+ response.json()["id"]
print(f"detection sent for cam {cam_id}")
diff --git a/containers/notebooks/requirements.txt b/containers/notebooks/requirements.txt
index e6ec850..3bff7e2 100644
--- a/containers/notebooks/requirements.txt
+++ b/containers/notebooks/requirements.txt
@@ -1,4 +1,4 @@
pandas
python-dotenv
pillow
-pyroclient @ git+https://github.com/pyronear/pyro-api.git@c45d5c0f3f22979fe9f74fdd0d831a3a8b911b54#subdirectory=client
+pyroclient @ git+https://github.com/pyronear/pyro-api.git@split_detections#subdirectory=client