Skip to content

Commit 1912414

Browse files
Handle errors caused by small geometries + update to pyxodr-omega-prime package (#5)
* changed to pyxodr-omega-prime + step_size argument for .parquet files + ignored_lane_types + handle boundaries with only 1 point + polygon plotting * updated requirements.txt and uv.lock * Apply Ruff formatting * changed plotting function * updated requirements.txt * updated uv.lock --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 36c2f51 commit 1912414

File tree

6 files changed

+1702
-1674
lines changed

6 files changed

+1702
-1674
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ __pycache__/
66
.pytest_cache/
77
.idea/
88
omegadoc
9-
*.html
9+
*.html
10+
*.parquet
11+
*.xodr
12+
*.png

omega_prime/map_odr.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import logging
22
from dataclasses import dataclass
33
from omega_prime.map import Map, Lane, LaneBoundary
4-
from shapely import LineString, Polygon, simplify
5-
import numpy as np
4+
from shapely import LineString, Polygon, simplify, MultiPolygon
65
from matplotlib.patches import Polygon as PltPolygon
6+
import numpy as np
77
import matplotlib.pyplot as plt
88
from betterosi import LaneClassificationType, LaneClassificationSubtype, LaneBoundaryClassificationType
9-
from pyxodr.road_objects.network import RoadNetwork as PyxodrRoadNetwork
10-
from pyxodr.road_objects.road import Road as PyxodrRoad
11-
from pyxodr.road_objects.lane import Lane as PyxodrLane
9+
from pyxodr_omega_prime.road_objects.network import RoadNetwork as PyxodrRoadNetwork
10+
from pyxodr_omega_prime.road_objects.road import Road as PyxodrRoad
11+
from pyxodr_omega_prime.road_objects.lane import Lane as PyxodrLane
1212
from pathlib import Path
1313
from lxml import etree
1414
import pyproj
@@ -28,11 +28,12 @@ def __init__(
2828
self,
2929
xml_string: str,
3030
resolution: float = 0.1,
31+
ignored_lane_types: set[str] = set([]),
3132
):
3233
self.root = etree.fromstring(xml_string.encode("utf-8"))
3334
self.tree = etree.ElementTree(self.root)
3435
self.resolution = resolution
35-
self.ignored_lane_types = set([])
36+
self.ignored_lane_types = ignored_lane_types
3637
self.road_ids_to_object = {}
3738

3839

@@ -159,11 +160,18 @@ def from_file(
159160
is_odr_xml: bool = False,
160161
is_mcap: bool = False,
161162
step_size=0.01,
163+
ignored_lane_types: set[str] = set([]),
162164
):
163165
if Path(filename).suffix in [".xodr", ".odr"] or is_odr_xml:
164166
with open(filename) as f:
165167
odr_xml = f.read()
166-
return cls.create(odr_xml=odr_xml, name=Path(filename).stem, step_size=step_size, parse=parse)
168+
return cls.create(
169+
odr_xml=odr_xml,
170+
name=Path(filename).stem,
171+
step_size=step_size,
172+
parse=parse,
173+
ignored_lane_types=ignored_lane_types,
174+
)
167175
if Path(filename).suffix in [".mcap"] or is_mcap:
168176
map = next(iter(betterosi.read(filename, mcap_topics=topics, mcap_return_betterosi=False)))
169177
return cls.create(
@@ -191,16 +199,17 @@ def lane_boundaries(self, val):
191199
self._lane_boundaries = val
192200

193201
@classmethod
194-
def create(cls, odr_xml, name, step_size=0.01, parse: bool = False):
202+
def create(cls, odr_xml, name, step_size=0.01, parse: bool = False, ignored_lane_types: set[str] = set([])):
195203
self = cls(odr_xml=odr_xml, name=name, step_size=step_size, lanes={}, lane_boundaries={})
196204
self._lane_boundaries = None
197205
self._lanes = None
206+
self.ignored_lane_types = ignored_lane_types
198207
if parse:
199208
self.parse()
200209
return self
201210

202211
def parse(self):
203-
rn = RoadNetwork(self.odr_xml, resolution=self.step_size)
212+
rn = RoadNetwork(self.odr_xml, resolution=self.step_size, ignored_lane_types=self.ignored_lane_types)
204213

205214
lane_boundaries = {}
206215
lanes = {}
@@ -345,9 +354,16 @@ def create(
345354
lane_idx: int = None,
346355
):
347356
if side == "left":
348-
polyline = LineString(boundary.boundary_line)
357+
if len(boundary.boundary_line) == 1:
358+
polyline = LineString([boundary.boundary_line[0]] * 2)
359+
else:
360+
polyline = LineString(boundary.boundary_line)
349361
elif side == "right":
350-
polyline = LineString(boundary.lane_reference_line)
362+
if len(boundary.lane_reference_line) == 1:
363+
polyline = LineString([boundary.lane_reference_line[0]] * 2)
364+
else:
365+
polyline = LineString(boundary.lane_reference_line)
366+
351367
else:
352368
raise ValueError(f"Invalid side '{side}'. Expected 'left' or 'right'.")
353369

@@ -462,12 +478,19 @@ def set_polygon(self):
462478
raise ValueError(f"Could not compute valid polygon for Lane {self.xodr_idx}")
463479
else:
464480
warnings.warn(f"Needed to simplify and buffer polygon for Lane {self.xodr_idx}.")
481+
pass
465482
else:
466483
warnings.warn(f"Needed to simplify polygon for Lane {self.xodr_idx}.")
484+
pass
467485
self.polygon = polygon
468486
return self
469487

470488
def plot(self, ax: plt.Axes):
471489
c = "green" if self.type != LaneClassificationType.TYPE_INTERSECTION else "black"
472490
ax.plot(*np.asarray(self.centerline).T, color=c, alpha=0.5)
473-
ax.add_patch(PltPolygon(self.polygon.exterior.coords, fc="blue", alpha=0.2, ec="black"))
491+
if isinstance(self.polygon, MultiPolygon):
492+
ps = self.polygon.geoms
493+
else:
494+
ps = [self.polygon]
495+
for p in ps:
496+
ax.add_patch(PltPolygon(p.exterior.coords, fc="blue", alpha=0.2, ec="black"))

omega_prime/recording.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,9 +435,12 @@ def from_file(
435435
validate: bool = False,
436436
parse_map: bool = False,
437437
compute_polygons: bool = False,
438+
step_size: float = 0.01,
438439
):
439440
if Path(filepath).suffix == ".parquet":
440-
return cls.from_parquet(filepath, parse_map=parse_map, validate=validate, compute_polygons=compute_polygons)
441+
return cls.from_parquet(
442+
filepath, parse_map=parse_map, validate=validate, compute_polygons=compute_polygons, step_size=step_size
443+
)
441444

442445
gts = betterosi.read(filepath, return_ground_truth=True, mcap_return_betterosi=False)
443446
gts, tmp_gts = itertools.tee(gts, 2)
@@ -552,14 +555,15 @@ def plot_mv_frame(self, ax: plt.Axes, frame: int):
552555
ax.add_patch(PltPolygon(p.exterior.coords, fc="red"))
553556

554557
@classmethod
555-
def from_parquet(cls, filename, parse_map: bool = False, **kwargs):
558+
def from_parquet(cls, filename, parse_map: bool = False, step_size: float = 0.01, **kwargs):
556559
t = pq.read_table(filename)
557560
df = pl.DataFrame(t)
558561
if t.schema.metadata is not None and b"xodr" in t.schema.metadata:
559562
m = MapOdr.create(
560563
odr_xml=t.schema.metadata[b"xodr"].decode(),
561564
name=t.schema.metadata[b"xodr_name"].decode(),
562565
parse=parse_map,
566+
step_size=step_size,
563567
)
564568
else:
565569
m = None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ dependencies = [
4141
'pyproj',
4242
'betterosi>=0.3.4',
4343
'lxd-io>=0.4.6',
44-
'pyxodr',
44+
'pyxodr-omega-prime',
4545
'joblib',
4646
'tqdm_joblib'
4747
]

requirements.txt

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# This file was autogenerated by uv via the following command:
2-
# uv pip compile .\pyproject.toml
2+
# uv pip compile ./pyproject.toml
33
altair==5.5.0
44
# via omega-prime (./pyproject.toml)
55
annotated-types==0.7.0
@@ -20,18 +20,13 @@ certifi==2025.4.26
2020
# pyproj
2121
click==8.1.8
2222
# via typer
23-
colorama==0.4.6
24-
# via
25-
# click
26-
# loguru
27-
# tqdm
2823
contourpy==1.3.2
2924
# via matplotlib
3025
cycler==0.12.1
3126
# via matplotlib
32-
fonttools==4.57.0
27+
fonttools==4.58.0
3328
# via matplotlib
34-
grpclib==0.4.7
29+
grpclib==0.4.8
3530
# via betterproto2
3631
h2==4.2.0
3732
# via grpclib
@@ -41,7 +36,7 @@ hyperframe==6.1.0
4136
# via h2
4237
jinja2==3.1.6
4338
# via altair
44-
joblib==1.4.2
39+
joblib==1.5.0
4540
# via omega-prime (./pyproject.toml)
4641
jsonschema==4.23.0
4742
# via altair
@@ -56,19 +51,19 @@ lxd-io==0.4.6
5651
lxml==5.4.0
5752
# via
5853
# omega-prime (./pyproject.toml)
59-
# pyxodr
54+
# pyxodr-omega-prime
6055
lz4==4.4.4
6156
# via mcap
6257
markdown-it-py==3.0.0
6358
# via rich
6459
markupsafe==3.0.2
6560
# via jinja2
66-
matplotlib==3.10.1
61+
matplotlib==3.10.3
6762
# via
6863
# omega-prime (./pyproject.toml)
6964
# betterosi
7065
# lxd-io
71-
# pyxodr
66+
# pyxodr-omega-prime
7267
mcap==1.2.2
7368
# via
7469
# betterosi
@@ -81,21 +76,20 @@ multidict==6.4.3
8176
# via grpclib
8277
mypy-extensions==1.1.0
8378
# via typing-inspect
84-
narwhals==1.37.0
79+
narwhals==1.40.0
8580
# via altair
8681
networkx==3.4.2
8782
# via omega-prime (./pyproject.toml)
88-
numpy==2.2.5
83+
numpy==2.2.6
8984
# via
9085
# omega-prime (./pyproject.toml)
9186
# betterosi
9287
# contourpy
9388
# lxd-io
9489
# matplotlib
9590
# pandas
96-
# pandera
9791
# pyogrio
98-
# pyxodr
92+
# pyxodr-omega-prime
9993
# scipy
10094
# shapely
10195
# xarray
@@ -109,35 +103,34 @@ packaging==25.0
109103
pandas==2.2.3
110104
# via
111105
# omega-prime (./pyproject.toml)
112-
# pandera
113106
# xarray
114-
pandera==0.23.1
107+
pandera==0.24.0
115108
# via omega-prime (./pyproject.toml)
116109
pillow==11.2.1
117110
# via matplotlib
118-
polars==1.28.1
111+
polars==1.29.0
119112
# via
120113
# omega-prime (./pyproject.toml)
121114
# lxd-io
122115
# pandera
123116
# polars-st
124-
polars-st==0.1.0a28
117+
polars-st==0.1.0a29
125118
# via omega-prime (./pyproject.toml)
126-
protobuf==6.30.2
119+
protobuf==6.31.0
127120
# via
128121
# betterosi
129122
# mcap-protobuf-support
130123
pyarrow==20.0.0
131124
# via
132125
# pandas
133126
# polars-st
134-
pydantic==2.11.3
127+
pydantic==2.11.4
135128
# via pandera
136-
pydantic-core==2.33.1
129+
pydantic-core==2.33.2
137130
# via pydantic
138131
pygments==2.19.1
139132
# via rich
140-
pyogrio==0.10.0
133+
pyogrio==0.11.0
141134
# via polars-st
142135
pyparsing==3.2.3
143136
# via matplotlib
@@ -150,28 +143,28 @@ python-dateutil==2.9.0.post0
150143
# pandas
151144
pytz==2025.2
152145
# via pandas
153-
pyxodr==0.1.3
146+
pyxodr-omega-prime==0.1.5
154147
# via omega-prime (./pyproject.toml)
155148
referencing==0.36.2
156149
# via
157150
# jsonschema
158151
# jsonschema-specifications
159152
rich==14.0.0
160153
# via
161-
# pyxodr
154+
# pyxodr-omega-prime
162155
# typer
163-
rpds-py==0.24.0
156+
rpds-py==0.25.0
164157
# via
165158
# jsonschema
166159
# referencing
167-
scipy==1.15.2
160+
scipy==1.15.3
168161
# via
169162
# omega-prime (./pyproject.toml)
170-
# pyxodr
171-
shapely==2.1.0
163+
# pyxodr-omega-prime
164+
shapely==2.1.1
172165
# via
173166
# omega-prime (./pyproject.toml)
174-
# pyxodr
167+
# pyxodr-omega-prime
175168
shellingham==1.5.4
176169
# via typer
177170
six==1.17.0
@@ -186,14 +179,15 @@ tqdm-joblib==0.0.4
186179
# via omega-prime (./pyproject.toml)
187180
typeguard==4.4.2
188181
# via pandera
189-
typer==0.15.3
182+
typer==0.15.4
190183
# via
191184
# omega-prime (./pyproject.toml)
192185
# betterosi
193186
typing-extensions==4.13.2
194187
# via
195188
# altair
196189
# betterproto2
190+
# pandera
197191
# pydantic
198192
# pydantic-core
199193
# referencing
@@ -207,9 +201,7 @@ typing-inspection==0.4.0
207201
# via pydantic
208202
tzdata==2025.2
209203
# via pandas
210-
win32-setctime==1.2.0
211-
# via loguru
212-
xarray==2025.3.1
204+
xarray==2025.4.0
213205
# via omega-prime (./pyproject.toml)
214206
zstandard==0.23.0
215207
# via mcap

0 commit comments

Comments
 (0)