diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..ee9b920 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +github: + Mindful-AI-Assistants +Custom: + https://github.com/sponsors/Mindful-AI-Assistants/card + diff --git a/AI Training/My-First-Project-2_zipped.zip b/AI Training/My-First-Project-2_zipped.zip deleted file mode 100644 index cdb07ca..0000000 Binary files a/AI Training/My-First-Project-2_zipped.zip and /dev/null differ diff --git a/Analise.ipynb b/Analysis.ipynb similarity index 100% rename from Analise.ipynb rename to Analysis.ipynb diff --git a/Analysis_yolo_results.md b/Analysis_yolo_results.md new file mode 100644 index 0000000..be84711 --- /dev/null +++ b/Analysis_yolo_results.md @@ -0,0 +1,160 @@ +# 📊 Análise de Resultados — Helipoint Detector +### YOLOv8n · 60 épocas · Dataset de Helipontos em São Paulo + +

+ +## 🏆 Métricas de Destaque (dados reais do `results.csv`) + +| | Melhor Época (54) | Época Final (60) | +|---|:---:|:---:| +| **Precision** | **1.000** | 0.992 | +| **Recall** | **0.963** | 0.971 | +| **mAP\@50** | **0.994** | 0.994 | +| **mAP\@50–95** | **0.881** | 0.841 | + +
+ +> [!IMPORTANT] +> O modelo atingiu **Precision = 1.00** e **mAP\@50 = 0.994** na época 54 — resultado excepcionalmente forte para um dataset com apenas ~116 imagens de treino. + +

+ +## 📈 Gráficos Gerados + +### Figura 1 — Evolução das Losses por Época + +

+ + +Image + +

+ + +**O que observar:** + +- Todas as três losses (Box, Cls, DFL) caem de forma consistente tanto no treino quanto na validação. +- Não há sinal de *overfitting* nas primeiras 60 épocas — a `val_loss` acompanha a `train_loss` sem se distanciar. +- A `val/cls_loss` apresenta oscilação nos primeiros 20 epochs, o que é esperado em datasets pequenos, mas estabiliza a partir do epoch 30. + +

+ +### Figura 2 — Precision e Recall ao longo do Treino + +

+ +!Image + +

+ + +**O que observar:** + +- **Precision** sobe rapidamente e se estabiliza acima de **0.95** a partir do epoch 30 — o modelo raramente detecta "helipontos" onde não existem. +- **Recall** também alcança **0.97+** nas épocas finais — o modelo consegue encontrar quase todos os helipontos reais nas imagens. +- O cruzamento das duas curvas ocorre cedo (~epoch 20), indicando que o modelo equilibrou bem a captura de objetos e a filtragem de falsos positivos. + +

+ +### Figura 3 — mAP\@50 e mAP\@50–95 + + +

+ + +Image + + +

+ + +**O que observar:** +- **mAP\@50 = 0.994** na melhor época — praticamente perfeito no critério padrão de IoU 50%. +- **mAP\@50–95 = 0.881** — excelente resultado mesmo com o critério rigoroso (padrão COCO), mostrando que as *bounding boxes* são precisas além de somente se sobrepor ao objeto. +- O pico ocorre na **época 54**, após o qual as métricas flutuam levemente, sugerindo que 55–60 épocas são o ponto ótimo para este dataset. + +

+ + + +## 🔍 Análise Qualitativa — Roteiro para Slides Visuais + +Use o seguinte roteiro ao exibir imagens de predição: + +| Tipo | O que mostrar | O que explicar | +|------|--------------|----------------| +| ✅ **Acerto claro** | Heliponto detectado com caixa bem ajustada e confiança > 0.8 | "O modelo identificou o 'H' característico mesmo com sombra no rooftop" | +| ✅ **Acerto desafiador** | Heliponto parcialmente coberto ou em ângulo | "Alta confiança mesmo com oclusão parcial, mostrando robustez" | +| ⚠️ **Falso Positivo** | Padrão circular ou 'H' em piscina/quadra detectado | "Estruturas similares ao 'H' de helipontos geram FPs — tratável com mais exemplos negativos" | +| ❌ **Falso Negativo** | Heliponto não detectado | "Helipontos desbotados ou com sombra densa ainda escapam — área de melhoria" | + + +

+ +## 🛠️ Código Python Completo para Reproduzir os Gráficos + +Copie e cole diretamente no `Analysis.ipynb`: + +```python +import pandas as pd +import matplotlib.pyplot as plt + +df = pd.read_csv('AI Training/runs/detect/runs/exp1-2/results.csv', + skipinitialspace=True) +df.columns = df.columns.str.strip() +epoch = df["epoch"] + +# ── Figura 1: Losses ──────────────────────────────────────────── +fig, axes = plt.subplots(1, 3, figsize=(16, 4)) +fig.suptitle("Evolução da Loss por Época", fontsize=14, fontweight="bold") + +for ax, (tr, vl, name) in zip(axes, [ + ("train/box_loss","val/box_loss","Box Loss"), + ("train/cls_loss","val/cls_loss","Cls Loss"), + ("train/dfl_loss","val/dfl_loss","DFL Loss"), +]): + ax.plot(epoch, df[tr], label="Treino", color="#14b8a6", lw=2) + ax.plot(epoch, df[vl], label="Validação", color="#f97316", lw=2, ls="--") + ax.set_title(name); ax.set_xlabel("Época"); ax.legend(); ax.grid(True) + +plt.tight_layout(); plt.show() + +# ── Figura 2: Precision e Recall ──────────────────────────────── +fig, (a1, a2) = plt.subplots(1, 2, figsize=(12, 4)) +fig.suptitle("Precision e Recall ao longo do Treino", fontsize=14, fontweight="bold") + +a1.plot(epoch, df["metrics/precision(B)"], color="#14b8a6", lw=2) +a1.fill_between(epoch, df["metrics/precision(B)"], alpha=0.1, color="#14b8a6") +a1.set_title("Precision"); a1.set_xlabel("Época"); a1.set_ylim(0,1.05); a1.grid(True) + +a2.plot(epoch, df["metrics/recall(B)"], color="#ec4899", lw=2) +a2.fill_between(epoch, df["metrics/recall(B)"], alpha=0.1, color="#ec4899") +a2.set_title("Recall"); a2.set_xlabel("Época"); a2.set_ylim(0,1.05); a2.grid(True) + +plt.tight_layout(); plt.show() + +# ── Figura 3: mAP ─────────────────────────────────────────────── +fig, ax = plt.subplots(figsize=(10, 4)) +fig.suptitle("mAP50 e mAP50-95 ao longo do Treino", fontsize=14, fontweight="bold") + +ax.plot(epoch, df["metrics/mAP50(B)"], color="#6366f1", lw=2.5, label="mAP@50") +ax.plot(epoch, df["metrics/mAP50-95(B)"], color="#ec4899", lw=2.5, label="mAP@50-95", ls="--") +ax.fill_between(epoch, df["metrics/mAP50(B)"], alpha=0.1, color="#6366f1") +ax.fill_between(epoch, df["metrics/mAP50-95(B)"], alpha=0.1, color="#ec4899") +ax.set_xlabel("Época"); ax.set_ylim(0,1.05); ax.legend(); ax.grid(True) + +plt.tight_layout(); plt.show() + +# ── Tabela: métricas finais ───────────────────────────────────── +best = df.loc[df["metrics/mAP50-95(B)"].idxmax()] +final = df.iloc[-1] + +resumo = pd.DataFrame({ + "Precision": [best["metrics/precision(B)"], final["metrics/precision(B)"]], + "Recall": [best["metrics/recall(B)"], final["metrics/recall(B)"]], + "mAP@50": [best["metrics/mAP50(B)"], final["metrics/mAP50(B)"]], + "mAP@50-95": [best["metrics/mAP50-95(B)"], final["metrics/mAP50-95(B)"]], +}, index=[f"Melhor (ép. {int(best['epoch'])})", f"Final (ép. {int(final['epoch'])})"]) + +display(resumo.style.format("{:.4f}").background_gradient(cmap="YlGn", axis=None)) +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..51955e1 --- /dev/null +++ b/README.md @@ -0,0 +1,689 @@ +
+ +\[[🇧🇷 Português](README.pt_BR.md)\] \[**[🇺🇸 English](README.md)**\] + +

+ +#

🧠 AI/ML Project 2 · Computer Vision · Helipoint Detector

+ +###

Automated Helipad Detection Using YOLO and Satellite Imagery of São Paulo, Brazil 🚁

+ +
+ +
+ +Satellite Imagery +  ✦   +Urban Analytics +  ✦   +Object Detection +  ✦   +YOLO (v8 / v11) +  ✦   +Geospatial Intelligence + +
+ +

+ +####

Teaching YOLO to spot the city's most exclusive landing spots.

+ +

+Finding hidden H’s in the concrete jungle

+One rooftop at a time. 🚁⚡️ +

+ +
+ +# + +

+ + + +####

