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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idorganization_idpasswordloginrolecreated_at
022testtest77agent2024-02-23T08:18:45.447773
133testtest07agent2024-02-23T08:18:45.447773
\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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
idorganization_idnameangle_of_viewelevationlatlonis_trustablecreated_at
022videlles-0154.2110.048.4783002.424200True2023-11-07T15:07:19.226673
132videlles-0254.2110.048.4783002.424200True2023-11-07T15:07:19.226673
242croix-augas-0154.2110.048.4267002.710900True2023-11-07T15:07:19.226673
352croix-augas-0254.2110.048.4267002.710900True2023-11-07T15:07:19.226673
462moret-sur-loing-0154.2110.048.3792002.820800True2023-11-07T15:07:19.226673
572moret-sur-loing-0254.2110.048.3792002.820800True2023-11-07T15:07:19.226673
682nemours-0154.2110.048.2605002.706400True2023-11-07T15:07:19.226673
792nemours-0254.2110.048.2605002.706400True2023-11-07T15:07:19.226673
8103brison-0187.0110.044.5451004.216500True2023-11-07T15:07:19.226673
9113brison-0287.0110.044.5451004.216500True2023-11-07T15:07:19.226673
10123brison-0387.0110.044.5451004.216500True2023-11-07T15:07:19.226673
11133brison-0487.0110.044.5451004.216500True2023-11-07T15:07:19.226673
12143serre-de-barre-0154.2881.044.3969004.083700True2025-02-07T16:49:44.312941
13153st-peray-0154.2200.044.9275704.841640True2025-02-07T16:49:44.312941
14163st-peray-0254.2200.044.9275704.841640True2025-02-07T16:49:44.312941
15173st-peray-03180.0200.044.9275704.841640True2025-02-07T16:49:44.312941
16183marguerite-0187.0987.044.6884924.313501True2025-02-07T16:49:44.312941
\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