Skip to content

Commit f14ee9f

Browse files
authoredSep 13, 2023
feat: result test (#246)
1 parent a6bb894 commit f14ee9f

16 files changed

+134
-42
lines changed
 

‎.driving_log_replayer.cspell.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"words": [
3+
"autouse",
34
"bbox",
45
"kaleido",
56
"xaxes",

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
log/
22
.cspell.json
3+
*.pyc
34
# MkDocs's dir
45
site/

‎driving_log_replayer/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ rclcpp_components_register_node(obstacle_segmentation_evaluator_component
2323
if(BUILD_TESTING)
2424
find_package(ament_lint_auto REQUIRED)
2525
ament_lint_auto_find_test_dependencies()
26+
find_package(ament_cmake_pytest)
27+
ament_add_pytest_test(driving_log_replayer
28+
test
29+
)
2630
endif()
2731

2832
ament_python_install_package(${PROJECT_NAME})

‎driving_log_replayer/driving_log_replayer/evaluator.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from abc import ABC
1616
from abc import abstractmethod
1717
from collections.abc import Callable
18-
import os
1918
from os.path import expandvars
2019
from pathlib import Path
2120
from typing import TYPE_CHECKING
@@ -69,7 +68,7 @@ def __init__(self, name: str) -> None:
6968

7069
self._scenario_yaml_obj = None
7170
try:
72-
with open(self._scenario_path) as scenario_file:
71+
with Path(self._scenario_path).open() as scenario_file:
7372
self._scenario_yaml_obj = yaml.safe_load(scenario_file)
7473
except (FileNotFoundError, PermissionError, yaml.YAMLError) as e:
7574
self.get_logger().error(f"An error occurred while loading the scenario. {e}")
@@ -229,8 +228,8 @@ def check_scenario(self) -> None:
229228

230229
@classmethod
231230
def get_goal_pose_from_t4_dataset(cls, dataset_path: str) -> PoseStamped:
232-
ego_pose_json_path = os.path.join(dataset_path, "annotation", "ego_pose.json")
233-
with open(ego_pose_json_path) as ego_pose_file:
231+
ego_pose_json_path = Path(dataset_path).joinpath("annotation", "ego_pose.json")
232+
with ego_pose_json_path.open() as ego_pose_file:
234233
ego_pose_json = json.load(ego_pose_file)
235234
last_ego_pose = ego_pose_json[-1]
236235
goal_pose = PoseStamped()

‎driving_log_replayer/driving_log_replayer/result.py

+37-22
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414

1515
from abc import ABC
1616
from abc import abstractmethod
17-
import os
17+
from os.path import expandvars
18+
from pathlib import Path
1819
import pickle
20+
from typing import Any
1921

2022
from rclpy.clock import Clock
2123
from rclpy.clock import ClockType
@@ -28,12 +30,15 @@ def __init__(self) -> None:
2830
self._summary = "NoData"
2931
self._frame = {}
3032

33+
@property
3134
def success(self) -> bool:
3235
return self._success
3336

37+
@property
3438
def summary(self) -> str:
3539
return self._summary
3640

41+
@property
3742
def frame(self) -> dict:
3843
return self._frame
3944

@@ -48,33 +53,45 @@ def set_frame(self) -> None:
4853

4954
class ResultWriter:
5055
def __init__(self, result_json_path: str, ros_clock: Clock, condition: dict) -> None:
51-
# 拡張子を書き換える
52-
result_file = os.path.splitext(os.path.expandvars(result_json_path))[0] + ".jsonl"
53-
self._result_file = open(result_file, "w") # noqa
56+
self._result_path = self.create_jsonl_path(result_json_path)
57+
self._result_file = self._result_path.open("w")
5458
self._ros_clock = ros_clock
5559
self._system_clock = Clock(clock_type=ClockType.SYSTEM_TIME)
56-
self.write_condition(condition)
57-
self.write_header()
60+
self.write_line({"Condition": condition})
61+
self.write_line(self.get_header())
62+
63+
@property
64+
def result_path(self) -> Path:
65+
return self._result_path
66+
67+
def create_jsonl_path(self, result_json_path: str) -> Path:
68+
# For compatibility with previous versions.
69+
# If a json file name is passed, replace it with the filename + jsonl
70+
original_path = Path(expandvars(result_json_path))
71+
return original_path.parent.joinpath(original_path.stem + ".jsonl")
5872

5973
def close(self) -> None:
6074
self._result_file.close()
6175

62-
def write_condition(self, condition: dict) -> None:
63-
dict_condition = {"Condition": condition}
64-
str_condition = json.dumps(dict_condition, ignore_nan=True) + "\n"
65-
self._result_file.write(str_condition)
76+
def delete_result_file(self) -> None:
77+
self._result_path.unlink()
78+
79+
def write_line(self, write_obj: Any) -> None:
80+
str_record = json.dumps(write_obj, ignore_nan=True) + "\n"
81+
self._result_file.write(str_record)
82+
83+
def write_result(self, result: ResultBase) -> None:
84+
self.write_line(self.get_result(result))
6685

67-
def write_header(self) -> None:
86+
def get_header(self) -> dict:
6887
system_time = self._system_clock.now()
69-
dict_header = {
88+
return {
7089
"Result": {"Success": False, "Summary": "NoData"},
7190
"Stamp": {"System": system_time.nanoseconds / pow(10, 9)},
7291
"Frame": {},
7392
}
74-
str_header = json.dumps(dict_header, ignore_nan=True) + "\n"
75-
self._result_file.write(str_header)
7693

77-
def write(self, result: ResultBase) -> None:
94+
def get_result(self, result: ResultBase) -> dict:
7895
system_time = self._system_clock.now()
7996
time_dict = {"System": system_time.nanoseconds / pow(10, 9)}
8097
if self._ros_clock.ros_time_is_active:
@@ -83,16 +100,14 @@ def write(self, result: ResultBase) -> None:
83100
else:
84101
time_dict["ROS"] = 0.0
85102

86-
dict_record = {
87-
"Result": {"Success": result.success(), "Summary": result.summary()},
103+
return {
104+
"Result": {"Success": result.success, "Summary": result.summary},
88105
"Stamp": time_dict,
89-
"Frame": result.frame(),
106+
"Frame": result.frame,
90107
}
91-
str_record = json.dumps(dict_record, ignore_nan=True) + "\n"
92-
self._result_file.write(str_record)
93108

94109

95110
class PickleWriter:
96-
def __init__(self, out_pkl_path: str, write_object) -> None: # noqa
97-
with open(os.path.expandvars(out_pkl_path), "wb") as pkl_file:
111+
def __init__(self, out_pkl_path: str, write_object: Any) -> None:
112+
with Path(expandvars(out_pkl_path)).open("wb") as pkl_file:
98113
pickle.dump(write_object, pkl_file)

‎driving_log_replayer/package.xml

+2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
<exec_depend>rosidl_runtime_py</exec_depend>
4747

4848
<test_depend>ament_cmake_gtest</test_depend>
49+
<test_depend>ament_cmake_pytest</test_depend>
4950
<test_depend>ament_lint_auto</test_depend>
5051
<test_depend>autoware_lint_common</test_depend>
52+
<test_depend>launch_pytest</test_depend>
5153

5254
<export>
5355
<build_type>ament_cmake</build_type>

‎driving_log_replayer/scripts/eagleye_evaluator_node.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def check_scenario(self) -> None:
8080

8181
def diagnostics_cb(self, msg: DiagnosticArray) -> None:
8282
self.__result.set_frame(msg)
83-
self._result_writer.write(self.__result)
83+
self._result_writer.write_result(self.__result)
8484

8585

8686
@evaluator_main

‎driving_log_replayer/scripts/localization_evaluator_node.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def tp_cb(self, msg: Float32Stamped) -> None:
310310
DLREvaluator.transform_stamped_with_euler_angle(map_to_baselink),
311311
self.__latest_nvtl,
312312
)
313-
self._result_writer.write(self.__result)
313+
self._result_writer.write_result(self.__result)
314314

315315
def nvtl_cb(self, msg: Float32Stamped) -> None:
316316
self.__latest_nvtl = msg
@@ -323,7 +323,7 @@ def nvtl_cb(self, msg: Float32Stamped) -> None:
323323
DLREvaluator.transform_stamped_with_euler_angle(map_to_baselink),
324324
self.__latest_tp,
325325
)
326-
self._result_writer.write(self.__result)
326+
self._result_writer.write_result(self.__result)
327327

328328
def pose_cb(self, msg: PoseStamped) -> None:
329329
map_to_baselink = self.lookup_transform(msg.header.stamp)
@@ -335,11 +335,11 @@ def pose_cb(self, msg: PoseStamped) -> None:
335335
self.__latest_iteration_num,
336336
)
337337
self.__pub_lateral_distance.publish(msg_lateral_distance)
338-
self._result_writer.write(self.__result)
338+
self._result_writer.write_result(self.__result)
339339

340340
def diagnostics_cb(self, msg: DiagnosticArray) -> None:
341341
self.__result.set_frame(msg)
342-
self._result_writer.write(self.__result)
342+
self._result_writer.write_result(self.__result)
343343

344344

345345
@evaluator_main

‎driving_log_replayer/scripts/obstacle_segmentation_evaluator_node.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ def obstacle_segmentation_input_cb(self, msg: ObstacleSegmentationInput) -> None
603603
pcd_header,
604604
topic_rate=msg.topic_rate,
605605
)
606-
self._result_writer.write(self.__result)
606+
self._result_writer.write_result(self.__result)
607607

608608
topic_rate_data = ObstacleSegmentationMarker()
609609
topic_rate_data.header = msg.pointcloud.header

‎driving_log_replayer/scripts/perception_2d_evaluator_node.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def write_metrics(self) -> None:
218218
conf_mat_dict = conf_mat_df.to_dict()
219219
final_metrics = {"Score": score_dict, "ConfusionMatrix": conf_mat_dict}
220220
self.__result.set_final_metrics(final_metrics)
221-
self._result_writer.write(self.__result)
221+
self._result_writer.write_result(self.__result)
222222

223223
def list_dynamic_object_2d_from_ros_msg(
224224
self,
@@ -281,7 +281,7 @@ def detected_objs_cb(self, msg: DetectedObjectsWithFeature, camera_type: str) ->
281281
DLREvaluator.transform_stamped_with_euler_angle(map_to_baselink),
282282
camera_type,
283283
)
284-
self._result_writer.write(self.__result)
284+
self._result_writer.write_result(self.__result)
285285

286286
def get_final_result(self) -> MetricsScore:
287287
final_metric_score = self.__evaluator.get_scene_result()

‎driving_log_replayer/scripts/perception_evaluator_node.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def write_metrics(self) -> None:
227227
if self.__evaluation_task == "fp_validation":
228228
final_metrics = self.get_fp_result()
229229
self.__result.set_final_metrics(final_metrics)
230-
self._result_writer.write(self.__result)
230+
self._result_writer.write_result(self.__result)
231231
else:
232232
self.get_final_result()
233233
score_dict = {}
@@ -243,7 +243,7 @@ def write_metrics(self) -> None:
243243
)
244244
final_metrics = {"Score": score_dict, "Error": error_dict}
245245
self.__result.set_final_metrics(final_metrics)
246-
self._result_writer.write(self.__result)
246+
self._result_writer.write_result(self.__result)
247247