[![Sponsor Mindful AI Assistants](https://img.shields.io/badge/Sponsor-%C2%B7%C2%B7%C2%B7%20Mindful%20AI%20Assistants%20%C2%B7%C2%B7%C2%B7-brightgreen?logo=GitHub)](https://github.com/sponsors/Mindful-AI-Assistants) + +

+ + + +

+ +

+ + +


+ + + +## [Institutional Information]() + +[**Institution:**]() Pontifical Catholic University of São Paulo (PUC‑SP) — FACEI +[**Course:**]() BSc in Humanistic AI & Data Science — 5th semester — 2026 +[**Subject:**]() Machine Learning / Computer Vision — YOLO +[**Project:**]() P2 — Object Detection in Satellite Images with YOLO + +**Professor:** [✨ Rooney Ribeiro Albuquerque Coelho](https://www.linkedin.com/in/rooney-coelho-320857182/) +**Authors:** +- [Carlos Antonio Roth Gorham](https://github.com/RothGorham?tab=followers) +- [Fabiana ⚡️ Campanari](https://linktr.ee/fabianacampanari) +- [Pedro Vyctor Almeida](https://www.linkedin.com/in/pedro-vyctor-almeida-285b89273/) + + +

+ +# + +

+ + + + +

+ + Streamlit Repository Helipoint Detector + +

+ + + + + +

+ + + React Presentation Slides and Overview + + + + + + + + + Data Analysis Report and PDF + + +

+ +

+ +# + +

+ + +

+ + + +

+ +

+ + + +

+ +

+ + +

+ + +

+ +# + +

+ + + + +> [!WARNING] +> +> ⚠️ Projects may be publicly shared when permitted. +> The focus is on applied, hands-on learning with real datasets in AI governance and security contexts. +> All sensitive content remains protected in private repositories when required. +> + +

+ + + +> [!TIP] +> +> This repository is part of the flagship ecosystem: +> +> ## 🧠 AI & Machine Learning — Main Hub +> +> Explore the complete collection of projects, notebooks, research materials, analyses, and interactive applications available in the central repository: +> +> 🔗 **[AI & Machine Learning — Hub](https://github.com/Mindful-AI-Assistants/1-AI_Machine-Learning_Hub)** +> +> # +> +> ### Related Project in this Series +> +> 🔗 **[AI/ML Project 1 · Computer Vision · EMNIST Vision Intelligence](https://github.com/Mindful-AI-Assistants/2-project-ai-ml-emnist-vision-intelligence)** +> +> A deep learning system for handwritten character recognition using PyTorch and Streamlit. +> +> # +> +> ✨ Part of the *Humanistic AI & Machine Learning Series* +> +> [*From handwriting to rooftops — simplicity was never in the roadmap.*]() ⚡️ + +

+ + + +> [!IMPORTANT] +> +> This repository documents an end-to-end academic project in Computer Vision for automatic +> detection of **helipads on rooftops** using satellite images of the city of São Paulo. +> The focus goes beyond model training: it emphasizes **dataset construction**, **annotation governance**, **reproducibility**, and +> [**evaluation on unseen neighborhoods**](), in line with the briefing of Project 2 in the Machine Learning course. + +

+ + +## [Table of Contents]() + +- [Project Definition](#project-definition) +- [Objective](#objective) +- [Why Helipads?](#why-helipads) +- [Data Source](#data-source) +- [Project Context](#project-context) +- [Business and Research Problem](#business-and-research-problem) +- [Extra Automation Contribution](#extra-automation-contribution) +- [Overall Flow Architecture](#overall-flow-architecture) +- [AI/ML Ops Pipeline](#aiml-ops-pipeline) +- [Repository Structure](#repository-structure) +- [What is `Heliponto.rar`?](#what-is-helipontorar) +- [What is Roboflow in This Project?](#what-is-roboflow-in-this-project) +- [Methodology](#methodology) +- [Full Technical Pipeline](#full-technical-pipeline) +- [Image Collection and Generation](#image-collection-and-generation) +- [Annotation and Roboflow](#annotation-and-roboflow) +- [Modeling with YOLO](#modeling-with-yolo) +- [Evaluation](#evaluation) +- [Inference and Generalization](#inference-and-generalization) +- [Web Application (Optional Layer)](#web-application-optional-layer) +- [Gains from the Extra Resource](#gains-from-the-extra-resource) +- [Educational Value](#educational-value) +- [Image and Text Sources](#image-and-text-sources) +- [Technologies Used](#technologies-used) +- [How to Run](#how-to-run) +- [Deliverables Covered](#deliverables-covered) +- [Results Analysis](#results-analysis) +- [Strengths, Limitations and Future Improvements](#strengths-limitations-and-future-improvements) +- [Ethics, LGPD and Governance](#ethics-lgpd-and-governance) +- [Image Attribution](#image-attribution) +- [References](#references) +- [Acknowledgements](#acknowledgements) +- [Final Statement](#final-statement) + +

+ +## [Project Definition]() + +The **Helipoint Detector** project implements a full **Object Detection** pipeline to identify helipads on rooftops in the city of São Paulo, using high-resolution aerial and orbital imagery and models from the **YOLOv8/YOLOv11** family. + +The repository covers the entire lifecycle of an applied AI system in computer vision: programmatic image collection, geospatial automation, dataset creation and curation, annotation, preprocessing, training, quantitative and qualitative evaluation, inference on unseen neighborhoods and, optionally, an application layer for demo purposes. + +The repository was designed to present the complete lifecycle of an AI application in a documented and academically transparent form, making it useful for professors, beginner practitioners, and students learning object detection with real urban imagery. + +Rather than relying on a ready-made benchmark, the project emphasizes the construction of an original dataset, reproducible experimentation, and inference on unseen regions. + +

+ + +## [Objective]() + +The main objective is to build an **end-to-end system** capable of detecting helipads on rooftops in the city of São Paulo, following all model lifecycle stages defined in the briefing: + +[-]() programmatic acquisition of satellite data +[-]() visual curation and tile filtering +[-]() annotation with well-defined bounding boxes +[-]() preprocessing and augmentations +[-]() training and monitoring in Colab +[-]() quantitative evaluation and qualitative error analysis +[-]() inference on an entire neighborhood not used during training + +From an educational perspective, the work was also designed to help students understand how a real AI pipeline is built, validated, and communicated. The project therefore integrates data collection, annotation, preprocessing, model training, evaluation, and simple deployment in one coherent workflow. + +Methodologically, the project reinforces that model performance is directly tied to **data quality**, annotation consistency and geographical diversity, rather than small tweaks to the architecture. + +

+ +## [Why Helipads?]() + +Helipads are a compelling educational target because they often present a distinctive top-down geometric pattern while still being difficult enough to create realistic detection challenges. + +In urban satellite imagery, helipads may be confused with rooftop structures, sports markings, bright reflective surfaces, or architectural patterns. This makes them ideal for discussing false positives, annotation quality, and model generalization. + +

+ +## [Data Source]() + +The project dataset was built from satellite imagery collected over São Paulo, with a focus on neighborhoods relevant to the academic briefing and regions where helipads are more likely to appear. + +The geographical scope follows the briefing: **city of São Paulo**, focusing on neighborhoods near the PUC‑SP campus in Perdizes and regions with high helipad density, such as: + +[-]() Perdizes, Higienópolis, Pacaembu and Sumaré +[-]() Paulista Avenue, Itaim Bibi and Pinheiros +[-]() Faria Lima, Berrini, Vila Olímpia and Brooklin +[-]() other relevant urban areas such as Morumbi and adjacent regions + +### [***Image sources***]() + +- [**ESRI World Imagery (XYZ tiles)**]() — main source, with sub-meter resolution and programmatic HTTP access +- [**Google Earth Web**]() — complementary source, used only for punctual captures of specific targets, not for bulk collection +- [**GeoSampa**]() — mentioned as an alternative high-resolution source, possible extra beyond the base scope + +Images are stored as `.jpg` or `.png`, as required by the project. + +Whenever imagery or derived mosaics are reproduced, the required attribution is: +[***Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community***.]() + +

+ +## [Project Context]() + +The work was developed in the context of **Project 2 — Object Detection in Satellite Images with YOLO**, whose briefing requires each group to: + +[-]() choose [**a single target class**]() +[-]() build an [**original dataset**]() , without using pre-made sets +[-]() use [**ESRI World Imagery (XYZ tiles)**]() as the main image source +[-]() perform programmatic collection, annotation, training, evaluation and inference on an unseen neighborhood +[-]() deliver an annotated dataset, notebooks, model weights, report and presentation + +The central pedagogical message is that **around 80% of the effort in AI is in the data, not in the architecture**. The YOLO model is practically the same for all groups; the real differentiator comes from dataset quality, manual curation and annotation consistency. + +

+ +## [Business and Research Problem]() + +Manually identifying helipads in dense urban environments is a slow, subjective and hard-to-scale task. On high-resolution imagery, rooftops with circular patterns, HVAC equipment, sport markings, shadows, reflections and urban geometry can visually resemble the characteristic helipad “H”. + +This project addresses that challenge with an **Object Detection** pipeline that turns raw geospatial imagery into structured visual intelligence, reducing manual effort and enabling: + +[-]() faster, more systematic helipad localization +[-]() assessment of the model’s generalization ability across different neighborhoods +[-]() study of error patterns in real urban contexts +[-]() organized and reproducible data, image and evidence handling + +

+ +## [Extra Automation Contribution]() + +Beyond the minimum briefing requirements, the group developed an **extra geospatial automation resource** to speed up helipad discovery before the annotation stage. + +### [Technical title of the contribution]() + +
+ +> [!TIP] +> +> 👌🏻 [**Extra Resource — Automation System to Speed Up the Search for Geographic Points and Helipads**](https://github.com/Mindful-AI-Assistants/3-project-ai-ml-yolo-helipoint-detector/tree/2013898e5ed890337f05e5778c1ddf6bab1eb897/geographical_coordinates) + +

+ +### [Core idea]() + +Instead of relying solely on manual inspection in maps, the system: + +[1.]() queries a public aviation website with airport and helipad records +[2.]() automates navigation and scraping with Selenium +[3.]() extracts geographic coordinates and metadata for each helipad +[4.]() converts these coordinates into geographic bounding boxes +[5.]() uses these boxes as input to download ESRI satellite tiles +[6.]() generates mosaics ready for triage, annotation and upload to Roboflow + +
+ +> [!WARNING] +> +> This resource drastically reduces target search time and strengthens construction of a broader, traceable dataset useful for future +> training cycles. + + +

+ +## [Overall Flow Architecture]() + +The solution can be viewed as an architecture with **seven main blocks**: + +1. [**Helipad discovery**]() — automation on an aviation website to locate records with coordinates +2. [**Geographic extraction**]() — conversion and normalization of coordinates to usable decimal format +3. [**Geographic perimeter generation**]() — creation of bounding boxes around each point +4. [**Visual acquisition**]() — download of ESRI World Imagery satellite tiles based on these boxes +5. [**Visual triage**]() — manual selection of crops with clear helipad presence +6. [**Annotation and versioning**]() — use of Roboflow for labeling, preprocessing, splits and augmentations +7. [**Training, evaluation and inference**]() — YOLO training in Colab, performance measurement and generalization tests on unseen neighborhoods + +

+ +## [MLOps Pipeline Architecture]() + +```mermaid +%%{ + init: { + "theme": "dark", + "themeVariables": { + "background": "#0b1220", + "primaryColor": "#000000", + "primaryTextColor": "#ffffff", + "primaryBorderColor": "#000000", + "lineColor": "#14b8a6", + "secondaryColor": "#000000", + "secondaryTextColor": "#ffffff", + "secondaryBorderColor": "#000000", + "tertiaryColor": "#000000", + "tertiaryTextColor": "#ffffff", + "tertiaryBorderColor": "#000000", + "mainBkg": "#000000", + "nodeBorder": "#000000", + "clusterBkg": "#020617", + "clusterBorder": "#000000", + "titleColor": "#ffffff", + "edgeLabelBackground": "#0b1220", + "fontFamily": "Inter, Segoe UI, Arial, sans-serif" + } + } +}%% + +flowchart TD + + A["FlightMarket / aviation website"] --> B["Selenium automation
BOTHELIPONTO.py"] + B --> C["Helipad records + metadata"] + C --> D["Coordinates CSV
cordenadasheli.csv"] + D --> E["Coordinate conversion
Transformarcordenadas.py"] + E --> F["Geographic bounding boxes"] + F --> G["ESRI World Imagery
XYZ tile download"] + G --> H["Image mosaics by region
Imagens.ipynb"] + H --> I["Manual visual triage"] + I --> J["Selected images with helipads"] + J --> K["Roboflow upload"] + K --> L["Bounding box annotation
single class: helipad"] + L --> M["Preprocessing + augmentations
resize 640x640"] + M --> N["Dataset split
train / valid / test"] + N --> O["YOLO export
data.yaml + labels"] + O --> P["Google Colab training
Ultralytics YOLOv8 / YOLOv11"] + P --> Q["Runs, weights and metrics
runs/detect/.../best.pt"] + Q --> R["Quantitative evaluation
mAP, Precision, Recall, confusion matrix"] + Q --> S["Qualitative analysis
hits, false positives, false negatives"] + Q --> T["Inference on unseen neighborhood
New Images/"] + T --> U["Generalization assessment"] + Q --> V["Optional web app
Site.py"] + + subgraph G1["Geospatial Discovery"] + A + B + C + D + E + F + end + + subgraph G2["Visual Acquisition"] + G + H + I + J + end + + subgraph G3["Dataset Engineering"] + K + L + M + N + O + end + + subgraph G4["Modeling and Validation"] + P + Q + R + S + T + U + V + end +``` + + +

+ +> [!TIP] +> +> The pipeline should be understood as a learning architecture as much as a software architecture. It shows how raw geospatial imagery is > > gradually transformed into a validated and demonstrable AI artifact. + +

+ +## [Repository Structure]() + +The repository structure was organized to reflect pipeline stages, including geographic automation, image generation, training, inference, evaluation and documentation. + +
+ + +```bash +Helipoint-Detector/ +│ +├── .devcontainer/ # Dev environment configuration +├── AI Training/ # Trained weights, checkpoints and training artifacts +├── Briefing/ # Reference materials for Project 2 +├── New Images/ # Unseen images used for inference and generalization tests +├── Preprocessing Data/ # Inputs and intermediate preprocessing outputs +├── runs/detect/ # YOLO outputs (logs, curves, confusion matrices, weights) +│ +├── Analise.ipynb # Main notebook for analysis, evaluation and inference +├── Treinamento_YOLO.ipynb # Notebook focused on YOLO model training +├── Imagens.ipynb # Notebook for ESRI tile download and mosaic creation +│ +├── BOTHELIPONTO.py # Selenium bot for helipad search automation +├── Transformarcordenadas.py # Coordinate conversion and geographic bounding box generation +├── cordenadasheli.csv # Consolidated helipad coordinates and metadata +├── Site.py # Application script (optional web/interface layer) +│ +├── requirements.txt # Python dependencies +├── packages.txt # Additional environment/system dependencies +└── README.md # Main documentation +``` + +

+ +> [!TIP] +> +> This organization facilitates navigation, reproducibility and project evolution, clearly separating collection, preprocessing, training, > inference and application. + + +

+ +## [What is `Heliponto.rar`?]() + +`Heliponto.rar` is the compressed annotated dataset used in the project workflow. + +It is not a prebuilt third-party benchmark. Instead, it represents the packaged output of the group’s own dataset-building process: programmatic tile acquisition, manual curation, annotation, export in YOLO-compatible format, and organization for training reuse. + +This distinction is academically important because it makes clear that the dataset itself is part of the project deliverable, not an external shortcut. + +

+ +## [What is Roboflow in This Project?]() + +In this project, **Roboflow** was used as the annotation and dataset management platform rather than as the origin of the imagery. + +Its role was to support image upload, bounding-box labeling, dataset versioning, augmentation, train/validation/test splitting, and export in YOLOv8-compatible format. In practical terms, Roboflow bridges the gap between raw tiles and a training-ready object detection dataset. + +

+ +## [Methodology]() + +The project follows an end-to-end methodology aligned with educational best practices in applied Computer Vision. + +1. [**Data collection**:]() satellite tiles are collected programmatically from ESRI World Imagery.
+2. [**Manual curation**:]() irrelevant tiles are discarded to improve dataset quality. +3. [**Annotation**:]() helipads are labeled with tight bounding boxes in Roboflow. +4. [**Preprocessing**:]() the dataset is standardized and split into training, validation, and test subsets. +5. [**Training**:]() a YOLO model is trained in a GPU-enabled environment. +6. [**Evaluation**: performance is examined with metrics and qualitative error analysis. +7. [**Inference**:]() the trained model is applied to unseen images and new geographic areas. +8. [**Application layer**:]() a lightweight interface makes the model easier to demonstrate and inspect. + + +

+ +> [!IMPORTANT] +> +> This methodology highlights a key lesson in AI education: the quality of results is strongly influenced by data engineering and +> annotation decisions, not only by the network architecture. + + +

+ + +## [Full Technical Pipeline]() + +The Helipoint Detector technical pipeline can be summarized in 12 steps: + +[1.]() Discover helipad records on an aviation website
+[2.]() Extract coordinates and location information
+[3.]() Save and organize the data in `cordenadasheli.csv`
+[4.]() Convert coordinates into geographic bounding boxes
+[5.]() Download ESRI World Imagery satellite tiles
+[6.]() Build mosaics per neighborhood or region
+[7.]() Manually triage mosaics, keeping only images with helipads
+[8](). Upload selected images to Roboflow
+[9.]() Annotate helipads with consistent bounding boxes
+[10.]() Generate dataset versions with resize, splits and augmentations, exporting in YOLO format
+[11.]() Train YOLO models in Colab, monitoring metrics and train/validation curves
+[12.]() Run inference on unseen neighborhoods and analyze results + +

+ +> [!TIP] +> +> This turns a manual, scattered search into a more scalable, traceable and reproducible process. + + +

+ +## [Image Collection and Generation***]() + +### [***Programmatic collection (ESRI World Imagery)***]() + +Programmatic collection follows the XYZ tile pattern of the **ESRI World Imagery** public service, as recommended in the briefing: + +[-]( define [**zoom**]() by target type +[-]( use `z = 19` for helipads and other small targets +[-]( define **bounding boxes** per neighborhood `(lon_min, lat_min, lon_max, lat_max)` +[-]( convert bounding boxes to tile indices `(z, x, y)` via a `deg2tile` function +[-]( download each tile, checking HTTP status and filtering placeholders +[-]( organize tiles into folders by neighborhood and zoom + +

+ +> [!TIP] +> +> The `Imagens.ipynb` notebook generalizes this flow for multiple coordinates and bounding boxes, reading `cordenadasheli.csv` and producing > mosaics and crops ready for triage. + +

+ +### [***Complementary manual collection (Google Earth Web***]() + +In some cases, **Google Earth Web** may be used as a complement: + +[-](only for specific helipad examples +[-]( preserving consistent zoom +[-]( cropping approximately square areas and resizing to `640×640` + +
+ +> [!TIP] +> +> Bulk screenshot collection from Google is not used, in line with usage restrictions and the briefing. + +

+ +### [***Curation and dataset volume***]() + + + + + + + + + + + + + + + + +

+

+

+

+

+

+ + + +

+ + +## 💌 [Let the data flow... Ping Us !](mailto:fabicampanari@proton.me) + +
+ + +####

🛸๋ My Contacts [Hub](https://linktr.ee/fabianacampanari) + + +
+ +###

+ + +

+ +

────────────── ⊹🔭๋ ────────────── + + + +
+ +

➣➢➤ Back to Top + + + +# + +#####

Copyright 2026 Mindful-AI-Assistants. Code released under the [MIT license.](https://github.com/Mindful-AI-Assistants/CDIA-Entrepreneurship-Soft-Skills-PUC-SP/blob/21961c2693169d461c6e05900e3d25e28a292297/LICENSE) + + + + + + + diff --git a/README.pt_BR.md b/README.pt_BR.md new file mode 100644 index 0000000..6fc5725 --- /dev/null +++ b/README.pt_BR.md @@ -0,0 +1,199 @@ +
+ +

+ +#

🧠 AI/ML Project 2 · Computer Vision · Helipoint Detector

+ +###

Automated Helipad Detection Using YOLO and Satellite Imagery of São Paulo, Brazil

+ +
+ +
+ +Satellite Imagery +  ✦   +Urban Analytics +  ✦   +Object Detection +  ✦   +YOLO (v8 / v11) +  ✦   +Geospatial Intelligence + +
+ +

+ +####

Teaching YOLO to spot the city's most exclusive landing spots.

+ +

+Finding hidden H’s in the concrete jungle

+One rooftop at a time. 🚁⚡️ +

+ +
+ +# + +

+ + + +####

[![Sponsor Mindful AI Assistants](https://img.shields.io/badge/Sponsor-%C2%B7%C2%B7%C2%B7%20Mindful%20AI%20Assistants%20%C2%B7%C2%B7%C2%B7-brightgreen?logo=GitHub)](https://github.com/sponsors/Mindful-AI-Assistants) + +

+ + + +

+ +

+ + +


+ + + +## [Institutional Information]() + +[**Institution:**]() Pontifical Catholic University of São Paulo (PUC‑SP) — FACEI +[**Course:**]() BSc in Humanistic AI & Data Science — 5th semester — 2026 +[**Subject:**]() Machine Learning / Computer Vision — YOLO +[**Project:**]() P2 — Object Detection in Satellite Images with YOLO + +**Professor:** [✨ Rooney Ribeiro Albuquerque Coelho](https://www.linkedin.com/in/rooney-coelho-320857182/) +**Authors:** +- [Carlos Antonio Roth Gorham](https://github.com/RothGorham?tab=followers) +- [Fabiana ⚡️ Campanari](https://linktr.ee/fabianacampanari) +- [Pedro Vyctor Almeida](https://www.linkedin.com/in/pedro-vyctor-almeida-285b89273/) + + +

+ +# + +

+ + + + +

+ + Streamlit Repository Helipoint Detector + +

+ + + + + +

+ + + React Presentation Slides and Overview + + + + + + + + + Data Analysis Report and PDF + + +

+ +

+ +# + +

+ + +

+ + + +

+ +

+ + + +

+ +

+ + +

+ + +

+ +# + +

+ + + + +> [!WARNING] +> +> ⚠️ Projects may be publicly shared when permitted. +> The focus is on applied, hands-on learning with real datasets in AI governance and security contexts. +> All sensitive content remains protected in private repositories when required. +> + +

+ + + +> [!TIP] +> +> This repository is part of the flagship ecosystem: +> +> ## 🧠 AI & Machine Learning — Main Hub +> +> Explore the complete collection of projects, notebooks, research materials, analyses, and interactive applications available in the central repository: +> +> 🔗 **[AI & Machine Learning — Hub](https://github.com/Mindful-AI-Assistants/1-AI_Machine-Learning_Hub)** +> +> # +> +> ### Related Project in this Series +> +> 🔗 **[AI/ML Project 1 · Computer Vision · EMNIST Vision Intelligence](https://github.com/Mindful-AI-Assistants/2-project-ai-ml-emnist-vision-intelligence)** +> +> A deep learning system for handwritten character recognition using PyTorch and Streamlit. +> +> # +> +> ✨ Part of the *Humanistic AI & Machine Learning Series* +> +> [*From handwriting to rooftops — simplicity was never in the roadmap.*]() ⚡️ + +

+ + + +> [!IMPORTANT] +> +> This repository documents an end-to-end academic project in Computer Vision for automatic +> detection of **helipads on rooftops** using satellite images of the city of São Paulo. +> The focus goes beyond model training: it emphasizes **dataset construction**, **annotation governance**, **reproducibility**, and +> [**evaluation on unseen neighborhoods**](), in line with the briefing of Project 2 in the Machine Learning course. + +

diff --git "a/data_analysing_execitiuve_ report/\360\237\207\247\360\237\207\267Helipoint_Detector_Model_Performance_and_Data_Analysis.pages" "b/data_analysing_execitiuve_ report/\360\237\207\247\360\237\207\267Helipoint_Detector_Model_Performance_and_Data_Analysis.pages" new file mode 100644 index 0000000..7745d22 Binary files /dev/null and "b/data_analysing_execitiuve_ report/\360\237\207\247\360\237\207\267Helipoint_Detector_Model_Performance_and_Data_Analysis.pages" differ diff --git "a/data_analysing_execitiuve_ report/\360\237\207\247\360\237\207\267Helipoint_Detector_Model_Performance_and_Data_Analysis.pdf" "b/data_analysing_execitiuve_ report/\360\237\207\247\360\237\207\267Helipoint_Detector_Model_Performance_and_Data_Analysis.pdf" new file mode 100644 index 0000000..6196f81 Binary files /dev/null and "b/data_analysing_execitiuve_ report/\360\237\207\247\360\237\207\267Helipoint_Detector_Model_Performance_and_Data_Analysis.pdf" differ diff --git "a/data_analysing_execitiuve_ report/\360\237\207\254\360\237\207\247Helipoint_Detector_Model_Performance_and_Data_Analysis.pages" "b/data_analysing_execitiuve_ report/\360\237\207\254\360\237\207\247Helipoint_Detector_Model_Performance_and_Data_Analysis.pages" new file mode 100644 index 0000000..57f89ad Binary files /dev/null and "b/data_analysing_execitiuve_ report/\360\237\207\254\360\237\207\247Helipoint_Detector_Model_Performance_and_Data_Analysis.pages" differ diff --git "a/data_analysing_execitiuve_ report/\360\237\207\254\360\237\207\247Helipoint_Detector_Model_Performance_and_Data_Analysis.pdf" "b/data_analysing_execitiuve_ report/\360\237\207\254\360\237\207\247Helipoint_Detector_Model_Performance_and_Data_Analysis.pdf" new file mode 100644 index 0000000..3d256e7 Binary files /dev/null and "b/data_analysing_execitiuve_ report/\360\237\207\254\360\237\207\247Helipoint_Detector_Model_Performance_and_Data_Analysis.pdf" differ diff --git a/docs/MLOps-Architecture.md b/docs/MLOps-Architecture.md new file mode 100644 index 0000000..783cbd5 --- /dev/null +++ b/docs/MLOps-Architecture.md @@ -0,0 +1,88 @@ + +## [Mermaid Pipeline ajustado]() + +```mermaid +%%{ + init: { + "theme": "dark", + "themeVariables": { + "background": "#0b1220", + "primaryColor": "#000000", + "primaryTextColor": "#ffffff", + "primaryBorderColor": "#000000", + "lineColor": "#14b8a6", + "secondaryColor": "#000000", + "secondaryTextColor": "#ffffff", + "secondaryBorderColor": "#000000", + "tertiaryColor": "#000000", + "tertiaryTextColor": "#ffffff", + "tertiaryBorderColor": "#000000", + "mainBkg": "#000000", + "nodeBorder": "#000000", + "clusterBkg": "#020617", + "clusterBorder": "#000000", + "titleColor": "#ffffff", + "edgeLabelBackground": "#0b1220", + "fontFamily": "Inter, Segoe UI, Arial, sans-serif" + } + } +}%% + +flowchart TD + + A["FlightMarket / aviation website"] --> B["Selenium automation
BOTHELIPONTO.py"] + B --> C["Helipad records + metadata"] + C --> D["Coordinates CSV
cordenadasheli.csv"] + D --> E["Coordinate conversion
Transformarcordenadas.py"] + E --> F["Geographic bounding boxes"] + F --> G["ESRI World Imagery
XYZ tile download"] + G --> H["Image mosaics by region
Imagens.ipynb"] + H --> I["Manual visual triage"] + I --> J["Selected images with helipads"] + J --> K["Roboflow upload"] + K --> L["Bounding box annotation
single class: helipad"] + L --> M["Preprocessing + augmentations
resize 640x640"] + M --> N["Dataset split
train / valid / test"] + N --> O["YOLO export
data.yaml + labels"] + O --> P["Google Colab training
Ultralytics YOLOv8 / YOLOv11"] + P --> Q["Runs, weights and metrics
runs/detect/.../best.pt"] + Q --> R["Quantitative evaluation
mAP, Precision, Recall, confusion matrix"] + Q --> S["Qualitative analysis
hits, false positives, false negatives"] + Q --> T["Inference on unseen neighborhood
New Images/"] + T --> U["Generalization assessment"] + Q --> V["Optional web app
Site.py"] + + subgraph G1["Geospatial Discovery"] + A + B + C + D + E + F + end + + subgraph G2["Visual Acquisition"] + G + H + I + J + end + + subgraph G3["Dataset Engineering"] + K + L + M + N + O + end + + subgraph G4["Modeling and Validation"] + P + Q + R + S + T + U + V + end +``` diff --git a/geographical_coordinates/BOTHELIPONTO.py b/geographical_coordinates/BOTHELIPONTO.py new file mode 100644 index 0000000..0c276d0 --- /dev/null +++ b/geographical_coordinates/BOTHELIPONTO.py @@ -0,0 +1,405 @@ +import argparse +import csv +import os +import re +import sys +import time +from datetime import datetime +from typing import Optional, List, Dict + +try: + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.firefox.options import Options as FirefoxOptions + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + from selenium.common.exceptions import TimeoutException, WebDriverException +except ImportError: + sys.exit("[ERRO] Selenium não encontrado. Rode: pip install selenium") + +BASE = "https://www.flightmarket.com.br" + +ICAO_RE = re.compile(r"/pt/aeroportos/([A-Z]{4})\b") + +_SEP = r"[^0-9NSEWnsew]{0,3}" +_SEP2 = r"[^0-9NSEWnsew]{1,8}" +COORD_RE = re.compile( + rf"(\d{{1,3}}){_SEP}(\d{{1,2}}){_SEP}(\d{{1,2}}(?:[.,]\d+)?){_SEP}([NSns])" + rf"{_SEP2}(\d{{1,3}}){_SEP}(\d{{1,2}}){_SEP}(\d{{1,2}}(?:[.,]\d+)?){_SEP}([EWew])" +) + +LOC_RE = re.compile(r"Localiza[cç][aã]o\s*[:\-]?\s*([^\n]+)") + +DEFAULT_ESTADOS = [ + "RJ", "MG", "RS", "PR", "BA", + "CE", "GO", "SC", "PE", "DF", + "AM", "PA", "ES", "MT", "MS", +] + +CSV_HEADER = ["Carimbo de data/hora", "Coordenadas da Bounding Box", "Nome do Bairro"] + + +def _dms_para_decimal(d: str, m: str, s: str, h: str) -> float: + v = float(d) + float(m) / 60 + float(str(s).replace(",", ".")) / 3600 + return -v if h.upper() in ("S", "W") else v + + +def _formatar_dms(d1, m1, s1, h1, d2, m2, s2, h2) -> str: + def _norm(d, m, s): + s = round(float(str(s).replace(",", "."))) + if s >= 60: + s -= 60 + m += 1 + if m >= 60: + m -= 60 + d += 1 + return int(d), int(m), int(s) + + d1n, m1n, s1n = _norm(int(d1), int(m1), s1) + d2n, m2n, s2n = _norm(int(d2), int(m2), s2) + + lat_str = f"{d1n}°{m1n}'{s1n}\"{h1.upper()}" + lon_str = f"{d2n}°{m2n}'{s2n}\"{h2.upper()}" + return f"{lat_str} {lon_str}" + + +def parse_coords(texto: str): + m = COORD_RE.search(texto) + if not m: + return None, None, "" + d1, m1, s1, h1, d2, m2, s2, h2 = m.groups() + lat = _dms_para_decimal(d1, m1, s1, h1) + lon = _dms_para_decimal(d2, m2, s2, h2) + dms = _formatar_dms(d1, m1, s1, h1, d2, m2, s2, h2) + return lat, lon, dms + +def extrair_cidade(texto: str) -> str: + m = LOC_RE.search(texto) # Procura por "Localização: ..." + if m: + loc = m.group(1).strip(" ·-") + return loc.split(" - ")[0].strip() + return "" + + +def extrair_nome(titulo: str, icao: str) -> str: + partes = [p.strip() for p in titulo.split(" - ") if p.strip()] + if len(partes) >= 2: + nome = re.sub(r"(?i)^helip\w+\s+", "", partes[1]).strip() + if nome: + return nome + return icao + + +def eh_heliponto(titulo: str, texto: str) -> bool: + palavras = ("heliport", "heliponto", "heliporto") + titulo_lower = titulo.lower() + texto_lower = texto.lower() + return any(p in titulo_lower or p in texto_lower for p in palavras) + + +def geocode_bairro(lat, lon) -> str: + # Faz busca reversa nas coordenadas + # Retorna: suburb, neighbourhood, quarter, city_district, city, town + if lat is None or lon is None: + return "" + try: + import requests + resp = requests.get( + "https://nominatim.openstreetmap.org/reverse", + params={ + "lat": lat, "lon": lon, + "format": "jsonv2", + "zoom": 16, + "addressdetails": 1, + }, + headers={"User-Agent": "helipontos-cidades-csv/2.0 (estudo pessoal)"}, + timeout=10, + ) + addr = resp.json().get("address", {}) + return ( + addr.get("suburb") + or addr.get("neighbourhood") + or addr.get("quarter") + or addr.get("city_district") + or addr.get("city") + or addr.get("town") + or "" + ) + except Exception: + return "" + finally: + time.sleep(1.0) + + +def make_driver(headless: bool = True) -> webdriver.Firefox: + opts = FirefoxOptions() + if headless: + opts.add_argument("-headless") + driver = webdriver.Firefox(options=opts) + driver.set_page_load_timeout(30) + return driver + + +def coletar_icaos_do_estado( + driver: webdriver.Firefox, + estado: str, + alvo: int, + max_iter: int = 30, + espera: float = 1.3, +) -> List[str]: + seed_url = f"{BASE}/pt/aeroportos/{estado}" + print(f" Carregando: {seed_url}") + try: + driver.get(seed_url) + except WebDriverException as exc: + print(f" ⚠ Falha ao carregar página do estado {estado}: {exc}") + return [] + + encontrados: set = set() + sem_novos = 0 + + for iteracao in range(max_iter): + antes = len(encontrados) + + for el in driver.find_elements(By.CSS_SELECTOR, "a[href*='/pt/aeroportos/']"): + href = el.get_attribute("href") or "" + m = ICAO_RE.search(href) + if m: + encontrados.add(m.group(1)) + + if len(encontrados) == antes: + sem_novos += 1 + else: + sem_novos = 0 + + if sem_novos >= 3 or len(encontrados) >= max(alvo * 5, 50): + break + + botao_clicado = False + for rotulo in ("Próxima", "Proxima", "próxima", "Carregar mais", "›", ">", "next"): + try: + btn = driver.find_element( + By.XPATH, + f"//*[contains(normalize-space(.), '{rotulo}')]" + ) + driver.execute_script("arguments[0].scrollIntoView(true);", btn) + btn.click() + time.sleep(espera) + botao_clicado = True + break + except Exception: + pass + + driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") + time.sleep(espera) + + return list(encontrados) + + +def extrair_entry( + driver: webdriver.Firefox, + icao: str, + geocode: bool = False, + wait_secs: float = 12, +) -> Optional[Dict]: + url = f"{BASE}/pt/aeroportos/{icao}" + try: + driver.get(url) + WebDriverWait(driver, wait_secs).until( + EC.presence_of_element_located( + (By.XPATH, "//*[contains(text(),'Coordenadas')]") + ) + ) + except (TimeoutException, WebDriverException): + return None + + titulo = driver.title or "" + texto = driver.find_element(By.TAG_NAME, "body").text + + if not eh_heliponto(titulo, texto): + return None + + lat, lon, dms = parse_coords(texto) + if not dms: + return None + + cidade = extrair_cidade(texto) + + # Tenta geocoding primeiro para obter o nome real + if lat is not None and lon is not None: + bairro = geocode_bairro(lat, lon) + else: + bairro = "" + + # Se geocoding falhou, tenta usar a cidade extraída do site + if not bairro: + bairro = cidade + + # Se tudo falhou, usa o código ICAO como último recurso + if not bairro: + bairro = icao + + return { + "ts": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), + "coords": dms, + "bairro": bairro, + "icao": icao, + "nome": extrair_nome(titulo, icao), + "url": url, + "estado": "", + } + + +def append_csv(entry: Dict, path: str) -> None: + novo = not (os.path.isfile(path) and os.path.getsize(path) > 0) + with open(path, "a", newline="", encoding="utf-8-sig") as f: + w = csv.writer(f, quoting=csv.QUOTE_MINIMAL) + if novo: + w.writerow(CSV_HEADER) + w.writerow([entry["ts"], entry["coords"], entry["bairro"]]) + + +def mostrar_menu(): + print("\n" + "=" * 60) + print(" COLETA DE HELIPONTOS - FlightMarket") + print("=" * 60 + "\n") + + while True: + print("Quantos helipontos deseja coletar?") + print("(Digite um número entre 1 e 500)") + try: + quantidade = int(input("\nQuantidade: ")) + if 1 <= quantidade <= 500: + return quantidade + else: + print("[ERRO] Digite um número entre 1 e 500.\n") + except (ValueError, EOFError): + print("[ERRO] Digite um número válido.\n") + +def main() -> None: + quantidade = mostrar_menu() + + ap = argparse.ArgumentParser( + description="Coleta helipontos do FlightMarket (sem SP) → CSV formatado", + ) + ap.add_argument( + "--estados", nargs="+", default=DEFAULT_ESTADOS, + metavar="UF", + help="Siglas dos estados a varrer (ex: RJ MG RS). SP é sempre ignorado.", + ) + ap.add_argument( + "--output", default="helipontos_resultado.csv", + help="Caminho do arquivo CSV de saída (padrão: helipontos_resultado.csv).", + ) + ap.add_argument( + "--no-headless", action="store_true", + help="Exibe o Firefox na tela (útil para depuração).", + ) + ap.add_argument( + "--geocode", action="store_true", default=True, + help="Busca o bairro real via Nominatim/OSM — mais preciso, mas lento (1 req/seg).", + ) + ap.add_argument( + "--delay", type=float, default=1.5, + help="Pausa em segundos entre requisições de páginas (padrão: 1.5).", + ) + args = ap.parse_args() + + estados = [e.upper() for e in args.estados if e.upper() != "SP"] + if not estados: + sys.exit("[ERRO] Nenhum estado válido informado (SP não é permitido).") + + if quantidade <= 0: + sys.exit("[ERRO] A quantidade precisa ser maior que zero.") + print() + print("=" * 60) + print(" COLETA DE HELIPONTOS - FlightMarket") + print("=" * 60) + print(f" Estados : {', '.join(estados)}") + print(f" Meta : {quantidade} heliponto(s)") + print(f" Saída : {args.output}") + print(f" Geocode : {'sim (OSM/Nominatim)' if args.geocode else 'não (cidade do site)'}") + print(f" Firefox : {'visível' if args.no_headless else 'headless'}") + print("=" * 60) + print() + + try: + driver = make_driver(headless=not args.no_headless) + except WebDriverException as exc: + sys.exit( + f"[ERRO] Não foi possível iniciar o Firefox.\n" + f" Verifique se firefox + geckodriver estão instalados.\n" + f" Detalhe: {exc}" + ) + + coletados: List[Dict] = [] + visitados: set = set() + por_estado: Dict[str, int] = {} + + try: + for estado in estados: + if len(coletados) >= quantidade: + break + + faltam = quantidade - len(coletados) + print(f"\n{'─'*60}") + print(f" Estado: {estado} │ Restam: {faltam} │ " + f"Total: {len(coletados)}/{quantidade}") + print(f"{'─'*60}") + + icaos = coletar_icaos_do_estado(driver, estado, faltam, espera=args.delay) + novos = [i for i in icaos if i not in visitados] + print(f" {len(icaos)} candidato(s) encontrado(s) ({len(novos)} novo(s))\n") + + por_estado[estado] = 0 + + for icao in novos: + if len(coletados) >= quantidade: + break + + visitados.add(icao) + print(f" [{len(coletados)+1:3d}/{quantidade}] {icao} ...", end=" ", flush=True) + + try: + entry = extrair_entry( + driver, icao, + geocode=args.geocode, + wait_secs=12, + ) + except Exception as exc: + print(f"ERRO: {exc}") + continue + + if entry is None: + print("não é heliponto / sem coordenadas — pulando") + continue + + entry["estado"] = estado + print(f"✓ {entry['coords']} ← {entry['bairro']}") + + coletados.append(entry) + por_estado[estado] = por_estado.get(estado, 0) + 1 + append_csv(entry, args.output) + time.sleep(args.delay) + + except KeyboardInterrupt: + print("\n\n⚠ Interrompido pelo usuário.") + + finally: + driver.quit() + + print() + print("=" * 60) + print(f" [SUCESSO] Concluído! {len(coletados)} heliponto(s) coletado(s)") + print(f" [ARQUIVO] Arquivo: {os.path.abspath(args.output)}") + print() + if por_estado: + print(" Por estado:") + for uf, qtd in por_estado.items(): + if qtd > 0: + print(f" {uf}: {qtd}") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/geographical_coordinates/Imagens.ipynb b/geographical_coordinates/Imagens.ipynb new file mode 100644 index 0000000..5f41901 --- /dev/null +++ b/geographical_coordinates/Imagens.ipynb @@ -0,0 +1,295 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b215433a", + "metadata": {}, + "source": [ + "# Download de Mosaicos de Satélite a partir de CSV\n", + "Lê um arquivo CSV com colunas: `Carimbo de data/hora`, `Coordenadas da Bounding Box`, `Nome do Bairro`\n", + "e baixa imagens de satélite (ESRI World Imagery) para cada bairro." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bfc7da1", + "metadata": {}, + "outputs": [], + "source": [ + "import math, os, io, time\n", + "from pathlib import Path\n", + "from concurrent.futures import ThreadPoolExecutor\n", + "\n", + "import pandas as pd\n", + "import requests\n", + "from PIL import Image\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7048bc92", + "metadata": {}, + "outputs": [], + "source": [ + "# ── CONFIGURAÇÕES ─────────────────────────────────────────────────────────────\n", + "CSV_PATH = \"cordenadasheli.csv\" # <-- coloque aqui o caminho do seu arquivo CSV\n", + "ZOOM = 19 # zoom do tile (19 = máximo prático para imagens urbanas)\n", + "TILE_URL = \"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}\"\n", + "HEADERS = {\"User-Agent\": \"Projeto-Imagens-Satelite/1.0\"}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9b6f57f", + "metadata": {}, + "outputs": [], + "source": [ + "# ── LER O CSV ─────────────────────────────────────────────────────────────────\n", + "df = pd.read_csv(CSV_PATH)\n", + "print(f\"Linhas carregadas: {len(df)}\")\n", + "print(df.head())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44ddcd2e", + "metadata": {}, + "outputs": [], + "source": [ + "# ── PARSEAR COORDENADAS ────────────────────────────────────────────────────────\n", + "# Coluna esperada: \"lon_min lat_min lon_max lat_max\" (separado por tabs ou espaços)\n", + "def parse_bbox(raw):\n", + " \"\"\"Converte string '−43.659 −22.961 −43.658 −22.960' em tupla de floats.\"\"\"\n", + " parts = str(raw).replace(',', ' ').split()\n", + " return tuple(float(p) for p in parts) # lon_min, lat_min, lon_max, lat_max\n", + "\n", + "df[\"bbox\"] = df[\"Coordenadas da Bounding Box\"].apply(parse_bbox)\n", + "\n", + "# Garante nomes únicos de pasta (bairros podem se repetir no CSV)\n", + "name_count = {}\n", + "slugs = []\n", + "for nome in df[\"Nome do Bairro\"]:\n", + " slug = nome.strip().replace(\" \", \"_\").replace(\"/\", \"-\")\n", + " n = name_count.get(slug, 0)\n", + " name_count[slug] = n + 1\n", + " slugs.append(slug if n == 0 else f\"{slug}_{n+1}\")\n", + "\n", + "df[\"slug\"] = slugs\n", + "print(df[[\"Nome do Bairro\", \"slug\", \"bbox\"]].to_string())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17c4d826", + "metadata": {}, + "outputs": [], + "source": [ + "# ── CRIAR PASTAS DE SAÍDA ─────────────────────────────────────────────────────\n", + "OUT_DIRS = {}\n", + "for slug in df[\"slug\"]:\n", + " folder = Path(f\"mosaico_{slug}\")\n", + " folder.mkdir(exist_ok=True)\n", + " OUT_DIRS[slug] = folder\n", + " print(\"Pasta criada:\", folder.resolve())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efc80425", + "metadata": {}, + "outputs": [], + "source": [ + "# ── FUNÇÕES DE CONVERSÃO TILE ─────────────────────────────────────────────────\n", + "def deg2tile(lat, lon, z):\n", + " n = 2.0 ** z\n", + " x = int((lon + 180.0) / 360.0 * n)\n", + " lat_rad = math.radians(lat)\n", + " y = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)\n", + " return x, y\n", + "\n", + "def deg_to_meters(lat_ref, dlat, dlon):\n", + " return dlat * 111_132.0, dlon * 111_320.0 * math.cos(math.radians(lat_ref))\n", + "\n", + "# Resumo de cada bairro\n", + "for _, row in df.iterrows():\n", + " lon_min, lat_min, lon_max, lat_max = row[\"bbox\"]\n", + " x_min, y_max = deg2tile(lat_min, lon_min, ZOOM)\n", + " x_max, y_min = deg2tile(lat_max, lon_max, ZOOM)\n", + " n_cols = x_max - x_min + 1\n", + " n_rows = y_max - y_min + 1\n", + " h_m, w_m = deg_to_meters((lat_min+lat_max)/2, lat_max-lat_min, lon_max-lon_min)\n", + " print(f\"{row['slug']}: {n_cols}x{n_rows} tiles | \"\n", + " f\"{n_cols*256}x{n_rows*256} px | \"\n", + " f\"{w_m:.0f}m x {h_m:.0f}m\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "106ead23", + "metadata": {}, + "outputs": [], + "source": [ + "# ── DOWNLOAD DOS TILES ────────────────────────────────────────────────────────\n", + "def baixar_tile(args):\n", + " z, x, y, out_dir = args\n", + " path = out_dir / f\"tile_z{z}_x{x}_y{y}.jpg\"\n", + " if path.exists():\n", + " return path, True, 0\n", + " for tentativa in range(3):\n", + " try:\n", + " r = requests.get(TILE_URL.format(z=z, x=x, y=y),\n", + " headers=HEADERS, timeout=25)\n", + " if r.status_code == 200 and len(r.content) > 2521:\n", + " path.write_bytes(r.content)\n", + " return path, True, tentativa\n", + " except Exception:\n", + " time.sleep(0.5)\n", + " return path, False, 3\n", + "\n", + "for _, row in df.iterrows():\n", + " slug = row[\"slug\"]\n", + " lon_min, lat_min, lon_max, lat_max = row[\"bbox\"]\n", + " out_dir = OUT_DIRS[slug]\n", + "\n", + " x_min, y_max = deg2tile(lat_min, lon_min, ZOOM)\n", + " x_max, y_min = deg2tile(lat_max, lon_max, ZOOM)\n", + "\n", + " jobs = [(ZOOM, x, y, out_dir)\n", + " for x in range(x_min, x_max + 1)\n", + " for y in range(y_min, y_max + 1)]\n", + "\n", + " print(f\"\\n[{slug}] Baixando {len(jobs)} tiles...\")\n", + " t0 = time.time()\n", + " ok = falhas = 0\n", + " with ThreadPoolExecutor(max_workers=4) as ex:\n", + " for path, sucesso, _ in ex.map(baixar_tile, jobs):\n", + " if sucesso: ok += 1\n", + " else: falhas += 1\n", + "\n", + " dt = time.time() - t0\n", + " mb = sum(p.stat().st_size for p in out_dir.glob('*.jpg')) / 1024 / 1024\n", + " print(f\" OK: {ok}/{len(jobs)} | Falhas: {falhas} | \"\n", + " f\"Tempo: {dt:.1f}s | Disco: {mb:.1f} MB\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c3a9d55", + "metadata": {}, + "outputs": [], + "source": [ + "# ── AMOSTRA DE TILES (grade 3x3) ──────────────────────────────────────────────\n", + "for _, row in df.iterrows():\n", + " slug = row[\"slug\"]\n", + " lon_min, lat_min, lon_max, lat_max = row[\"bbox\"]\n", + " out_dir = OUT_DIRS[slug]\n", + "\n", + " x_min, y_max = deg2tile(lat_min, lon_min, ZOOM)\n", + " x_max, y_min = deg2tile(lat_max, lon_max, ZOOM)\n", + "\n", + " xs = np.linspace(x_min, x_max, 3, dtype=int)\n", + " ys = np.linspace(y_min, y_max, 3, dtype=int)\n", + "\n", + " fig, axes = plt.subplots(3, 3, figsize=(9, 9))\n", + " for i, y in enumerate(ys):\n", + " for j, x in enumerate(xs):\n", + " p = out_dir / f\"tile_z{ZOOM}_x{x}_y{y}.jpg\"\n", + " ax = axes[i, j]\n", + " if p.exists():\n", + " ax.imshow(Image.open(p))\n", + " ax.set_title(f\"x={x}, y={y}\", fontsize=8)\n", + " ax.set_xticks([]); ax.set_yticks([])\n", + "\n", + " plt.suptitle(f\"Amostras — {row['Nome do Bairro']} @ zoom {ZOOM}\",\n", + " fontweight=\"bold\", y=1.0)\n", + " plt.tight_layout()\n", + " plt.savefig(f\"amostra_tiles_{slug}.png\", dpi=110, bbox_inches=\"tight\")\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3f658b8", + "metadata": {}, + "outputs": [], + "source": [ + "# ── MONTAR MOSAICO COMPLETO ───────────────────────────────────────────────────\n", + "TILE_PX = 256\n", + "\n", + "for _, row in df.iterrows():\n", + " slug = row[\"slug\"]\n", + " lon_min, lat_min, lon_max, lat_max = row[\"bbox\"]\n", + " out_dir = OUT_DIRS[slug]\n", + "\n", + " x_min, y_max = deg2tile(lat_min, lon_min, ZOOM)\n", + " x_max, y_min = deg2tile(lat_max, lon_max, ZOOM)\n", + " n_cols = x_max - x_min + 1\n", + " n_rows = y_max - y_min + 1\n", + " W, H = n_cols * TILE_PX, n_rows * TILE_PX\n", + "\n", + " mosaico = Image.new(\"RGB\", (W, H), (0, 0, 0))\n", + " faltando = 0\n", + " for x in range(x_min, x_max + 1):\n", + " for y in range(y_min, y_max + 1):\n", + " p = out_dir / f\"tile_z{ZOOM}_x{x}_y{y}.jpg\"\n", + " if p.exists():\n", + " mosaico.paste(Image.open(p),\n", + " ((x - x_min) * TILE_PX, (y - y_min) * TILE_PX))\n", + " else:\n", + " faltando += 1\n", + "\n", + " print(f\"{slug}: {W}x{H} px | {faltando} tile(s) faltando\")\n", + "\n", + " mosaico.save(f\"mosaico_{slug}_hires_full.jpg\", quality=88)\n", + "\n", + " preview = mosaico.copy()\n", + " preview.thumbnail((3000, 3000))\n", + " preview.save(f\"mosaico_{slug}_hires_preview.jpg\", quality=85)\n", + "\n", + " plt.figure(figsize=(12, 12 * H / W))\n", + " plt.imshow(preview)\n", + " plt.title(\n", + " f\"Mosaico de {row['Nome do Bairro']} @ z={ZOOM} — {n_cols}x{n_rows} tiles ({W}x{H} px)\\n\"\n", + " \"Fonte: Esri, Maxar, Earthstar Geographics, and the GIS User Community\",\n", + " fontsize=10\n", + " )\n", + " plt.axis(\"off\")\n", + " plt.tight_layout()\n", + " plt.savefig(f\"mosaico_{slug}_hires_anotado.png\", dpi=110, bbox_inches=\"tight\")\n", + " plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/geographical_coordinates/Transformarcordenadas.py b/geographical_coordinates/Transformarcordenadas.py new file mode 100644 index 0000000..4a924b7 --- /dev/null +++ b/geographical_coordinates/Transformarcordenadas.py @@ -0,0 +1,108 @@ +import csv +import re +import sys +from pathlib import Path + +_NUM = r"[-+]?\d+(?:\.\d+)?" +_COORD_RE = re.compile( + rf""" + (?P{_NUM})\s*[°ºo]?\s* + (?:(?P{_NUM})\s*['’′]?\s*)? + (?:(?P{_NUM})\s*["”″]?\s*)? + (?P[NSEWnsew])? + """, + re.VERBOSE, +) + +# Margem ao redor do ponto, em graus decimais. +# 0.0005 graus equivale a aproximadamente 55 metros. +MARGEM = 0.0005 + +COLUNA_COORD = "Coordenadas da Bounding Box" + + +def dms_to_dd(graus, minutos, segundos, direcao=""): + dd = abs(graus) + minutos / 60 + segundos / 3600 + if direcao.upper() in ("S", "W") or graus < 0: + dd = -dd + return dd + + +def parse_dms_pair(texto): + coords = [] + for m in _COORD_RE.finditer(texto): + if m.group("g") is None or m.group(0).strip() == "": + continue + graus = float(m.group("g")) + minutos = float(m.group("m")) if m.group("m") else 0.0 + segundos = float(m.group("s")) if m.group("s") else 0.0 + direcao = m.group("dir") or "" + coords.append(dms_to_dd(graus, minutos, segundos, direcao)) + if len(coords) < 2: + raise ValueError(f"coordenada inválida: {texto!r}") + return coords[0], coords[1] # lat, lon + + +def bbox_para_ponto(texto_coord): + lat, lon = parse_dms_pair(texto_coord) + lon_min = lon - MARGEM + lon_max = lon + MARGEM + lat_min = lat - MARGEM + lat_max = lat + MARGEM + return f"{lon_min:.6f}\t{lat_min:.6f}\t{lon_max:.6f}\t{lat_max:.6f}" + + +def processar(entrada: Path, saida: Path): + if not entrada.exists(): + print(f"Erro: arquivo não encontrado -> {entrada}") + sys.exit(1) + + with open(entrada, newline="", encoding="utf-8-sig") as f_in: + reader = csv.DictReader(f_in) + fieldnames = reader.fieldnames + + if not fieldnames or COLUNA_COORD not in fieldnames: + print(f"Erro: coluna '{COLUNA_COORD}' não encontrada no CSV.") + print(f"Colunas encontradas: {fieldnames}") + sys.exit(1) + + linhas = list(reader) + + convertidas = 0 + falhas = 0 + + with open(saida, "w", newline="", encoding="utf-8") as f_out: + writer = csv.DictWriter(f_out, fieldnames=fieldnames) + writer.writeheader() + for i, linha in enumerate(linhas, start=2): # linha 1 = cabeçalho + coord_original = linha.get(COLUNA_COORD, "") + try: + linha[COLUNA_COORD] = bbox_para_ponto(coord_original) + convertidas += 1 + except ValueError as e: + print(f"Aviso (linha {i}): {e} -> mantida sem alteração") + falhas += 1 + writer.writerow(linha) + + print(f"\nConcluído: {convertidas} linha(s) convertida(s), {falhas} falha(s).") + print(f"Arquivo gerado: {saida}") + + +def main(): + if len(sys.argv) >= 3: + entrada = Path(sys.argv[1]) + saida = Path(sys.argv[2]) + elif len(sys.argv) == 2: + entrada = Path(sys.argv[1]) + saida = entrada.with_name(entrada.stem + "_bbox.csv") + else: + # Procura os arquivos na mesma pasta do script + script_dir = Path(__file__).parent + entrada = script_dir / "helipontos_resultado.csv" + saida = script_dir / "cordenadasheli.csv" + + processar(entrada, saida) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/geographical_coordinates/cordenadasheli.csv b/geographical_coordinates/cordenadasheli.csv new file mode 100644 index 0000000..9ad8309 --- /dev/null +++ b/geographical_coordinates/cordenadasheli.csv @@ -0,0 +1,134 @@ +Carimbo de data/hora,Coordenadas da Bounding Box,Nome do Bairro +22/06/2026 00:00:00,-46.685000 -23.590000 -46.665000 -23.575000,Itaim_Bibi +22/06/2026 00:00:00,-46.669884 -23.580502 -46.633996 -23.552062,Av_Paulista +22/06/2026 00:00:00,-46.690000 -23.598000 -46.670000 -23.585000,Vila_Olimpia +22/06/2026 00:00:00,-46.704205 -23.574847 -46.674487 -23.558183,Pinheiros +22/06/2026 00:00:00,-46.699949 -23.620544 -46.682294 -23.596478,Brooklin +22/06/2026 00:00:00,-46.860248 -23.505565 -46.840035 -23.490924,Alphaville_Industrial +22/06/2026 00:00:00,-46.681131339 -23.5980107441 -46.6639652013 -23.585366627,Vila_nova_Conceicao +22/06/2026 00:00:00,-46.692633 -23.596674 -46.680058 -23.581694,Inter_Zonas +21/06/2026 23:36:19,-46.653000 -23.595778 -46.652000 -23.594778,Vila Clementino +21/06/2026 23:36:30,-46.649667 -23.593278 -46.648667 -23.592278,Vila Clementino +21/06/2026 23:36:52,-46.646889 -23.593833 -46.645889 -23.592833,Vila Clementino +21/06/2026 23:38:15,-46.661333 -23.601333 -46.660333 -23.600333,Moema +21/06/2026 23:38:33,-46.661056 -23.602722 -46.660056 -23.601722,Moema +22/06/2026 00:06:33,-46.778556 -23.482167 -46.777556 -23.481167,OSASCO +22/06/2026 00:07:02,-46.781333 -23.478833 -46.780333 -23.477833,OSASCO +22/06/2026 00:07:14,-46.778556 -23.477167 -46.777556 -23.476167,OSASCO +22/06/2026 00:15:37,-46.631056 -23.485222 -46.630056 -23.484222,Santana +22/06/2026 00:16:39,-46.639944 -23.473278 -46.638944 -23.472278,Santana +22/06/2026 00:16:58,-46.619667 -23.470778 -46.618667 -23.469778,Santana +22/06/2026 00:17:14,-46.610778 -23.478833 -46.609778 -23.477833,Santana +22/06/2026 00:18:08,-46.582444 -23.465778 -46.581444 -23.464778,Santana +22/06/2026 00:18:33,-46.585778 -23.431056 -46.584778 -23.430056,Santana +22/06/2026 00:18:44,-46.581056 -23.415222 -46.580056 -23.414222,Santana +22/06/2026 00:19:29,-46.763556 -23.521056 -46.762556 -23.520056,OSASCO +22/06/2026 00:20:11,-46.772444 -23.549667 -46.771444 -23.548667,OSASCO +22/06/2026 00:21:05,-46.782444 -23.534389 -46.781444 -23.533389,OSASCO +22/06/2026 00:21:39,-46.744667 -23.529667 -46.743667 -23.528667,Vila Leopoldina +22/06/2026 00:21:55,-46.744389 -23.526333 -46.743389 -23.525333,Vila Leopoldina +22/06/2026 00:23:59,-46.737167 -23.547167 -46.736167 -23.546167,Jaguaré +22/06/2026 00:24:28,-46.733833 -23.573833 -46.732833 -23.572833,Jaguaré +22/06/2026 00:25:18,-46.703556 -23.591056 -46.702556 -23.590056,Morumbi +22/06/2026 00:25:35,-46.711056 -23.595778 -46.710056 -23.594778,Morumbi +22/06/2026 00:25:55,-46.707444 -23.598278 -46.706444 -23.597278,Morumbi +22/06/2026 00:26:25,-46.713556 -23.601056 -46.712556 -23.600056,Morumbi +22/06/2026 00:26:36,-46.715778 -23.600778 -46.714778 -23.599778,Morumbi +22/06/2026 00:31:05,-46.703556 -23.618833 -46.702556 -23.617833,Vila Andrade +22/06/2026 00:32:56,-46.702167 -23.617167 -46.701167 -23.616167,Vila Andrade +22/06/2026 00:33:06,-46.699111 -23.620500 -46.698111 -23.619500,Vila Andrade +22/06/2026 00:33:20,-46.699389 -23.617444 -46.698389 -23.616444,Vila Andrade +22/06/2026 00:33:32,-46.698556 -23.615222 -46.697556 -23.614222,Vila Andrade +22/06/2026 00:33:59,-46.697167 -23.614944 -46.696167 -23.613944,Vila Andrade +22/06/2026 00:38:18,-46.580778 -23.617167 -46.579778 -23.616167,SÃO CAETANO DO SUL +22/06/2026 00:38:41,-46.571056 -23.611333 -46.570056 -23.610333,SÃO CAETANO DO SUL +22/06/2026 00:38:52,-46.569667 -23.614944 -46.568667 -23.613944,SÃO CAETANO DO SUL +22/06/2026 00:39:09,-46.547167 -23.615778 -46.546167 -23.614778,SÃO CAETANO DO SUL +22/06/2026 00:39:41,-46.528556 -23.656056 -46.527556 -23.655056,Santo André +22/06/2026 00:43:45,-46.504944 -23.567722 -46.503944 -23.566722,Cidade Líder +22/06/2026 00:44:12,-46.461889 -23.554667 -46.460889 -23.553667,Cidade Líder +22/06/2026 00:44:38,-46.478278 -23.544667 -46.477278 -23.543667,Cidade Líder +22/06/2026 04:35:00,"22°58'56""S 43°23'38""W",Barra Olímpica +22/06/2026 04:35:05,"22°55'57""S 43°22'27""W",Taquara +22/06/2026 04:35:09,"22°58'50""S 43°13'54""W",Gávea +22/06/2026 04:35:32,"19°57'47""S 43°57'20""W",São Bento +22/06/2026 04:35:37,"19°51'6""S 43°57'2""W",Jaraguá +22/06/2026 04:35:43,"19°54'33""S 43°59'21""W",Padre Eustáquio +22/06/2026 04:35:47,"19°55'40""S 43°56'58""W",Santo Agostinho +22/06/2026 04:35:51,"19°55'44""S 43°57'2""W",Santo Agostinho +22/06/2026 04:35:55,"19°54'51""S 43°56'16""W",Centro +22/06/2026 04:35:59,"19°56'7""S 43°55'27""W",Serra +22/06/2026 04:36:19,"29°24'36""S 49°48'37""W",Torres +22/06/2026 04:36:26,"27°39'47""S 52°16'18""W",Aeroporto +22/06/2026 04:36:45,"25°26'17""S 49°14'26""W",Jardim Botânico +22/06/2026 04:36:50,"25°25'57""S 49°19'33""W",Mossunguê +22/06/2026 04:36:54,"25°26'20""S 49°16'4""W",Rebouças +22/06/2026 04:37:00,"25°26'11""S 49°14'41""W",Cristo Rei +22/06/2026 04:37:05,"25°22'33""S 49°13'40""W",Santa Cândida +22/06/2026 04:37:10,"25°31'54""S 49°10'33""W",Área Industrial Aeroportuária +22/06/2026 04:37:29,"12°56'38""S 38°25'32""W",Centro Administrativo da Bahia +22/06/2026 04:37:33,"12°47'44""S 38°38'36""W",Ilha dos Frades / Ilha de Santo Antônio +22/06/2026 04:37:38,"12°58'46""S 38°27'41""W",Caminho das Árvores +22/06/2026 04:37:43,"13°0'33""S 38°31'13""W",Barra +22/06/2026 04:37:48,"12°59'21""S 38°26'53""W",STIEP +22/06/2026 04:38:05,"3°44'6""S 38°30'11""W",Aldeota +22/06/2026 04:38:10,"3°44'29""S 38°29'14""W",Cocó +22/06/2026 04:38:14,"3°49'31""S 38°28'17""W",Lagoa Redonda +22/06/2026 04:38:19,"3°46'32""S 38°31'55""W",Aeroporto +22/06/2026 04:38:23,"3°43'59""S 38°30'33""W",Aldeota +22/06/2026 04:38:28,"3°44'26""S 38°30'33""W",Aldeota +22/06/2026 04:38:33,"3°45'59""S 38°29'9""W",Guararapes +22/06/2026 04:38:38,"3°49'15""S 38°32'2""W",Passaré +22/06/2026 04:38:55,"16°39'18""S 49°14'56""W",Osvaldo Rosa +22/06/2026 04:39:01,"16°38'52""S 49°12'32""W",Residencial Carlos de Freitas +22/06/2026 04:39:05,"16°41'48""S 49°16'10""W",Setor Marista +22/06/2026 04:39:10,"16°41'49""S 49°15'20""W",Setor Sul +22/06/2026 04:39:14,"16°42'17""S 49°14'26""W",Jardim Goiás +22/06/2026 04:39:19,"16°37'51""S 49°12'2""W",Sítio de Recreio Ipê +22/06/2026 04:39:25,"16°40'54""S 49°15'20""W",Setor Central +22/06/2026 04:39:44,"26°52'42""S 48°39'2""W",Centro +22/06/2026 04:39:50,"26°47'17""S 50°56'23""W",Aeroporto +22/06/2026 04:39:55,"27°17'13""S 50°36'14""W",Curitibanos +22/06/2026 04:39:59,"27°27'20""S 48°27'41""W",Canasvieiras +22/06/2026 04:40:05,"28°40'31""S 49°3'37""W",Jaguaruna +22/06/2026 04:40:09,"27°46'55""S 50°16'54""W",Guarujá +22/06/2026 04:40:25,"8°3'43""S 34°57'50""W",UR-07 +22/06/2026 04:40:31,"8°6'3""S 34°53'3""W",Boa Viagem +22/06/2026 04:40:35,"8°5'11""S 34°54'7""W",Pina +22/06/2026 04:40:43,"8°50'3""S 36°28'17""W",São Pedro +22/06/2026 04:40:49,"8°24'28""S 37°5'17""W",Santos Dumont +22/06/2026 04:41:08,"15°47'44""S 47°53'36""W",Setor Comercial Sul +22/06/2026 04:41:12,"15°47'26""S 47°52'45""W",Brasília +22/06/2026 04:41:17,"15°51'15""S 47°48'33""W",Reserva Jardim Botânico +22/06/2026 04:41:22,"15°46'23""S 47°42'10""W",Paranoá +22/06/2026 04:41:26,"15°49'50""S 47°52'0""W",Lago Sul +22/06/2026 04:41:32,"15°49'45""S 48°1'44""W",Águas Claras +22/06/2026 04:41:37,"15°50'7""S 47°54'35""W",Asa Sul +22/06/2026 04:41:54,"2°59'53""S 60°1'48""W",Colonia Terra Nova +22/06/2026 04:41:59,"5°48'39""S 61°16'41""W",Manicoré +22/06/2026 04:42:07,"3°6'57""S 60°1'9""W",Nossa Senhora das Graças +22/06/2026 04:42:13,"7°10'19""S 59°50'20""W",Apuí +22/06/2026 04:42:32,"1°16'1""S 48°25'47""W",Outeiro +22/06/2026 04:42:36,"3°46'36""S 49°43'10""W",Tucuruí +22/06/2026 04:42:41,"1°24'54""S 48°27'32""W",Souza +22/06/2026 04:42:46,"6°38'29""S 51°57'7""W",São Félix do Xingu +22/06/2026 04:42:50,"6°14'7""S 57°46'32""W",Jacareacanga +22/06/2026 04:42:56,"3°1'10""S 47°18'59""W",Paragominas +22/06/2026 04:43:00,"1°26'22""S 48°28'54""W",Umarizal +22/06/2026 04:43:19,"19°27'51""S 40°40'1""W",Colatina +22/06/2026 04:43:25,"19°49'35""S 40°6'2""W",Barra do Riacho +22/06/2026 04:43:30,"20°15'29""S 40°17'11""W",Aeroporto +22/06/2026 04:43:36,"19°56'14""S 40°24'39""W",Cocal +22/06/2026 04:43:41,"20°13'50""S 40°16'36""W",Carapina Grande +22/06/2026 04:44:00,"13°49'15""S 56°2'18""W",Nova Mutum +22/06/2026 04:44:06,"14°41'52""S 52°20'49""W",Nova Xavantina +22/06/2026 04:44:11,"14°39'42""S 57°26'38""W",Jardim Aeroporto +22/06/2026 04:44:19,"15°33'15""S 55°41'30""W",Cuiabá +22/06/2026 04:44:24,"15°39'0""S 56°7'2""W",Centro Sul +22/06/2026 04:44:45,"20°45'42""S 54°26'52""W",Campo Grande +22/06/2026 04:44:51,"20°34'17""S 54°42'55""W",Recreio Itaim +22/06/2026 04:44:58,"20°29'9""S 54°28'59""W",Terras do Golfe + + + + diff --git a/packages.txt b/packages.txt index 52f9a7b..e69de29 100644 --- a/packages.txt +++ b/packages.txt @@ -1 +0,0 @@ -libgl1 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dc11f0c..9119611 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,42 @@ +######################## +# Core de IA / YOLO +######################## ultralytics>=8.4.73 -numpy>=2.4.2 -matplotlib>=3.10.8 -pillow>=12.1.1 -requests>=2.34.2 -opencv-python-headless>=4.10.0.84 \ No newline at end of file +torch>=1.8.0 +torchvision>=0.9.0 +tqdm>=4.64.0 +pyyaml>=5.3.1 +scipy>=1.4.1 + +######################## +# Científico / análise +######################## +numpy>=1.22.2 +pandas>=1.1.4 +matplotlib>=3.3.0 +seaborn>=0.11.0 + +######################## +# Imagens e visão +######################## +pillow>=7.1.2 +opencv-python-headless>=4.6.0 + +######################## +# Web, APIs e apps +######################## +flask>=2.0.0 +streamlit>=1.20.0 + +######################## +# Selenium / automação +######################## +selenium>=4.0.0 +webdriver-manager>=3.8.0 + +######################## +# Utilidades gerais +######################## +requests>=2.23.0 +psutil +py-cpuinfo diff --git a/Site.py b/streamlit/Site.py similarity index 100% rename from Site.py rename to streamlit/Site.py diff --git a/streamlit/streamlit-app_link.md b/streamlit/streamlit-app_link.md new file mode 100644 index 0000000..251891e --- /dev/null +++ b/streamlit/streamlit-app_link.md @@ -0,0 +1,2 @@ + +https://helipoint-detector.streamlit.app