Spaces:
Running
Running
Sync DemoService 42773efa3fee367a7ac26de8f7f0006519a6e1e8
Browse files- api/ai-service/app/api/v1/endpoints/route_generation.py +2 -2
- api/ai-service/tests/test_gpx_shape_templates.py +15 -0
- api/ai-service/tests/test_route_generation_recovery.py +4 -3
- router/ai/data/dog_shape_templates_all.json +0 -0
- router/ai/scripts/build_dog_shape_templates.py +43 -0
- router/ai/scripts/clip_osm_region_test.py +1 -1
api/ai-service/app/api/v1/endpoints/route_generation.py
CHANGED
|
@@ -437,8 +437,8 @@ def _run_fast_road_pipeline(
|
|
| 437 |
rank_projections = getattr(module, "rank_road_template_projections", None)
|
| 438 |
for search_distance_km in (6.0, 11.0, 18.0):
|
| 439 |
template_kwargs = {}
|
| 440 |
-
if selected_label
|
| 441 |
-
# Screen every supplied
|
| 442 |
# only the best fits enter the expensive shortest-path stage.
|
| 443 |
template_kwargs["template_examples"] = rank_projections(
|
| 444 |
graph, examples, search_distance_km * 1000.0,
|
|
|
|
| 437 |
rank_projections = getattr(module, "rank_road_template_projections", None)
|
| 438 |
for search_distance_km in (6.0, 11.0, 18.0):
|
| 439 |
template_kwargs = {}
|
| 440 |
+
if selected_label in {"symbol_star", "dog"} and callable(rank_projections):
|
| 441 |
+
# Screen every supplied animal/shape outline against this road graph;
|
| 442 |
# only the best fits enter the expensive shortest-path stage.
|
| 443 |
template_kwargs["template_examples"] = rank_projections(
|
| 444 |
graph, examples, search_distance_km * 1000.0,
|
api/ai-service/tests/test_gpx_shape_templates.py
CHANGED
|
@@ -11,6 +11,7 @@ APPROVED_TEMPLATES = ROOT / "router" / "ai" / "data" / "approved_road_shape_temp
|
|
| 11 |
TSHIRT_TEMPLATES = ROOT / "router" / "ai" / "data" / "tshirt_shape_templates.json"
|
| 12 |
STAR_TEMPLATES = ROOT / "router" / "ai" / "data" / "star_shape_templates.json"
|
| 13 |
ALL_STAR_TEMPLATES = ROOT / "router" / "ai" / "data" / "star_shape_templates_all.json"
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def test_dog_and_star_gpx_examples_are_normalized_and_closed():
|
|
@@ -69,6 +70,20 @@ def test_new_star_gpx_examples_are_available_for_road_generation():
|
|
| 69 |
assert all([tuple(point) for point in outline] in loaded["symbol_star"] for outline in all_outlines)
|
| 70 |
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
def test_every_star_outline_is_screened_before_road_pathfinding():
|
| 73 |
import math
|
| 74 |
import networkx as nx
|
|
|
|
| 11 |
TSHIRT_TEMPLATES = ROOT / "router" / "ai" / "data" / "tshirt_shape_templates.json"
|
| 12 |
STAR_TEMPLATES = ROOT / "router" / "ai" / "data" / "star_shape_templates.json"
|
| 13 |
ALL_STAR_TEMPLATES = ROOT / "router" / "ai" / "data" / "star_shape_templates_all.json"
|
| 14 |
+
ALL_DOG_TEMPLATES = ROOT / "router" / "ai" / "data" / "dog_shape_templates_all.json"
|
| 15 |
|
| 16 |
|
| 17 |
def test_dog_and_star_gpx_examples_are_normalized_and_closed():
|
|
|
|
| 70 |
assert all([tuple(point) for point in outline] in loaded["symbol_star"] for outline in all_outlines)
|
| 71 |
|
| 72 |
|
| 73 |
+
def test_new_dog_gpx_examples_are_available_for_road_generation():
|
| 74 |
+
outlines = json.loads(ALL_DOG_TEMPLATES.read_text(encoding="utf-8"))["dog"]
|
| 75 |
+
assert len(outlines) == 998
|
| 76 |
+
assert len({tuple(map(tuple, outline)) for outline in outlines}) == 998
|
| 77 |
+
assert all(outline[0] == outline[-1] for outline in outlines)
|
| 78 |
+
script = ROOT / "router" / "ai" / "scripts" / "clip_osm_region_test.py"
|
| 79 |
+
tree = ast.parse(script.read_text(encoding="utf-8-sig"))
|
| 80 |
+
loader = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "_load_gpx_shape_templates")
|
| 81 |
+
scope = {"__file__": str(script), "Path": Path, "json": json}
|
| 82 |
+
exec(compile(ast.Module(body=[loader], type_ignores=[]), str(script), "exec"), scope)
|
| 83 |
+
loaded = scope["_load_gpx_shape_templates"]()
|
| 84 |
+
assert all([tuple(point) for point in outline] in loaded["dog"] for outline in outlines)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
def test_every_star_outline_is_screened_before_road_pathfinding():
|
| 88 |
import math
|
| 89 |
import networkx as nx
|
api/ai-service/tests/test_route_generation_recovery.py
CHANGED
|
@@ -157,7 +157,8 @@ def test_shape_priority_searches_multiple_lengths_and_ranks_by_shape(monkeypatch
|
|
| 157 |
assert result["target_distance_km"] == 0
|
| 158 |
|
| 159 |
|
| 160 |
-
|
|
|
|
| 161 |
examples = [[(index, 0), (index, 1), (index + 1, 1), (index, 0)] for index in range(1000)]
|
| 162 |
screened = []
|
| 163 |
routed = []
|
|
@@ -172,7 +173,7 @@ def test_star_search_screens_all_outlines_before_road_routing(monkeypatch):
|
|
| 172 |
"shape_quality": 0.6, "is_loop": True}]
|
| 173 |
|
| 174 |
module = SimpleNamespace(
|
| 175 |
-
GPX_SHAPE_TEMPLATES={
|
| 176 |
_build_or_get_walk_graph=lambda *_: (object(), "test"),
|
| 177 |
rank_road_template_projections=screen,
|
| 178 |
generate_candidate_routes=candidates,
|
|
@@ -181,7 +182,7 @@ def test_star_search_screens_all_outlines_before_road_routing(monkeypatch):
|
|
| 181 |
monkeypatch.setattr(routes, "_route_nodes_to_coords", lambda _graph, nodes: [[nodes[0], 0], [nodes[1], 1]])
|
| 182 |
monkeypatch.setattr(routes, "best_gpx_shape_match", lambda _coords, templates: (0.9, len(templates) - 1))
|
| 183 |
routes._run_fast_road_pipeline(module, place=None, center_lat=37, center_lon=127,
|
| 184 |
-
radius_km=5, route_count=2, target_distance_km=6, selected_label=
|
| 185 |
assert screened == [(1000, 6000), (1000, 11000), (1000, 18000)]
|
| 186 |
assert len(routed) == 3
|
| 187 |
assert all(len(templates) == 36 and templates[-1] == examples[-1] for templates in routed)
|
|
|
|
| 157 |
assert result["target_distance_km"] == 0
|
| 158 |
|
| 159 |
|
| 160 |
+
@pytest.mark.parametrize("label", ["symbol_star", "dog"])
|
| 161 |
+
def test_gpx_search_screens_all_outlines_before_road_routing(monkeypatch, label):
|
| 162 |
examples = [[(index, 0), (index, 1), (index + 1, 1), (index, 0)] for index in range(1000)]
|
| 163 |
screened = []
|
| 164 |
routed = []
|
|
|
|
| 173 |
"shape_quality": 0.6, "is_loop": True}]
|
| 174 |
|
| 175 |
module = SimpleNamespace(
|
| 176 |
+
GPX_SHAPE_TEMPLATES={label: examples},
|
| 177 |
_build_or_get_walk_graph=lambda *_: (object(), "test"),
|
| 178 |
rank_road_template_projections=screen,
|
| 179 |
generate_candidate_routes=candidates,
|
|
|
|
| 182 |
monkeypatch.setattr(routes, "_route_nodes_to_coords", lambda _graph, nodes: [[nodes[0], 0], [nodes[1], 1]])
|
| 183 |
monkeypatch.setattr(routes, "best_gpx_shape_match", lambda _coords, templates: (0.9, len(templates) - 1))
|
| 184 |
routes._run_fast_road_pipeline(module, place=None, center_lat=37, center_lon=127,
|
| 185 |
+
radius_km=5, route_count=2, target_distance_km=6, selected_label=label, shape_priority=True)
|
| 186 |
assert screened == [(1000, 6000), (1000, 11000), (1000, 18000)]
|
| 187 |
assert len(routed) == 3
|
| 188 |
assert all(len(templates) == 36 and templates[-1] == examples[-1] for templates in routed)
|
router/ai/data/dog_shape_templates_all.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
router/ai/scripts/build_dog_shape_templates.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Package location-free dog outlines from a multi-track GPX file.
|
| 2 |
+
|
| 3 |
+
Usage: python build_dog_shape_templates.py DOG_ALL.gpx OUTPUT_JSON
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import json
|
| 9 |
+
import sys
|
| 10 |
+
import xml.etree.ElementTree as ET
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
from build_gpx_shape_templates import normalized_outline_from_track
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def build(source: Path) -> dict[str, list[list[list[float]]]]:
|
| 17 |
+
outlines = []
|
| 18 |
+
seen = set()
|
| 19 |
+
root = ET.parse(source).getroot()
|
| 20 |
+
for index, track in enumerate(node for node in root if node.tag.rsplit("}", 1)[-1] == "trk"):
|
| 21 |
+
points = [
|
| 22 |
+
(float(node.attrib["lat"]), float(node.attrib["lon"]))
|
| 23 |
+
for node in track.iter()
|
| 24 |
+
if node.tag.rsplit("}", 1)[-1] == "trkpt"
|
| 25 |
+
]
|
| 26 |
+
outline = normalized_outline_from_track(points, f"{source.name}:{index}")
|
| 27 |
+
signature = tuple(map(tuple, outline))
|
| 28 |
+
if signature not in seen:
|
| 29 |
+
seen.add(signature)
|
| 30 |
+
outlines.append(outline)
|
| 31 |
+
if not outlines:
|
| 32 |
+
raise ValueError("No dog outlines found")
|
| 33 |
+
return {"dog": outlines}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
if __name__ == "__main__":
|
| 37 |
+
if len(sys.argv) != 3:
|
| 38 |
+
raise SystemExit("Usage: build_dog_shape_templates.py DOG_ALL.gpx OUTPUT_JSON")
|
| 39 |
+
output = Path(sys.argv[2])
|
| 40 |
+
templates = build(Path(sys.argv[1]))
|
| 41 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 42 |
+
output.write_text(json.dumps(templates, separators=(",", ":")) + "\n", encoding="utf-8")
|
| 43 |
+
print({label: len(items) for label, items in templates.items()})
|
router/ai/scripts/clip_osm_region_test.py
CHANGED
|
@@ -147,7 +147,7 @@ def _load_gpx_shape_templates() -> dict[str, list[list[tuple[float, float]]]]:
|
|
| 147 |
data_dir = Path(__file__).resolve().parents[1] / "data"
|
| 148 |
templates: dict[str, list[list[tuple[float, float]]]] = {"dog": [], "symbol_star": [], "t_shirt": []}
|
| 149 |
seen: dict[str, set[tuple[tuple[float, float], ...]]] = {label: set() for label in templates}
|
| 150 |
-
for filename in ("gpx_shape_templates.json", "approved_road_shape_templates.json", "tshirt_shape_templates.json", "star_shape_templates.json", "star_shape_templates_all.json"):
|
| 151 |
try:
|
| 152 |
payload = json.loads((data_dir / filename).read_text(encoding="utf-8"))
|
| 153 |
except (OSError, ValueError, TypeError):
|
|
|
|
| 147 |
data_dir = Path(__file__).resolve().parents[1] / "data"
|
| 148 |
templates: dict[str, list[list[tuple[float, float]]]] = {"dog": [], "symbol_star": [], "t_shirt": []}
|
| 149 |
seen: dict[str, set[tuple[tuple[float, float], ...]]] = {label: set() for label in templates}
|
| 150 |
+
for filename in ("gpx_shape_templates.json", "approved_road_shape_templates.json", "tshirt_shape_templates.json", "star_shape_templates.json", "star_shape_templates_all.json", "dog_shape_templates_all.json"):
|
| 151 |
try:
|
| 152 |
payload = json.loads((data_dir / filename).read_text(encoding="utf-8"))
|
| 153 |
except (OSError, ValueError, TypeError):
|