248248
def list_dynamic_object_from_ros_msg(
249249
self,
@@ -330,7 +330,7 @@ def perception_cb(self, msg: DetectedObjects | TrackedObjects) -> None:
330330
msg.header,
331331
DLREvaluator.transform_stamped_with_euler_angle(map_to_baselink),
332332
)
333-
self._result_writer.write(self.__result)
333+
self._result_writer.write_result(self.__result)
334334
self.__pub_marker_ground_truth.publish(marker_ground_truth)
335335
self.__pub_marker_results.publish(marker_results)
336336

‎driving_log_replayer/scripts/performance_diag_evaluator_node.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ def diag_cb(self, msg: DiagnosticArray) -> None:
361361
for k, v in msg_blockage_levels.items():
362362
if v is not None:
363363
self.__pub_blockage_levels[k].publish(v)
364-
self._result_writer.write(self.__result)
364+
self._result_writer.write_result(self.__result)
365365

366366

367367
@evaluator_main

‎driving_log_replayer/scripts/traffic_light_evaluator_node.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def write_metrics(self) -> None:
184184
conf_mat_dict = conf_mat_df.to_dict()
185185
final_metrics = {"Score": score_dict, "ConfusionMatrix": conf_mat_dict}
186186
self.__result.set_final_metrics(final_metrics)
187-
self._result_writer.write(self.__result)
187+
self._result_writer.write_result(self.__result)
188188

189189
def list_dynamic_object_2d_from_ros_msg(
190190
self,
@@ -235,7 +235,7 @@ def traffic_signals_cb(self, msg: TrafficSignalArray) -> None:
235235
msg.header,
236236
DLREvaluator.transform_stamped_with_euler_angle(map_to_baselink),
237237
)
238-
self._result_writer.write(self.__result)
238+
self._result_writer.write_result(self.__result)
239239

240240
def get_final_result(self) -> MetricsScore:
241241
final_metric_score = self.__evaluator.get_scene_result()

‎driving_log_replayer/scripts/yabloc_evaluator_node.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def check_scenario(self) -> None:
8181

8282
def diagnostics_cb(self, msg: DiagnosticArray) -> None:
8383
self.__result.set_frame(msg)
84-
self._result_writer.write(self.__result)
84+
self._result_writer.write_result(self.__result)
8585

8686

8787
@evaluator_main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright (c) 2023 TIER IV.inc
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from collections.abc import Callable
16+
from os.path import expandvars
17+
18+
import pytest
19+
from rclpy.clock import ClockType
20+
from rclpy.clock import ROSClock
21+
from rclpy.time import Time
22+
23+
from driving_log_replayer.result import ResultBase
24+
from driving_log_replayer.result import ResultWriter
25+
26+
27+
class SampleResult(ResultBase):
28+
def __init__(self) -> None:
29+
super().__init__()
30+
31+
def update(self) -> None:
32+
self._success = True
33+
self._summary = "Sample OK"
34+
35+
def set_frame(self) -> None:
36+
self._frame = {"Test": "ok"}
37+
38+
39+
@pytest.fixture()
40+
def create_writer() -> ResultWriter:
41+
json_path = "$HOME/dlr_result.json"
42+
ros_clock = ROSClock()
43+
ros_time = Time(seconds=123, nanoseconds=456, clock_type=ClockType.ROS_TIME)
44+
ros_clock.set_ros_time_override(ros_time)
45+
ros_clock._set_ros_time_is_active(True) # noqa
46+
condition = {"sample_condition": "sample"}
47+
writer = ResultWriter(json_path, ros_clock, condition)
48+
yield writer
49+
# delete created jsonl file
50+
writer.delete_result_file()
51+
52+
53+
def test_create_jsonl_path(create_writer: Callable) -> None:
54+
writer: ResultWriter = create_writer
55+
assert writer.result_path.as_posix() == expandvars("$HOME/dlr_result.jsonl")
56+
57+
58+
def test_get_result(create_writer: Callable) -> None:
59+
writer: ResultWriter = create_writer
60+
sample_result = SampleResult()
61+
sample_result.set_frame()
62+
sample_result.update()
63+
record = writer.get_result(sample_result)
64+
writer.write_line(record)
65+
assert record["Result"]["Success"] == True # noqa
66+
assert record["Result"]["Summary"] == "Sample OK"
67+
assert record["Frame"] == {"Test": "ok"}
68+
# The system time stamp is not checked since it changes with each execution.
69+
# Since the logic is the same as that of ros time, it is sufficient to check ros time.
70+
assert record["Stamp"]["ROS"] == 123.000000456 # noqa

‎pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ show-source = true
5151
# https://beta.ruff.rs/docs/rules/
5252
select = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "ISC", "NPY", "PGH", "PIE", "PL", "PT", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
5353
# "INP", "PD", "PTH"
54-
ignore = ["Q000", "ANN101", "ANN102", "PGH004", "E501", "PLR0913", "S301", "S602", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D212", "D404", "D417"]
54+
ignore = ["Q000", "ANN101", "ANN102", "ANN401", "PGH004", "E501", "PLR0913", "S101", "S301", "S602", "SIM115", "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203", "D212", "D404", "D417"]
5555
fixable=["D", "I", "ANN", "COM", "EXE", "PIE"]
5656

5757
[tool.ruff.isort]

0 commit comments

Comments
 (0)