osm-ai-helper / app.py
github-actions[bot]
Sync with https://github.com/mozilla-ai/osm-ai-helper
204c72a
import json
import xml.etree.ElementTree as ET
from pathlib import Path
from shutil import move
import folium
import streamlit as st
from branca.element import MacroElement
from jinja2 import Template
from huggingface_hub import hf_hub_download
from PIL import Image
from streamlit_folium import st_folium
from osm_ai_helper.run_inference import run_inference
from osm_ai_helper.export_osm import convert_polygons
@st.fragment
def show_map():
class LatLngPopup(MacroElement):
_template = Template(
"""
{% macro script(this, kwargs) %}
var {{this.get_name()}} = L.popup();
function latLngPop(e) {
{{this.get_name()}}
.setLatLng(e.latlng)
.setContent(e.latlng.lat.toFixed(4) + ", " + e.latlng.lng.toFixed(4))
.openOn({{this._parent.get_name()}});
}
{{this._parent.get_name()}}.on('click', latLngPop);
{% endmacro %}
"""
)
def __init__(self):
super().__init__()
self._name = "LatLngPopup"
m = folium.Map(location=[42.8075, -8.1519], zoom_start=8, tiles="OpenStreetMap")
m.add_child(LatLngPopup())
st_folium(m, height=400, width=800)
@st.fragment
def inference(lat_lon):
with st.spinner("Downloading model..."):
hf_hub_download(
"mozilla-ai/swimming-pool-detector",
filename="model.pt",
repo_type="model",
local_dir="models",
)
with st.spinner("Downloading image and Running inference..."):
output_path, existing, new, missed = run_inference(
yolo_model_file="models/model.pt",
output_dir="/tmp/results",
lat_lon=lat_lon,
margin=2,
save_full_images=False,
batch_size=64,
)
return output_path, existing, new
@st.fragment
def handle_polygon(polygon):
raw_image = Image.open(polygon.with_suffix(".png"))
painted_image = Image.open(f"{polygon.parent}/{polygon.stem}_painted.png")
st.subheader(f"Reviewing: {polygon.name}")
col1, col2 = st.columns(2)
with col1:
st.image(raw_image, caption="Raw Image", use_container_width=True)
with col2:
st.image(painted_image, caption="Painted Image", use_container_width=True)
if st.button("Keep Polygon", key=f"keep_{polygon}"):
keep_folder = polygon.parent / "keep"
keep_folder.mkdir(parents=True, exist_ok=True)
move(polygon, keep_folder / polygon.name)
st.success(f"Polygon moved to {keep_folder}")
elif st.button("Discard Polygon", key=f"discard_{polygon.stem}"):
discard_folder = polygon.parent / "discard"
discard_folder.mkdir(parents=True, exist_ok=True)
move(polygon, discard_folder / polygon.name)
st.warning(f"Polygon moved to {discard_folder}")
@st.fragment
def download_results(output_path):
st.divider()
st.header("Export Results")
st.markdown(
"The results will be exported in [OsmChange](https://wiki.openstreetmap.org/wiki/OsmChange) format."
"\nYou can then import the file in [any of the supported editors](https://wiki.openstreetmap.org/wiki/OsmChange#Editors) format."
)
lon_lat_polygons = [
json.loads(result.read_text())
for result in (output_path / "keep").glob("*.json")
]
osmchange = convert_polygons(
lon_lat_polygons=lon_lat_polygons,
tags={"leisure": "swimming_pool", "access": "private", "location": "outdoor"},
)
st.download_button(
label="Download all polygons in `keep`",
data=ET.tostring(osmchange, "utf-8"),
file_name="exported_results.osc",
mime="type/xml",
)
st.title("OpenStreetMap AI Helper")
st.markdown(
"""
This demo was created with the repo [mozilla-ai/osm-ai-helper](https://github.com/mozilla-ai/osm-ai-helper).
It uses the model [mozilla-ai/swimming-pool-detector](https://huggingface.co/mozilla-ai/swimming-pool-detector).
You can check the [Create Dataset](https://colab.research.google.com/github/mozilla-ai//osm-ai-helper/blob/main/demo/create_dataset.ipyn)
and [Finetune Model](https://colab.research.google.com/github/mozilla-ai//osm-ai-helper/blob/main/demo/finetune_model.ipynb) notebooks to learn how to train your own model.
"""
)
st.divider()
st.subheader("Click on the map to select a latitude and longitude.")
st.markdown(
"""
The model will try to find swimming pools around this location.
Note that this model was trained with data from [Galicia](https://nominatim.openstreetmap.org/ui/details.html?osmtype=R&osmid=349036&class=boundary),
so it might fail to generalize to significantly different places.
"""
)
show_map()
lat_lon = st.text_input("Paste the copied (latitude, longitude)")
if st.button("Run Inference") and lat_lon:
lat, lon = lat_lon.split(",")
output_path, existing, new = inference(
lat_lon=(float(lat.strip()), float(lon.strip()))
)
st.info(f"Found {len(existing)} swimming pools already in OpenStreetMaps.")
if new:
st.divider()
st.header("Review `new` swimming pools")
st.markdown(
"Every `new` swimming pool will be displayed at the center of the image in `yellow`."
)
st.markdown(
"Swimming pools in other colors are those already existing in OpenStreetMap and they just "
"indicate whether the model has found them (`green`) or missed them (`red`)."
)
for new in Path(output_path).glob("*.json"):
handle_polygon(new)
download_results(output_path)
else:
st.warning("No `new` swimming pools were found. Try a different location.")