From 4f30f169d661c147325f419572b15c5e80b1f725 Mon Sep 17 00:00:00 2001 From: Niraj Adhikari <41701707+nrjadkry@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:30:28 +0545 Subject: [PATCH] Get a list of images that contains a GCP Point from a whole Project (#396) * fix: calculate bbox for the drone image * fix: calculate image footprints * feat: get a list of images that contains a point --- src/backend/app/gcp/gcp_crud.py | 71 +++++++++++++++++++++++++++-- src/backend/app/gcp/gcp_routes.py | 29 ++++++++++-- src/backend/app/tasks/task_logic.py | 11 +++++ 3 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/backend/app/gcp/gcp_crud.py b/src/backend/app/gcp/gcp_crud.py index 763f9499..2dc1df0b 100644 --- a/src/backend/app/gcp/gcp_crud.py +++ b/src/backend/app/gcp/gcp_crud.py @@ -7,6 +7,7 @@ from app.waypoints import waypoint_schemas from app.config import settings from pyproj import Transformer +from loguru import logger as log async def calculate_bounding_box( @@ -76,9 +77,13 @@ def fetch_json_from_presigned_url(url: str): """ Fetch a JSON file from an AWS presigned URL. """ - response = requests.get(url) - response.raise_for_status() # Raise an exception for HTTP errors - return response.json() + try: + response = requests.get(url) + response.raise_for_status() # Raise an exception for HTTP errors + return response.json() + except Exception as e: + log.warning(f"Error fetching JSON file: {e}") + return None async def find_matching_images_that_contains_point(bounding_boxes, gps_coordinate): @@ -115,7 +120,7 @@ async def calculate_bbox_from_images_file( # Calculate bounding boxes for each image bounding_boxes = {} - for image in images: + for image in images or []: filename = image["filename"] lat = image["latitude"] lon = image["longitude"] @@ -274,7 +279,63 @@ def find_images_with_coordinate( return matching_images -async def process_images_for_point( +async def find_images_in_a_project_for_point( + project_id: uuid.UUID, + task_id_list: List[uuid.UUID], + point: waypoint_schemas.PointField, + fov_degree: float, + altitude: float, +) -> List[str]: + """ + Process images to find those containing a specific point and return their pre-signed URLs. + + Args: + project_id (uuid.UUID): The ID of the project. + task_id (uuid.UUID): The ID of the task. + point (waypoint_schemas.PointField): The point to check. + + Returns: + List[str]: A list of pre-signed URLs for matching images. + """ + + # Extract the longitude and latitude of the point + point_tuple = (point.longitude, point.latitude) + + # Find the matching images from each task + images_list = [] + for task_id in task_id_list: + task_id_str = str(task_id[0]) + s3_images_json_path_for_task = ( + f"dtm-data/projects/{project_id}/{task_id_str}/images.json" + ) + s3_images_json_url = get_presigned_url( + settings.S3_BUCKET_NAME, s3_images_json_path_for_task + ) + + # Fetch bounding boxes from the `images.json` file + bbox_list = await calculate_bbox_from_images_file( + s3_images_json_url, fov_degree, altitude + ) + + # Find images whose bounding boxes contain the given point + matching_images = await find_matching_images_that_contains_point( + bbox_list, point_tuple + ) + images_list += [f"{task_id_str}/images/{image}" for image in matching_images] + + # Generate pre-signed URLs for the matching images + presigned_urls = [ + get_presigned_url( + settings.S3_BUCKET_NAME, + f"dtm-data/projects/{project_id}/{image}", + ) + for image in images_list + ] + + return presigned_urls + + +async def find_images_in_a_task_for_point( project_id: uuid.UUID, task_id: uuid.UUID, point: waypoint_schemas.PointField, diff --git a/src/backend/app/gcp/gcp_routes.py b/src/backend/app/gcp/gcp_routes.py index d6429e28..31799e50 100644 --- a/src/backend/app/gcp/gcp_routes.py +++ b/src/backend/app/gcp/gcp_routes.py @@ -1,9 +1,13 @@ import uuid from app.config import settings -from fastapi import APIRouter +from fastapi import APIRouter, Depends from app.waypoints import waypoint_schemas from app.gcp import gcp_crud from typing import List +from psycopg import Connection +from app.db import database +from typing import Annotated +from app.tasks.task_logic import list_task_id_for_project router = APIRouter( @@ -13,7 +17,7 @@ ) -@router.post("/find-images") +@router.post("/find-images/") async def find_images( project_id: uuid.UUID, task_id: uuid.UUID, @@ -24,6 +28,25 @@ async def find_images( fov_degree = 82.1 # For DJI Mini 4 Pro altitude = 100 # TODO: Get this from db - return await gcp_crud.process_images_for_point( + return await gcp_crud.find_images_in_a_task_for_point( project_id, task_id, point, fov_degree, altitude ) + + +@router.post("/find-project-images/") +async def find_images_for_a_project( + project_id: uuid.UUID, + db: Annotated[Connection, Depends(database.get_db)], + point: waypoint_schemas.PointField = None, +) -> List[str]: + """Find images that contain a specified point in a project.""" + + fov_degree = 82.1 # For DJI Mini 4 Pro + altitude = 100 # TODO: Get this from db + + # Get all task IDs for the project from database + task_id_list = await list_task_id_for_project(db, project_id) + + return await gcp_crud.find_images_in_a_project_for_point( + project_id, task_id_list, point, fov_degree, altitude + ) diff --git a/src/backend/app/tasks/task_logic.py b/src/backend/app/tasks/task_logic.py index 0d3fa7c7..b08833ab 100644 --- a/src/backend/app/tasks/task_logic.py +++ b/src/backend/app/tasks/task_logic.py @@ -12,6 +12,17 @@ from app.config import settings +async def list_task_id_for_project(db: Connection, project_id: uuid.UUID): + query = """ + SELECT id + FROM tasks + WHERE project_id = %(project_id)s; + """ + async with db.cursor() as cur: + await cur.execute(query, {"project_id": str(project_id)}) + return await cur.fetchall() + + async def get_task_stats(db: Connection, user_data: AuthUser): try: async with db.cursor(row_factory=class_row(TaskStats)) as cur: