diff --git a/.gitignore b/.gitignore index f69bd67..50c0312 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,11 @@ data/credentials-wildfire.json #minio stuff data/minio/.minio.sys/ + +data/.Trash-0 +containers/notebooks/app/.Trash-0 + +.DS_STORE + +data/alert_samples/ +data/triangulated_sequences/ diff --git a/containers/notebooks/Dockerfile b/containers/notebooks/Dockerfile index 0a38d51..064e4c0 100644 --- a/containers/notebooks/Dockerfile +++ b/containers/notebooks/Dockerfile @@ -1,5 +1,5 @@ # Use an official Python runtime as a parent image -FROM python:3.8 +FROM python:3.11 # Set the working directory to /app WORKDIR /app diff --git a/containers/notebooks/app/Download_Sequences_from_a_DistantAlertAPI.ipynb b/containers/notebooks/app/Download_Sequences_from_a_DistantAlertAPI.ipynb index af39784..80501b3 100644 --- a/containers/notebooks/app/Download_Sequences_from_a_DistantAlertAPI.ipynb +++ b/containers/notebooks/app/Download_Sequences_from_a_DistantAlertAPI.ipynb @@ -19,18 +19,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "649157c6-0ce3-4324-a7b6-15c911ddaf36", "metadata": {}, "outputs": [], "source": [ - "SEQUENCE_ID_LIST = [13802, 9456]\n", - "BASE_DIRECTORY = \"alerts\" # directory where to put sequences data" + "SEQUENCE_ID_LIST = [15462, 15463, 15561, 15526, 13880, 13572, 13537, 12997, 12562, 15376, 15526]\n", + "BASE_DIRECTORY = \"../data/alert_samples\" # directory where to put sequences data" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "4e511d97-245c-45cc-9216-a16d31b663aa", "metadata": {}, "outputs": [], @@ -49,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "ac07ba29-68d0-42d2-bafe-e94d961ec6c2", "metadata": {}, "outputs": [], @@ -72,48 +72,141 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "d3c12b1d-54f1-4d8d-b591-dc04ecc35db2", + "execution_count": 4, + "id": "07192f5f-9a69-41a4-9a97-1f1fcefaaf5b", "metadata": {}, "outputs": [], "source": [ - "for seq_id in SEQUENCE_ID_LIST: \n", - " sequences = api_client.fetch_sequences_detections(sequence_id=seq_id).json()\n", + "def dl_seqs_in_target_dir(sequence_id_list, target_dir, api_client):\n", + " \"\"\"\n", + " Download sequences from sequence_id_list to target_dir, using an instanciated api_client (of distant Pyronear alert API)\n", "\n", - " cam_name = [item['name'] for item in cameras if item['id'] == sequences[0]['camera_id']][0]\n", - " created_at_rounded = sequences[0][\"created_at\"].split('.')[0].replace(':', '-').replace('T', '_')\n", + " \"\"\"\n", " \n", - " print(f\"== Download Alerts data for sequence ID {seq_id} - camera {cam_name} at {created_at_rounded}\")\n", + " for seq_id in sequence_id_list: \n", + " sequences = api_client.fetch_sequences_detections(sequence_id=seq_id, limit=10, desc=False).json()\n", " \n", - " alert_dir = os.path.join(f\"{cam_name}_{created_at_rounded}\")\n", - " image_dir = os.path.join(base_dir,alert_dir, \"images\")\n", - " pred_dir = os.path.join(base_dir, alert_dir, \"labels_predictions\")\n", - " os.makedirs(image_dir, exist_ok=True)\n", - " os.makedirs(pred_dir, exist_ok=True)\n", - "\n", - " for seq in sequences:\n", + " cam_name = [item['name'] for item in cameras if item['id'] == sequences[0]['camera_id']][0]\n", + " created_at_rounded = sequences[0][\"created_at\"].split('.')[0].replace(':', '-').replace('T', '_')\n", + " cam_id_distant_api = sequences[0][\"camera_id\"]\n", + " print(f\"== Download Alerts data for sequence ID {seq_id} - camera {cam_name} at {created_at_rounded}\")\n", " \n", - " # bbox\n", - " #yolo_format_bbox = ' '.join(map(str,ast.literal_eval(seq[\"bboxes\"])[0]))\n", - " bboxes = seq[\"bboxes\"]\n", + " alert_dir = os.path.join(f\"{cam_id_distant_api}_{cam_name}_{created_at_rounded}\")\n", + " image_dir = os.path.join(target_dir,alert_dir, \"images\")\n", + " pred_dir = os.path.join(target_dir, alert_dir, \"labels_predictions\")\n", + " os.makedirs(image_dir, exist_ok=True)\n", + " os.makedirs(pred_dir, exist_ok=True)\n", + " \n", + " for seq in sequences:\n", + " \n", + " # bbox\n", + " #yolo_format_bbox = ' '.join(map(str,ast.literal_eval(seq[\"bboxes\"])[0]))\n", + " bboxes = seq[\"bboxes\"]\n", + " \n", + " bbox_file_name = seq[\"bucket_key\"][:-4] + \".txt\"\n", + " \n", + " with open(os.path.join(target_dir, alert_dir, \"labels_predictions\",bbox_file_name), 'w') as f:\n", + " f.write(bboxes)\n", + " \n", + " url = seq['url']\n", + " nom_fichier = seq[\"bucket_key\"]\n", + " # image\n", + " response = requests.get(url, stream=True)\n", + " if response.status_code == 200:\n", + " full_img_path = os.path.join(image_dir, nom_fichier)\n", + " with open(full_img_path, 'wb') as f:\n", + " for chunk in response.iter_content(1024):\n", + " f.write(chunk)\n", + " else:\n", + " print(f\"Error during download.\")\n", + " print(\"Download complete\")\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "9503b5d5-9930-4c9e-9fbe-1eb1e563fd44", + "metadata": {}, + "source": [ + "# Download sequences from SEQUENCE_ID_LIST list" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b2d033e9-7ce2-4cf4-995e-01ea2c15e235", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== Download Alerts data for sequence ID 15462 - camera serre-de-barre-01 at 2025-09-29_13-56-58\n", + "== Download Alerts data for sequence ID 15463 - camera brison-03 at 2025-09-29_13-58-36\n", + "== Download Alerts data for sequence ID 15561 - camera pouncho-agast-01 at 2025-10-01_06-18-57\n", + "== Download Alerts data for sequence ID 15526 - camera pouncho-agast-01 at 2025-09-30_15-29-49\n", + "== Download Alerts data for sequence ID 13880 - camera fontaneilles-01 at 2025-09-04_07-34-32\n", + "== Download Alerts data for sequence ID 13572 - camera pouncho-agast-01 at 2025-08-29_06-59-39\n", + "== Download Alerts data for sequence ID 13537 - camera pouncho-agast-01 at 2025-08-28_15-31-17\n", + "== Download Alerts data for sequence ID 12997 - camera pouncho-agast-01 at 2025-08-17_15-52-27\n", + "== Download Alerts data for sequence ID 12562 - camera pouncho-agast-01 at 2025-08-06_12-32-09\n", + "== Download Alerts data for sequence ID 15376 - camera pouncho-agast-02 at 2025-09-28_10-52-39\n", + "== Download Alerts data for sequence ID 15526 - camera pouncho-agast-01 at 2025-09-30_15-29-49\n", + "Download complete\n" + ] + } + ], + "source": [ + "single_alerts = os.path.join(BASE_DIRECTORY, \"single_sequences\")\n", + "os.makedirs(single_alerts, exist_ok=True)\n", "\n", - " bbox_file_name = seq[\"bucket_key\"][:-4] + \".txt\"\n", - " \n", - " with open(os.path.join(base_dir, alert_dir, \"labels_predictions\",bbox_file_name), 'w') as f:\n", - " f.write(bboxes)\n", + "dl_seqs_in_target_dir(SEQUENCE_ID_LIST, single_alerts, api_client)" + ] + }, + { + "cell_type": "markdown", + "id": "e9d109db-324c-4268-bae6-27c5960def55", + "metadata": {}, + "source": [ + "## Download triangulated alerts\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "68a2cff8-f847-4e6c-8c74-37b7ecffc9af", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== Download Alerts data for sequence ID 10535 - camera moret-sur-loing-01 at 2025-07-14_11-22-05\n", + "== Download Alerts data for sequence ID 10537 - camera nemours-02 at 2025-07-14_11-24-10\n", + "== Download Alerts data for sequence ID 10538 - camera croix-augas-02 at 2025-07-14_11-24-31\n", + "== Download Alerts data for sequence ID 15462 - camera serre-de-barre-01 at 2025-09-29_13-56-58\n", + "== Download Alerts data for sequence ID 15463 - camera brison-03 at 2025-09-29_13-58-36\n", + "Download complete\n" + ] + } + ], + "source": [ "\n", - " url = seq['url']\n", - " nom_fichier = seq[\"bucket_key\"]\n", - " # image\n", - " response = requests.get(url, stream=True)\n", - " if response.status_code == 200:\n", - " full_img_path = os.path.join(image_dir, nom_fichier)\n", - " with open(full_img_path, 'wb') as f:\n", - " for chunk in response.iter_content(1024):\n", - " f.write(chunk)\n", - " else:\n", - " print(f\"Error during download.\")" + "TRIANGULATED_SEQUENCE_LIST = [10535, 10537, 10538, 15462, 15463]\n", + "TRIANGULATED_DIRECTORY= \"triangulated_sequences\"\n", + "triangulated_dir = os.path.join(BASE_DIRECTORY, TRIANGULATED_DIRECTORY)\n", + "os.makedirs(triangulated_dir, exist_ok=True)\n", + "\n", + "dl_seqs_in_target_dir(TRIANGULATED_SEQUENCE_LIST, triangulated_dir, api_client)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ffd56f84-ec77-4843-837a-06de85232c94", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -132,7 +225,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.20" + "version": "3.13.7" } }, "nbformat": 4, diff --git a/containers/notebooks/app/send_real_alerts.ipynb b/containers/notebooks/app/send_real_alerts.ipynb index 8d46c8e..ce63162 100644 --- a/containers/notebooks/app/send_real_alerts.ipynb +++ b/containers/notebooks/app/send_real_alerts.ipynb @@ -1,11 +1,25 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Intro\n", + "\n", + "This notebook is designed to generate alerts so that you can work on the development environment.\n", + "\n", + "It works as it is (just run blocks from the setup section), then you can run any section.\n", + "\n", + "It is also possible to run the entire notebook at once: To do so, in the menu, click ‘Run’ then ‘Run All Cells’." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "import ast\n", "import pandas as pd\n", "from dotenv import load_dotenv\n", "import os\n", @@ -19,19 +33,10 @@ "import requests\n", "import random\n", "import shutil\n", - "from utils import read_pred_file\n", + "from utils import dl_seqs_from_url, read_pred_file, send_triangulated_alerts\n", "import time" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cat ../.env" - ] - }, { "cell_type": "code", "execution_count": null, @@ -58,34 +63,20 @@ "users" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Select user via its login" - ] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "user = \"test77\"\n", - "organization_id = users[users[\"login\"]==user][\"organization_id\"].item()\n", - "\n", - "user_cameras = cameras[cameras[\"organization_id\"]==organization_id]\n", - "user_cameras" + "cameras" ] }, { "cell_type": "markdown", - "metadata": { - "jp-MarkdownHeadingCollapsed": true - }, + "metadata": {}, "source": [ - "# Send some alerts\n", - "Download a file containing relevant images and predictions, then send alerts to selected cameras (with a random azimuth)." + "#### Download alerts sequences" ] }, { @@ -94,7 +85,25 @@ "metadata": {}, "outputs": [], "source": [ - "send_alert_from_cam_ids = [2, 4, 5, 7] # select cameras" + "BASE_DIRECTORY = \"../data\"\n", + "\n", + "SAMPLE_PATH = \"alert_samples\"\n", + "url = \"https://github.com/pyronear/pyro-envdev/releases/download/v0.0.1/alert_samples.zip\"\n", + "\n", + "if not os.path.isdir(os.path.join(BASE_DIRECTORY, SAMPLE_PATH)):\n", + " print(\"Images not found, dowloading ...\")\n", + " #os.makedirs(SAMPLE_PATH, exist_ok=True)\n", + " dl_seqs_from_url(url, os.path.join(BASE_DIRECTORY, SAMPLE_PATH))\n", + "else:\n", + " print(f\"SAMPLE_PATH {SAMPLE_PATH} directory exists, assume alerts inside, no download\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Send some random alerts sequences from a list of cameras\n", + "Download a file containing relevant images and predictions, then send alerts to selected cameras (with a random azimuth and random imgs picked from the alerts folder downloaded below)." ] }, { @@ -103,26 +112,7 @@ "metadata": {}, "outputs": [], "source": [ - "if not os.path.isdir(\"selection-true-positives\"):\n", - " print(\"Images not found, dowloading ...\")\n", - " url = \"https://github.com/pyronear/pyro-envdev/releases/download/v0.0.1/selection-true-positives.zip\"\n", - " output_path = \"selection-true-positives.zip\"\n", - "\n", - " response = requests.get(url, stream=True)\n", - " response.raise_for_status() # Raises an error for bad status codes\n", - "\n", - " with open(output_path, 'wb') as f:\n", - " for chunk in response.iter_content(chunk_size=8192):\n", - " f.write(chunk)\n", - "\n", - " zip_path = \"selection-true-positives.zip\"\n", - " extract_dir = \"selection-true-positives\" # Current directory\n", - "\n", - " shutil.unpack_archive(zip_path, extract_dir, 'zip')\n", - "\n", - " print(\"Extraction completed.\")\n", - "\n", - " " + "send_alert_from_cam_ids = [2, 4, 5, 14] # select cameras" ] }, { @@ -131,8 +121,16 @@ "metadata": {}, "outputs": [], "source": [ - "sequances_folders = glob.glob(\"selection-true-positives/*\")\n", - "len(sequances_folders)" + "SAMPLE_PATH = \"selection-true-positives\"\n", + "url = \"https://github.com/pyronear/pyro-envdev/releases/download/v0.0.1/selection-true-positives.zip\"\n", + "\n", + "if not os.path.isdir(SAMPLE_PATH):\n", + " print(\"Images not found, dowloading ...\")\n", + " \n", + " os.makedirs(SAMPLE_PATH, exist_ok=True)\n", + " dl_seqs_from_url(url, SAMPLE_PATH)\n", + "else:\n", + " print(f\"SAMPLE_PATH {SAMPLE_PATH} directory exists, assume alerts inside, no download\")" ] }, { @@ -141,18 +139,19 @@ "metadata": {}, "outputs": [], "source": [ + "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", "\n", - " sequances_folder = sequances_folders[camera_id]\n", - "\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", + " \n", " cam_center_azimuth = random.randint(0,360)\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", @@ -172,8 +171,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Create 2 sequences that triangulate\n", - "Download a file containning 2 alerts from 2 cameras that triangulate, then it send alerts to the api. " + "## Send custom alerts sequences\n", + "\n", + "The following code block helps you send some alerts/sequences from a specific diretory containing sequences data\n", + "\n", + "- Create a directory with the name you want, ex ```alerts_with_style```\n", + "- In directory ```../data```or in current dir, there are some alerts folders downloaded\n", + "- Copy each inteterting alert dir (dir that contains a dir img, and a dir label predictions) and paste them to your ```alerts_with_style``` directory\n", + "- then below, fill the ```SEQUENCES_DIR_PATH``` with the path to you directory\n", + "- fill the CAM_MAPPING mapping dict : the first number in alert folder are dict keys that will map with camera id in database : ex {2:78} -> all alert folder starting with 2 will create alerts for the camera of id 78\n", + " - you can find cameras id in the object ```cameras``` (created at the beginning of this notebook), or look in the DB table cameras\n", + " - If you put alert dir with prefix number that does not match, no alerts will be created\n", + " " ] }, { @@ -182,75 +191,141 @@ "metadata": {}, "outputs": [], "source": [ - "if not os.path.isdir(\"triangulated_sequences\"):\n", - " print(\"Images not found, dowloading ...\")\n", - " output_path = \"triangulated_sequences.zip\"\n", + "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", "\n", - " url = \"https://github.com/pyronear/pyro-envdev/releases/download/v0.0.1/\"+\"triangulated_sequences.zip\"\n", - "\n", - " response = requests.get(url, stream=True)\n", - " response.raise_for_status() # Raises an error for bad status codes\n", - "\n", - " with open(output_path, 'wb') as f:\n", - " for chunk in response.iter_content(chunk_size=8192):\n", - " f.write(chunk)\n", + "for seq_path in sequences_directories:\n", + " \n", + " seq_prefix_num = int(seq_path.split(\"/\")[-1].split(\"_\")[0])\n", "\n", - " zip_path = output_path\n", - " extract_dir = \"triangulated_sequences\" # Current directory\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", - " shutil.unpack_archive(zip_path, extract_dir, 'zip')\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\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Send 2 sequences that triangulate (user test07)\n", + "Download a file containning 2 alerts from 2 cameras that triangulate, then it send alerts to the api. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BASE_DIRECTORY = \"../data\"\n", + "SAMPLE_PATH = \"triangulated_sequences\"\n", + "url = \"https://github.com/pyronear/pyro-envdev/releases/download/v0.0.1/triangulated_sequences.zip\" \n", + "if not os.path.isdir(os.path.join(BASE_DIRECTORY, SAMPLE_PATH)):\n", + " print(\"Images not found, dowloading ...\")\n", + " dl_seqs_from_url(url, os.path.join(BASE_DIRECTORY, SAMPLE_PATH))\n", + "else:\n", + " print(f\"SAMPLE_PATH {SAMPLE_PATH} directory exists, assume alerts inside, no download\")\n", "\n", - " print(\"Extraction completed.\")\n", "\n", "cam_triangulation = {\n", " \"12\": {\n", " \"azimuth\": 226,\n", - " \"path\": \"triangulated_sequences/brison-03\"\n", + " \"path\": os.path.join(BASE_DIRECTORY, SAMPLE_PATH, \"brison-03\")\n", " },\n", " \"13\": {\n", " \"azimuth\": 54,\n", - " \"path\": \"triangulated_sequences/serre-de-barre-01\"\n", + " \"path\": os.path.join(BASE_DIRECTORY, SAMPLE_PATH, \"serre-de-barre-01\")\n", " }\n", "}\n", "\n", - "for cam_id, info in cam_triangulation.items():\n", - " camera_token = get_camera_token(API_URL, cam_id, admin_access_token)\n", - " camera_client = Client(camera_token, API_URL)\n", - " info[\"client\"] = camera_client\n", - "\n", - " seq_folder = info[\"path\"]\n", - "\n", - " imgs = glob.glob(f\"{seq_folder}/images/*\")\n", - " imgs.sort()\n", - " preds = glob.glob(f\"{seq_folder}/labels_predictions/*\")\n", - " preds.sort()\n", - "\n", - " print(f\"Cam {cam_id}: {len(imgs)} images, {len(preds)} preds\") # debug\n", - "\n", - " info[\"seq_data_pair\"] = list(zip(imgs, preds))\n", - "\n", - "print(\"Send some entrelaced detections\")\n", - "for files in itertools.zip_longest(*(info[\"seq_data_pair\"] for info in cam_triangulation.values())):\n", - "\n", - " for (cam_id, info), pair in zip(cam_triangulation.items(), files):\n", - " if pair is None:\n", - " continue #len of sequences might be different\n", - " img_file, pred_file = pair\n", - " client = info[\"client\"]\n", - " azimuth = info[\"azimuth\"]\n", - "\n", - " stream = io.BytesIO()\n", - " im = Image.open(img_file)\n", - " im.save(stream, format=\"JPEG\", quality=80)\n", + "send_triangulated_alerts(cam_triangulation, API_URL, Client, admin_access_token)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Send 2 other sequences that triangulates (user test07)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cam_triangulation = {\n", + " \"11\": {\n", + " \"azimuth\": 226,\n", + " \"path\": \"../data/alert_samples/triangulated_sequences/14_brison-03_2025-09-29_13-58-36\"\n", + " },\n", + " \"13\": {\n", + " \"azimuth\": 173,\n", + " \"path\": \"../data/alert_samples/triangulated_sequences/22_serre-de-barre-01_2025-09-29_13-56-58\"\n", + " }\n", + "}\n", "\n", - " with open(pred_file, \"r\") as file:\n", - " bboxes = file.read()\n", "\n", - " response = client.create_detection(stream.getvalue(),azimuth,eval(bboxes) )\n", - " time.sleep(1)\n", + "send_triangulated_alerts(cam_triangulation, API_URL, Client, admin_access_token)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Send 3 sequences that triangulates (user test77)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cam_triangulation = {\n", + " \"4\": {\n", + " \"azimuth\": 190,\n", + " \"path\": \"../data/alert_samples/triangulated_sequences/41_croix-augas-02_2025-07-14_11-24-31\"\n", + " },\n", + " \"8\": {\n", + " \"azimuth\": 25,\n", + " \"path\": \"../data/alert_samples/triangulated_sequences/65_nemours-02_2025-07-14_11-24-10\"\n", + " },\n", + " \"5\": {\n", + " \"azimuth\": 280,\n", + " \"path\": \"../data/alert_samples/triangulated_sequences/66_moret-sur-loing-01_2025-07-14_11-22-05\"\n", + " }\n", + "}\n", "\n", - " response.json()[\"id\"] # Force a KeyError if the request failed\n", - " print(f\"detection sent for cam {cam_id}\")" + "send_triangulated_alerts(cam_triangulation, API_URL, Client, admin_access_token)\n" ] }, { @@ -277,7 +352,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.20" + "version": "3.13.7" } }, "nbformat": 4, diff --git a/containers/notebooks/app/utils.py b/containers/notebooks/app/utils.py index db6da98..6dc7a1c 100644 --- a/containers/notebooks/app/utils.py +++ b/containers/notebooks/app/utils.py @@ -1,7 +1,15 @@ +import os +import io +import glob import numpy as np import shutil +import itertools +import time +from PIL import Image import requests +from api import get_camera_token + def xywh2xyxy(x: np.ndarray): """ @@ -108,3 +116,70 @@ def generate_bbox_with_jitter( bbox = np.array([(x0, x0, x1, x1, base_conf)]) return bbox + + +def dl_seqs_from_url(url, output_path): + """ + Download sequences zip file, exrtact them in target directory + Args: + url (string): url to sequences as zip file + output_path (string): target directory + + Returns: + """ + response = requests.get(url, stream=True) + response.raise_for_status() # Raises an error for bad status codes + + zip_path = f"{output_path}.zip" + + with open(zip_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + shutil.unpack_archive(zip_path, output_path, "zip") + os.remove(zip_path) + print("Extraction completed.") + + +def send_triangulated_alerts( + cam_triangulation, API_URL, Client, admin_access_token, sleep_seconds=1 +): + 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) + info["client"] = camera_client + + seq_folder = info["path"] + + imgs = glob.glob(f"{seq_folder}/images/*") + imgs.sort() + preds = glob.glob(f"{seq_folder}/labels_predictions/*") + preds.sort() + + print(f"Cam {cam_id}: {len(imgs)} images, {len(preds)} preds") # debug + + info["seq_data_pair"] = list(zip(imgs, preds)) + + print("Send some entrelaced detections") + for files in itertools.zip_longest( + *(info["seq_data_pair"] for info in cam_triangulation.values()) + ): + for (cam_id, info), pair in zip(cam_triangulation.items(), files): + if pair is None: + continue # len of sequences might be different + img_file, pred_file = pair + client = info["client"] + azimuth = info["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() + + response = client.create_detection(stream.getvalue(), azimuth, eval(bboxes)) + time.sleep(sleep_seconds) + + response.json()["id"] # Force a KeyError if the request failed + print(f"detection sent for cam {cam_id}") diff --git a/data/csv/API_DATA_DEV - cameras.csv b/data/csv/API_DATA_DEV - cameras.csv index 376ae3d..9a60d45 100644 --- a/data/csv/API_DATA_DEV - cameras.csv +++ b/data/csv/API_DATA_DEV - cameras.csv @@ -12,3 +12,7 @@ id,organization_id,name,angle_of_view,elevation,lat,lon,is_trustable,created_at 12,3,brison-03,87,110,44.5451,4.2165,True,2023-11-07T15:07:19.226673 13,3,brison-04,87,110,44.5451,4.2165,True,2023-11-07T15:07:19.226673 14,3,serre-de-barre-01,54.2,881,44.3969,4.0837,True,2025-02-07T16:49:44.312941 +15,3,st-peray-01,54.2,200.0,44.92757,4.84164,True,2025-02-07T16:49:44.312941 +16,3,st-peray-02,54.2,200.0,44.92757,4.84164,True,2025-02-07T16:49:44.312941 +17,3,st-peray-03,180.0,200.0,44.92757,4.84164,True,2025-02-07T16:49:44.312941 +18,3,marguerite-01,87.0,987,44.6884918213,4.3135008812,True,2025-02-07T16:49:44.312941