diff --git a/.cursor/commands/1.research/1.1_research_assesment_acceptance_criteria.md b/.cursor/commands/1.research/1.1_research_assesment_acceptance_criteria.md index a2ac6e7..33a8a27 100644 --- a/.cursor/commands/1.research/1.1_research_assesment_acceptance_criteria.md +++ b/.cursor/commands/1.research/1.1_research_assesment_acceptance_criteria.md @@ -1,14 +1,14 @@ ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md.md`. + `@_docs/00_problem/restrictions.md.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Role You are a professional software architect diff --git a/.cursor/commands/1.research/1.2_research_problem.md b/.cursor/commands/1.research/1.2_research_problem.md index 03c2bd2..d92c5a6 100644 --- a/.cursor/commands/1.research/1.2_research_problem.md +++ b/.cursor/commands/1.research/1.2_research_problem.md @@ -1,14 +1,14 @@ ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md.md`. + `@_docs/00_problem/restrictions.md.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Role You are a professional software architect diff --git a/.cursor/commands/1.research/1.3_solution_draft_assessment.md b/.cursor/commands/1.research/1.3_solution_draft_assessment.md index a4b9437..adce85b 100644 --- a/.cursor/commands/1.research/1.3_solution_draft_assessment.md +++ b/.cursor/commands/1.research/1.3_solution_draft_assessment.md @@ -1,17 +1,17 @@ ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md.md`. + `@_docs/00_problem/restrictions.md.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Existing solution draft: - `@docs/01_solution/solution_draft.md` + `@_docs/01_solution/solution_draft.md` ## Role You are a professional software architect diff --git a/.cursor/commands/2.planning/2.10_gen_components.md b/.cursor/commands/2.planning/2.10_gen_components.md index 8bf5702..9bfd28d 100644 --- a/.cursor/commands/2.planning/2.10_gen_components.md +++ b/.cursor/commands/2.planning/2.10_gen_components.md @@ -1,19 +1,19 @@ # decompose ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md`. + `@_docs/00_problem/restrictions.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Existing solution: - `@docs/01_solution/solution_draft.md` + `@_docs/01_solution/solution_draft.md` ## Role You are a professional software architect @@ -27,7 +27,7 @@ - When you've got full understanding of how exactly each component will interact with each other, create components ## Output - - Store description of each component to the file `docs/02_components/[##]_[component_name]/[##]._component_[component_name].md` with the next structure: + - Store description of each component to the file `_docs/02_components/[##]_[component_name]/[##]._component_[component_name].md` with the next structure: - Component Name - Detailed description - API methods, for each method: @@ -40,7 +40,7 @@ - Test cases for the method - Integration tests for the component if needed. - Non-functional tests for the component if needed. - - Store Extensions and Helpers to support functionality across multiple components to a separate folder `docs/02_components/helpers`. + - Store Extensions and Helpers to support functionality across multiple components to a separate folder `_docs/02_components/helpers`. - Generate draw.io components diagram shows relations between components. ## Notes diff --git a/.cursor/commands/2.planning/2.15_components_assesment.md b/.cursor/commands/2.planning/2.15_components_assesment.md index 45c4245..40a3e0a 100644 --- a/.cursor/commands/2.planning/2.15_components_assesment.md +++ b/.cursor/commands/2.planning/2.15_components_assesment.md @@ -4,7 +4,7 @@ - @00_problem ## Solution and decomposition - - @docs/01_solution/solution.md + - @_docs/01_solution/solution.md - @02_components ## Role diff --git a/.cursor/commands/2.planning/2.20_gen_epics.md b/.cursor/commands/2.planning/2.20_gen_epics.md index 83d78dd..e484996 100644 --- a/.cursor/commands/2.planning/2.20_gen_epics.md +++ b/.cursor/commands/2.planning/2.20_gen_epics.md @@ -1,19 +1,19 @@ # generate Jira Epics ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md`. + `@_docs/00_problem/restrictions.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Existing solution: - `@docs/01_solution/solution.md` + `@_docs/01_solution/solution.md` ## Role You are a world class product manager diff --git a/.cursor/commands/2.planning/2.30_gen_tests.md b/.cursor/commands/2.planning/2.30_gen_tests.md index 107f5ae..e7d8f7a 100644 --- a/.cursor/commands/2.planning/2.30_gen_tests.md +++ b/.cursor/commands/2.planning/2.30_gen_tests.md @@ -1,19 +1,19 @@ # generate Tests ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is examples from the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md`. + `@_docs/00_problem/restrictions.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Existing solution: - `@docs/01_solution/solution_draft.md` + `@_docs/01_solution/solution_draft.md` ## Role You are a professional Quality Assurance Engineer @@ -23,7 +23,7 @@ - Cover all the the criteria with tests specs ## Output - Store all tests specs to the files `docs/03_tests/[##]_[test_name]_spec.md` + Store all tests specs to the files `_docs/03_tests/[##]_[test_name]_spec.md` Types and structures of tests: - Integration tests diff --git a/.cursor/commands/2.planning/2.40_gen_features.md b/.cursor/commands/2.planning/2.40_gen_features.md index fe61d04..29411ce 100644 --- a/.cursor/commands/2.planning/2.40_gen_features.md +++ b/.cursor/commands/2.planning/2.40_gen_features.md @@ -4,10 +4,10 @@ --component component_spec.md ## Existing solution: - `@docs/01_solution/solution.md` + `@_docs/01_solution/solution.md` ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Role You are a professional software architect diff --git a/.cursor/commands/3.implementation/3.05_implement_initial_structure.md b/.cursor/commands/3.implementation/3.05_implement_initial_structure.md index 33cf758..a7faecc 100644 --- a/.cursor/commands/3.implementation/3.05_implement_initial_structure.md +++ b/.cursor/commands/3.implementation/3.05_implement_initial_structure.md @@ -1,34 +1,35 @@ # Create initial structure ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is an example of the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is an example of the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md`. + `@_docs/00_problem/restrictions.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Existing solution spec: - `@docs/01_solution/solution.md` + `@_docs/01_solution/solution.md` ## Components with Features specs - `@docs/02_components` + `@_docs/02_components` ## Role - You are a professional software architect and developer + You are a professional software architect ## Task - - Read carefully all the component specs and features in the components folder: `@docs/02_components` + - Read carefully all the component specs and features in the components folder: `@_docs/02_components` - Investgate in internet what are the best way and tools to implement components and its features - - Create initial structure: + - Make a plan for the creating initial structure: - DTOs - component's interfaces - empty implementations - helpers - empty implementations or interfaces + - add README.md, describe the project by @_docs/01_solution/solution.md ## Notes - Follow SOLID principles diff --git a/.cursor/commands/3.implementation/3.10_implement_component.md b/.cursor/commands/3.implementation/3.10_implement_component.md index b21f302..717d32a 100644 --- a/.cursor/commands/3.implementation/3.10_implement_component.md +++ b/.cursor/commands/3.implementation/3.10_implement_component.md @@ -4,26 +4,26 @@ component_folder ## The problem description - `@docs/00_problem/problem_description.md`. + `@_docs/00_problem/problem_description.md`. ## Data samples - Located here: `@docs/00_problem/input_data`. They are for reference only, yet it is an example of the real data. + Located here: `@_docs/00_problem/input_data`. They are for reference only, yet it is an example of the real data. ## Restrictions for the input data - `@docs/00_problem/restrictions.md`. + `@_docs/00_problem/restrictions.md`. ## Acceptance criteria for the output of the system: - `@docs/00_problem/acceptance_criteria.md`. + `@_docs/00_problem/acceptance_criteria.md`. ## Existing solution: - `@docs/01_solution/solution.md` + `@_docs/01_solution/solution.md` ## Role You are a professional software architect and developer ## Task - - Read carefully component spec in the component_folder: `@docs/02_components/[##]_[component_name]/[##]._component_[component_name]` - - Read carefully all the component features in the component_folder: `@docs/02_components/[##]_[component_name]/[##].[##]_feature_[feature_name]` + - Read carefully component spec in the component_folder: `@_docs/02_components/[##]_[component_name]/[##]._component_[component_name]` + - Read carefully all the component features in the component_folder: `@_docs/02_components/[##]_[component_name]/[##].[##]_feature_[feature_name]` - Investgate in internet what are the best way and tools to implement component and its features - During the investigation is is possible that found solutions required architecturally reorganization of the features. It is ok, propose that and if user agrees, include reorganization in the build feature plan. Also it is possible that interface could be changed or even removed or added new one. It is ok. - Make sure feature is connected and communicated properly with other features and existing code diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e4f0cd7 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# Database Configuration +DATABASE_HOST=localhost +DATABASE_PORT=5432 +DATABASE_NAME=gps_denied +DATABASE_USER=postgres +DATABASE_PASSWORD= + +# API Configuration +API_HOST=0.0.0.0 +API_PORT=8000 +API_DEBUG=false + +# Model Paths +SUPERPOINT_MODEL_PATH=models/superpoint.engine +LIGHTGLUE_MODEL_PATH=models/lightglue.engine +DINOV2_MODEL_PATH=models/dinov2.engine +LITESAM_MODEL_PATH=models/litesam.engine + +# Satellite Data +SATELLITE_CACHE_DIR=satellite_cache +GOOGLE_MAPS_API_KEY= diff --git a/.gitignore b/.gitignore index 4befed3..69a3101 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,47 @@ .DS_Store .idea + +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +.env +.venv +env/ +venv/ +ENV/ + +*.swp +*.swo +*~ + +*.log +*.sqlite +*.db + +satellite_cache/ +image_storage/ +models/*.engine +models/*.onnx + +.coverage +htmlcov/ +.pytest_cache/ +.mypy_cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..deb202a --- /dev/null +++ b/README.md @@ -0,0 +1,169 @@ +# Azaion GPS Denied Desktop + +**A Resilient, GNSS-Denied Geo-Localization System for Wing-Type UAVs** + +GPS-denied UAV localization system using visual odometry and satellite imagery matching for fixed-wing UAVs operating over Eastern/Southern Ukraine. + +## Overview + +Azaion GPS-Denied addresses the challenge of autonomous navigation in GNSS-denied environments where traditional GPS is unavailable or unreliable. The system is designed for high-speed, fixed-wing UAVs operating without IMU data over visually homogeneous agricultural terrain. + +### Key Features + +- **Tri-Layer Localization**: Sequential tracking, global re-localization, and metric refinement +- **Sharp Turn Recovery**: Handles 0% image overlap during banking maneuvers +- **350m Outlier Tolerance**: Robust to large positional errors from airframe tilt +- **Real-time Processing**: <5 seconds per frame on RTX 2060/3070 +- **Human-in-the-Loop**: Fallback to user input when automation fails + +### Accuracy Targets + +| Metric | Target | +|--------|--------| +| Photos within 50m error | 80% | +| Photos within 20m error | 60% | +| Processing time per frame | <5 seconds | +| Image registration rate | >95% | + +## Architecture + +### Processing Layers + +| Layer | Purpose | Algorithm | Latency | +|-------|---------|-----------|---------| +| L1: Sequential Tracking | Frame-to-frame pose | SuperPoint + LightGlue | ~50-100ms | +| L2: Global Re-Localization | Recovery after track loss | DINOv2 + VLAD (AnyLoc) | ~200ms | +| L3: Metric Refinement | Precise GPS anchoring | LiteSAM | ~300-500ms | + +### Core Components + +- **Factor Graph Optimizer** (GTSAM): Fuses relative and absolute measurements +- **Atlas Multi-Map**: Route chunks as first-class entities for handling disconnected segments +- **Satellite Data Manager**: Google Maps tile caching with Web Mercator projection + +## Tech Stack + +- **Python**: 3.10+ (GTSAM compatibility) +- **Web Framework**: FastAPI (async) +- **Database**: PostgreSQL + SQLAlchemy ORM +- **ML Runtime**: TensorRT (primary), ONNX Runtime (fallback) +- **Graph Optimization**: GTSAM +- **Similarity Search**: Faiss + +## Development Setup + +### Prerequisites + +- Python 3.10+ +- PostgreSQL 14+ +- NVIDIA GPU with CUDA support (RTX 2060/3070 recommended) +- [uv](https://docs.astral.sh/uv/) package manager + +### Installation + +1. **Clone the repository** + ```bash + git clone + cd gps-denied + ``` + +2. **Install uv** (if not already installed) + ```bash + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` + +3. **Create virtual environment and install dependencies** + ```bash + uv venv + source .venv/bin/activate # On Windows: .venv\Scripts\activate + uv pip install -e ".[dev]" + ``` + +4. **Install ML dependencies** (optional, requires CUDA) + ```bash + uv pip install -e ".[ml]" + ``` + +5. **Set up PostgreSQL database** + ```bash + createdb gps_denied + ``` + +6. **Configure environment** + ```bash + cp .env.example .env + # Edit .env with your database credentials + ``` + +### Running the Service + +```bash +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +### API Documentation + +Once running, access the interactive API docs at: +- Swagger UI: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc + +### Running Tests + +```bash +pytest +``` + +With coverage: +```bash +pytest --cov=. --cov-report=html +``` + +## API Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/v1/flights` | Create new flight | +| GET | `/api/v1/flights/{id}` | Get flight details | +| GET | `/api/v1/flights/{id}/status` | Get processing status | +| POST | `/api/v1/flights/{id}/batches` | Upload image batch | +| POST | `/api/v1/flights/{id}/user-fix` | Submit user GPS anchor | +| GET | `/api/v1/stream/{id}` | SSE stream for real-time results | + +## Project Structure + +``` +gps-denied/ +├── main.py # FastAPI application entry point +├── pyproject.toml # Project dependencies +├── api/ # REST API routes +│ ├── routes/ +│ │ ├── flights.py # Flight management endpoints +│ │ ├── images.py # Image upload endpoints +│ │ └── stream.py # SSE streaming +│ └── dependencies.py # Dependency injection +├── components/ # Core processing components +│ ├── flight_api/ # API layer component +│ ├── flight_processing_engine/ +│ ├── sequential_visual_odometry/ +│ ├── global_place_recognition/ +│ ├── metric_refinement/ +│ ├── factor_graph_optimizer/ +│ ├── satellite_data_manager/ +│ └── ... +├── models/ # Pydantic DTOs +│ ├── core/ # GPS, Camera, Pose models +│ ├── flight/ # Flight, Waypoint models +│ ├── processing/ # VO, Matching results +│ ├── chunks/ # Route chunk models +│ └── ... +├── helpers/ # Utility functions +├── db/ # Database layer +│ ├── models.py # SQLAlchemy models +│ └── connection.py # Async database connection +└── _docs/ # Documentation +``` + +## License + +Proprietary - All rights reserved + diff --git a/docs/00_problem/1.2_research_prompt.md b/_docs/00_problem/1.2_research_prompt.md similarity index 100% rename from docs/00_problem/1.2_research_prompt.md rename to _docs/00_problem/1.2_research_prompt.md diff --git a/docs/00_problem/1.3_01_assesment_prompt.md b/_docs/00_problem/1.3_01_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_01_assesment_prompt.md rename to _docs/00_problem/1.3_01_assesment_prompt.md diff --git a/docs/00_problem/1.3_02_assesment_prompt.md b/_docs/00_problem/1.3_02_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_02_assesment_prompt.md rename to _docs/00_problem/1.3_02_assesment_prompt.md diff --git a/docs/00_problem/1.3_03_assesment_prompt.md b/_docs/00_problem/1.3_03_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_03_assesment_prompt.md rename to _docs/00_problem/1.3_03_assesment_prompt.md diff --git a/docs/00_problem/1.3_04_assesment_prompt.md b/_docs/00_problem/1.3_04_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_04_assesment_prompt.md rename to _docs/00_problem/1.3_04_assesment_prompt.md diff --git a/docs/00_problem/1.3_05.1_assesment_prompt.md b/_docs/00_problem/1.3_05.1_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_05.1_assesment_prompt.md rename to _docs/00_problem/1.3_05.1_assesment_prompt.md diff --git a/docs/00_problem/1.3_05_assesment_prompt.md b/_docs/00_problem/1.3_05_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_05_assesment_prompt.md rename to _docs/00_problem/1.3_05_assesment_prompt.md diff --git a/docs/00_problem/1.3_06_assesment_prompt.md b/_docs/00_problem/1.3_06_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_06_assesment_prompt.md rename to _docs/00_problem/1.3_06_assesment_prompt.md diff --git a/docs/00_problem/1.3_07_assesment_prompt.md b/_docs/00_problem/1.3_07_assesment_prompt.md similarity index 100% rename from docs/00_problem/1.3_07_assesment_prompt.md rename to _docs/00_problem/1.3_07_assesment_prompt.md diff --git a/docs/00_problem/acceptance_criteria.md b/_docs/00_problem/acceptance_criteria.md similarity index 100% rename from docs/00_problem/acceptance_criteria.md rename to _docs/00_problem/acceptance_criteria.md diff --git a/docs/00_problem/input_data/AD000001.jpg b/_docs/00_problem/input_data/AD000001.jpg similarity index 100% rename from docs/00_problem/input_data/AD000001.jpg rename to _docs/00_problem/input_data/AD000001.jpg diff --git a/docs/00_problem/input_data/AD000001_gmaps.png b/_docs/00_problem/input_data/AD000001_gmaps.png similarity index 100% rename from docs/00_problem/input_data/AD000001_gmaps.png rename to _docs/00_problem/input_data/AD000001_gmaps.png diff --git a/docs/00_problem/input_data/AD000002.jpg b/_docs/00_problem/input_data/AD000002.jpg similarity index 100% rename from docs/00_problem/input_data/AD000002.jpg rename to _docs/00_problem/input_data/AD000002.jpg diff --git a/docs/00_problem/input_data/AD000002_gmaps.png b/_docs/00_problem/input_data/AD000002_gmaps.png similarity index 100% rename from docs/00_problem/input_data/AD000002_gmaps.png rename to _docs/00_problem/input_data/AD000002_gmaps.png diff --git a/docs/00_problem/input_data/AD000003.jpg b/_docs/00_problem/input_data/AD000003.jpg similarity index 100% rename from docs/00_problem/input_data/AD000003.jpg rename to _docs/00_problem/input_data/AD000003.jpg diff --git a/docs/00_problem/input_data/AD000004.jpg b/_docs/00_problem/input_data/AD000004.jpg similarity index 100% rename from docs/00_problem/input_data/AD000004.jpg rename to _docs/00_problem/input_data/AD000004.jpg diff --git a/docs/00_problem/input_data/AD000005.jpg b/_docs/00_problem/input_data/AD000005.jpg similarity index 100% rename from docs/00_problem/input_data/AD000005.jpg rename to _docs/00_problem/input_data/AD000005.jpg diff --git a/docs/00_problem/input_data/AD000006.jpg b/_docs/00_problem/input_data/AD000006.jpg similarity index 100% rename from docs/00_problem/input_data/AD000006.jpg rename to _docs/00_problem/input_data/AD000006.jpg diff --git a/docs/00_problem/input_data/AD000007.jpg b/_docs/00_problem/input_data/AD000007.jpg similarity index 100% rename from docs/00_problem/input_data/AD000007.jpg rename to _docs/00_problem/input_data/AD000007.jpg diff --git a/docs/00_problem/input_data/AD000008.jpg b/_docs/00_problem/input_data/AD000008.jpg similarity index 100% rename from docs/00_problem/input_data/AD000008.jpg rename to _docs/00_problem/input_data/AD000008.jpg diff --git a/docs/00_problem/input_data/AD000009.jpg b/_docs/00_problem/input_data/AD000009.jpg similarity index 100% rename from docs/00_problem/input_data/AD000009.jpg rename to _docs/00_problem/input_data/AD000009.jpg diff --git a/docs/00_problem/input_data/AD000010.jpg b/_docs/00_problem/input_data/AD000010.jpg similarity index 100% rename from docs/00_problem/input_data/AD000010.jpg rename to _docs/00_problem/input_data/AD000010.jpg diff --git a/docs/00_problem/input_data/AD000011.jpg b/_docs/00_problem/input_data/AD000011.jpg similarity index 100% rename from docs/00_problem/input_data/AD000011.jpg rename to _docs/00_problem/input_data/AD000011.jpg diff --git a/docs/00_problem/input_data/AD000012.jpg b/_docs/00_problem/input_data/AD000012.jpg similarity index 100% rename from docs/00_problem/input_data/AD000012.jpg rename to _docs/00_problem/input_data/AD000012.jpg diff --git a/docs/00_problem/input_data/AD000013.jpg b/_docs/00_problem/input_data/AD000013.jpg similarity index 100% rename from docs/00_problem/input_data/AD000013.jpg rename to _docs/00_problem/input_data/AD000013.jpg diff --git a/docs/00_problem/input_data/AD000014.jpg b/_docs/00_problem/input_data/AD000014.jpg similarity index 100% rename from docs/00_problem/input_data/AD000014.jpg rename to _docs/00_problem/input_data/AD000014.jpg diff --git a/docs/00_problem/input_data/AD000015.jpg b/_docs/00_problem/input_data/AD000015.jpg similarity index 100% rename from docs/00_problem/input_data/AD000015.jpg rename to _docs/00_problem/input_data/AD000015.jpg diff --git a/docs/00_problem/input_data/AD000016.jpg b/_docs/00_problem/input_data/AD000016.jpg similarity index 100% rename from docs/00_problem/input_data/AD000016.jpg rename to _docs/00_problem/input_data/AD000016.jpg diff --git a/docs/00_problem/input_data/AD000017.jpg b/_docs/00_problem/input_data/AD000017.jpg similarity index 100% rename from docs/00_problem/input_data/AD000017.jpg rename to _docs/00_problem/input_data/AD000017.jpg diff --git a/docs/00_problem/input_data/AD000018.jpg b/_docs/00_problem/input_data/AD000018.jpg similarity index 100% rename from docs/00_problem/input_data/AD000018.jpg rename to _docs/00_problem/input_data/AD000018.jpg diff --git a/docs/00_problem/input_data/AD000019.jpg b/_docs/00_problem/input_data/AD000019.jpg similarity index 100% rename from docs/00_problem/input_data/AD000019.jpg rename to _docs/00_problem/input_data/AD000019.jpg diff --git a/docs/00_problem/input_data/AD000020.jpg b/_docs/00_problem/input_data/AD000020.jpg similarity index 100% rename from docs/00_problem/input_data/AD000020.jpg rename to _docs/00_problem/input_data/AD000020.jpg diff --git a/docs/00_problem/input_data/AD000021.jpg b/_docs/00_problem/input_data/AD000021.jpg similarity index 100% rename from docs/00_problem/input_data/AD000021.jpg rename to _docs/00_problem/input_data/AD000021.jpg diff --git a/docs/00_problem/input_data/AD000022.jpg b/_docs/00_problem/input_data/AD000022.jpg similarity index 100% rename from docs/00_problem/input_data/AD000022.jpg rename to _docs/00_problem/input_data/AD000022.jpg diff --git a/docs/00_problem/input_data/AD000023.jpg b/_docs/00_problem/input_data/AD000023.jpg similarity index 100% rename from docs/00_problem/input_data/AD000023.jpg rename to _docs/00_problem/input_data/AD000023.jpg diff --git a/docs/00_problem/input_data/AD000024.jpg b/_docs/00_problem/input_data/AD000024.jpg similarity index 100% rename from docs/00_problem/input_data/AD000024.jpg rename to _docs/00_problem/input_data/AD000024.jpg diff --git a/docs/00_problem/input_data/AD000025.jpg b/_docs/00_problem/input_data/AD000025.jpg similarity index 100% rename from docs/00_problem/input_data/AD000025.jpg rename to _docs/00_problem/input_data/AD000025.jpg diff --git a/docs/00_problem/input_data/AD000026.jpg b/_docs/00_problem/input_data/AD000026.jpg similarity index 100% rename from docs/00_problem/input_data/AD000026.jpg rename to _docs/00_problem/input_data/AD000026.jpg diff --git a/docs/00_problem/input_data/AD000027.jpg b/_docs/00_problem/input_data/AD000027.jpg similarity index 100% rename from docs/00_problem/input_data/AD000027.jpg rename to _docs/00_problem/input_data/AD000027.jpg diff --git a/docs/00_problem/input_data/AD000028.jpg b/_docs/00_problem/input_data/AD000028.jpg similarity index 100% rename from docs/00_problem/input_data/AD000028.jpg rename to _docs/00_problem/input_data/AD000028.jpg diff --git a/docs/00_problem/input_data/AD000029.jpg b/_docs/00_problem/input_data/AD000029.jpg similarity index 100% rename from docs/00_problem/input_data/AD000029.jpg rename to _docs/00_problem/input_data/AD000029.jpg diff --git a/docs/00_problem/input_data/AD000030.jpg b/_docs/00_problem/input_data/AD000030.jpg similarity index 100% rename from docs/00_problem/input_data/AD000030.jpg rename to _docs/00_problem/input_data/AD000030.jpg diff --git a/docs/00_problem/input_data/AD000031.jpg b/_docs/00_problem/input_data/AD000031.jpg similarity index 100% rename from docs/00_problem/input_data/AD000031.jpg rename to _docs/00_problem/input_data/AD000031.jpg diff --git a/docs/00_problem/input_data/AD000032.jpg b/_docs/00_problem/input_data/AD000032.jpg similarity index 100% rename from docs/00_problem/input_data/AD000032.jpg rename to _docs/00_problem/input_data/AD000032.jpg diff --git a/docs/00_problem/input_data/AD000033.jpg b/_docs/00_problem/input_data/AD000033.jpg similarity index 100% rename from docs/00_problem/input_data/AD000033.jpg rename to _docs/00_problem/input_data/AD000033.jpg diff --git a/docs/00_problem/input_data/AD000034.jpg b/_docs/00_problem/input_data/AD000034.jpg similarity index 100% rename from docs/00_problem/input_data/AD000034.jpg rename to _docs/00_problem/input_data/AD000034.jpg diff --git a/docs/00_problem/input_data/AD000035.jpg b/_docs/00_problem/input_data/AD000035.jpg similarity index 100% rename from docs/00_problem/input_data/AD000035.jpg rename to _docs/00_problem/input_data/AD000035.jpg diff --git a/docs/00_problem/input_data/AD000036.jpg b/_docs/00_problem/input_data/AD000036.jpg similarity index 100% rename from docs/00_problem/input_data/AD000036.jpg rename to _docs/00_problem/input_data/AD000036.jpg diff --git a/docs/00_problem/input_data/AD000037.jpg b/_docs/00_problem/input_data/AD000037.jpg similarity index 100% rename from docs/00_problem/input_data/AD000037.jpg rename to _docs/00_problem/input_data/AD000037.jpg diff --git a/docs/00_problem/input_data/AD000038.jpg b/_docs/00_problem/input_data/AD000038.jpg similarity index 100% rename from docs/00_problem/input_data/AD000038.jpg rename to _docs/00_problem/input_data/AD000038.jpg diff --git a/docs/00_problem/input_data/AD000039.jpg b/_docs/00_problem/input_data/AD000039.jpg similarity index 100% rename from docs/00_problem/input_data/AD000039.jpg rename to _docs/00_problem/input_data/AD000039.jpg diff --git a/docs/00_problem/input_data/AD000040.jpg b/_docs/00_problem/input_data/AD000040.jpg similarity index 100% rename from docs/00_problem/input_data/AD000040.jpg rename to _docs/00_problem/input_data/AD000040.jpg diff --git a/docs/00_problem/input_data/AD000041.jpg b/_docs/00_problem/input_data/AD000041.jpg similarity index 100% rename from docs/00_problem/input_data/AD000041.jpg rename to _docs/00_problem/input_data/AD000041.jpg diff --git a/docs/00_problem/input_data/AD000042.jpg b/_docs/00_problem/input_data/AD000042.jpg similarity index 100% rename from docs/00_problem/input_data/AD000042.jpg rename to _docs/00_problem/input_data/AD000042.jpg diff --git a/docs/00_problem/input_data/AD000043.jpg b/_docs/00_problem/input_data/AD000043.jpg similarity index 100% rename from docs/00_problem/input_data/AD000043.jpg rename to _docs/00_problem/input_data/AD000043.jpg diff --git a/docs/00_problem/input_data/AD000044.jpg b/_docs/00_problem/input_data/AD000044.jpg similarity index 100% rename from docs/00_problem/input_data/AD000044.jpg rename to _docs/00_problem/input_data/AD000044.jpg diff --git a/docs/00_problem/input_data/AD000045.jpg b/_docs/00_problem/input_data/AD000045.jpg similarity index 100% rename from docs/00_problem/input_data/AD000045.jpg rename to _docs/00_problem/input_data/AD000045.jpg diff --git a/docs/00_problem/input_data/AD000046.jpg b/_docs/00_problem/input_data/AD000046.jpg similarity index 100% rename from docs/00_problem/input_data/AD000046.jpg rename to _docs/00_problem/input_data/AD000046.jpg diff --git a/docs/00_problem/input_data/AD000047.jpg b/_docs/00_problem/input_data/AD000047.jpg similarity index 100% rename from docs/00_problem/input_data/AD000047.jpg rename to _docs/00_problem/input_data/AD000047.jpg diff --git a/docs/00_problem/input_data/AD000048.jpg b/_docs/00_problem/input_data/AD000048.jpg similarity index 100% rename from docs/00_problem/input_data/AD000048.jpg rename to _docs/00_problem/input_data/AD000048.jpg diff --git a/docs/00_problem/input_data/AD000049.jpg b/_docs/00_problem/input_data/AD000049.jpg similarity index 100% rename from docs/00_problem/input_data/AD000049.jpg rename to _docs/00_problem/input_data/AD000049.jpg diff --git a/docs/00_problem/input_data/AD000050.jpg b/_docs/00_problem/input_data/AD000050.jpg similarity index 100% rename from docs/00_problem/input_data/AD000050.jpg rename to _docs/00_problem/input_data/AD000050.jpg diff --git a/docs/00_problem/input_data/AD000051.jpg b/_docs/00_problem/input_data/AD000051.jpg similarity index 100% rename from docs/00_problem/input_data/AD000051.jpg rename to _docs/00_problem/input_data/AD000051.jpg diff --git a/docs/00_problem/input_data/AD000052.jpg b/_docs/00_problem/input_data/AD000052.jpg similarity index 100% rename from docs/00_problem/input_data/AD000052.jpg rename to _docs/00_problem/input_data/AD000052.jpg diff --git a/docs/00_problem/input_data/AD000053.jpg b/_docs/00_problem/input_data/AD000053.jpg similarity index 100% rename from docs/00_problem/input_data/AD000053.jpg rename to _docs/00_problem/input_data/AD000053.jpg diff --git a/docs/00_problem/input_data/AD000054.jpg b/_docs/00_problem/input_data/AD000054.jpg similarity index 100% rename from docs/00_problem/input_data/AD000054.jpg rename to _docs/00_problem/input_data/AD000054.jpg diff --git a/docs/00_problem/input_data/AD000055.jpg b/_docs/00_problem/input_data/AD000055.jpg similarity index 100% rename from docs/00_problem/input_data/AD000055.jpg rename to _docs/00_problem/input_data/AD000055.jpg diff --git a/docs/00_problem/input_data/AD000056.jpg b/_docs/00_problem/input_data/AD000056.jpg similarity index 100% rename from docs/00_problem/input_data/AD000056.jpg rename to _docs/00_problem/input_data/AD000056.jpg diff --git a/docs/00_problem/input_data/AD000057.jpg b/_docs/00_problem/input_data/AD000057.jpg similarity index 100% rename from docs/00_problem/input_data/AD000057.jpg rename to _docs/00_problem/input_data/AD000057.jpg diff --git a/docs/00_problem/input_data/AD000058.jpg b/_docs/00_problem/input_data/AD000058.jpg similarity index 100% rename from docs/00_problem/input_data/AD000058.jpg rename to _docs/00_problem/input_data/AD000058.jpg diff --git a/docs/00_problem/input_data/AD000059.jpg b/_docs/00_problem/input_data/AD000059.jpg similarity index 100% rename from docs/00_problem/input_data/AD000059.jpg rename to _docs/00_problem/input_data/AD000059.jpg diff --git a/docs/00_problem/input_data/AD000060.jpg b/_docs/00_problem/input_data/AD000060.jpg similarity index 100% rename from docs/00_problem/input_data/AD000060.jpg rename to _docs/00_problem/input_data/AD000060.jpg diff --git a/docs/00_problem/input_data/coordinates.csv b/_docs/00_problem/input_data/coordinates.csv similarity index 100% rename from docs/00_problem/input_data/coordinates.csv rename to _docs/00_problem/input_data/coordinates.csv diff --git a/docs/00_problem/input_data/data_parameters.md b/_docs/00_problem/input_data/data_parameters.md similarity index 100% rename from docs/00_problem/input_data/data_parameters.md rename to _docs/00_problem/input_data/data_parameters.md diff --git a/docs/00_problem/problem_description.md b/_docs/00_problem/problem_description.md similarity index 100% rename from docs/00_problem/problem_description.md rename to _docs/00_problem/problem_description.md diff --git a/docs/00_problem/restrictions.md b/_docs/00_problem/restrictions.md similarity index 100% rename from docs/00_problem/restrictions.md rename to _docs/00_problem/restrictions.md diff --git a/docs/01_solution/01_solution_draft.md b/_docs/01_solution/01_solution_draft.md similarity index 100% rename from docs/01_solution/01_solution_draft.md rename to _docs/01_solution/01_solution_draft.md diff --git a/docs/01_solution/02_solution_draft.md b/_docs/01_solution/02_solution_draft.md similarity index 100% rename from docs/01_solution/02_solution_draft.md rename to _docs/01_solution/02_solution_draft.md diff --git a/docs/01_solution/03_solution_draft.md b/_docs/01_solution/03_solution_draft.md similarity index 100% rename from docs/01_solution/03_solution_draft.md rename to _docs/01_solution/03_solution_draft.md diff --git a/docs/01_solution/04_solution_draft.md b/_docs/01_solution/04_solution_draft.md similarity index 100% rename from docs/01_solution/04_solution_draft.md rename to _docs/01_solution/04_solution_draft.md diff --git a/docs/01_solution/05_solution_draft.md b/_docs/01_solution/05_solution_draft.md similarity index 100% rename from docs/01_solution/05_solution_draft.md rename to _docs/01_solution/05_solution_draft.md diff --git a/docs/01_solution/06_solution_draft.md b/_docs/01_solution/06_solution_draft.md similarity index 100% rename from docs/01_solution/06_solution_draft.md rename to _docs/01_solution/06_solution_draft.md diff --git a/docs/01_solution/solution.md b/_docs/01_solution/solution.md similarity index 100% rename from docs/01_solution/solution.md rename to _docs/01_solution/solution.md diff --git a/docs/02_components/01_flight_api/01.01_feature_flight_management.md b/_docs/02_components/01_flight_api/01.01_feature_flight_management.md similarity index 100% rename from docs/02_components/01_flight_api/01.01_feature_flight_management.md rename to _docs/02_components/01_flight_api/01.01_feature_flight_management.md diff --git a/docs/02_components/01_flight_api/01.02_feature_image_upload.md b/_docs/02_components/01_flight_api/01.02_feature_image_upload.md similarity index 100% rename from docs/02_components/01_flight_api/01.02_feature_image_upload.md rename to _docs/02_components/01_flight_api/01.02_feature_image_upload.md diff --git a/docs/02_components/01_flight_api/01.03_feature_user_interaction.md b/_docs/02_components/01_flight_api/01.03_feature_user_interaction.md similarity index 100% rename from docs/02_components/01_flight_api/01.03_feature_user_interaction.md rename to _docs/02_components/01_flight_api/01.03_feature_user_interaction.md diff --git a/docs/02_components/01_flight_api/01.04_feature_sse_streaming.md b/_docs/02_components/01_flight_api/01.04_feature_sse_streaming.md similarity index 100% rename from docs/02_components/01_flight_api/01.04_feature_sse_streaming.md rename to _docs/02_components/01_flight_api/01.04_feature_sse_streaming.md diff --git a/docs/02_components/01_flight_api/01._component_light_api.md b/_docs/02_components/01_flight_api/01._component_light_api.md similarity index 100% rename from docs/02_components/01_flight_api/01._component_light_api.md rename to _docs/02_components/01_flight_api/01._component_light_api.md diff --git a/docs/02_components/02_flight_processor/02.1.01_feature_flight_waypoint_management.md b/_docs/02_components/02_flight_processor/02.1.01_feature_flight_waypoint_management.md similarity index 100% rename from docs/02_components/02_flight_processor/02.1.01_feature_flight_waypoint_management.md rename to _docs/02_components/02_flight_processor/02.1.01_feature_flight_waypoint_management.md diff --git a/docs/02_components/02_flight_processor/02.1.02_feature_processing_delegation.md b/_docs/02_components/02_flight_processor/02.1.02_feature_processing_delegation.md similarity index 100% rename from docs/02_components/02_flight_processor/02.1.02_feature_processing_delegation.md rename to _docs/02_components/02_flight_processor/02.1.02_feature_processing_delegation.md diff --git a/docs/02_components/02_flight_processor/02.1.03_feature_system_initialization.md b/_docs/02_components/02_flight_processor/02.1.03_feature_system_initialization.md similarity index 100% rename from docs/02_components/02_flight_processor/02.1.03_feature_system_initialization.md rename to _docs/02_components/02_flight_processor/02.1.03_feature_system_initialization.md diff --git a/docs/02_components/02_flight_processor/02.1._component_flight_lifecycle_manager.md b/_docs/02_components/02_flight_processor/02.1._component_flight_lifecycle_manager.md similarity index 100% rename from docs/02_components/02_flight_processor/02.1._component_flight_lifecycle_manager.md rename to _docs/02_components/02_flight_processor/02.1._component_flight_lifecycle_manager.md diff --git a/docs/02_components/02_flight_processor/02.2.01_feature_frame_processing_loop.md b/_docs/02_components/02_flight_processor/02.2.01_feature_frame_processing_loop.md similarity index 100% rename from docs/02_components/02_flight_processor/02.2.01_feature_frame_processing_loop.md rename to _docs/02_components/02_flight_processor/02.2.01_feature_frame_processing_loop.md diff --git a/docs/02_components/02_flight_processor/02.2.02_feature_tracking_loss_recovery.md b/_docs/02_components/02_flight_processor/02.2.02_feature_tracking_loss_recovery.md similarity index 100% rename from docs/02_components/02_flight_processor/02.2.02_feature_tracking_loss_recovery.md rename to _docs/02_components/02_flight_processor/02.2.02_feature_tracking_loss_recovery.md diff --git a/docs/02_components/02_flight_processor/02.2.03_feature_chunk_lifecycle_orchestration.md b/_docs/02_components/02_flight_processor/02.2.03_feature_chunk_lifecycle_orchestration.md similarity index 100% rename from docs/02_components/02_flight_processor/02.2.03_feature_chunk_lifecycle_orchestration.md rename to _docs/02_components/02_flight_processor/02.2.03_feature_chunk_lifecycle_orchestration.md diff --git a/docs/02_components/02_flight_processor/02.2._spec_flight_processing_engine_spec.md b/_docs/02_components/02_flight_processor/02.2._spec_flight_processing_engine_spec.md similarity index 100% rename from docs/02_components/02_flight_processor/02.2._spec_flight_processing_engine_spec.md rename to _docs/02_components/02_flight_processor/02.2._spec_flight_processing_engine_spec.md diff --git a/docs/02_components/03_flight_database/03.01_feature_flight_crud_operations.md b/_docs/02_components/03_flight_database/03.01_feature_flight_crud_operations.md similarity index 100% rename from docs/02_components/03_flight_database/03.01_feature_flight_crud_operations.md rename to _docs/02_components/03_flight_database/03.01_feature_flight_crud_operations.md diff --git a/docs/02_components/03_flight_database/03.02_feature_processing_state_persistence.md b/_docs/02_components/03_flight_database/03.02_feature_processing_state_persistence.md similarity index 100% rename from docs/02_components/03_flight_database/03.02_feature_processing_state_persistence.md rename to _docs/02_components/03_flight_database/03.02_feature_processing_state_persistence.md diff --git a/docs/02_components/03_flight_database/03.03_feature_auxiliary_data_persistence.md b/_docs/02_components/03_flight_database/03.03_feature_auxiliary_data_persistence.md similarity index 100% rename from docs/02_components/03_flight_database/03.03_feature_auxiliary_data_persistence.md rename to _docs/02_components/03_flight_database/03.03_feature_auxiliary_data_persistence.md diff --git a/docs/02_components/03_flight_database/03._component_flight_database.md b/_docs/02_components/03_flight_database/03._component_flight_database.md similarity index 100% rename from docs/02_components/03_flight_database/03._component_flight_database.md rename to _docs/02_components/03_flight_database/03._component_flight_database.md diff --git a/docs/02_components/04_satellite_data_manager/04.01_feature_tile_cache_management.md b/_docs/02_components/04_satellite_data_manager/04.01_feature_tile_cache_management.md similarity index 100% rename from docs/02_components/04_satellite_data_manager/04.01_feature_tile_cache_management.md rename to _docs/02_components/04_satellite_data_manager/04.01_feature_tile_cache_management.md diff --git a/docs/02_components/04_satellite_data_manager/04.02_feature_tile_coordinate_operations.md b/_docs/02_components/04_satellite_data_manager/04.02_feature_tile_coordinate_operations.md similarity index 100% rename from docs/02_components/04_satellite_data_manager/04.02_feature_tile_coordinate_operations.md rename to _docs/02_components/04_satellite_data_manager/04.02_feature_tile_coordinate_operations.md diff --git a/docs/02_components/04_satellite_data_manager/04.03_feature_tile_fetching.md b/_docs/02_components/04_satellite_data_manager/04.03_feature_tile_fetching.md similarity index 100% rename from docs/02_components/04_satellite_data_manager/04.03_feature_tile_fetching.md rename to _docs/02_components/04_satellite_data_manager/04.03_feature_tile_fetching.md diff --git a/docs/02_components/04_satellite_data_manager/04._component_satellite_data_manager.md b/_docs/02_components/04_satellite_data_manager/04._component_satellite_data_manager.md similarity index 100% rename from docs/02_components/04_satellite_data_manager/04._component_satellite_data_manager.md rename to _docs/02_components/04_satellite_data_manager/04._component_satellite_data_manager.md diff --git a/docs/02_components/05_image_input_pipeline/05.01_feature_batch_queue_management.md b/_docs/02_components/05_image_input_pipeline/05.01_feature_batch_queue_management.md similarity index 100% rename from docs/02_components/05_image_input_pipeline/05.01_feature_batch_queue_management.md rename to _docs/02_components/05_image_input_pipeline/05.01_feature_batch_queue_management.md diff --git a/docs/02_components/05_image_input_pipeline/05.02_feature_image_storage_retrieval.md b/_docs/02_components/05_image_input_pipeline/05.02_feature_image_storage_retrieval.md similarity index 100% rename from docs/02_components/05_image_input_pipeline/05.02_feature_image_storage_retrieval.md rename to _docs/02_components/05_image_input_pipeline/05.02_feature_image_storage_retrieval.md diff --git a/docs/02_components/05_image_input_pipeline/05._component_image_input_pipeline.md b/_docs/02_components/05_image_input_pipeline/05._component_image_input_pipeline.md similarity index 100% rename from docs/02_components/05_image_input_pipeline/05._component_image_input_pipeline.md rename to _docs/02_components/05_image_input_pipeline/05._component_image_input_pipeline.md diff --git a/docs/02_components/06_image_rotation_manager/06.01_feature_image_rotation_core.md b/_docs/02_components/06_image_rotation_manager/06.01_feature_image_rotation_core.md similarity index 100% rename from docs/02_components/06_image_rotation_manager/06.01_feature_image_rotation_core.md rename to _docs/02_components/06_image_rotation_manager/06.01_feature_image_rotation_core.md diff --git a/docs/02_components/06_image_rotation_manager/06.02_feature_heading_management.md b/_docs/02_components/06_image_rotation_manager/06.02_feature_heading_management.md similarity index 100% rename from docs/02_components/06_image_rotation_manager/06.02_feature_heading_management.md rename to _docs/02_components/06_image_rotation_manager/06.02_feature_heading_management.md diff --git a/docs/02_components/06_image_rotation_manager/06.03_feature_rotation_sweep_orchestration.md b/_docs/02_components/06_image_rotation_manager/06.03_feature_rotation_sweep_orchestration.md similarity index 100% rename from docs/02_components/06_image_rotation_manager/06.03_feature_rotation_sweep_orchestration.md rename to _docs/02_components/06_image_rotation_manager/06.03_feature_rotation_sweep_orchestration.md diff --git a/docs/02_components/06_image_rotation_manager/06._component_image_rotation_manager.md b/_docs/02_components/06_image_rotation_manager/06._component_image_rotation_manager.md similarity index 100% rename from docs/02_components/06_image_rotation_manager/06._component_image_rotation_manager.md rename to _docs/02_components/06_image_rotation_manager/06._component_image_rotation_manager.md diff --git a/docs/02_components/07_sequential_visual_odometry/07.01_feature_combined_neural_inference.md b/_docs/02_components/07_sequential_visual_odometry/07.01_feature_combined_neural_inference.md similarity index 100% rename from docs/02_components/07_sequential_visual_odometry/07.01_feature_combined_neural_inference.md rename to _docs/02_components/07_sequential_visual_odometry/07.01_feature_combined_neural_inference.md diff --git a/docs/02_components/07_sequential_visual_odometry/07.02_feature_geometric_pose_estimation.md b/_docs/02_components/07_sequential_visual_odometry/07.02_feature_geometric_pose_estimation.md similarity index 100% rename from docs/02_components/07_sequential_visual_odometry/07.02_feature_geometric_pose_estimation.md rename to _docs/02_components/07_sequential_visual_odometry/07.02_feature_geometric_pose_estimation.md diff --git a/docs/02_components/07_sequential_visual_odometry/07._component_sequential_visual_odometry.md b/_docs/02_components/07_sequential_visual_odometry/07._component_sequential_visual_odometry.md similarity index 100% rename from docs/02_components/07_sequential_visual_odometry/07._component_sequential_visual_odometry.md rename to _docs/02_components/07_sequential_visual_odometry/07._component_sequential_visual_odometry.md diff --git a/docs/02_components/08_global_place_recognition/08.01_feature_index_management.md b/_docs/02_components/08_global_place_recognition/08.01_feature_index_management.md similarity index 100% rename from docs/02_components/08_global_place_recognition/08.01_feature_index_management.md rename to _docs/02_components/08_global_place_recognition/08.01_feature_index_management.md diff --git a/docs/02_components/08_global_place_recognition/08.02_feature_descriptor_computation.md b/_docs/02_components/08_global_place_recognition/08.02_feature_descriptor_computation.md similarity index 100% rename from docs/02_components/08_global_place_recognition/08.02_feature_descriptor_computation.md rename to _docs/02_components/08_global_place_recognition/08.02_feature_descriptor_computation.md diff --git a/docs/02_components/08_global_place_recognition/08.03_feature_candidate_retrieval.md b/_docs/02_components/08_global_place_recognition/08.03_feature_candidate_retrieval.md similarity index 100% rename from docs/02_components/08_global_place_recognition/08.03_feature_candidate_retrieval.md rename to _docs/02_components/08_global_place_recognition/08.03_feature_candidate_retrieval.md diff --git a/docs/02_components/08_global_place_recognition/08._component_global_place_recognition.md b/_docs/02_components/08_global_place_recognition/08._component_global_place_recognition.md similarity index 100% rename from docs/02_components/08_global_place_recognition/08._component_global_place_recognition.md rename to _docs/02_components/08_global_place_recognition/08._component_global_place_recognition.md diff --git a/docs/02_components/09_metric_refinement/09.01_feature_single_image_alignment.md b/_docs/02_components/09_metric_refinement/09.01_feature_single_image_alignment.md similarity index 100% rename from docs/02_components/09_metric_refinement/09.01_feature_single_image_alignment.md rename to _docs/02_components/09_metric_refinement/09.01_feature_single_image_alignment.md diff --git a/docs/02_components/09_metric_refinement/09.02_feature_chunk_alignment.md b/_docs/02_components/09_metric_refinement/09.02_feature_chunk_alignment.md similarity index 100% rename from docs/02_components/09_metric_refinement/09.02_feature_chunk_alignment.md rename to _docs/02_components/09_metric_refinement/09.02_feature_chunk_alignment.md diff --git a/docs/02_components/09_metric_refinement/09._component_metric_refinement.md b/_docs/02_components/09_metric_refinement/09._component_metric_refinement.md similarity index 100% rename from docs/02_components/09_metric_refinement/09._component_metric_refinement.md rename to _docs/02_components/09_metric_refinement/09._component_metric_refinement.md diff --git a/docs/02_components/10_factor_graph_optimizer/10.01_feature_core_factor_management.md b/_docs/02_components/10_factor_graph_optimizer/10.01_feature_core_factor_management.md similarity index 100% rename from docs/02_components/10_factor_graph_optimizer/10.01_feature_core_factor_management.md rename to _docs/02_components/10_factor_graph_optimizer/10.01_feature_core_factor_management.md diff --git a/docs/02_components/10_factor_graph_optimizer/10.02_feature_trajectory_optimization.md b/_docs/02_components/10_factor_graph_optimizer/10.02_feature_trajectory_optimization.md similarity index 100% rename from docs/02_components/10_factor_graph_optimizer/10.02_feature_trajectory_optimization.md rename to _docs/02_components/10_factor_graph_optimizer/10.02_feature_trajectory_optimization.md diff --git a/docs/02_components/10_factor_graph_optimizer/10.03_feature_chunk_subgraph_operations.md b/_docs/02_components/10_factor_graph_optimizer/10.03_feature_chunk_subgraph_operations.md similarity index 100% rename from docs/02_components/10_factor_graph_optimizer/10.03_feature_chunk_subgraph_operations.md rename to _docs/02_components/10_factor_graph_optimizer/10.03_feature_chunk_subgraph_operations.md diff --git a/docs/02_components/10_factor_graph_optimizer/10.04_feature_chunk_merging_global_optimization.md b/_docs/02_components/10_factor_graph_optimizer/10.04_feature_chunk_merging_global_optimization.md similarity index 100% rename from docs/02_components/10_factor_graph_optimizer/10.04_feature_chunk_merging_global_optimization.md rename to _docs/02_components/10_factor_graph_optimizer/10.04_feature_chunk_merging_global_optimization.md diff --git a/docs/02_components/10_factor_graph_optimizer/10.05_feature_multi_flight_graph_lifecycle.md b/_docs/02_components/10_factor_graph_optimizer/10.05_feature_multi_flight_graph_lifecycle.md similarity index 100% rename from docs/02_components/10_factor_graph_optimizer/10.05_feature_multi_flight_graph_lifecycle.md rename to _docs/02_components/10_factor_graph_optimizer/10.05_feature_multi_flight_graph_lifecycle.md diff --git a/docs/02_components/10_factor_graph_optimizer/10._component_factor_graph_optimizer.md b/_docs/02_components/10_factor_graph_optimizer/10._component_factor_graph_optimizer.md similarity index 100% rename from docs/02_components/10_factor_graph_optimizer/10._component_factor_graph_optimizer.md rename to _docs/02_components/10_factor_graph_optimizer/10._component_factor_graph_optimizer.md diff --git a/docs/02_components/11_failure_recovery_coordinator/11.01_feature_confidence_assessment.md b/_docs/02_components/11_failure_recovery_coordinator/11.01_feature_confidence_assessment.md similarity index 100% rename from docs/02_components/11_failure_recovery_coordinator/11.01_feature_confidence_assessment.md rename to _docs/02_components/11_failure_recovery_coordinator/11.01_feature_confidence_assessment.md diff --git a/docs/02_components/11_failure_recovery_coordinator/11.02_feature_progressive_search.md b/_docs/02_components/11_failure_recovery_coordinator/11.02_feature_progressive_search.md similarity index 100% rename from docs/02_components/11_failure_recovery_coordinator/11.02_feature_progressive_search.md rename to _docs/02_components/11_failure_recovery_coordinator/11.02_feature_progressive_search.md diff --git a/docs/02_components/11_failure_recovery_coordinator/11.03_feature_user_input_handling.md b/_docs/02_components/11_failure_recovery_coordinator/11.03_feature_user_input_handling.md similarity index 100% rename from docs/02_components/11_failure_recovery_coordinator/11.03_feature_user_input_handling.md rename to _docs/02_components/11_failure_recovery_coordinator/11.03_feature_user_input_handling.md diff --git a/docs/02_components/11_failure_recovery_coordinator/11.04_feature_chunk_recovery_coordination.md b/_docs/02_components/11_failure_recovery_coordinator/11.04_feature_chunk_recovery_coordination.md similarity index 100% rename from docs/02_components/11_failure_recovery_coordinator/11.04_feature_chunk_recovery_coordination.md rename to _docs/02_components/11_failure_recovery_coordinator/11.04_feature_chunk_recovery_coordination.md diff --git a/docs/02_components/11_failure_recovery_coordinator/11._component_failure_recovery_coordinator.md b/_docs/02_components/11_failure_recovery_coordinator/11._component_failure_recovery_coordinator.md similarity index 100% rename from docs/02_components/11_failure_recovery_coordinator/11._component_failure_recovery_coordinator.md rename to _docs/02_components/11_failure_recovery_coordinator/11._component_failure_recovery_coordinator.md diff --git a/docs/02_components/12_route_chunk_manager/12.01_feature_chunk_lifecycle_management.md b/_docs/02_components/12_route_chunk_manager/12.01_feature_chunk_lifecycle_management.md similarity index 100% rename from docs/02_components/12_route_chunk_manager/12.01_feature_chunk_lifecycle_management.md rename to _docs/02_components/12_route_chunk_manager/12.01_feature_chunk_lifecycle_management.md diff --git a/docs/02_components/12_route_chunk_manager/12.02_feature_chunk_data_retrieval.md b/_docs/02_components/12_route_chunk_manager/12.02_feature_chunk_data_retrieval.md similarity index 100% rename from docs/02_components/12_route_chunk_manager/12.02_feature_chunk_data_retrieval.md rename to _docs/02_components/12_route_chunk_manager/12.02_feature_chunk_data_retrieval.md diff --git a/docs/02_components/12_route_chunk_manager/12.03_feature_chunk_matching_coordination.md b/_docs/02_components/12_route_chunk_manager/12.03_feature_chunk_matching_coordination.md similarity index 100% rename from docs/02_components/12_route_chunk_manager/12.03_feature_chunk_matching_coordination.md rename to _docs/02_components/12_route_chunk_manager/12.03_feature_chunk_matching_coordination.md diff --git a/docs/02_components/12_route_chunk_manager/12.04_feature_chunk_state_persistence.md b/_docs/02_components/12_route_chunk_manager/12.04_feature_chunk_state_persistence.md similarity index 100% rename from docs/02_components/12_route_chunk_manager/12.04_feature_chunk_state_persistence.md rename to _docs/02_components/12_route_chunk_manager/12.04_feature_chunk_state_persistence.md diff --git a/docs/02_components/12_route_chunk_manager/12._component_route_chunk_manager.md b/_docs/02_components/12_route_chunk_manager/12._component_route_chunk_manager.md similarity index 100% rename from docs/02_components/12_route_chunk_manager/12._component_route_chunk_manager.md rename to _docs/02_components/12_route_chunk_manager/12._component_route_chunk_manager.md diff --git a/docs/02_components/13_coordinate_transformer/13.01_feature_enu_coordinate_management.md b/_docs/02_components/13_coordinate_transformer/13.01_feature_enu_coordinate_management.md similarity index 100% rename from docs/02_components/13_coordinate_transformer/13.01_feature_enu_coordinate_management.md rename to _docs/02_components/13_coordinate_transformer/13.01_feature_enu_coordinate_management.md diff --git a/docs/02_components/13_coordinate_transformer/13.02_feature_pixel_gps_projection.md b/_docs/02_components/13_coordinate_transformer/13.02_feature_pixel_gps_projection.md similarity index 100% rename from docs/02_components/13_coordinate_transformer/13.02_feature_pixel_gps_projection.md rename to _docs/02_components/13_coordinate_transformer/13.02_feature_pixel_gps_projection.md diff --git a/docs/02_components/13_coordinate_transformer/13._component_coordinate_transformer.md b/_docs/02_components/13_coordinate_transformer/13._component_coordinate_transformer.md similarity index 100% rename from docs/02_components/13_coordinate_transformer/13._component_coordinate_transformer.md rename to _docs/02_components/13_coordinate_transformer/13._component_coordinate_transformer.md diff --git a/docs/02_components/14_result_manager/14.01_feature_frame_result_persistence.md b/_docs/02_components/14_result_manager/14.01_feature_frame_result_persistence.md similarity index 100% rename from docs/02_components/14_result_manager/14.01_feature_frame_result_persistence.md rename to _docs/02_components/14_result_manager/14.01_feature_frame_result_persistence.md diff --git a/docs/02_components/14_result_manager/14.02_feature_result_retrieval.md b/_docs/02_components/14_result_manager/14.02_feature_result_retrieval.md similarity index 100% rename from docs/02_components/14_result_manager/14.02_feature_result_retrieval.md rename to _docs/02_components/14_result_manager/14.02_feature_result_retrieval.md diff --git a/docs/02_components/14_result_manager/14.03_feature_batch_refinement_updates.md b/_docs/02_components/14_result_manager/14.03_feature_batch_refinement_updates.md similarity index 100% rename from docs/02_components/14_result_manager/14.03_feature_batch_refinement_updates.md rename to _docs/02_components/14_result_manager/14.03_feature_batch_refinement_updates.md diff --git a/docs/02_components/14_result_manager/14._component_result_manager.md b/_docs/02_components/14_result_manager/14._component_result_manager.md similarity index 100% rename from docs/02_components/14_result_manager/14._component_result_manager.md rename to _docs/02_components/14_result_manager/14._component_result_manager.md diff --git a/docs/02_components/15_sse_event_streamer/15.01_feature_connection_lifecycle_management.md b/_docs/02_components/15_sse_event_streamer/15.01_feature_connection_lifecycle_management.md similarity index 100% rename from docs/02_components/15_sse_event_streamer/15.01_feature_connection_lifecycle_management.md rename to _docs/02_components/15_sse_event_streamer/15.01_feature_connection_lifecycle_management.md diff --git a/docs/02_components/15_sse_event_streamer/15.02_feature_event_broadcasting.md b/_docs/02_components/15_sse_event_streamer/15.02_feature_event_broadcasting.md similarity index 100% rename from docs/02_components/15_sse_event_streamer/15.02_feature_event_broadcasting.md rename to _docs/02_components/15_sse_event_streamer/15.02_feature_event_broadcasting.md diff --git a/docs/02_components/15_sse_event_streamer/15._component_sse_event_streamer.md b/_docs/02_components/15_sse_event_streamer/15._component_sse_event_streamer.md similarity index 100% rename from docs/02_components/15_sse_event_streamer/15._component_sse_event_streamer.md rename to _docs/02_components/15_sse_event_streamer/15._component_sse_event_streamer.md diff --git a/docs/02_components/16_model_manager/16.01_feature_model_lifecycle_management.md b/_docs/02_components/16_model_manager/16.01_feature_model_lifecycle_management.md similarity index 100% rename from docs/02_components/16_model_manager/16.01_feature_model_lifecycle_management.md rename to _docs/02_components/16_model_manager/16.01_feature_model_lifecycle_management.md diff --git a/docs/02_components/16_model_manager/16.02_feature_inference_engine_provisioning.md b/_docs/02_components/16_model_manager/16.02_feature_inference_engine_provisioning.md similarity index 100% rename from docs/02_components/16_model_manager/16.02_feature_inference_engine_provisioning.md rename to _docs/02_components/16_model_manager/16.02_feature_inference_engine_provisioning.md diff --git a/docs/02_components/16_model_manager/16._component_model_manager.md b/_docs/02_components/16_model_manager/16._component_model_manager.md similarity index 100% rename from docs/02_components/16_model_manager/16._component_model_manager.md rename to _docs/02_components/16_model_manager/16._component_model_manager.md diff --git a/docs/02_components/17_configuration_manager/17.01_feature_system_configuration.md b/_docs/02_components/17_configuration_manager/17.01_feature_system_configuration.md similarity index 100% rename from docs/02_components/17_configuration_manager/17.01_feature_system_configuration.md rename to _docs/02_components/17_configuration_manager/17.01_feature_system_configuration.md diff --git a/docs/02_components/17_configuration_manager/17.02_feature_flight_configuration.md b/_docs/02_components/17_configuration_manager/17.02_feature_flight_configuration.md similarity index 100% rename from docs/02_components/17_configuration_manager/17.02_feature_flight_configuration.md rename to _docs/02_components/17_configuration_manager/17.02_feature_flight_configuration.md diff --git a/docs/02_components/17_configuration_manager/17._component_configuration_manager.md b/_docs/02_components/17_configuration_manager/17._component_configuration_manager.md similarity index 100% rename from docs/02_components/17_configuration_manager/17._component_configuration_manager.md rename to _docs/02_components/17_configuration_manager/17._component_configuration_manager.md diff --git a/docs/02_components/astral_next_components_diagram.drawio b/_docs/02_components/astral_next_components_diagram.drawio similarity index 100% rename from docs/02_components/astral_next_components_diagram.drawio rename to _docs/02_components/astral_next_components_diagram.drawio diff --git a/docs/02_components/astral_next_components_diagram.png b/_docs/02_components/astral_next_components_diagram.png similarity index 100% rename from docs/02_components/astral_next_components_diagram.png rename to _docs/02_components/astral_next_components_diagram.png diff --git a/docs/02_components/decomposition_plan.md b/_docs/02_components/decomposition_plan.md similarity index 100% rename from docs/02_components/decomposition_plan.md rename to _docs/02_components/decomposition_plan.md diff --git a/docs/02_components/helpers/h01_camera_model_spec.md b/_docs/02_components/helpers/h01_camera_model_spec.md similarity index 100% rename from docs/02_components/helpers/h01_camera_model_spec.md rename to _docs/02_components/helpers/h01_camera_model_spec.md diff --git a/docs/02_components/helpers/h02_gsd_calculator_spec.md b/_docs/02_components/helpers/h02_gsd_calculator_spec.md similarity index 100% rename from docs/02_components/helpers/h02_gsd_calculator_spec.md rename to _docs/02_components/helpers/h02_gsd_calculator_spec.md diff --git a/docs/02_components/helpers/h03_robust_kernels_spec.md b/_docs/02_components/helpers/h03_robust_kernels_spec.md similarity index 100% rename from docs/02_components/helpers/h03_robust_kernels_spec.md rename to _docs/02_components/helpers/h03_robust_kernels_spec.md diff --git a/docs/02_components/helpers/h04_faiss_index_manager_spec.md b/_docs/02_components/helpers/h04_faiss_index_manager_spec.md similarity index 100% rename from docs/02_components/helpers/h04_faiss_index_manager_spec.md rename to _docs/02_components/helpers/h04_faiss_index_manager_spec.md diff --git a/docs/02_components/helpers/h05_performance_monitor_spec.md b/_docs/02_components/helpers/h05_performance_monitor_spec.md similarity index 100% rename from docs/02_components/helpers/h05_performance_monitor_spec.md rename to _docs/02_components/helpers/h05_performance_monitor_spec.md diff --git a/docs/02_components/helpers/h06_web_mercator_utils_spec.md b/_docs/02_components/helpers/h06_web_mercator_utils_spec.md similarity index 100% rename from docs/02_components/helpers/h06_web_mercator_utils_spec.md rename to _docs/02_components/helpers/h06_web_mercator_utils_spec.md diff --git a/docs/02_components/helpers/h07_image_rotation_utils_spec.md b/_docs/02_components/helpers/h07_image_rotation_utils_spec.md similarity index 100% rename from docs/02_components/helpers/h07_image_rotation_utils_spec.md rename to _docs/02_components/helpers/h07_image_rotation_utils_spec.md diff --git a/docs/02_components/helpers/h08_batch_validator_spec.md b/_docs/02_components/helpers/h08_batch_validator_spec.md similarity index 100% rename from docs/02_components/helpers/h08_batch_validator_spec.md rename to _docs/02_components/helpers/h08_batch_validator_spec.md diff --git a/_docs/02_components/structure_plan.md b/_docs/02_components/structure_plan.md new file mode 100644 index 0000000..be00ddd --- /dev/null +++ b/_docs/02_components/structure_plan.md @@ -0,0 +1,284 @@ +# Initial Structure for gps-denied + +## Technology Stack + +- **Python**: 3.10+ (GTSAM compatibility) +- **Web Framework**: FastAPI (async) +- **Database**: PostgreSQL + SQLAlchemy ORM +- **Validation**: Pydantic v2 +- **ML Runtime**: TensorRT (primary), ONNX Runtime (fallback) - NO PyTorch needed for inference +- **Graph Optimization**: GTSAM +- **Similarity Search**: Faiss + +## Project Structure + +``` +gps-denied/ +├── main.py # FastAPI app entry point +├── pyproject.toml +├── .gitignore +├── .env.example +├── README.md +│ +├── models/ # Pydantic DTOs (1 file per model) +│ ├── __init__.py # Re-exports all models +│ ├── core/ # Core shared models +│ │ ├── __init__.py +│ │ ├── gps_point.py +│ │ ├── camera_parameters.py +│ │ ├── pose.py +│ │ ├── polygon.py +│ │ └── validation_result.py +│ ├── flight/ # Flight domain models +│ │ ├── __init__.py +│ │ ├── flight.py +│ │ ├── flight_state.py +│ │ ├── waypoint.py +│ │ ├── geofences.py +│ │ └── heading_record.py +│ ├── processing/ # Visual processing models +│ │ ├── __init__.py +│ │ ├── relative_pose.py +│ │ ├── motion.py +│ │ ├── matches.py +│ │ ├── alignment_result.py +│ │ └── rotation_result.py +│ ├── chunks/ # Chunk-related models +│ │ ├── __init__.py +│ │ ├── chunk_handle.py +│ │ ├── chunk_bounds.py +│ │ └── sim3_transform.py +│ ├── satellite/ # Satellite tile models +│ │ ├── __init__.py +│ │ ├── tile_coords.py +│ │ ├── tile_bounds.py +│ │ └── tile_candidate.py +│ ├── recovery/ # Recovery/search models +│ │ ├── __init__.py +│ │ ├── search_session.py +│ │ ├── confidence_assessment.py +│ │ ├── user_anchor.py +│ │ └── user_input_request.py +│ ├── results/ # Result models +│ │ ├── __init__.py +│ │ ├── frame_result.py +│ │ ├── flight_results.py +│ │ ├── refined_frame_result.py +│ │ └── optimization_result.py +│ ├── images/ # Image-related models +│ │ ├── __init__.py +│ │ ├── image_data.py +│ │ ├── image_metadata.py +│ │ ├── image_batch.py +│ │ └── processing_status.py +│ ├── config/ # Configuration models +│ │ ├── __init__.py +│ │ ├── system_config.py +│ │ ├── flight_config.py +│ │ ├── database_config.py +│ │ ├── model_config.py +│ │ ├── rotation_config.py +│ │ └── recovery_config.py +│ └── api/ # API request/response models +│ ├── __init__.py +│ ├── flight_requests.py +│ ├── flight_responses.py +│ ├── batch_requests.py +│ └── user_fix_requests.py +│ +├── components/ # Interface (base.py) + implementation together +│ ├── __init__.py +│ ├── flight_api/ +│ │ ├── __init__.py +│ │ ├── base.py # ABC interface +│ │ └── flight_api.py # Implementation +│ ├── flight_lifecycle_manager/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── flight_lifecycle_manager.py +│ ├── flight_processing_engine/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── flight_processing_engine.py +│ ├── flight_database/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── flight_database.py +│ ├── satellite_data_manager/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── satellite_data_manager.py +│ ├── image_input_pipeline/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── image_input_pipeline.py +│ ├── image_rotation_manager/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── image_rotation_manager.py +│ ├── sequential_visual_odometry/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── sequential_visual_odometry.py +│ ├── global_place_recognition/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── global_place_recognition.py +│ ├── metric_refinement/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── metric_refinement.py +│ ├── factor_graph_optimizer/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── factor_graph_optimizer.py +│ ├── failure_recovery_coordinator/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── failure_recovery_coordinator.py +│ ├── route_chunk_manager/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── route_chunk_manager.py +│ ├── coordinate_transformer/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── coordinate_transformer.py +│ ├── result_manager/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── result_manager.py +│ ├── sse_event_streamer/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── sse_event_streamer.py +│ ├── model_manager/ +│ │ ├── __init__.py +│ │ ├── base.py +│ │ └── model_manager.py +│ └── configuration_manager/ +│ ├── __init__.py +│ ├── base.py +│ └── configuration_manager.py +│ +├── helpers/ # Single file per helper +│ ├── __init__.py +│ ├── camera_model.py +│ ├── gsd_calculator.py +│ ├── robust_kernels.py +│ ├── faiss_index_manager.py +│ ├── performance_monitor.py +│ ├── web_mercator_utils.py +│ ├── image_rotation_utils.py +│ └── batch_validator.py +│ +├── db/ # Database layer +│ ├── __init__.py +│ ├── connection.py +│ ├── models.py # SQLAlchemy ORM models +│ └── migrations/ +│ +├── api/ # FastAPI routes +│ ├── __init__.py +│ ├── routes/ +│ │ ├── __init__.py +│ │ ├── flights.py +│ │ ├── images.py +│ │ └── stream.py +│ └── dependencies.py +│ +└── _docs/ # Documentation +``` + +## Dependencies (pyproject.toml) + +```toml +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "azaion-gps-denied-desktop" +version = "0.1.0" +requires-python = ">=3.10" +dependencies = [ + "fastapi>=0.109.0", + "uvicorn[standard]>=0.27.0", + "pydantic>=2.5.0", + "sqlalchemy[asyncio]>=2.0.0", + "asyncpg>=0.29.0", + "alembic>=1.13.0", + "numpy>=1.26.0", + "opencv-python>=4.9.0", + "sse-starlette>=2.0.0", + "python-multipart>=0.0.6", + "httpx>=0.26.0", + "pyyaml>=6.0", + "gtsam>=4.2", +] + +[project.optional-dependencies] +ml = [ + "tensorrt>=10.0.0", + "onnxruntime-gpu>=1.17.0", + "faiss-gpu>=1.7.4", +] +dev = [ + "pytest>=7.4.0", + "pytest-asyncio>=0.21.0", + "pytest-cov>=4.1.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["models", "components", "helpers", "db", "api"] +``` + +Note: PyTorch removed - TensorRT and ONNX Runtime handle inference without it. + +## Implementation Phases + +### Phase 1: Project Setup ✓ + +- Create package structure with `__init__.py` files +- Create `pyproject.toml`, `.gitignore`, `README.md`, `.env.example` +- Setup uv for dependency management + +### Phase 2: Core DTOs ✓ + +- `models/core/` - GPSPoint, CameraParameters, Pose, Polygon, ValidationResult +- `models/flight/` - Flight, FlightState, Waypoint, Geofences, HeadingRecord +- `models/processing/` - RelativePose, AlignmentResult, Matches, Motion, RotationResult +- `models/chunks/` - ChunkHandle, ChunkBounds, Sim3Transform +- All remaining model subdirectories (satellite, recovery, results, images, config, api) + +### Phase 3: Components with Interfaces ✓ + +- Each component folder contains: + - `base.py` - ABC interface + - `{component_name}.py` - Implementation stub with `NotImplementedError` +- All 18 components implemented + +### Phase 4: Helpers ✓ + +- Single file helpers with class stubs +- All 8 helpers implemented + +### Phase 5: Database Schema ✓ + +- SQLAlchemy ORM models (Flight, Waypoint, FrameResult, Chunk) +- Async connection management with asyncpg +- Alembic migrations folder created + +### Phase 6: API Routes ✓ + +- FastAPI routes for flights, images, SSE streaming +- Dependency injection setup + +## Completed Tasks + +- [x] Create package structure, pyproject.toml, .gitignore +- [x] Create all Pydantic DTOs (models/ directory) +- [x] Create components with base.py (ABC) + stub implementations +- [x] Create helper files with empty implementations +- [x] Create SQLAlchemy models and Alembic setup +- [x] Create API routes and dependencies diff --git a/docs/02_components/system_flows.md b/_docs/02_components/system_flows.md similarity index 100% rename from docs/02_components/system_flows.md rename to _docs/02_components/system_flows.md diff --git a/docs/02_components/system_flows_diagrams.md b/_docs/02_components/system_flows_diagrams.md similarity index 100% rename from docs/02_components/system_flows_diagrams.md rename to _docs/02_components/system_flows_diagrams.md diff --git a/docs/03_tests/00_test_summary.md b/_docs/03_tests/00_test_summary.md similarity index 100% rename from docs/03_tests/00_test_summary.md rename to _docs/03_tests/00_test_summary.md diff --git a/docs/03_tests/01_sequential_visual_odometry_integration_spec.md b/_docs/03_tests/01_sequential_visual_odometry_integration_spec.md similarity index 100% rename from docs/03_tests/01_sequential_visual_odometry_integration_spec.md rename to _docs/03_tests/01_sequential_visual_odometry_integration_spec.md diff --git a/docs/03_tests/02_global_place_recognition_integration_spec.md b/_docs/03_tests/02_global_place_recognition_integration_spec.md similarity index 100% rename from docs/03_tests/02_global_place_recognition_integration_spec.md rename to _docs/03_tests/02_global_place_recognition_integration_spec.md diff --git a/docs/03_tests/03_metric_refinement_integration_spec.md b/_docs/03_tests/03_metric_refinement_integration_spec.md similarity index 100% rename from docs/03_tests/03_metric_refinement_integration_spec.md rename to _docs/03_tests/03_metric_refinement_integration_spec.md diff --git a/docs/03_tests/04_factor_graph_optimizer_integration_spec.md b/_docs/03_tests/04_factor_graph_optimizer_integration_spec.md similarity index 100% rename from docs/03_tests/04_factor_graph_optimizer_integration_spec.md rename to _docs/03_tests/04_factor_graph_optimizer_integration_spec.md diff --git a/docs/03_tests/05_satellite_data_manager_integration_spec.md b/_docs/03_tests/05_satellite_data_manager_integration_spec.md similarity index 100% rename from docs/03_tests/05_satellite_data_manager_integration_spec.md rename to _docs/03_tests/05_satellite_data_manager_integration_spec.md diff --git a/docs/03_tests/06_coordinate_transformer_integration_spec.md b/_docs/03_tests/06_coordinate_transformer_integration_spec.md similarity index 100% rename from docs/03_tests/06_coordinate_transformer_integration_spec.md rename to _docs/03_tests/06_coordinate_transformer_integration_spec.md diff --git a/docs/03_tests/07_image_input_pipeline_integration_spec.md b/_docs/03_tests/07_image_input_pipeline_integration_spec.md similarity index 100% rename from docs/03_tests/07_image_input_pipeline_integration_spec.md rename to _docs/03_tests/07_image_input_pipeline_integration_spec.md diff --git a/docs/03_tests/08_image_rotation_manager_integration_spec.md b/_docs/03_tests/08_image_rotation_manager_integration_spec.md similarity index 100% rename from docs/03_tests/08_image_rotation_manager_integration_spec.md rename to _docs/03_tests/08_image_rotation_manager_integration_spec.md diff --git a/docs/03_tests/09_rest_api_integration_spec.md b/_docs/03_tests/09_rest_api_integration_spec.md similarity index 100% rename from docs/03_tests/09_rest_api_integration_spec.md rename to _docs/03_tests/09_rest_api_integration_spec.md diff --git a/docs/03_tests/10_sse_event_streamer_integration_spec.md b/_docs/03_tests/10_sse_event_streamer_integration_spec.md similarity index 100% rename from docs/03_tests/10_sse_event_streamer_integration_spec.md rename to _docs/03_tests/10_sse_event_streamer_integration_spec.md diff --git a/docs/03_tests/11a_flight_lifecycle_manager_integration_spec.md b/_docs/03_tests/11a_flight_lifecycle_manager_integration_spec.md similarity index 100% rename from docs/03_tests/11a_flight_lifecycle_manager_integration_spec.md rename to _docs/03_tests/11a_flight_lifecycle_manager_integration_spec.md diff --git a/docs/03_tests/11b_flight_processing_engine_integration_spec.md b/_docs/03_tests/11b_flight_processing_engine_integration_spec.md similarity index 100% rename from docs/03_tests/11b_flight_processing_engine_integration_spec.md rename to _docs/03_tests/11b_flight_processing_engine_integration_spec.md diff --git a/docs/03_tests/12_result_manager_integration_spec.md b/_docs/03_tests/12_result_manager_integration_spec.md similarity index 100% rename from docs/03_tests/12_result_manager_integration_spec.md rename to _docs/03_tests/12_result_manager_integration_spec.md diff --git a/docs/03_tests/13_model_manager_integration_spec.md b/_docs/03_tests/13_model_manager_integration_spec.md similarity index 100% rename from docs/03_tests/13_model_manager_integration_spec.md rename to _docs/03_tests/13_model_manager_integration_spec.md diff --git a/docs/03_tests/14_failure_recovery_coordinator_integration_spec.md b/_docs/03_tests/14_failure_recovery_coordinator_integration_spec.md similarity index 100% rename from docs/03_tests/14_failure_recovery_coordinator_integration_spec.md rename to _docs/03_tests/14_failure_recovery_coordinator_integration_spec.md diff --git a/docs/03_tests/15_configuration_manager_integration_spec.md b/_docs/03_tests/15_configuration_manager_integration_spec.md similarity index 100% rename from docs/03_tests/15_configuration_manager_integration_spec.md rename to _docs/03_tests/15_configuration_manager_integration_spec.md diff --git a/docs/03_tests/16_database_layer_integration_spec.md b/_docs/03_tests/16_database_layer_integration_spec.md similarity index 100% rename from docs/03_tests/16_database_layer_integration_spec.md rename to _docs/03_tests/16_database_layer_integration_spec.md diff --git a/docs/03_tests/21_end_to_end_normal_flight_spec.md b/_docs/03_tests/21_end_to_end_normal_flight_spec.md similarity index 100% rename from docs/03_tests/21_end_to_end_normal_flight_spec.md rename to _docs/03_tests/21_end_to_end_normal_flight_spec.md diff --git a/docs/03_tests/22_satellite_to_vision_pipeline_spec.md b/_docs/03_tests/22_satellite_to_vision_pipeline_spec.md similarity index 100% rename from docs/03_tests/22_satellite_to_vision_pipeline_spec.md rename to _docs/03_tests/22_satellite_to_vision_pipeline_spec.md diff --git a/docs/03_tests/23_vision_to_optimization_pipeline_spec.md b/_docs/03_tests/23_vision_to_optimization_pipeline_spec.md similarity index 100% rename from docs/03_tests/23_vision_to_optimization_pipeline_spec.md rename to _docs/03_tests/23_vision_to_optimization_pipeline_spec.md diff --git a/docs/03_tests/24_multi_component_error_propagation_spec.md b/_docs/03_tests/24_multi_component_error_propagation_spec.md similarity index 100% rename from docs/03_tests/24_multi_component_error_propagation_spec.md rename to _docs/03_tests/24_multi_component_error_propagation_spec.md diff --git a/docs/03_tests/25_real_time_streaming_pipeline_spec.md b/_docs/03_tests/25_real_time_streaming_pipeline_spec.md similarity index 100% rename from docs/03_tests/25_real_time_streaming_pipeline_spec.md rename to _docs/03_tests/25_real_time_streaming_pipeline_spec.md diff --git a/docs/03_tests/31_accuracy_50m_baseline_spec.md b/_docs/03_tests/31_accuracy_50m_baseline_spec.md similarity index 100% rename from docs/03_tests/31_accuracy_50m_baseline_spec.md rename to _docs/03_tests/31_accuracy_50m_baseline_spec.md diff --git a/docs/03_tests/32_accuracy_50m_varied_terrain_spec.md b/_docs/03_tests/32_accuracy_50m_varied_terrain_spec.md similarity index 100% rename from docs/03_tests/32_accuracy_50m_varied_terrain_spec.md rename to _docs/03_tests/32_accuracy_50m_varied_terrain_spec.md diff --git a/docs/03_tests/33_accuracy_20m_high_precision_spec.md b/_docs/03_tests/33_accuracy_20m_high_precision_spec.md similarity index 100% rename from docs/03_tests/33_accuracy_20m_high_precision_spec.md rename to _docs/03_tests/33_accuracy_20m_high_precision_spec.md diff --git a/docs/03_tests/34_outlier_350m_single_spec.md b/_docs/03_tests/34_outlier_350m_single_spec.md similarity index 100% rename from docs/03_tests/34_outlier_350m_single_spec.md rename to _docs/03_tests/34_outlier_350m_single_spec.md diff --git a/docs/03_tests/35_outlier_350m_multiple_spec.md b/_docs/03_tests/35_outlier_350m_multiple_spec.md similarity index 100% rename from docs/03_tests/35_outlier_350m_multiple_spec.md rename to _docs/03_tests/35_outlier_350m_multiple_spec.md diff --git a/docs/03_tests/36_sharp_turn_zero_overlap_spec.md b/_docs/03_tests/36_sharp_turn_zero_overlap_spec.md similarity index 100% rename from docs/03_tests/36_sharp_turn_zero_overlap_spec.md rename to _docs/03_tests/36_sharp_turn_zero_overlap_spec.md diff --git a/docs/03_tests/37_sharp_turn_minimal_overlap_spec.md b/_docs/03_tests/37_sharp_turn_minimal_overlap_spec.md similarity index 100% rename from docs/03_tests/37_sharp_turn_minimal_overlap_spec.md rename to _docs/03_tests/37_sharp_turn_minimal_overlap_spec.md diff --git a/docs/03_tests/38_outlier_anchor_detection_spec.md b/_docs/03_tests/38_outlier_anchor_detection_spec.md similarity index 100% rename from docs/03_tests/38_outlier_anchor_detection_spec.md rename to _docs/03_tests/38_outlier_anchor_detection_spec.md diff --git a/docs/03_tests/39_route_chunk_connection_spec.md b/_docs/03_tests/39_route_chunk_connection_spec.md similarity index 100% rename from docs/03_tests/39_route_chunk_connection_spec.md rename to _docs/03_tests/39_route_chunk_connection_spec.md diff --git a/docs/03_tests/40_user_input_recovery_spec.md b/_docs/03_tests/40_user_input_recovery_spec.md similarity index 100% rename from docs/03_tests/40_user_input_recovery_spec.md rename to _docs/03_tests/40_user_input_recovery_spec.md diff --git a/docs/03_tests/41_performance_single_image_spec.md b/_docs/03_tests/41_performance_single_image_spec.md similarity index 100% rename from docs/03_tests/41_performance_single_image_spec.md rename to _docs/03_tests/41_performance_single_image_spec.md diff --git a/docs/03_tests/42_performance_sustained_throughput_spec.md b/_docs/03_tests/42_performance_sustained_throughput_spec.md similarity index 100% rename from docs/03_tests/42_performance_sustained_throughput_spec.md rename to _docs/03_tests/42_performance_sustained_throughput_spec.md diff --git a/docs/03_tests/43_realtime_streaming_spec.md b/_docs/03_tests/43_realtime_streaming_spec.md similarity index 100% rename from docs/03_tests/43_realtime_streaming_spec.md rename to _docs/03_tests/43_realtime_streaming_spec.md diff --git a/docs/03_tests/44_async_refinement_spec.md b/_docs/03_tests/44_async_refinement_spec.md similarity index 100% rename from docs/03_tests/44_async_refinement_spec.md rename to _docs/03_tests/44_async_refinement_spec.md diff --git a/docs/03_tests/45_registration_rate_baseline_spec.md b/_docs/03_tests/45_registration_rate_baseline_spec.md similarity index 100% rename from docs/03_tests/45_registration_rate_baseline_spec.md rename to _docs/03_tests/45_registration_rate_baseline_spec.md diff --git a/docs/03_tests/46_registration_rate_challenging_spec.md b/_docs/03_tests/46_registration_rate_challenging_spec.md similarity index 100% rename from docs/03_tests/46_registration_rate_challenging_spec.md rename to _docs/03_tests/46_registration_rate_challenging_spec.md diff --git a/docs/03_tests/47_reprojection_error_spec.md b/_docs/03_tests/47_reprojection_error_spec.md similarity index 100% rename from docs/03_tests/47_reprojection_error_spec.md rename to _docs/03_tests/47_reprojection_error_spec.md diff --git a/docs/03_tests/48_long_flight_3000_images_spec.md b/_docs/03_tests/48_long_flight_3000_images_spec.md similarity index 100% rename from docs/03_tests/48_long_flight_3000_images_spec.md rename to _docs/03_tests/48_long_flight_3000_images_spec.md diff --git a/docs/03_tests/49_degraded_satellite_data_spec.md b/_docs/03_tests/49_degraded_satellite_data_spec.md similarity index 100% rename from docs/03_tests/49_degraded_satellite_data_spec.md rename to _docs/03_tests/49_degraded_satellite_data_spec.md diff --git a/docs/03_tests/50_complete_system_acceptance_spec.md b/_docs/03_tests/50_complete_system_acceptance_spec.md similarity index 100% rename from docs/03_tests/50_complete_system_acceptance_spec.md rename to _docs/03_tests/50_complete_system_acceptance_spec.md diff --git a/docs/03_tests/51_test_baseline_standard_flight_spec.md b/_docs/03_tests/51_test_baseline_standard_flight_spec.md similarity index 100% rename from docs/03_tests/51_test_baseline_standard_flight_spec.md rename to _docs/03_tests/51_test_baseline_standard_flight_spec.md diff --git a/docs/03_tests/52_test_outlier_350m_scenario_spec.md b/_docs/03_tests/52_test_outlier_350m_scenario_spec.md similarity index 100% rename from docs/03_tests/52_test_outlier_350m_scenario_spec.md rename to _docs/03_tests/52_test_outlier_350m_scenario_spec.md diff --git a/docs/03_tests/53_test_sharp_turn_scenarios_spec.md b/_docs/03_tests/53_test_sharp_turn_scenarios_spec.md similarity index 100% rename from docs/03_tests/53_test_sharp_turn_scenarios_spec.md rename to _docs/03_tests/53_test_sharp_turn_scenarios_spec.md diff --git a/docs/03_tests/54_test_long_flight_full_spec.md b/_docs/03_tests/54_test_long_flight_full_spec.md similarity index 100% rename from docs/03_tests/54_test_long_flight_full_spec.md rename to _docs/03_tests/54_test_long_flight_full_spec.md diff --git a/docs/03_tests/55_chunk_rotation_recovery_spec.md b/_docs/03_tests/55_chunk_rotation_recovery_spec.md similarity index 100% rename from docs/03_tests/55_chunk_rotation_recovery_spec.md rename to _docs/03_tests/55_chunk_rotation_recovery_spec.md diff --git a/docs/03_tests/56_multi_chunk_simultaneous_spec.md b/_docs/03_tests/56_multi_chunk_simultaneous_spec.md similarity index 100% rename from docs/03_tests/56_multi_chunk_simultaneous_spec.md rename to _docs/03_tests/56_multi_chunk_simultaneous_spec.md diff --git a/docs/_metodology/tutorial.md b/_docs/_metodology/tutorial.md similarity index 66% rename from docs/_metodology/tutorial.md rename to _docs/_metodology/tutorial.md index 1324dc9..2fa4211 100644 --- a/docs/_metodology/tutorial.md +++ b/_docs/_metodology/tutorial.md @@ -3,7 +3,7 @@ ## 1.0 Problem statement ### Discuss - Discuss the problem and create in the `docs/00_problem` next files and folders: + Discuss the problem and create in the `_docs/00_problem` next files and folders: - `problem_description.md`: Our problem to solve with the end result we want to achieve. - `input_data`: Put to this folder all the necessary input data and expected results for the further tests. Analyze very thoroughly input data and form system's restrictions and acceptance ctiteria - `restrictions.md`: Restrictions we have in real world in the -dashed list format. @@ -60,7 +60,7 @@ - Revise the result from AI. - Research the problem as well - Add/modify/remove some solution details in the draft. (Also with AI) - - Store it to the `docs/01_solution/solution_draft.md` + - Store it to the `_docs/01_solution/solution_draft.md` ## 1.3 **✨AI Research**: Solution draft assessment @@ -77,10 +77,10 @@ ### Iterate - Rename previous `solution_draft.md` to `{xx}_solution_draft.md`. Start {xx} from 01 - - Store the new revised result draft to the `docs/01_solution/solution_draft.md` + - Store the new revised result draft to the `_docs/01_solution/solution_draft.md` - Repeat the process 1.3 from the beginning - When the next solution wouldn't differ much from the previous one, or become actually worse, store the last draft as `docs/01_solution/solution.md` + When the next solution wouldn't differ much from the previous one, or become actually worse, store the last draft as `_docs/01_solution/solution.md` @@ -89,57 +89,57 @@ ## 2.10 **🤖📋AI plan**: Generate components - ### Execute `/2.planning/2.10_gen_components` + ### Execute `/2.planning/2.10_gen_components` - ### Revise - - Revise the plan, answer questions, put detailed descriptions - - Make sure stored components are coherent and make sense + ### Revise + - Revise the plan, answer questions, put detailed descriptions + - Make sure stored components are coherent and make sense - ### Store plan - - Save plan to `docs/02_components/00_decomposition_plan.md` + ### Store plan + - Save plan to `_docs/02_components/00_decomposition_plan.md` ## 2.15 **🤖AI agent**: Components assesment - ### Execute `/2.planning/2.15_components_assesment` + ### Execute `/2.planning/2.15_components_assesment` - ### Revise - - Clarify the proposals and ask to fix found issues + ### Revise + - Clarify the proposals and ask to fix found issues ## 2.20 **🤖AI agent**: Generate Jira Epics - ### Jira MCP + ### Jira MCP Add Jira MCP to the list in IDE: ``` "Jira-MCP-Server": { "url": "https://mcp.atlassian.com/v1/sse" } ``` - ### Execute `/2.planning/2.20_gen_epics use jira mcp` + ### Execute `/2.planning/2.20_gen_epics use jira mcp` - ### Revise - - Revise the epics, answer questions, put detailed descriptions - - Make sure epics are coherent and make sense + ### Revise + - Revise the epics, answer questions, put detailed descriptions + - Make sure epics are coherent and make sense ## 2.30 **🤖AI agent**: Generate tests - ### Execute `/2.planning/2.30_gen_tests` + ### Execute `/2.planning/2.30_gen_tests` - ### Revise - - Revise the tests, answer questions, put detailed descriptions - - Make sure stored tests are coherent and make sense + ### Revise + - Revise the tests, answer questions, put detailed descriptions + - Make sure stored tests are coherent and make sense ## 2.40 **🤖📋AI agent**: Component Decomposition To Features - ### Execute - For each component in `docs/02_components` run + ### Execute + For each component in `_docs/02_components` run `/2.planning/2.40_gen_features --component @xx__spec_[component_name].md` - ### Revise - - Revise the features, answer questions, put detailed descriptions - - Make sure features are coherent and make sense + ### Revise + - Revise the features, answer questions, put detailed descriptions + - Make sure features are coherent and make sense @@ -147,46 +147,59 @@ ## 3.05 **🤖📋AI plan**: Initial structure - ### Execute: `/3.implementation/3.05_implement_initial_structure` + ### Jira MCP + Add context7 MCP to the list in IDE: + ``` + "context7": { + "command": "npx", + "args": [ + "-y", + "@upstash/context7-mcp" + ] + } + ``` - ### Review Plan - - Analyze the proposals, answer questions - - Improve plan as much as possible so it would be clear what exactly to do + ### Execute: `/3.implementation/3.05_implement_initial_structure` - ### Save Plan - - when plan is final and ready, save it as `docs/02_components/structure_plan.md` + ### Review Plan + - Analyze the proposals, answer questions + - Improve plan as much as possible so it would be clear what exactly to do + + ### Save Plan + - when plan is final and ready, save it as `_docs/02_components/structure_plan.md` - ### Execute Plan - - Press build and let AI generate the structure + ### Execute Plan + - Press build and let AI generate the structure - ### Revise Code - - Read the code and check that everything is ok + ### Revise Code + - Read the code and check that everything is ok ## 3.10 **🤖📋AI plan**: Feature implementation - ### Execute - For each component in `docs/02_components` run + ### Execute + For each component in `_docs/02_components` run `/3.implementation/3.10_implement_component @component_folder` - ### Revise Plan - - Analyze the proposed development plan in a great detail, provide all necessary information - - Possibly reorganize plan if needed, think and add more input constraints if needed - - Improve plan as much as possible so it would be clear what exactly to do + ### Revise Plan + - Analyze the proposed development plan in a great detail, provide all necessary information + - Possibly reorganize plan if needed, think and add more input constraints if needed + - Improve plan as much as possible so it would be clear what exactly to do ### Save Plan - when plan is final and ready, save it as `[##]._plan_[component_name]` to component's folder - ### Execute Plan - - Press build and let AI generate the code + ### Execute Plan + - Press build and let AI generate the code - ### Revise Code - - Read the code and check that everything is ok + ### Revise Code + - Read the code and check that everything is ok ## 3.20 **🤖AI agent**: Solution composition and integration tests -``` - Read all the files here `docs/03_tests/` and for each file write down tests and run it. - Compose a final test results in a csv with the next format: + + +Read all the files here `_docs/03_tests/` and for each file write down tests and run it. +Compose a final test results in a csv with the next format: - Test filename - Execution time - Result @@ -194,7 +207,6 @@ Fix all problems if tests failed until we got a successful result. In case if one or more tests was failed due to missing data from user or API or other system, ask it from developer. Repeat test cycle until no failed tests. - ``` diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..c018662 --- /dev/null +++ b/api/__init__.py @@ -0,0 +1,5 @@ +from .routes import router +from .dependencies import get_flight_api, get_sse_streamer + +__all__ = ["router", "get_flight_api", "get_sse_streamer"] + diff --git a/api/dependencies.py b/api/dependencies.py new file mode 100644 index 0000000..5dabeaf --- /dev/null +++ b/api/dependencies.py @@ -0,0 +1,20 @@ +from components.flight_api import FlightAPI, FlightAPIBase +from components.sse_event_streamer import SSEEventStreamer, SSEEventStreamerBase + +_flight_api: FlightAPIBase | None = None +_sse_streamer: SSEEventStreamerBase | None = None + + +def get_flight_api() -> FlightAPIBase: + global _flight_api + if _flight_api is None: + _flight_api = FlightAPI() + return _flight_api + + +def get_sse_streamer() -> SSEEventStreamerBase: + global _sse_streamer + if _sse_streamer is None: + _sse_streamer = SSEEventStreamer() + return _sse_streamer + diff --git a/api/routes/__init__.py b/api/routes/__init__.py new file mode 100644 index 0000000..a164278 --- /dev/null +++ b/api/routes/__init__.py @@ -0,0 +1,13 @@ +from fastapi import APIRouter + +from .flights import router as flights_router +from .images import router as images_router +from .stream import router as stream_router + +router = APIRouter() +router.include_router(flights_router, prefix="/flights", tags=["flights"]) +router.include_router(images_router, prefix="/flights", tags=["images"]) +router.include_router(stream_router, prefix="/stream", tags=["stream"]) + +__all__ = ["router"] + diff --git a/api/routes/flights.py b/api/routes/flights.py new file mode 100644 index 0000000..782fb80 --- /dev/null +++ b/api/routes/flights.py @@ -0,0 +1,80 @@ +from fastapi import APIRouter, Depends + +from models.api import ( + FlightCreateRequest, + FlightResponse, + FlightDetailResponse, + FlightStatusResponse, + DeleteResponse, + UpdateResponse, + UserFixRequest, + UserFixResponse, + ObjectGPSResponse, +) +from models.core import GPSPoint +from api.dependencies import get_flight_api +from components.flight_api import FlightAPIBase + +router = APIRouter() + + +@router.post("", response_model=FlightResponse) +async def create_flight( + request: FlightCreateRequest, + api: FlightAPIBase = Depends(get_flight_api), +) -> FlightResponse: + return await api.create_flight(request) + + +@router.get("/{flight_id}", response_model=FlightDetailResponse) +async def get_flight( + flight_id: str, + api: FlightAPIBase = Depends(get_flight_api), +) -> FlightDetailResponse: + return await api.get_flight(flight_id) + + +@router.get("/{flight_id}/status", response_model=FlightStatusResponse) +async def get_flight_status( + flight_id: str, + api: FlightAPIBase = Depends(get_flight_api), +) -> FlightStatusResponse: + return await api.get_flight_status(flight_id) + + +@router.delete("/{flight_id}", response_model=DeleteResponse) +async def delete_flight( + flight_id: str, + api: FlightAPIBase = Depends(get_flight_api), +) -> DeleteResponse: + return await api.delete_flight(flight_id) + + +@router.put("/{flight_id}/waypoints", response_model=UpdateResponse) +async def update_waypoints( + flight_id: str, + waypoints: list[GPSPoint], + api: FlightAPIBase = Depends(get_flight_api), +) -> UpdateResponse: + return await api.update_waypoints(flight_id, waypoints) + + +@router.post("/{flight_id}/user-fix", response_model=UserFixResponse) +async def submit_user_fix( + flight_id: str, + request: UserFixRequest, + api: FlightAPIBase = Depends(get_flight_api), +) -> UserFixResponse: + return await api.submit_user_fix(flight_id, request) + + +@router.get("/{flight_id}/frames/{frame_id}/object-gps", response_model=ObjectGPSResponse) +async def get_object_gps( + flight_id: str, + frame_id: int, + pixel_x: float, + pixel_y: float, + api: FlightAPIBase = Depends(get_flight_api), +) -> ObjectGPSResponse: + return await api.get_object_gps(flight_id, frame_id, (pixel_x, pixel_y)) + diff --git a/api/routes/images.py b/api/routes/images.py new file mode 100644 index 0000000..40ada9d --- /dev/null +++ b/api/routes/images.py @@ -0,0 +1,27 @@ +from typing import Annotated +from fastapi import APIRouter, Depends, UploadFile, File, Form + +from models.api import BatchResponse +from api.dependencies import get_flight_api +from components.flight_api import FlightAPIBase + +router = APIRouter() + + +@router.post("/{flight_id}/batches", response_model=BatchResponse) +async def upload_batch( + flight_id: str, + files: Annotated[list[UploadFile], File()], + start_sequence: Annotated[int, Form()], + end_sequence: Annotated[int, Form()], + batch_number: Annotated[int, Form()], + api: FlightAPIBase = Depends(get_flight_api), +) -> BatchResponse: + return await api.upload_batch( + flight_id, + files, + start_sequence, + end_sequence, + batch_number, + ) + diff --git a/api/routes/stream.py b/api/routes/stream.py new file mode 100644 index 0000000..01ce45e --- /dev/null +++ b/api/routes/stream.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter, Depends +from sse_starlette.sse import EventSourceResponse + +from api.dependencies import get_flight_api +from components.flight_api import FlightAPIBase + +router = APIRouter() + + +@router.get("/{flight_id}") +async def stream_events( + flight_id: str, + api: FlightAPIBase = Depends(get_flight_api), +) -> EventSourceResponse: + return EventSourceResponse(api.stream_events(flight_id)) + diff --git a/components/__init__.py b/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/components/configuration_manager/__init__.py b/components/configuration_manager/__init__.py new file mode 100644 index 0000000..08f07bd --- /dev/null +++ b/components/configuration_manager/__init__.py @@ -0,0 +1,5 @@ +from .base import ConfigurationManagerBase +from .configuration_manager import ConfigurationManager + +__all__ = ["ConfigurationManagerBase", "ConfigurationManager"] + diff --git a/components/configuration_manager/base.py b/components/configuration_manager/base.py new file mode 100644 index 0000000..a97b017 --- /dev/null +++ b/components/configuration_manager/base.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod +from typing import Optional, Any + +from models.config import SystemConfig + + +class ConfigurationManagerBase(ABC): + @abstractmethod + async def load_config(self, path: str) -> SystemConfig: + pass + + @abstractmethod + async def save_config(self, config: SystemConfig, path: str) -> bool: + pass + + @abstractmethod + def get_config(self) -> SystemConfig: + pass + + @abstractmethod + def get_value(self, key: str, default: Any = None) -> Any: + pass + + @abstractmethod + async def update_value(self, key: str, value: Any) -> bool: + pass + + @abstractmethod + async def validate_config(self, config: SystemConfig) -> list[str]: + pass + + @abstractmethod + async def reload_config(self) -> bool: + pass + diff --git a/components/configuration_manager/configuration_manager.py b/components/configuration_manager/configuration_manager.py new file mode 100644 index 0000000..65518c3 --- /dev/null +++ b/components/configuration_manager/configuration_manager.py @@ -0,0 +1,28 @@ +from typing import Any + +from .base import ConfigurationManagerBase +from models.config import SystemConfig + + +class ConfigurationManager(ConfigurationManagerBase): + async def load_config(self, path: str) -> SystemConfig: + raise NotImplementedError + + async def save_config(self, config: SystemConfig, path: str) -> bool: + raise NotImplementedError + + def get_config(self) -> SystemConfig: + raise NotImplementedError + + def get_value(self, key: str, default: Any = None) -> Any: + raise NotImplementedError + + async def update_value(self, key: str, value: Any) -> bool: + raise NotImplementedError + + async def validate_config(self, config: SystemConfig) -> list[str]: + raise NotImplementedError + + async def reload_config(self) -> bool: + raise NotImplementedError + diff --git a/components/coordinate_transformer/__init__.py b/components/coordinate_transformer/__init__.py new file mode 100644 index 0000000..94e4d2b --- /dev/null +++ b/components/coordinate_transformer/__init__.py @@ -0,0 +1,5 @@ +from .base import CoordinateTransformerBase +from .coordinate_transformer import CoordinateTransformer + +__all__ = ["CoordinateTransformerBase", "CoordinateTransformer"] + diff --git a/components/coordinate_transformer/base.py b/components/coordinate_transformer/base.py new file mode 100644 index 0000000..cd96ce0 --- /dev/null +++ b/components/coordinate_transformer/base.py @@ -0,0 +1,56 @@ +from abc import ABC, abstractmethod +import numpy as np + +from models.core import GPSPoint +from models.satellite import TileBounds + + +class CoordinateTransformerBase(ABC): + @abstractmethod + def gps_to_local( + self, gps: GPSPoint, origin: GPSPoint + ) -> tuple[float, float]: + pass + + @abstractmethod + def local_to_gps( + self, local: tuple[float, float], origin: GPSPoint + ) -> GPSPoint: + pass + + @abstractmethod + def pixel_to_gps( + self, + pixel: tuple[float, float], + homography: np.ndarray, + tile_bounds: TileBounds, + ) -> GPSPoint: + pass + + @abstractmethod + def gps_to_pixel( + self, + gps: GPSPoint, + homography: np.ndarray, + tile_bounds: TileBounds, + ) -> tuple[float, float]: + pass + + @abstractmethod + def compute_distance_meters( + self, gps1: GPSPoint, gps2: GPSPoint + ) -> float: + pass + + @abstractmethod + def compute_bearing( + self, from_gps: GPSPoint, to_gps: GPSPoint + ) -> float: + pass + + @abstractmethod + def offset_gps( + self, gps: GPSPoint, distance_m: float, bearing_deg: float + ) -> GPSPoint: + pass + diff --git a/components/coordinate_transformer/coordinate_transformer.py b/components/coordinate_transformer/coordinate_transformer.py new file mode 100644 index 0000000..ac252cd --- /dev/null +++ b/components/coordinate_transformer/coordinate_transformer.py @@ -0,0 +1,49 @@ +import numpy as np + +from .base import CoordinateTransformerBase +from models.core import GPSPoint +from models.satellite import TileBounds + + +class CoordinateTransformer(CoordinateTransformerBase): + def gps_to_local( + self, gps: GPSPoint, origin: GPSPoint + ) -> tuple[float, float]: + raise NotImplementedError + + def local_to_gps( + self, local: tuple[float, float], origin: GPSPoint + ) -> GPSPoint: + raise NotImplementedError + + def pixel_to_gps( + self, + pixel: tuple[float, float], + homography: np.ndarray, + tile_bounds: TileBounds, + ) -> GPSPoint: + raise NotImplementedError + + def gps_to_pixel( + self, + gps: GPSPoint, + homography: np.ndarray, + tile_bounds: TileBounds, + ) -> tuple[float, float]: + raise NotImplementedError + + def compute_distance_meters( + self, gps1: GPSPoint, gps2: GPSPoint + ) -> float: + raise NotImplementedError + + def compute_bearing( + self, from_gps: GPSPoint, to_gps: GPSPoint + ) -> float: + raise NotImplementedError + + def offset_gps( + self, gps: GPSPoint, distance_m: float, bearing_deg: float + ) -> GPSPoint: + raise NotImplementedError + diff --git a/components/factor_graph_optimizer/__init__.py b/components/factor_graph_optimizer/__init__.py new file mode 100644 index 0000000..924637c --- /dev/null +++ b/components/factor_graph_optimizer/__init__.py @@ -0,0 +1,5 @@ +from .base import FactorGraphOptimizerBase +from .factor_graph_optimizer import FactorGraphOptimizer + +__all__ = ["FactorGraphOptimizerBase", "FactorGraphOptimizer"] + diff --git a/components/factor_graph_optimizer/base.py b/components/factor_graph_optimizer/base.py new file mode 100644 index 0000000..16e08a9 --- /dev/null +++ b/components/factor_graph_optimizer/base.py @@ -0,0 +1,58 @@ +from abc import ABC, abstractmethod +from typing import Optional +import numpy as np + +from models.core import Pose, GPSPoint +from models.results import OptimizationResult, RefinedFrameResult +from models.processing import RelativePose + + +class FactorGraphOptimizerBase(ABC): + @abstractmethod + async def initialize_graph(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def add_odometry_factor( + self, + flight_id: str, + from_frame: int, + to_frame: int, + relative_pose: RelativePose, + ) -> bool: + pass + + @abstractmethod + async def add_gps_prior( + self, + flight_id: str, + frame_id: int, + gps: GPSPoint, + covariance: np.ndarray, + ) -> bool: + pass + + @abstractmethod + async def optimize( + self, flight_id: str, max_iterations: int = 100 + ) -> OptimizationResult: + pass + + @abstractmethod + async def get_optimized_poses( + self, flight_id: str + ) -> list[RefinedFrameResult]: + pass + + @abstractmethod + async def get_pose( + self, flight_id: str, frame_id: int + ) -> Optional[Pose]: + pass + + @abstractmethod + async def marginalize_old_poses( + self, flight_id: str, keep_last_n: int + ) -> bool: + pass + diff --git a/components/factor_graph_optimizer/factor_graph_optimizer.py b/components/factor_graph_optimizer/factor_graph_optimizer.py new file mode 100644 index 0000000..f8d24b7 --- /dev/null +++ b/components/factor_graph_optimizer/factor_graph_optimizer.py @@ -0,0 +1,51 @@ +from typing import Optional +import numpy as np + +from .base import FactorGraphOptimizerBase +from models.core import Pose, GPSPoint +from models.results import OptimizationResult, RefinedFrameResult +from models.processing import RelativePose + + +class FactorGraphOptimizer(FactorGraphOptimizerBase): + async def initialize_graph(self, flight_id: str) -> bool: + raise NotImplementedError + + async def add_odometry_factor( + self, + flight_id: str, + from_frame: int, + to_frame: int, + relative_pose: RelativePose, + ) -> bool: + raise NotImplementedError + + async def add_gps_prior( + self, + flight_id: str, + frame_id: int, + gps: GPSPoint, + covariance: np.ndarray, + ) -> bool: + raise NotImplementedError + + async def optimize( + self, flight_id: str, max_iterations: int = 100 + ) -> OptimizationResult: + raise NotImplementedError + + async def get_optimized_poses( + self, flight_id: str + ) -> list[RefinedFrameResult]: + raise NotImplementedError + + async def get_pose( + self, flight_id: str, frame_id: int + ) -> Optional[Pose]: + raise NotImplementedError + + async def marginalize_old_poses( + self, flight_id: str, keep_last_n: int + ) -> bool: + raise NotImplementedError + diff --git a/components/failure_recovery_coordinator/__init__.py b/components/failure_recovery_coordinator/__init__.py new file mode 100644 index 0000000..5ce98db --- /dev/null +++ b/components/failure_recovery_coordinator/__init__.py @@ -0,0 +1,5 @@ +from .base import FailureRecoveryCoordinatorBase +from .failure_recovery_coordinator import FailureRecoveryCoordinator + +__all__ = ["FailureRecoveryCoordinatorBase", "FailureRecoveryCoordinator"] + diff --git a/components/failure_recovery_coordinator/base.py b/components/failure_recovery_coordinator/base.py new file mode 100644 index 0000000..d16aec3 --- /dev/null +++ b/components/failure_recovery_coordinator/base.py @@ -0,0 +1,68 @@ +from abc import ABC, abstractmethod +from typing import Optional +import numpy as np + +from models.recovery import ( + SearchSession, + ConfidenceAssessment, + UserAnchor, + UserInputRequest, +) +from models.core import GPSPoint +from models.satellite import TileCandidate + + +class FailureRecoveryCoordinatorBase(ABC): + @abstractmethod + async def start_search_session( + self, + flight_id: str, + frame_id: int, + estimated_center: GPSPoint, + ) -> SearchSession: + pass + + @abstractmethod + async def expand_search( + self, session_id: str + ) -> Optional[list[TileCandidate]]: + pass + + @abstractmethod + async def assess_confidence( + self, flight_id: str, frame_id: int + ) -> ConfidenceAssessment: + pass + + @abstractmethod + async def request_user_input( + self, + flight_id: str, + frame_id: int, + uav_image: np.ndarray, + candidates: list[TileCandidate], + ) -> UserInputRequest: + pass + + @abstractmethod + async def process_user_anchor( + self, flight_id: str, anchor: UserAnchor + ) -> bool: + pass + + @abstractmethod + async def is_recovery_needed( + self, confidence: ConfidenceAssessment + ) -> bool: + pass + + @abstractmethod + async def get_active_session( + self, flight_id: str + ) -> Optional[SearchSession]: + pass + + @abstractmethod + async def cancel_session(self, session_id: str) -> bool: + pass + diff --git a/components/failure_recovery_coordinator/failure_recovery_coordinator.py b/components/failure_recovery_coordinator/failure_recovery_coordinator.py new file mode 100644 index 0000000..3a9bf08 --- /dev/null +++ b/components/failure_recovery_coordinator/failure_recovery_coordinator.py @@ -0,0 +1,60 @@ +from typing import Optional +import numpy as np + +from .base import FailureRecoveryCoordinatorBase +from models.recovery import ( + SearchSession, + ConfidenceAssessment, + UserAnchor, + UserInputRequest, +) +from models.core import GPSPoint +from models.satellite import TileCandidate + + +class FailureRecoveryCoordinator(FailureRecoveryCoordinatorBase): + async def start_search_session( + self, + flight_id: str, + frame_id: int, + estimated_center: GPSPoint, + ) -> SearchSession: + raise NotImplementedError + + async def expand_search( + self, session_id: str + ) -> Optional[list[TileCandidate]]: + raise NotImplementedError + + async def assess_confidence( + self, flight_id: str, frame_id: int + ) -> ConfidenceAssessment: + raise NotImplementedError + + async def request_user_input( + self, + flight_id: str, + frame_id: int, + uav_image: np.ndarray, + candidates: list[TileCandidate], + ) -> UserInputRequest: + raise NotImplementedError + + async def process_user_anchor( + self, flight_id: str, anchor: UserAnchor + ) -> bool: + raise NotImplementedError + + async def is_recovery_needed( + self, confidence: ConfidenceAssessment + ) -> bool: + raise NotImplementedError + + async def get_active_session( + self, flight_id: str + ) -> Optional[SearchSession]: + raise NotImplementedError + + async def cancel_session(self, session_id: str) -> bool: + raise NotImplementedError + diff --git a/components/flight_api/__init__.py b/components/flight_api/__init__.py new file mode 100644 index 0000000..eca8b8a --- /dev/null +++ b/components/flight_api/__init__.py @@ -0,0 +1,5 @@ +from .base import FlightAPIBase +from .flight_api import FlightAPI + +__all__ = ["FlightAPIBase", "FlightAPI"] + diff --git a/components/flight_api/base.py b/components/flight_api/base.py new file mode 100644 index 0000000..7b0ece5 --- /dev/null +++ b/components/flight_api/base.py @@ -0,0 +1,69 @@ +from abc import ABC, abstractmethod +from typing import AsyncIterator +from fastapi import UploadFile + +from models.api import ( + FlightCreateRequest, + FlightResponse, + FlightDetailResponse, + FlightStatusResponse, + DeleteResponse, + UpdateResponse, + BatchResponse, + UserFixRequest, + UserFixResponse, + ObjectGPSResponse, +) +from models.core import GPSPoint + + +class FlightAPIBase(ABC): + @abstractmethod + async def create_flight(self, request: FlightCreateRequest) -> FlightResponse: + pass + + @abstractmethod + async def get_flight(self, flight_id: str) -> FlightDetailResponse: + pass + + @abstractmethod + async def get_flight_status(self, flight_id: str) -> FlightStatusResponse: + pass + + @abstractmethod + async def delete_flight(self, flight_id: str) -> DeleteResponse: + pass + + @abstractmethod + async def update_waypoints( + self, flight_id: str, waypoints: list[GPSPoint] + ) -> UpdateResponse: + pass + + @abstractmethod + async def upload_batch( + self, + flight_id: str, + files: list[UploadFile], + start_sequence: int, + end_sequence: int, + batch_number: int, + ) -> BatchResponse: + pass + + @abstractmethod + async def submit_user_fix( + self, flight_id: str, request: UserFixRequest + ) -> UserFixResponse: + pass + + @abstractmethod + async def get_object_gps( + self, flight_id: str, frame_id: int, pixel: tuple[float, float] + ) -> ObjectGPSResponse: + pass + + @abstractmethod + def stream_events(self, flight_id: str) -> AsyncIterator[dict]: + pass + diff --git a/components/flight_api/flight_api.py b/components/flight_api/flight_api.py new file mode 100644 index 0000000..2ad99a1 --- /dev/null +++ b/components/flight_api/flight_api.py @@ -0,0 +1,61 @@ +from typing import AsyncIterator +from fastapi import UploadFile + +from .base import FlightAPIBase +from models.api import ( + FlightCreateRequest, + FlightResponse, + FlightDetailResponse, + FlightStatusResponse, + DeleteResponse, + UpdateResponse, + BatchResponse, + UserFixRequest, + UserFixResponse, + ObjectGPSResponse, +) +from models.core import GPSPoint + + +class FlightAPI(FlightAPIBase): + async def create_flight(self, request: FlightCreateRequest) -> FlightResponse: + raise NotImplementedError + + async def get_flight(self, flight_id: str) -> FlightDetailResponse: + raise NotImplementedError + + async def get_flight_status(self, flight_id: str) -> FlightStatusResponse: + raise NotImplementedError + + async def delete_flight(self, flight_id: str) -> DeleteResponse: + raise NotImplementedError + + async def update_waypoints( + self, flight_id: str, waypoints: list[GPSPoint] + ) -> UpdateResponse: + raise NotImplementedError + + async def upload_batch( + self, + flight_id: str, + files: list[UploadFile], + start_sequence: int, + end_sequence: int, + batch_number: int, + ) -> BatchResponse: + raise NotImplementedError + + async def submit_user_fix( + self, flight_id: str, request: UserFixRequest + ) -> UserFixResponse: + raise NotImplementedError + + async def get_object_gps( + self, flight_id: str, frame_id: int, pixel: tuple[float, float] + ) -> ObjectGPSResponse: + raise NotImplementedError + + async def stream_events(self, flight_id: str) -> AsyncIterator[dict]: + raise NotImplementedError + yield + diff --git a/components/flight_database/__init__.py b/components/flight_database/__init__.py new file mode 100644 index 0000000..ad55ad5 --- /dev/null +++ b/components/flight_database/__init__.py @@ -0,0 +1,5 @@ +from .base import FlightDatabaseBase +from .flight_database import FlightDatabase + +__all__ = ["FlightDatabaseBase", "FlightDatabase"] + diff --git a/components/flight_database/base.py b/components/flight_database/base.py new file mode 100644 index 0000000..eaef383 --- /dev/null +++ b/components/flight_database/base.py @@ -0,0 +1,70 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from models.flight import Flight, FlightState, Waypoint +from models.results import FrameResult +from models.chunks import ChunkHandle +from models.core import Pose + + +class FlightDatabaseBase(ABC): + @abstractmethod + async def create_flight(self, flight: Flight) -> str: + pass + + @abstractmethod + async def get_flight(self, flight_id: str) -> Optional[Flight]: + pass + + @abstractmethod + async def delete_flight(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def update_flight_state(self, state: FlightState) -> bool: + pass + + @abstractmethod + async def get_flight_state(self, flight_id: str) -> Optional[FlightState]: + pass + + @abstractmethod + async def save_frame_result(self, flight_id: str, result: FrameResult) -> bool: + pass + + @abstractmethod + async def get_frame_result( + self, flight_id: str, frame_id: int + ) -> Optional[FrameResult]: + pass + + @abstractmethod + async def get_all_frame_results(self, flight_id: str) -> list[FrameResult]: + pass + + @abstractmethod + async def save_chunk(self, chunk: ChunkHandle) -> bool: + pass + + @abstractmethod + async def get_chunk(self, chunk_id: str) -> Optional[ChunkHandle]: + pass + + @abstractmethod + async def get_flight_chunks(self, flight_id: str) -> list[ChunkHandle]: + pass + + @abstractmethod + async def save_pose(self, flight_id: str, pose: Pose) -> bool: + pass + + @abstractmethod + async def get_poses(self, flight_id: str) -> list[Pose]: + pass + + @abstractmethod + async def update_waypoints( + self, flight_id: str, waypoints: list[Waypoint] + ) -> bool: + pass + diff --git a/components/flight_database/flight_database.py b/components/flight_database/flight_database.py new file mode 100644 index 0000000..af0a1f3 --- /dev/null +++ b/components/flight_database/flight_database.py @@ -0,0 +1,56 @@ +from typing import Optional + +from .base import FlightDatabaseBase +from models.flight import Flight, FlightState, Waypoint +from models.results import FrameResult +from models.chunks import ChunkHandle +from models.core import Pose + + +class FlightDatabase(FlightDatabaseBase): + async def create_flight(self, flight: Flight) -> str: + raise NotImplementedError + + async def get_flight(self, flight_id: str) -> Optional[Flight]: + raise NotImplementedError + + async def delete_flight(self, flight_id: str) -> bool: + raise NotImplementedError + + async def update_flight_state(self, state: FlightState) -> bool: + raise NotImplementedError + + async def get_flight_state(self, flight_id: str) -> Optional[FlightState]: + raise NotImplementedError + + async def save_frame_result(self, flight_id: str, result: FrameResult) -> bool: + raise NotImplementedError + + async def get_frame_result( + self, flight_id: str, frame_id: int + ) -> Optional[FrameResult]: + raise NotImplementedError + + async def get_all_frame_results(self, flight_id: str) -> list[FrameResult]: + raise NotImplementedError + + async def save_chunk(self, chunk: ChunkHandle) -> bool: + raise NotImplementedError + + async def get_chunk(self, chunk_id: str) -> Optional[ChunkHandle]: + raise NotImplementedError + + async def get_flight_chunks(self, flight_id: str) -> list[ChunkHandle]: + raise NotImplementedError + + async def save_pose(self, flight_id: str, pose: Pose) -> bool: + raise NotImplementedError + + async def get_poses(self, flight_id: str) -> list[Pose]: + raise NotImplementedError + + async def update_waypoints( + self, flight_id: str, waypoints: list[Waypoint] + ) -> bool: + raise NotImplementedError + diff --git a/components/flight_lifecycle_manager/__init__.py b/components/flight_lifecycle_manager/__init__.py new file mode 100644 index 0000000..6c7e228 --- /dev/null +++ b/components/flight_lifecycle_manager/__init__.py @@ -0,0 +1,5 @@ +from .base import FlightLifecycleManagerBase +from .flight_lifecycle_manager import FlightLifecycleManager + +__all__ = ["FlightLifecycleManagerBase", "FlightLifecycleManager"] + diff --git a/components/flight_lifecycle_manager/base.py b/components/flight_lifecycle_manager/base.py new file mode 100644 index 0000000..e5311a1 --- /dev/null +++ b/components/flight_lifecycle_manager/base.py @@ -0,0 +1,46 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from models.flight import Flight, Waypoint +from models.core import GPSPoint + + +class FlightLifecycleManagerBase(ABC): + @abstractmethod + async def create_flight( + self, + name: str, + description: str, + start_gps: GPSPoint, + rough_waypoints: list[GPSPoint], + camera_params: dict, + altitude: float, + ) -> Flight: + pass + + @abstractmethod + async def delete_flight(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def update_waypoints( + self, flight_id: str, waypoints: list[Waypoint] + ) -> bool: + pass + + @abstractmethod + async def get_flight(self, flight_id: str) -> Optional[Flight]: + pass + + @abstractmethod + async def start_processing(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def stop_processing(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def get_processing_status(self, flight_id: str) -> dict: + pass + diff --git a/components/flight_lifecycle_manager/flight_lifecycle_manager.py b/components/flight_lifecycle_manager/flight_lifecycle_manager.py new file mode 100644 index 0000000..56cffbc --- /dev/null +++ b/components/flight_lifecycle_manager/flight_lifecycle_manager.py @@ -0,0 +1,39 @@ +from typing import Optional + +from .base import FlightLifecycleManagerBase +from models.flight import Flight, Waypoint +from models.core import GPSPoint + + +class FlightLifecycleManager(FlightLifecycleManagerBase): + async def create_flight( + self, + name: str, + description: str, + start_gps: GPSPoint, + rough_waypoints: list[GPSPoint], + camera_params: dict, + altitude: float, + ) -> Flight: + raise NotImplementedError + + async def delete_flight(self, flight_id: str) -> bool: + raise NotImplementedError + + async def update_waypoints( + self, flight_id: str, waypoints: list[Waypoint] + ) -> bool: + raise NotImplementedError + + async def get_flight(self, flight_id: str) -> Optional[Flight]: + raise NotImplementedError + + async def start_processing(self, flight_id: str) -> bool: + raise NotImplementedError + + async def stop_processing(self, flight_id: str) -> bool: + raise NotImplementedError + + async def get_processing_status(self, flight_id: str) -> dict: + raise NotImplementedError + diff --git a/components/flight_processing_engine/__init__.py b/components/flight_processing_engine/__init__.py new file mode 100644 index 0000000..b6b2d14 --- /dev/null +++ b/components/flight_processing_engine/__init__.py @@ -0,0 +1,5 @@ +from .base import FlightProcessingEngineBase +from .flight_processing_engine import FlightProcessingEngine + +__all__ = ["FlightProcessingEngineBase", "FlightProcessingEngine"] + diff --git a/components/flight_processing_engine/base.py b/components/flight_processing_engine/base.py new file mode 100644 index 0000000..3a7d9db --- /dev/null +++ b/components/flight_processing_engine/base.py @@ -0,0 +1,41 @@ +from abc import ABC, abstractmethod +from typing import Optional +import numpy as np + +from models.images import ImageData +from models.results import FrameResult +from models.processing import RelativePose +from models.recovery import UserAnchor + + +class FlightProcessingEngineBase(ABC): + @abstractmethod + async def process_frame( + self, flight_id: str, image: ImageData + ) -> Optional[FrameResult]: + pass + + @abstractmethod + async def get_relative_pose( + self, prev_image: np.ndarray, curr_image: np.ndarray + ) -> RelativePose: + pass + + @abstractmethod + async def apply_user_anchor( + self, flight_id: str, frame_id: int, anchor: UserAnchor + ) -> bool: + pass + + @abstractmethod + async def is_blocked(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def resume_processing(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def get_current_chunk_id(self, flight_id: str) -> Optional[str]: + pass + diff --git a/components/flight_processing_engine/flight_processing_engine.py b/components/flight_processing_engine/flight_processing_engine.py new file mode 100644 index 0000000..6ad7bae --- /dev/null +++ b/components/flight_processing_engine/flight_processing_engine.py @@ -0,0 +1,35 @@ +from typing import Optional +import numpy as np + +from .base import FlightProcessingEngineBase +from models.images import ImageData +from models.results import FrameResult +from models.processing import RelativePose +from models.recovery import UserAnchor + + +class FlightProcessingEngine(FlightProcessingEngineBase): + async def process_frame( + self, flight_id: str, image: ImageData + ) -> Optional[FrameResult]: + raise NotImplementedError + + async def get_relative_pose( + self, prev_image: np.ndarray, curr_image: np.ndarray + ) -> RelativePose: + raise NotImplementedError + + async def apply_user_anchor( + self, flight_id: str, frame_id: int, anchor: UserAnchor + ) -> bool: + raise NotImplementedError + + async def is_blocked(self, flight_id: str) -> bool: + raise NotImplementedError + + async def resume_processing(self, flight_id: str) -> bool: + raise NotImplementedError + + async def get_current_chunk_id(self, flight_id: str) -> Optional[str]: + raise NotImplementedError + diff --git a/components/global_place_recognition/__init__.py b/components/global_place_recognition/__init__.py new file mode 100644 index 0000000..466743c --- /dev/null +++ b/components/global_place_recognition/__init__.py @@ -0,0 +1,5 @@ +from .base import GlobalPlaceRecognitionBase +from .global_place_recognition import GlobalPlaceRecognition + +__all__ = ["GlobalPlaceRecognitionBase", "GlobalPlaceRecognition"] + diff --git a/components/global_place_recognition/base.py b/components/global_place_recognition/base.py new file mode 100644 index 0000000..33d3fc5 --- /dev/null +++ b/components/global_place_recognition/base.py @@ -0,0 +1,45 @@ +from abc import ABC, abstractmethod +import numpy as np + +from models.core import GPSPoint +from models.satellite import TileCandidate +from models.processing import AlignmentResult + + +class GlobalPlaceRecognitionBase(ABC): + @abstractmethod + async def extract_global_descriptor(self, image: np.ndarray) -> np.ndarray: + pass + + @abstractmethod + async def search_candidates( + self, + descriptor: np.ndarray, + search_center: GPSPoint, + search_radius: float, + top_k: int = 10, + ) -> list[TileCandidate]: + pass + + @abstractmethod + async def verify_candidate( + self, uav_image: np.ndarray, satellite_image: np.ndarray + ) -> AlignmentResult: + pass + + @abstractmethod + async def index_tile( + self, tile_image: np.ndarray, tile_id: str, gps_center: GPSPoint + ) -> bool: + pass + + @abstractmethod + async def build_index_for_area( + self, nw: GPSPoint, se: GPSPoint, zoom: int + ) -> int: + pass + + @abstractmethod + async def get_indexed_tile_count(self) -> int: + pass + diff --git a/components/global_place_recognition/global_place_recognition.py b/components/global_place_recognition/global_place_recognition.py new file mode 100644 index 0000000..fca6f8f --- /dev/null +++ b/components/global_place_recognition/global_place_recognition.py @@ -0,0 +1,39 @@ +import numpy as np + +from .base import GlobalPlaceRecognitionBase +from models.core import GPSPoint +from models.satellite import TileCandidate +from models.processing import AlignmentResult + + +class GlobalPlaceRecognition(GlobalPlaceRecognitionBase): + async def extract_global_descriptor(self, image: np.ndarray) -> np.ndarray: + raise NotImplementedError + + async def search_candidates( + self, + descriptor: np.ndarray, + search_center: GPSPoint, + search_radius: float, + top_k: int = 10, + ) -> list[TileCandidate]: + raise NotImplementedError + + async def verify_candidate( + self, uav_image: np.ndarray, satellite_image: np.ndarray + ) -> AlignmentResult: + raise NotImplementedError + + async def index_tile( + self, tile_image: np.ndarray, tile_id: str, gps_center: GPSPoint + ) -> bool: + raise NotImplementedError + + async def build_index_for_area( + self, nw: GPSPoint, se: GPSPoint, zoom: int + ) -> int: + raise NotImplementedError + + async def get_indexed_tile_count(self) -> int: + raise NotImplementedError + diff --git a/components/image_input_pipeline/__init__.py b/components/image_input_pipeline/__init__.py new file mode 100644 index 0000000..82b765e --- /dev/null +++ b/components/image_input_pipeline/__init__.py @@ -0,0 +1,5 @@ +from .base import ImageInputPipelineBase +from .image_input_pipeline import ImageInputPipeline + +__all__ = ["ImageInputPipelineBase", "ImageInputPipeline"] + diff --git a/components/image_input_pipeline/base.py b/components/image_input_pipeline/base.py new file mode 100644 index 0000000..a545f99 --- /dev/null +++ b/components/image_input_pipeline/base.py @@ -0,0 +1,34 @@ +from abc import ABC, abstractmethod +from typing import Optional, AsyncIterator + +from models.images import ImageData, ImageBatch, ProcessingStatus +from models.core import ValidationResult + + +class ImageInputPipelineBase(ABC): + @abstractmethod + async def receive_batch( + self, flight_id: str, batch: ImageBatch + ) -> ValidationResult: + pass + + @abstractmethod + async def get_next_image(self, flight_id: str) -> Optional[ImageData]: + pass + + @abstractmethod + def stream_images(self, flight_id: str) -> AsyncIterator[ImageData]: + pass + + @abstractmethod + async def get_status(self, flight_id: str) -> ProcessingStatus: + pass + + @abstractmethod + async def clear_queue(self, flight_id: str) -> bool: + pass + + @abstractmethod + async def get_queue_size(self, flight_id: str) -> int: + pass + diff --git a/components/image_input_pipeline/image_input_pipeline.py b/components/image_input_pipeline/image_input_pipeline.py new file mode 100644 index 0000000..62954bb --- /dev/null +++ b/components/image_input_pipeline/image_input_pipeline.py @@ -0,0 +1,29 @@ +from typing import Optional, AsyncIterator + +from .base import ImageInputPipelineBase +from models.images import ImageData, ImageBatch, ProcessingStatus +from models.core import ValidationResult + + +class ImageInputPipeline(ImageInputPipelineBase): + async def receive_batch( + self, flight_id: str, batch: ImageBatch + ) -> ValidationResult: + raise NotImplementedError + + async def get_next_image(self, flight_id: str) -> Optional[ImageData]: + raise NotImplementedError + + async def stream_images(self, flight_id: str) -> AsyncIterator[ImageData]: + raise NotImplementedError + yield + + async def get_status(self, flight_id: str) -> ProcessingStatus: + raise NotImplementedError + + async def clear_queue(self, flight_id: str) -> bool: + raise NotImplementedError + + async def get_queue_size(self, flight_id: str) -> int: + raise NotImplementedError + diff --git a/components/image_rotation_manager/__init__.py b/components/image_rotation_manager/__init__.py new file mode 100644 index 0000000..a77c03b --- /dev/null +++ b/components/image_rotation_manager/__init__.py @@ -0,0 +1,5 @@ +from .base import ImageRotationManagerBase +from .image_rotation_manager import ImageRotationManager + +__all__ = ["ImageRotationManagerBase", "ImageRotationManager"] + diff --git a/components/image_rotation_manager/base.py b/components/image_rotation_manager/base.py new file mode 100644 index 0000000..6c65ee4 --- /dev/null +++ b/components/image_rotation_manager/base.py @@ -0,0 +1,41 @@ +from abc import ABC, abstractmethod +from typing import Optional +import numpy as np + +from models.processing import RotationResult +from models.flight import HeadingRecord + + +class ImageRotationManagerBase(ABC): + @abstractmethod + async def estimate_rotation( + self, uav_image: np.ndarray, satellite_image: np.ndarray + ) -> RotationResult: + pass + + @abstractmethod + async def get_rotation_for_frame( + self, flight_id: str, frame_id: int + ) -> Optional[float]: + pass + + @abstractmethod + async def update_heading_history( + self, flight_id: str, record: HeadingRecord + ) -> None: + pass + + @abstractmethod + async def predict_heading(self, flight_id: str) -> Optional[float]: + pass + + @abstractmethod + async def is_sharp_turn( + self, flight_id: str, current_heading: float + ) -> bool: + pass + + @abstractmethod + def rotate_image(self, image: np.ndarray, angle: float) -> np.ndarray: + pass + diff --git a/components/image_rotation_manager/image_rotation_manager.py b/components/image_rotation_manager/image_rotation_manager.py new file mode 100644 index 0000000..a5f8c16 --- /dev/null +++ b/components/image_rotation_manager/image_rotation_manager.py @@ -0,0 +1,35 @@ +from typing import Optional +import numpy as np + +from .base import ImageRotationManagerBase +from models.processing import RotationResult +from models.flight import HeadingRecord + + +class ImageRotationManager(ImageRotationManagerBase): + async def estimate_rotation( + self, uav_image: np.ndarray, satellite_image: np.ndarray + ) -> RotationResult: + raise NotImplementedError + + async def get_rotation_for_frame( + self, flight_id: str, frame_id: int + ) -> Optional[float]: + raise NotImplementedError + + async def update_heading_history( + self, flight_id: str, record: HeadingRecord + ) -> None: + raise NotImplementedError + + async def predict_heading(self, flight_id: str) -> Optional[float]: + raise NotImplementedError + + async def is_sharp_turn( + self, flight_id: str, current_heading: float + ) -> bool: + raise NotImplementedError + + def rotate_image(self, image: np.ndarray, angle: float) -> np.ndarray: + raise NotImplementedError + diff --git a/components/metric_refinement/__init__.py b/components/metric_refinement/__init__.py new file mode 100644 index 0000000..fffa4a8 --- /dev/null +++ b/components/metric_refinement/__init__.py @@ -0,0 +1,5 @@ +from .base import MetricRefinementBase +from .metric_refinement import MetricRefinement + +__all__ = ["MetricRefinementBase", "MetricRefinement"] + diff --git a/components/metric_refinement/base.py b/components/metric_refinement/base.py new file mode 100644 index 0000000..c57c330 --- /dev/null +++ b/components/metric_refinement/base.py @@ -0,0 +1,43 @@ +from abc import ABC, abstractmethod +import numpy as np + +from models.core import GPSPoint +from models.processing import AlignmentResult + + +class MetricRefinementBase(ABC): + @abstractmethod + async def refine_alignment( + self, + uav_image: np.ndarray, + satellite_image: np.ndarray, + initial_homography: np.ndarray, + ) -> AlignmentResult: + pass + + @abstractmethod + async def compute_precise_gps( + self, + uav_center_pixel: tuple[float, float], + homography: np.ndarray, + tile_bounds: dict, + ) -> GPSPoint: + pass + + @abstractmethod + async def estimate_reprojection_error( + self, + correspondences: np.ndarray, + homography: np.ndarray, + ) -> float: + pass + + @abstractmethod + async def filter_outliers( + self, + correspondences: np.ndarray, + homography: np.ndarray, + threshold: float = 3.0, + ) -> np.ndarray: + pass + diff --git a/components/metric_refinement/metric_refinement.py b/components/metric_refinement/metric_refinement.py new file mode 100644 index 0000000..efb70f1 --- /dev/null +++ b/components/metric_refinement/metric_refinement.py @@ -0,0 +1,39 @@ +import numpy as np + +from .base import MetricRefinementBase +from models.core import GPSPoint +from models.processing import AlignmentResult + + +class MetricRefinement(MetricRefinementBase): + async def refine_alignment( + self, + uav_image: np.ndarray, + satellite_image: np.ndarray, + initial_homography: np.ndarray, + ) -> AlignmentResult: + raise NotImplementedError + + async def compute_precise_gps( + self, + uav_center_pixel: tuple[float, float], + homography: np.ndarray, + tile_bounds: dict, + ) -> GPSPoint: + raise NotImplementedError + + async def estimate_reprojection_error( + self, + correspondences: np.ndarray, + homography: np.ndarray, + ) -> float: + raise NotImplementedError + + async def filter_outliers( + self, + correspondences: np.ndarray, + homography: np.ndarray, + threshold: float = 3.0, + ) -> np.ndarray: + raise NotImplementedError + diff --git a/components/model_manager/__init__.py b/components/model_manager/__init__.py new file mode 100644 index 0000000..fb7f805 --- /dev/null +++ b/components/model_manager/__init__.py @@ -0,0 +1,5 @@ +from .base import ModelManagerBase +from .model_manager import ModelManager + +__all__ = ["ModelManagerBase", "ModelManager"] + diff --git a/components/model_manager/base.py b/components/model_manager/base.py new file mode 100644 index 0000000..8ecbae1 --- /dev/null +++ b/components/model_manager/base.py @@ -0,0 +1,40 @@ +from abc import ABC, abstractmethod +from typing import Optional, Any +import numpy as np + +from models.config import ModelConfig + + +class ModelManagerBase(ABC): + @abstractmethod + async def load_model(self, config: ModelConfig) -> bool: + pass + + @abstractmethod + async def unload_model(self, model_name: str) -> bool: + pass + + @abstractmethod + async def get_model(self, model_name: str) -> Optional[Any]: + pass + + @abstractmethod + async def run_inference( + self, model_name: str, inputs: dict[str, np.ndarray] + ) -> dict[str, np.ndarray]: + pass + + @abstractmethod + async def warmup_model( + self, model_name: str, iterations: int = 3 + ) -> bool: + pass + + @abstractmethod + async def get_loaded_models(self) -> list[str]: + pass + + @abstractmethod + async def get_model_info(self, model_name: str) -> Optional[dict]: + pass + diff --git a/components/model_manager/model_manager.py b/components/model_manager/model_manager.py new file mode 100644 index 0000000..675d362 --- /dev/null +++ b/components/model_manager/model_manager.py @@ -0,0 +1,33 @@ +from typing import Optional, Any +import numpy as np + +from .base import ModelManagerBase +from models.config import ModelConfig + + +class ModelManager(ModelManagerBase): + async def load_model(self, config: ModelConfig) -> bool: + raise NotImplementedError + + async def unload_model(self, model_name: str) -> bool: + raise NotImplementedError + + async def get_model(self, model_name: str) -> Optional[Any]: + raise NotImplementedError + + async def run_inference( + self, model_name: str, inputs: dict[str, np.ndarray] + ) -> dict[str, np.ndarray]: + raise NotImplementedError + + async def warmup_model( + self, model_name: str, iterations: int = 3 + ) -> bool: + raise NotImplementedError + + async def get_loaded_models(self) -> list[str]: + raise NotImplementedError + + async def get_model_info(self, model_name: str) -> Optional[dict]: + raise NotImplementedError + diff --git a/components/result_manager/__init__.py b/components/result_manager/__init__.py new file mode 100644 index 0000000..059d95e --- /dev/null +++ b/components/result_manager/__init__.py @@ -0,0 +1,5 @@ +from .base import ResultManagerBase +from .result_manager import ResultManager + +__all__ = ["ResultManagerBase", "ResultManager"] + diff --git a/components/result_manager/base.py b/components/result_manager/base.py new file mode 100644 index 0000000..32e61a2 --- /dev/null +++ b/components/result_manager/base.py @@ -0,0 +1,49 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from models.results import FrameResult, FlightResults, RefinedFrameResult +from models.core import GPSPoint + + +class ResultManagerBase(ABC): + @abstractmethod + async def save_frame_result( + self, flight_id: str, result: FrameResult + ) -> bool: + pass + + @abstractmethod + async def get_frame_result( + self, flight_id: str, frame_id: int + ) -> Optional[FrameResult]: + pass + + @abstractmethod + async def get_flight_results(self, flight_id: str) -> FlightResults: + pass + + @abstractmethod + async def update_refined_result( + self, flight_id: str, result: RefinedFrameResult + ) -> bool: + pass + + @abstractmethod + async def get_object_gps( + self, + flight_id: str, + frame_id: int, + pixel: tuple[float, float], + ) -> GPSPoint: + pass + + @abstractmethod + async def export_results( + self, flight_id: str, format: str = "json" + ) -> bytes: + pass + + @abstractmethod + async def get_statistics(self, flight_id: str) -> dict: + pass + diff --git a/components/result_manager/result_manager.py b/components/result_manager/result_manager.py new file mode 100644 index 0000000..6730189 --- /dev/null +++ b/components/result_manager/result_manager.py @@ -0,0 +1,42 @@ +from typing import Optional + +from .base import ResultManagerBase +from models.results import FrameResult, FlightResults, RefinedFrameResult +from models.core import GPSPoint + + +class ResultManager(ResultManagerBase): + async def save_frame_result( + self, flight_id: str, result: FrameResult + ) -> bool: + raise NotImplementedError + + async def get_frame_result( + self, flight_id: str, frame_id: int + ) -> Optional[FrameResult]: + raise NotImplementedError + + async def get_flight_results(self, flight_id: str) -> FlightResults: + raise NotImplementedError + + async def update_refined_result( + self, flight_id: str, result: RefinedFrameResult + ) -> bool: + raise NotImplementedError + + async def get_object_gps( + self, + flight_id: str, + frame_id: int, + pixel: tuple[float, float], + ) -> GPSPoint: + raise NotImplementedError + + async def export_results( + self, flight_id: str, format: str = "json" + ) -> bytes: + raise NotImplementedError + + async def get_statistics(self, flight_id: str) -> dict: + raise NotImplementedError + diff --git a/components/route_chunk_manager/__init__.py b/components/route_chunk_manager/__init__.py new file mode 100644 index 0000000..2a6fc24 --- /dev/null +++ b/components/route_chunk_manager/__init__.py @@ -0,0 +1,5 @@ +from .base import RouteChunkManagerBase +from .route_chunk_manager import RouteChunkManager + +__all__ = ["RouteChunkManagerBase", "RouteChunkManager"] + diff --git a/components/route_chunk_manager/base.py b/components/route_chunk_manager/base.py new file mode 100644 index 0000000..abe7d6c --- /dev/null +++ b/components/route_chunk_manager/base.py @@ -0,0 +1,65 @@ +from abc import ABC, abstractmethod +from typing import Optional + +from models.chunks import ChunkHandle, ChunkBounds, Sim3Transform +from models.core import GPSPoint +from models.processing import ChunkAlignmentResult + + +class RouteChunkManagerBase(ABC): + @abstractmethod + async def create_chunk( + self, flight_id: str, start_frame_id: int + ) -> ChunkHandle: + pass + + @abstractmethod + async def add_frame_to_chunk( + self, chunk_id: str, frame_id: int + ) -> bool: + pass + + @abstractmethod + async def finalize_chunk(self, chunk_id: str) -> bool: + pass + + @abstractmethod + async def get_chunk(self, chunk_id: str) -> Optional[ChunkHandle]: + pass + + @abstractmethod + async def get_active_chunk( + self, flight_id: str + ) -> Optional[ChunkHandle]: + pass + + @abstractmethod + async def get_flight_chunks( + self, flight_id: str + ) -> list[ChunkHandle]: + pass + + @abstractmethod + async def estimate_chunk_bounds( + self, chunk_id: str + ) -> ChunkBounds: + pass + + @abstractmethod + async def anchor_chunk( + self, chunk_id: str, frame_id: int, gps: GPSPoint + ) -> bool: + pass + + @abstractmethod + async def match_chunk_to_satellite( + self, chunk_id: str + ) -> Optional[ChunkAlignmentResult]: + pass + + @abstractmethod + async def apply_transform_to_chunk( + self, chunk_id: str, transform: Sim3Transform + ) -> bool: + pass + diff --git a/components/route_chunk_manager/route_chunk_manager.py b/components/route_chunk_manager/route_chunk_manager.py new file mode 100644 index 0000000..f2a1fbe --- /dev/null +++ b/components/route_chunk_manager/route_chunk_manager.py @@ -0,0 +1,55 @@ +from typing import Optional + +from .base import RouteChunkManagerBase +from models.chunks import ChunkHandle, ChunkBounds, Sim3Transform +from models.core import GPSPoint +from models.processing import ChunkAlignmentResult + + +class RouteChunkManager(RouteChunkManagerBase): + async def create_chunk( + self, flight_id: str, start_frame_id: int + ) -> ChunkHandle: + raise NotImplementedError + + async def add_frame_to_chunk( + self, chunk_id: str, frame_id: int + ) -> bool: + raise NotImplementedError + + async def finalize_chunk(self, chunk_id: str) -> bool: + raise NotImplementedError + + async def get_chunk(self, chunk_id: str) -> Optional[ChunkHandle]: + raise NotImplementedError + + async def get_active_chunk( + self, flight_id: str + ) -> Optional[ChunkHandle]: + raise NotImplementedError + + async def get_flight_chunks( + self, flight_id: str + ) -> list[ChunkHandle]: + raise NotImplementedError + + async def estimate_chunk_bounds( + self, chunk_id: str + ) -> ChunkBounds: + raise NotImplementedError + + async def anchor_chunk( + self, chunk_id: str, frame_id: int, gps: GPSPoint + ) -> bool: + raise NotImplementedError + + async def match_chunk_to_satellite( + self, chunk_id: str + ) -> Optional[ChunkAlignmentResult]: + raise NotImplementedError + + async def apply_transform_to_chunk( + self, chunk_id: str, transform: Sim3Transform + ) -> bool: + raise NotImplementedError + diff --git a/components/satellite_data_manager/__init__.py b/components/satellite_data_manager/__init__.py new file mode 100644 index 0000000..3380cff --- /dev/null +++ b/components/satellite_data_manager/__init__.py @@ -0,0 +1,5 @@ +from .base import SatelliteDataManagerBase +from .satellite_data_manager import SatelliteDataManager + +__all__ = ["SatelliteDataManagerBase", "SatelliteDataManager"] + diff --git a/components/satellite_data_manager/base.py b/components/satellite_data_manager/base.py new file mode 100644 index 0000000..6ef5497 --- /dev/null +++ b/components/satellite_data_manager/base.py @@ -0,0 +1,45 @@ +from abc import ABC, abstractmethod +from typing import Optional +import numpy as np + +from models.core import GPSPoint +from models.satellite import TileCoords, TileBounds + + +class SatelliteDataManagerBase(ABC): + @abstractmethod + async def get_tile( + self, gps: GPSPoint, zoom: int + ) -> Optional[tuple[np.ndarray, TileBounds]]: + pass + + @abstractmethod + async def get_tile_by_coords( + self, coords: TileCoords + ) -> Optional[tuple[np.ndarray, TileBounds]]: + pass + + @abstractmethod + async def get_tiles_in_radius( + self, center: GPSPoint, radius_meters: float, zoom: int + ) -> list[tuple[np.ndarray, TileBounds]]: + pass + + @abstractmethod + async def get_tile_bounds(self, coords: TileCoords) -> TileBounds: + pass + + @abstractmethod + async def prefetch_area( + self, nw: GPSPoint, se: GPSPoint, zoom: int + ) -> int: + pass + + @abstractmethod + def gps_to_tile_coords(self, gps: GPSPoint, zoom: int) -> TileCoords: + pass + + @abstractmethod + def tile_coords_to_gps(self, coords: TileCoords) -> GPSPoint: + pass + diff --git a/components/satellite_data_manager/satellite_data_manager.py b/components/satellite_data_manager/satellite_data_manager.py new file mode 100644 index 0000000..fde75aa --- /dev/null +++ b/components/satellite_data_manager/satellite_data_manager.py @@ -0,0 +1,38 @@ +from typing import Optional +import numpy as np + +from .base import SatelliteDataManagerBase +from models.core import GPSPoint +from models.satellite import TileCoords, TileBounds + + +class SatelliteDataManager(SatelliteDataManagerBase): + async def get_tile( + self, gps: GPSPoint, zoom: int + ) -> Optional[tuple[np.ndarray, TileBounds]]: + raise NotImplementedError + + async def get_tile_by_coords( + self, coords: TileCoords + ) -> Optional[tuple[np.ndarray, TileBounds]]: + raise NotImplementedError + + async def get_tiles_in_radius( + self, center: GPSPoint, radius_meters: float, zoom: int + ) -> list[tuple[np.ndarray, TileBounds]]: + raise NotImplementedError + + async def get_tile_bounds(self, coords: TileCoords) -> TileBounds: + raise NotImplementedError + + async def prefetch_area( + self, nw: GPSPoint, se: GPSPoint, zoom: int + ) -> int: + raise NotImplementedError + + def gps_to_tile_coords(self, gps: GPSPoint, zoom: int) -> TileCoords: + raise NotImplementedError + + def tile_coords_to_gps(self, coords: TileCoords) -> GPSPoint: + raise NotImplementedError + diff --git a/components/sequential_visual_odometry/__init__.py b/components/sequential_visual_odometry/__init__.py new file mode 100644 index 0000000..52d74b0 --- /dev/null +++ b/components/sequential_visual_odometry/__init__.py @@ -0,0 +1,5 @@ +from .base import SequentialVisualOdometryBase +from .sequential_visual_odometry import SequentialVisualOdometry + +__all__ = ["SequentialVisualOdometryBase", "SequentialVisualOdometry"] + diff --git a/components/sequential_visual_odometry/base.py b/components/sequential_visual_odometry/base.py new file mode 100644 index 0000000..952f96f --- /dev/null +++ b/components/sequential_visual_odometry/base.py @@ -0,0 +1,39 @@ +from abc import ABC, abstractmethod +import numpy as np + +from models.processing import RelativePose, Matches + + +class SequentialVisualOdometryBase(ABC): + @abstractmethod + async def compute_relative_pose( + self, prev_image: np.ndarray, curr_image: np.ndarray + ) -> RelativePose: + pass + + @abstractmethod + async def extract_keypoints( + self, image: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + pass + + @abstractmethod + async def match_features( + self, + keypoints1: np.ndarray, + descriptors1: np.ndarray, + keypoints2: np.ndarray, + descriptors2: np.ndarray, + ) -> Matches: + pass + + @abstractmethod + async def estimate_motion( + self, matches: Matches, camera_matrix: np.ndarray + ) -> RelativePose: + pass + + @abstractmethod + def is_tracking_good(self, pose: RelativePose) -> bool: + pass + diff --git a/components/sequential_visual_odometry/sequential_visual_odometry.py b/components/sequential_visual_odometry/sequential_visual_odometry.py new file mode 100644 index 0000000..ea48339 --- /dev/null +++ b/components/sequential_visual_odometry/sequential_visual_odometry.py @@ -0,0 +1,34 @@ +import numpy as np + +from .base import SequentialVisualOdometryBase +from models.processing import RelativePose, Matches + + +class SequentialVisualOdometry(SequentialVisualOdometryBase): + async def compute_relative_pose( + self, prev_image: np.ndarray, curr_image: np.ndarray + ) -> RelativePose: + raise NotImplementedError + + async def extract_keypoints( + self, image: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + raise NotImplementedError + + async def match_features( + self, + keypoints1: np.ndarray, + descriptors1: np.ndarray, + keypoints2: np.ndarray, + descriptors2: np.ndarray, + ) -> Matches: + raise NotImplementedError + + async def estimate_motion( + self, matches: Matches, camera_matrix: np.ndarray + ) -> RelativePose: + raise NotImplementedError + + def is_tracking_good(self, pose: RelativePose) -> bool: + raise NotImplementedError + diff --git a/components/sse_event_streamer/__init__.py b/components/sse_event_streamer/__init__.py new file mode 100644 index 0000000..c0e19bd --- /dev/null +++ b/components/sse_event_streamer/__init__.py @@ -0,0 +1,5 @@ +from .base import SSEEventStreamerBase +from .sse_event_streamer import SSEEventStreamer + +__all__ = ["SSEEventStreamerBase", "SSEEventStreamer"] + diff --git a/components/sse_event_streamer/base.py b/components/sse_event_streamer/base.py new file mode 100644 index 0000000..7498970 --- /dev/null +++ b/components/sse_event_streamer/base.py @@ -0,0 +1,44 @@ +from abc import ABC, abstractmethod +from typing import AsyncIterator + +from models.results import FrameResult +from models.recovery import UserInputRequest + + +class SSEEventStreamerBase(ABC): + @abstractmethod + async def emit_frame_result( + self, flight_id: str, result: FrameResult + ) -> None: + pass + + @abstractmethod + async def emit_status_update( + self, flight_id: str, status: dict + ) -> None: + pass + + @abstractmethod + async def emit_user_input_request( + self, flight_id: str, request: UserInputRequest + ) -> None: + pass + + @abstractmethod + async def emit_error( + self, flight_id: str, error: str + ) -> None: + pass + + @abstractmethod + def subscribe(self, flight_id: str) -> AsyncIterator[dict]: + pass + + @abstractmethod + async def unsubscribe(self, flight_id: str, subscriber_id: str) -> None: + pass + + @abstractmethod + async def get_subscriber_count(self, flight_id: str) -> int: + pass + diff --git a/components/sse_event_streamer/sse_event_streamer.py b/components/sse_event_streamer/sse_event_streamer.py new file mode 100644 index 0000000..44e3c5e --- /dev/null +++ b/components/sse_event_streamer/sse_event_streamer.py @@ -0,0 +1,38 @@ +from typing import AsyncIterator + +from .base import SSEEventStreamerBase +from models.results import FrameResult +from models.recovery import UserInputRequest + + +class SSEEventStreamer(SSEEventStreamerBase): + async def emit_frame_result( + self, flight_id: str, result: FrameResult + ) -> None: + raise NotImplementedError + + async def emit_status_update( + self, flight_id: str, status: dict + ) -> None: + raise NotImplementedError + + async def emit_user_input_request( + self, flight_id: str, request: UserInputRequest + ) -> None: + raise NotImplementedError + + async def emit_error( + self, flight_id: str, error: str + ) -> None: + raise NotImplementedError + + async def subscribe(self, flight_id: str) -> AsyncIterator[dict]: + raise NotImplementedError + yield + + async def unsubscribe(self, flight_id: str, subscriber_id: str) -> None: + raise NotImplementedError + + async def get_subscriber_count(self, flight_id: str) -> int: + raise NotImplementedError + diff --git a/db/__init__.py b/db/__init__.py new file mode 100644 index 0000000..d1b0373 --- /dev/null +++ b/db/__init__.py @@ -0,0 +1,14 @@ +from .connection import get_engine, get_session, init_db +from .models import Base, FlightModel, FrameResultModel, ChunkModel, WaypointModel + +__all__ = [ + "get_engine", + "get_session", + "init_db", + "Base", + "FlightModel", + "FrameResultModel", + "ChunkModel", + "WaypointModel", +] + diff --git a/db/connection.py b/db/connection.py new file mode 100644 index 0000000..596a237 --- /dev/null +++ b/db/connection.py @@ -0,0 +1,49 @@ +from typing import AsyncGenerator +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker +from sqlalchemy.orm import DeclarativeBase + +from models.config import DatabaseConfig + + +class Base(DeclarativeBase): + pass + + +_engine = None +_session_factory = None + + +def get_engine(config: DatabaseConfig): + global _engine + if _engine is None: + url = f"postgresql+asyncpg://{config.username}:{config.password}@{config.host}:{config.port}/{config.database}" + _engine = create_async_engine( + url, + pool_size=config.pool_size, + max_overflow=config.max_overflow, + pool_timeout=config.pool_timeout, + pool_recycle=config.pool_recycle, + ) + return _engine + + +def get_session_factory(config: DatabaseConfig): + global _session_factory + if _session_factory is None: + engine = get_engine(config) + _session_factory = async_sessionmaker(engine, expire_on_commit=False) + return _session_factory + + +async def get_session(config: DatabaseConfig) -> AsyncGenerator[AsyncSession, None]: + factory = get_session_factory(config) + async with factory() as session: + yield session + + +async def init_db(config: DatabaseConfig) -> None: + engine = get_engine(config) + async with engine.begin() as conn: + from .models import Base + await conn.run_sync(Base.metadata.create_all) + diff --git a/db/migrations/.gitkeep b/db/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/db/models.py b/db/models.py new file mode 100644 index 0000000..519a3b8 --- /dev/null +++ b/db/models.py @@ -0,0 +1,85 @@ +from datetime import datetime +from typing import Optional +from sqlalchemy import String, Float, Integer, Boolean, DateTime, JSON, ForeignKey +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship + + +class Base(DeclarativeBase): + pass + + +class FlightModel(Base): + __tablename__ = "flights" + + id: Mapped[str] = mapped_column(String(36), primary_key=True) + name: Mapped[str] = mapped_column(String(255)) + description: Mapped[str] = mapped_column(String(1000)) + start_lat: Mapped[float] = mapped_column(Float) + start_lon: Mapped[float] = mapped_column(Float) + altitude: Mapped[float] = mapped_column(Float) + camera_params: Mapped[dict] = mapped_column(JSON) + geofences: Mapped[dict] = mapped_column(JSON) + status: Mapped[str] = mapped_column(String(50), default="created") + frames_processed: Mapped[int] = mapped_column(Integer, default=0) + frames_total: Mapped[int] = mapped_column(Integer, default=0) + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + waypoints: Mapped[list["WaypointModel"]] = relationship(back_populates="flight", cascade="all, delete-orphan") + frame_results: Mapped[list["FrameResultModel"]] = relationship(back_populates="flight", cascade="all, delete-orphan") + chunks: Mapped[list["ChunkModel"]] = relationship(back_populates="flight", cascade="all, delete-orphan") + + +class WaypointModel(Base): + __tablename__ = "waypoints" + + id: Mapped[str] = mapped_column(String(36), primary_key=True) + flight_id: Mapped[str] = mapped_column(String(36), ForeignKey("flights.id")) + lat: Mapped[float] = mapped_column(Float) + lon: Mapped[float] = mapped_column(Float) + altitude: Mapped[Optional[float]] = mapped_column(Float, nullable=True) + confidence: Mapped[float] = mapped_column(Float, default=0.0) + refined: Mapped[bool] = mapped_column(Boolean, default=False) + timestamp: Mapped[datetime] = mapped_column(DateTime) + + flight: Mapped["FlightModel"] = relationship(back_populates="waypoints") + + +class FrameResultModel(Base): + __tablename__ = "frame_results" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + flight_id: Mapped[str] = mapped_column(String(36), ForeignKey("flights.id")) + frame_id: Mapped[int] = mapped_column(Integer) + gps_lat: Mapped[float] = mapped_column(Float) + gps_lon: Mapped[float] = mapped_column(Float) + altitude: Mapped[float] = mapped_column(Float) + heading: Mapped[float] = mapped_column(Float) + confidence: Mapped[float] = mapped_column(Float) + refined: Mapped[bool] = mapped_column(Boolean, default=False) + objects: Mapped[dict] = mapped_column(JSON, default=list) + timestamp: Mapped[datetime] = mapped_column(DateTime) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + flight: Mapped["FlightModel"] = relationship(back_populates="frame_results") + + +class ChunkModel(Base): + __tablename__ = "chunks" + + id: Mapped[str] = mapped_column(String(36), primary_key=True) + flight_id: Mapped[str] = mapped_column(String(36), ForeignKey("flights.id")) + start_frame_id: Mapped[int] = mapped_column(Integer) + end_frame_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + frames: Mapped[list] = mapped_column(JSON, default=list) + is_active: Mapped[bool] = mapped_column(Boolean, default=True) + has_anchor: Mapped[bool] = mapped_column(Boolean, default=False) + anchor_frame_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + anchor_lat: Mapped[Optional[float]] = mapped_column(Float, nullable=True) + anchor_lon: Mapped[Optional[float]] = mapped_column(Float, nullable=True) + matching_status: Mapped[str] = mapped_column(String(50), default="unanchored") + created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + flight: Mapped["FlightModel"] = relationship(back_populates="chunks") + diff --git a/helpers/__init__.py b/helpers/__init__.py new file mode 100644 index 0000000..a4112d7 --- /dev/null +++ b/helpers/__init__.py @@ -0,0 +1,20 @@ +from .camera_model import CameraModel +from .gsd_calculator import GSDCalculator +from .robust_kernels import RobustKernels +from .faiss_index_manager import FaissIndexManager +from .performance_monitor import PerformanceMonitor +from .web_mercator_utils import WebMercatorUtils +from .image_rotation_utils import ImageRotationUtils +from .batch_validator import BatchValidator + +__all__ = [ + "CameraModel", + "GSDCalculator", + "RobustKernels", + "FaissIndexManager", + "PerformanceMonitor", + "WebMercatorUtils", + "ImageRotationUtils", + "BatchValidator", +] + diff --git a/helpers/batch_validator.py b/helpers/batch_validator.py new file mode 100644 index 0000000..4d73bfe --- /dev/null +++ b/helpers/batch_validator.py @@ -0,0 +1,38 @@ +from typing import Optional +import numpy as np + +from models.core import ValidationResult +from models.images import ImageBatch + + +class BatchValidator: + @staticmethod + def validate_batch(batch: ImageBatch) -> ValidationResult: + raise NotImplementedError + + @staticmethod + def validate_image_format(image_bytes: bytes) -> ValidationResult: + raise NotImplementedError + + @staticmethod + def validate_sequence_continuity( + current_batch: ImageBatch, + expected_start: int, + ) -> ValidationResult: + raise NotImplementedError + + @staticmethod + def validate_image_dimensions( + image: np.ndarray, + expected_width: int, + expected_height: int, + ) -> ValidationResult: + raise NotImplementedError + + @staticmethod + def validate_batch_size( + batch: ImageBatch, + max_size: int = 100, + ) -> ValidationResult: + raise NotImplementedError + diff --git a/helpers/camera_model.py b/helpers/camera_model.py new file mode 100644 index 0000000..d4a10e8 --- /dev/null +++ b/helpers/camera_model.py @@ -0,0 +1,35 @@ +import numpy as np + +from models.core import CameraParameters + + +class CameraModel: + def __init__(self, params: CameraParameters): + self._params = params + self._K: np.ndarray | None = None + + @property + def intrinsic_matrix(self) -> np.ndarray: + if self._K is None: + fx = self._params.focal_length * self._params.resolution_width / self._params.sensor_width + fy = self._params.focal_length * self._params.resolution_height / self._params.sensor_height + cx, cy = self._params.get_principal_point() + self._K = np.array([ + [fx, 0, cx], + [0, fy, cy], + [0, 0, 1] + ], dtype=np.float64) + return self._K + + def project(self, points_3d: np.ndarray) -> np.ndarray: + raise NotImplementedError + + def unproject(self, points_2d: np.ndarray, depth: float) -> np.ndarray: + raise NotImplementedError + + def undistort_points(self, points: np.ndarray) -> np.ndarray: + raise NotImplementedError + + def get_fov(self) -> tuple[float, float]: + raise NotImplementedError + diff --git a/helpers/faiss_index_manager.py b/helpers/faiss_index_manager.py new file mode 100644 index 0000000..eb720ac --- /dev/null +++ b/helpers/faiss_index_manager.py @@ -0,0 +1,34 @@ +from typing import Optional +import numpy as np + + +class FaissIndexManager: + def __init__(self, dimension: int, index_type: str = "IVF"): + self._dimension = dimension + self._index_type = index_type + self._index = None + self._id_map: dict[int, str] = {} + + def build_index(self, vectors: np.ndarray, ids: list[str]) -> bool: + raise NotImplementedError + + def add_vectors(self, vectors: np.ndarray, ids: list[str]) -> bool: + raise NotImplementedError + + def search( + self, query: np.ndarray, top_k: int = 10 + ) -> list[tuple[str, float]]: + raise NotImplementedError + + def remove_vectors(self, ids: list[str]) -> bool: + raise NotImplementedError + + def save_index(self, path: str) -> bool: + raise NotImplementedError + + def load_index(self, path: str) -> bool: + raise NotImplementedError + + def get_vector_count(self) -> int: + raise NotImplementedError + diff --git a/helpers/gsd_calculator.py b/helpers/gsd_calculator.py new file mode 100644 index 0000000..5df7e1c --- /dev/null +++ b/helpers/gsd_calculator.py @@ -0,0 +1,36 @@ +from models.core import CameraParameters + + +class GSDCalculator: + @staticmethod + def calculate_gsd( + altitude: float, + focal_length: float, + sensor_width: float, + image_width: int, + ) -> float: + raise NotImplementedError + + @staticmethod + def calculate_footprint( + altitude: float, + camera_params: CameraParameters, + ) -> tuple[float, float]: + raise NotImplementedError + + @staticmethod + def altitude_for_gsd( + target_gsd: float, + focal_length: float, + sensor_width: float, + image_width: int, + ) -> float: + raise NotImplementedError + + @staticmethod + def calculate_coverage_radius( + altitude: float, + camera_params: CameraParameters, + ) -> float: + raise NotImplementedError + diff --git a/helpers/image_rotation_utils.py b/helpers/image_rotation_utils.py new file mode 100644 index 0000000..f29b95f --- /dev/null +++ b/helpers/image_rotation_utils.py @@ -0,0 +1,42 @@ +import numpy as np + + +class ImageRotationUtils: + @staticmethod + def rotate_image( + image: np.ndarray, + angle: float, + center: tuple[float, float] | None = None, + ) -> np.ndarray: + raise NotImplementedError + + @staticmethod + def get_rotation_matrix( + angle: float, + center: tuple[float, float], + ) -> np.ndarray: + raise NotImplementedError + + @staticmethod + def rotate_points( + points: np.ndarray, + angle: float, + center: tuple[float, float], + ) -> np.ndarray: + raise NotImplementedError + + @staticmethod + def normalize_angle(angle: float) -> float: + raise NotImplementedError + + @staticmethod + def angle_difference(angle1: float, angle2: float) -> float: + raise NotImplementedError + + @staticmethod + def interpolate_angles( + angles: list[float], + weights: list[float] | None = None, + ) -> float: + raise NotImplementedError + diff --git a/helpers/performance_monitor.py b/helpers/performance_monitor.py new file mode 100644 index 0000000..594e4e9 --- /dev/null +++ b/helpers/performance_monitor.py @@ -0,0 +1,44 @@ +import time +from typing import Optional +from contextlib import contextmanager + + +class PerformanceMonitor: + def __init__(self): + self._timings: dict[str, list[float]] = {} + self._counters: dict[str, int] = {} + + @contextmanager + def measure(self, operation: str): + start = time.perf_counter() + try: + yield + finally: + elapsed = time.perf_counter() - start + if operation not in self._timings: + self._timings[operation] = [] + self._timings[operation].append(elapsed) + + def increment(self, counter: str, value: int = 1) -> None: + if counter not in self._counters: + self._counters[counter] = 0 + self._counters[counter] += value + + def get_average(self, operation: str) -> Optional[float]: + if operation not in self._timings or not self._timings[operation]: + return None + return sum(self._timings[operation]) / len(self._timings[operation]) + + def get_statistics(self, operation: str) -> Optional[dict]: + raise NotImplementedError + + def get_counter(self, counter: str) -> int: + return self._counters.get(counter, 0) + + def reset(self) -> None: + self._timings.clear() + self._counters.clear() + + def get_report(self) -> dict: + raise NotImplementedError + diff --git a/helpers/robust_kernels.py b/helpers/robust_kernels.py new file mode 100644 index 0000000..c5765d8 --- /dev/null +++ b/helpers/robust_kernels.py @@ -0,0 +1,32 @@ +import numpy as np + + +class RobustKernels: + @staticmethod + def huber(residual: float, delta: float = 1.0) -> float: + raise NotImplementedError + + @staticmethod + def cauchy(residual: float, c: float = 1.0) -> float: + raise NotImplementedError + + @staticmethod + def tukey(residual: float, c: float = 4.685) -> float: + raise NotImplementedError + + @staticmethod + def huber_weight(residual: float, delta: float = 1.0) -> float: + raise NotImplementedError + + @staticmethod + def cauchy_weight(residual: float, c: float = 1.0) -> float: + raise NotImplementedError + + @staticmethod + def compute_robust_covariance( + residuals: np.ndarray, + kernel: str = "huber", + **kwargs, + ) -> np.ndarray: + raise NotImplementedError + diff --git a/helpers/web_mercator_utils.py b/helpers/web_mercator_utils.py new file mode 100644 index 0000000..e6021b6 --- /dev/null +++ b/helpers/web_mercator_utils.py @@ -0,0 +1,44 @@ +import math + +from models.core import GPSPoint +from models.satellite import TileCoords, TileBounds + + +class WebMercatorUtils: + EARTH_RADIUS = 6378137.0 + TILE_SIZE = 256 + + @classmethod + def gps_to_tile(cls, gps: GPSPoint, zoom: int) -> TileCoords: + raise NotImplementedError + + @classmethod + def tile_to_gps(cls, coords: TileCoords) -> GPSPoint: + raise NotImplementedError + + @classmethod + def get_tile_bounds(cls, coords: TileCoords) -> TileBounds: + raise NotImplementedError + + @classmethod + def gps_to_pixel( + cls, gps: GPSPoint, zoom: int + ) -> tuple[float, float]: + raise NotImplementedError + + @classmethod + def pixel_to_gps( + cls, pixel: tuple[float, float], zoom: int + ) -> GPSPoint: + raise NotImplementedError + + @classmethod + def meters_per_pixel(cls, lat: float, zoom: int) -> float: + raise NotImplementedError + + @classmethod + def get_tiles_in_bounds( + cls, nw: GPSPoint, se: GPSPoint, zoom: int + ) -> list[TileCoords]: + raise NotImplementedError + diff --git a/main.py b/main.py new file mode 100644 index 0000000..a83107d --- /dev/null +++ b/main.py @@ -0,0 +1,17 @@ +from fastapi import FastAPI + +from api import router + +app = FastAPI( + title="GPS-Denied Desktop", + description="GPS-denied UAV localization system", + version="0.1.0", +) + +app.include_router(router, prefix="/api/v1") + + +@app.get("/health") +async def health_check(): + return {"status": "healthy"} + diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..534d089 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,11 @@ +from .core import * +from .flight import * +from .processing import * +from .chunks import * +from .satellite import * +from .recovery import * +from .results import * +from .images import * +from .config import * +from .api import * + diff --git a/models/api/__init__.py b/models/api/__init__.py new file mode 100644 index 0000000..f28510c --- /dev/null +++ b/models/api/__init__.py @@ -0,0 +1,27 @@ +from .flight_requests import FlightCreateRequest +from .flight_responses import ( + FlightResponse, + FlightDetailResponse, + FlightStatusResponse, + DeleteResponse, + UpdateResponse, + BatchUpdateResponse, +) +from .batch_requests import BatchMetadata, BatchResponse +from .user_fix_requests import UserFixRequest, UserFixResponse, ObjectGPSResponse + +__all__ = [ + "FlightCreateRequest", + "FlightResponse", + "FlightDetailResponse", + "FlightStatusResponse", + "DeleteResponse", + "UpdateResponse", + "BatchUpdateResponse", + "BatchMetadata", + "BatchResponse", + "UserFixRequest", + "UserFixResponse", + "ObjectGPSResponse", +] + diff --git a/models/api/batch_requests.py b/models/api/batch_requests.py new file mode 100644 index 0000000..af4e2fa --- /dev/null +++ b/models/api/batch_requests.py @@ -0,0 +1,16 @@ +from typing import Optional +from pydantic import BaseModel + + +class BatchMetadata(BaseModel): + start_sequence: int + end_sequence: int + batch_number: int + + +class BatchResponse(BaseModel): + accepted: bool + sequences: list[int] + next_expected: int + message: Optional[str] = None + diff --git a/models/api/flight_requests.py b/models/api/flight_requests.py new file mode 100644 index 0000000..b6cb294 --- /dev/null +++ b/models/api/flight_requests.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel +from ..core.gps_point import GPSPoint +from ..core.camera_parameters import CameraParameters +from ..flight.geofences import Geofences + + +class FlightCreateRequest(BaseModel): + name: str + description: str + start_gps: GPSPoint + rough_waypoints: list[GPSPoint] + geofences: Geofences + camera_params: CameraParameters + altitude: float + diff --git a/models/api/flight_responses.py b/models/api/flight_responses.py new file mode 100644 index 0000000..999186c --- /dev/null +++ b/models/api/flight_responses.py @@ -0,0 +1,61 @@ +from datetime import datetime +from typing import Optional +from pydantic import BaseModel +from ..core.gps_point import GPSPoint +from ..core.camera_parameters import CameraParameters +from ..flight.waypoint import Waypoint +from ..flight.geofences import Geofences + + +class FlightResponse(BaseModel): + flight_id: str + status: str + message: Optional[str] = None + created_at: datetime + + +class FlightDetailResponse(BaseModel): + flight_id: str + name: str + description: str + start_gps: GPSPoint + waypoints: list[Waypoint] + geofences: Geofences + camera_params: CameraParameters + altitude: float + status: str + frames_processed: int + frames_total: int + created_at: datetime + updated_at: datetime + + +class FlightStatusResponse(BaseModel): + status: str + frames_processed: int + frames_total: int + current_frame: Optional[int] = None + current_heading: Optional[float] = None + blocked: bool = False + search_grid_size: Optional[int] = None + message: Optional[str] = None + created_at: datetime + updated_at: datetime + + +class DeleteResponse(BaseModel): + deleted: bool + flight_id: str + + +class UpdateResponse(BaseModel): + updated: bool + waypoint_id: str + + +class BatchUpdateResponse(BaseModel): + success: bool + updated_count: int + failed_ids: list[str] = [] + errors: Optional[dict[str, str]] = None + diff --git a/models/api/user_fix_requests.py b/models/api/user_fix_requests.py new file mode 100644 index 0000000..421bb18 --- /dev/null +++ b/models/api/user_fix_requests.py @@ -0,0 +1,23 @@ +from typing import Optional +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class UserFixRequest(BaseModel): + frame_id: int + uav_pixel: tuple[float, float] + satellite_gps: GPSPoint + + +class UserFixResponse(BaseModel): + accepted: bool + processing_resumed: bool + message: Optional[str] = None + + +class ObjectGPSResponse(BaseModel): + gps: GPSPoint + accuracy_meters: float + frame_id: int + pixel: tuple[float, float] + diff --git a/models/chunks/__init__.py b/models/chunks/__init__.py new file mode 100644 index 0000000..a6b6c73 --- /dev/null +++ b/models/chunks/__init__.py @@ -0,0 +1,10 @@ +from .sim3_transform import Sim3Transform +from .chunk_handle import ChunkHandle +from .chunk_bounds import ChunkBounds + +__all__ = [ + "Sim3Transform", + "ChunkHandle", + "ChunkBounds", +] + diff --git a/models/chunks/chunk_bounds.py b/models/chunks/chunk_bounds.py new file mode 100644 index 0000000..1b92b14 --- /dev/null +++ b/models/chunks/chunk_bounds.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class ChunkBounds(BaseModel): + estimated_center: GPSPoint + estimated_radius: float + confidence: float + diff --git a/models/chunks/chunk_handle.py b/models/chunks/chunk_handle.py new file mode 100644 index 0000000..3019323 --- /dev/null +++ b/models/chunks/chunk_handle.py @@ -0,0 +1,17 @@ +from typing import Optional +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class ChunkHandle(BaseModel): + chunk_id: str + flight_id: str + start_frame_id: int + end_frame_id: Optional[int] = None + frames: list[int] = [] + is_active: bool = True + has_anchor: bool = False + anchor_frame_id: Optional[int] = None + anchor_gps: Optional[GPSPoint] = None + matching_status: str = "unanchored" + diff --git a/models/chunks/sim3_transform.py b/models/chunks/sim3_transform.py new file mode 100644 index 0000000..ceeb036 --- /dev/null +++ b/models/chunks/sim3_transform.py @@ -0,0 +1,11 @@ +import numpy as np +from pydantic import BaseModel, ConfigDict + + +class Sim3Transform(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + translation: np.ndarray + rotation: np.ndarray + scale: float + diff --git a/models/config/__init__.py b/models/config/__init__.py new file mode 100644 index 0000000..4da5c42 --- /dev/null +++ b/models/config/__init__.py @@ -0,0 +1,16 @@ +from .system_config import SystemConfig +from .flight_config import FlightConfig +from .database_config import DatabaseConfig +from .model_config import ModelConfig +from .rotation_config import RotationConfig +from .recovery_config import RecoveryConfig + +__all__ = [ + "SystemConfig", + "FlightConfig", + "DatabaseConfig", + "ModelConfig", + "RotationConfig", + "RecoveryConfig", +] + diff --git a/models/config/database_config.py b/models/config/database_config.py new file mode 100644 index 0000000..52cb036 --- /dev/null +++ b/models/config/database_config.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel + + +class DatabaseConfig(BaseModel): + host: str = "localhost" + port: int = 5432 + database: str = "gps_denied" + username: str = "postgres" + password: str = "" + pool_size: int = 50 + max_overflow: int = 50 + pool_timeout: int = 30 + pool_recycle: int = 3600 + diff --git a/models/config/flight_config.py b/models/config/flight_config.py new file mode 100644 index 0000000..5200ac8 --- /dev/null +++ b/models/config/flight_config.py @@ -0,0 +1,20 @@ +from typing import Optional +from pydantic import BaseModel +from ..core.camera_parameters import CameraParameters +from ..core.gps_point import GPSPoint + + +class OperationalArea(BaseModel): + name: str = "Eastern Ukraine" + min_lat: float = 45.0 + max_lat: float = 52.0 + min_lon: float = 22.0 + max_lon: float = 40.0 + + +class FlightConfig(BaseModel): + camera_params: CameraParameters + altitude: float + operational_area: OperationalArea = OperationalArea() + frame_spacing: float = 100.0 + diff --git a/models/config/model_config.py b/models/config/model_config.py new file mode 100644 index 0000000..712e0ba --- /dev/null +++ b/models/config/model_config.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class ModelConfig(BaseModel): + model_name: str + model_path: str + format: str = "tensorrt" + precision: str = "fp16" + warmup_iterations: int = 3 + diff --git a/models/config/recovery_config.py b/models/config/recovery_config.py new file mode 100644 index 0000000..0e6c765 --- /dev/null +++ b/models/config/recovery_config.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel + + +class RecoveryConfig(BaseModel): + search_grid_sizes: list[int] = [1, 4, 9, 16, 25] + min_chunk_frames_for_matching: int = 5 + max_chunk_frames_for_matching: int = 20 + user_input_threshold_tiles: int = 25 + chunk_matching_interval_seconds: float = 5.0 + confidence_threshold_good: float = 0.7 + confidence_threshold_degraded: float = 0.5 + min_inlier_count_good: int = 50 + min_inlier_count_tracking: int = 20 + diff --git a/models/config/rotation_config.py b/models/config/rotation_config.py new file mode 100644 index 0000000..954717b --- /dev/null +++ b/models/config/rotation_config.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel + + +class RotationConfig(BaseModel): + step_angle: float = 30.0 + sharp_turn_threshold: float = 45.0 + confidence_threshold: float = 0.7 + history_size: int = 10 + + @property + def rotation_iterations(self) -> int: + return int(360 / self.step_angle) + diff --git a/models/config/system_config.py b/models/config/system_config.py new file mode 100644 index 0000000..0004cc2 --- /dev/null +++ b/models/config/system_config.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel +from ..core.camera_parameters import CameraParameters +from .database_config import DatabaseConfig +from .flight_config import OperationalArea + + +class ModelPaths(BaseModel): + superpoint: str = "models/superpoint.engine" + lightglue: str = "models/lightglue.engine" + dinov2: str = "models/dinov2.engine" + litesam: str = "models/litesam.engine" + + +class APIConfig(BaseModel): + host: str = "0.0.0.0" + port: int = 8000 + debug: bool = False + + +class SystemConfig(BaseModel): + camera: CameraParameters + operational_area: OperationalArea = OperationalArea() + models: ModelPaths = ModelPaths() + database: DatabaseConfig = DatabaseConfig() + api: APIConfig = APIConfig() + diff --git a/models/core/__init__.py b/models/core/__init__.py new file mode 100644 index 0000000..cda489e --- /dev/null +++ b/models/core/__init__.py @@ -0,0 +1,14 @@ +from .gps_point import GPSPoint +from .camera_parameters import CameraParameters +from .pose import Pose +from .polygon import Polygon +from .validation_result import ValidationResult + +__all__ = [ + "GPSPoint", + "CameraParameters", + "Pose", + "Polygon", + "ValidationResult", +] + diff --git a/models/core/camera_parameters.py b/models/core/camera_parameters.py new file mode 100644 index 0000000..9f5f5e1 --- /dev/null +++ b/models/core/camera_parameters.py @@ -0,0 +1,18 @@ +from typing import Optional +from pydantic import BaseModel + + +class CameraParameters(BaseModel): + focal_length: float + sensor_width: float + sensor_height: float + resolution_width: int + resolution_height: int + principal_point: tuple[float, float] | None = None + distortion_coefficients: list[float] | None = None + + def get_principal_point(self) -> tuple[float, float]: + if self.principal_point: + return self.principal_point + return (self.resolution_width / 2.0, self.resolution_height / 2.0) + diff --git a/models/core/gps_point.py b/models/core/gps_point.py new file mode 100644 index 0000000..1d2afb6 --- /dev/null +++ b/models/core/gps_point.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel, field_validator + + +class GPSPoint(BaseModel): + lat: float + lon: float + + @field_validator("lat") + @classmethod + def validate_lat(cls, v: float) -> float: + if not -90 <= v <= 90: + raise ValueError("Latitude must be between -90 and 90") + return v + + @field_validator("lon") + @classmethod + def validate_lon(cls, v: float) -> float: + if not -180 <= v <= 180: + raise ValueError("Longitude must be between -180 and 180") + return v + diff --git a/models/core/polygon.py b/models/core/polygon.py new file mode 100644 index 0000000..62944b0 --- /dev/null +++ b/models/core/polygon.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel +from .gps_point import GPSPoint + + +class Polygon(BaseModel): + north_west: GPSPoint + south_east: GPSPoint + diff --git a/models/core/pose.py b/models/core/pose.py new file mode 100644 index 0000000..0112863 --- /dev/null +++ b/models/core/pose.py @@ -0,0 +1,15 @@ +from datetime import datetime +from typing import Optional +import numpy as np +from pydantic import BaseModel, ConfigDict + + +class Pose(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + frame_id: int + position: np.ndarray + orientation: np.ndarray + timestamp: datetime + covariance: Optional[np.ndarray] = None + diff --git a/models/core/validation_result.py b/models/core/validation_result.py new file mode 100644 index 0000000..a66209c --- /dev/null +++ b/models/core/validation_result.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class ValidationResult(BaseModel): + valid: bool + errors: list[str] = [] + diff --git a/models/flight/__init__.py b/models/flight/__init__.py new file mode 100644 index 0000000..153af12 --- /dev/null +++ b/models/flight/__init__.py @@ -0,0 +1,14 @@ +from .flight import Flight +from .flight_state import FlightState +from .waypoint import Waypoint +from .geofences import Geofences +from .heading_record import HeadingRecord + +__all__ = [ + "Flight", + "FlightState", + "Waypoint", + "Geofences", + "HeadingRecord", +] + diff --git a/models/flight/flight.py b/models/flight/flight.py new file mode 100644 index 0000000..9d9cfaa --- /dev/null +++ b/models/flight/flight.py @@ -0,0 +1,20 @@ +from datetime import datetime +from pydantic import BaseModel +from ..core.gps_point import GPSPoint +from ..core.camera_parameters import CameraParameters +from .waypoint import Waypoint +from .geofences import Geofences + + +class Flight(BaseModel): + id: str + name: str + description: str + start_gps: GPSPoint + waypoints: list[Waypoint] + geofences: Geofences + camera_params: CameraParameters + altitude: float + created_at: datetime + updated_at: datetime + diff --git a/models/flight/flight_state.py b/models/flight/flight_state.py new file mode 100644 index 0000000..ac6963d --- /dev/null +++ b/models/flight/flight_state.py @@ -0,0 +1,16 @@ +from datetime import datetime +from typing import Optional +from pydantic import BaseModel + + +class FlightState(BaseModel): + flight_id: str + status: str + frames_processed: int + frames_total: int + current_frame: Optional[int] = None + blocked: bool = False + search_grid_size: Optional[int] = None + created_at: datetime + updated_at: datetime + diff --git a/models/flight/geofences.py b/models/flight/geofences.py new file mode 100644 index 0000000..0ef4b3e --- /dev/null +++ b/models/flight/geofences.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel +from ..core.polygon import Polygon + + +class Geofences(BaseModel): + polygons: list[Polygon] + diff --git a/models/flight/heading_record.py b/models/flight/heading_record.py new file mode 100644 index 0000000..660e4c4 --- /dev/null +++ b/models/flight/heading_record.py @@ -0,0 +1,9 @@ +from datetime import datetime +from pydantic import BaseModel + + +class HeadingRecord(BaseModel): + frame_id: int + heading: float + timestamp: datetime + diff --git a/models/flight/waypoint.py b/models/flight/waypoint.py new file mode 100644 index 0000000..7d430d1 --- /dev/null +++ b/models/flight/waypoint.py @@ -0,0 +1,14 @@ +from datetime import datetime +from typing import Optional +from pydantic import BaseModel + + +class Waypoint(BaseModel): + id: str + lat: float + lon: float + altitude: Optional[float] = None + confidence: float + timestamp: datetime + refined: bool = False + diff --git a/models/images/__init__.py b/models/images/__init__.py new file mode 100644 index 0000000..1e1c360 --- /dev/null +++ b/models/images/__init__.py @@ -0,0 +1,12 @@ +from .image_data import ImageData +from .image_metadata import ImageMetadata +from .image_batch import ImageBatch +from .processing_status import ProcessingStatus + +__all__ = [ + "ImageData", + "ImageMetadata", + "ImageBatch", + "ProcessingStatus", +] + diff --git a/models/images/image_batch.py b/models/images/image_batch.py new file mode 100644 index 0000000..e9a0425 --- /dev/null +++ b/models/images/image_batch.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class ImageBatch(BaseModel): + images: list[bytes] + filenames: list[str] + start_sequence: int + end_sequence: int + batch_number: int + diff --git a/models/images/image_data.py b/models/images/image_data.py new file mode 100644 index 0000000..39f84fc --- /dev/null +++ b/models/images/image_data.py @@ -0,0 +1,14 @@ +import numpy as np +from pydantic import BaseModel, ConfigDict +from .image_metadata import ImageMetadata + + +class ImageData(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + flight_id: str + sequence: int + filename: str + image: np.ndarray + metadata: ImageMetadata + diff --git a/models/images/image_metadata.py b/models/images/image_metadata.py new file mode 100644 index 0000000..5becb69 --- /dev/null +++ b/models/images/image_metadata.py @@ -0,0 +1,13 @@ +from datetime import datetime +from typing import Optional +from pydantic import BaseModel + + +class ImageMetadata(BaseModel): + sequence: int + filename: str + dimensions: tuple[int, int] + file_size: int + timestamp: datetime + exif_data: Optional[dict] = None + diff --git a/models/images/processing_status.py b/models/images/processing_status.py new file mode 100644 index 0000000..001008c --- /dev/null +++ b/models/images/processing_status.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + + +class ProcessingStatus(BaseModel): + flight_id: str + total_images: int + processed_images: int + current_sequence: int + queued_batches: int + processing_rate: float + diff --git a/models/processing/__init__.py b/models/processing/__init__.py new file mode 100644 index 0000000..492ec17 --- /dev/null +++ b/models/processing/__init__.py @@ -0,0 +1,15 @@ +from .relative_pose import RelativePose +from .motion import Motion +from .matches import Matches +from .alignment_result import AlignmentResult, ChunkAlignmentResult +from .rotation_result import RotationResult + +__all__ = [ + "RelativePose", + "Motion", + "Matches", + "AlignmentResult", + "ChunkAlignmentResult", + "RotationResult", +] + diff --git a/models/processing/alignment_result.py b/models/processing/alignment_result.py new file mode 100644 index 0000000..ebb1cef --- /dev/null +++ b/models/processing/alignment_result.py @@ -0,0 +1,30 @@ +import numpy as np +from pydantic import BaseModel, ConfigDict +from ..core.gps_point import GPSPoint +from ..chunks.sim3_transform import Sim3Transform + + +class AlignmentResult(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + matched: bool + homography: np.ndarray + gps_center: GPSPoint + confidence: float + inlier_count: int + total_correspondences: int + reprojection_error: float = 0.0 + + +class ChunkAlignmentResult(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + matched: bool + chunk_id: str + chunk_center_gps: GPSPoint + rotation_angle: float + confidence: float + inlier_count: int + transform: Sim3Transform + reprojection_error: float = 0.0 + diff --git a/models/processing/matches.py b/models/processing/matches.py new file mode 100644 index 0000000..249401c --- /dev/null +++ b/models/processing/matches.py @@ -0,0 +1,12 @@ +import numpy as np +from pydantic import BaseModel, ConfigDict + + +class Matches(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + matches: np.ndarray + scores: np.ndarray + keypoints1: np.ndarray + keypoints2: np.ndarray + diff --git a/models/processing/motion.py b/models/processing/motion.py new file mode 100644 index 0000000..13b616b --- /dev/null +++ b/models/processing/motion.py @@ -0,0 +1,12 @@ +import numpy as np +from pydantic import BaseModel, ConfigDict + + +class Motion(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + translation: np.ndarray + rotation: np.ndarray + inliers: np.ndarray + inlier_count: int + diff --git a/models/processing/relative_pose.py b/models/processing/relative_pose.py new file mode 100644 index 0000000..561c9c9 --- /dev/null +++ b/models/processing/relative_pose.py @@ -0,0 +1,17 @@ +from typing import Optional +import numpy as np +from pydantic import BaseModel, ConfigDict + + +class RelativePose(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + translation: np.ndarray + rotation: np.ndarray + confidence: float + inlier_count: int + total_matches: int + tracking_good: bool + scale_ambiguous: bool = True + chunk_id: Optional[str] = None + diff --git a/models/processing/rotation_result.py b/models/processing/rotation_result.py new file mode 100644 index 0000000..bd0cf2b --- /dev/null +++ b/models/processing/rotation_result.py @@ -0,0 +1,14 @@ +import numpy as np +from pydantic import BaseModel, ConfigDict + + +class RotationResult(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + matched: bool + initial_angle: float + precise_angle: float + confidence: float + homography: np.ndarray + inlier_count: int = 0 + diff --git a/models/recovery/__init__.py b/models/recovery/__init__.py new file mode 100644 index 0000000..e8f2079 --- /dev/null +++ b/models/recovery/__init__.py @@ -0,0 +1,12 @@ +from .search_session import SearchSession +from .confidence_assessment import ConfidenceAssessment +from .user_anchor import UserAnchor +from .user_input_request import UserInputRequest + +__all__ = [ + "SearchSession", + "ConfidenceAssessment", + "UserAnchor", + "UserInputRequest", +] + diff --git a/models/recovery/confidence_assessment.py b/models/recovery/confidence_assessment.py new file mode 100644 index 0000000..3f2ec94 --- /dev/null +++ b/models/recovery/confidence_assessment.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class ConfidenceAssessment(BaseModel): + overall_confidence: float + vo_confidence: float + litesam_confidence: float + inlier_count: int + tracking_status: str + diff --git a/models/recovery/search_session.py b/models/recovery/search_session.py new file mode 100644 index 0000000..bde1536 --- /dev/null +++ b/models/recovery/search_session.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class SearchSession(BaseModel): + session_id: str + flight_id: str + frame_id: int + center_gps: GPSPoint + current_grid_size: int = 1 + max_grid_size: int = 25 + found: bool = False + exhausted: bool = False + diff --git a/models/recovery/user_anchor.py b/models/recovery/user_anchor.py new file mode 100644 index 0000000..08a6079 --- /dev/null +++ b/models/recovery/user_anchor.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class UserAnchor(BaseModel): + uav_pixel: tuple[float, float] + satellite_gps: GPSPoint + confidence: float = 1.0 + diff --git a/models/recovery/user_input_request.py b/models/recovery/user_input_request.py new file mode 100644 index 0000000..9429cd5 --- /dev/null +++ b/models/recovery/user_input_request.py @@ -0,0 +1,17 @@ +from datetime import datetime +import numpy as np +from pydantic import BaseModel, ConfigDict +from ..satellite.tile_candidate import TileCandidate + + +class UserInputRequest(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + request_id: str + flight_id: str + frame_id: int + uav_image: np.ndarray + candidate_tiles: list[TileCandidate] + message: str + created_at: datetime + diff --git a/models/results/__init__.py b/models/results/__init__.py new file mode 100644 index 0000000..4bc488d --- /dev/null +++ b/models/results/__init__.py @@ -0,0 +1,14 @@ +from .frame_result import FrameResult, ObjectLocation +from .flight_results import FlightResults, FlightStatistics +from .refined_frame_result import RefinedFrameResult +from .optimization_result import OptimizationResult + +__all__ = [ + "FrameResult", + "ObjectLocation", + "FlightResults", + "FlightStatistics", + "RefinedFrameResult", + "OptimizationResult", +] + diff --git a/models/results/flight_results.py b/models/results/flight_results.py new file mode 100644 index 0000000..98736b5 --- /dev/null +++ b/models/results/flight_results.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel +from .frame_result import FrameResult + + +class FlightStatistics(BaseModel): + total_frames: int + processed_frames: int + refined_frames: int + mean_confidence: float + processing_time: float + + +class FlightResults(BaseModel): + flight_id: str + frames: list[FrameResult] + statistics: FlightStatistics + diff --git a/models/results/frame_result.py b/models/results/frame_result.py new file mode 100644 index 0000000..c203e8e --- /dev/null +++ b/models/results/frame_result.py @@ -0,0 +1,24 @@ +from datetime import datetime +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class ObjectLocation(BaseModel): + object_id: str + pixel: tuple[float, float] + gps: GPSPoint + class_name: str + confidence: float + + +class FrameResult(BaseModel): + frame_id: int + gps_center: GPSPoint + altitude: float + heading: float + confidence: float + timestamp: datetime + refined: bool = False + objects: list[ObjectLocation] = [] + updated_at: datetime + diff --git a/models/results/optimization_result.py b/models/results/optimization_result.py new file mode 100644 index 0000000..27c771c --- /dev/null +++ b/models/results/optimization_result.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class OptimizationResult(BaseModel): + converged: bool + final_error: float + iterations_used: int + optimized_frames: list[int] + mean_reprojection_error: float = 0.0 + diff --git a/models/results/refined_frame_result.py b/models/results/refined_frame_result.py new file mode 100644 index 0000000..2f93d7d --- /dev/null +++ b/models/results/refined_frame_result.py @@ -0,0 +1,11 @@ +from typing import Optional +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class RefinedFrameResult(BaseModel): + frame_id: int + gps_center: GPSPoint + confidence: float + heading: Optional[float] = None + diff --git a/models/satellite/__init__.py b/models/satellite/__init__.py new file mode 100644 index 0000000..282daa5 --- /dev/null +++ b/models/satellite/__init__.py @@ -0,0 +1,10 @@ +from .tile_coords import TileCoords +from .tile_bounds import TileBounds +from .tile_candidate import TileCandidate + +__all__ = [ + "TileCoords", + "TileBounds", + "TileCandidate", +] + diff --git a/models/satellite/tile_bounds.py b/models/satellite/tile_bounds.py new file mode 100644 index 0000000..eb6df0f --- /dev/null +++ b/models/satellite/tile_bounds.py @@ -0,0 +1,12 @@ +from pydantic import BaseModel +from ..core.gps_point import GPSPoint + + +class TileBounds(BaseModel): + nw: GPSPoint + ne: GPSPoint + sw: GPSPoint + se: GPSPoint + center: GPSPoint + gsd: float + diff --git a/models/satellite/tile_candidate.py b/models/satellite/tile_candidate.py new file mode 100644 index 0000000..8dabb53 --- /dev/null +++ b/models/satellite/tile_candidate.py @@ -0,0 +1,14 @@ +from typing import Optional +from pydantic import BaseModel +from ..core.gps_point import GPSPoint +from .tile_bounds import TileBounds + + +class TileCandidate(BaseModel): + tile_id: str + gps_center: GPSPoint + bounds: TileBounds + similarity_score: float + rank: int + spatial_score: Optional[float] = None + diff --git a/models/satellite/tile_coords.py b/models/satellite/tile_coords.py new file mode 100644 index 0000000..474c9ee --- /dev/null +++ b/models/satellite/tile_coords.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + + +class TileCoords(BaseModel): + x: int + y: int + zoom: int + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d1b4c92 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "azaion-gps-denied-desktop" +version = "0.1.0" +requires-python = ">=3.10" +description = "GPS-denied UAV localization system using visual odometry and satellite imagery matching" +dependencies = [ + "fastapi>=0.109.0", + "uvicorn[standard]>=0.27.0", + "pydantic>=2.5.0", + "sqlalchemy[asyncio]>=2.0.0", + "asyncpg>=0.29.0", + "alembic>=1.13.0", + "numpy>=1.26.0", + "opencv-python>=4.9.0", + "sse-starlette>=2.0.0", + "python-multipart>=0.0.6", + "httpx>=0.26.0", + "pyyaml>=6.0", + "gtsam>=4.2", +] + +[project.optional-dependencies] +ml = [ + "tensorrt>=10.0.0", + "onnxruntime-gpu>=1.17.0", + "faiss-gpu>=1.7.4", +] +dev = [ + "pytest>=7.4.0", + "pytest-asyncio>=0.21.0", + "pytest-cov>=4.1.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["models", "components", "helpers", "db", "api"] +