From 486ddac449ba92db3084061866167f088e13534e Mon Sep 17 00:00:00 2001 From: DanielDauner Date: Sun, 18 Aug 2024 20:05:15 +0200 Subject: [PATCH] SLEDGE v0.1 release --- .flake8 | 17 + .gitignore | 20 + README.md | 68 +- docs/autoencoder.md | 31 + docs/diffusion.md | 23 + docs/installation.md | 81 + docs/simulation.md | 21 + environment.yml | 9 + requirements.txt | 54 + .../autoencoder/rvae/feature_caching_rvae.sh | 19 + .../autoencoder/rvae/latent_caching_rvae.sh | 18 + scripts/autoencoder/rvae/training_rvae.sh | 15 + scripts/autoencoder/vae/training_vae.sh | 14 + .../diffusion/scenario_caching_diffusion.sh | 16 + scripts/diffusion/training_diffusion.sh | 18 + scripts/download/download_cache.sh | 2 + scripts/download/download_nuplan.sh | 22 + scripts/simulation/simple_simulation.sh | 7 + setup.py | 29 + sledge/__init__.py | 0 sledge/autoencoder/__init__.py | 0 sledge/autoencoder/callbacks/__init__.py | 0 .../callbacks/rvae_visualization_callback.py | 224 + .../callbacks/vae_visualization_callback.py | 210 + .../autoencoder/data_augmentation/__init__.py | 0 .../data_augmentation/augmentation_utils.py | 123 + .../data_augmentation/rvae_augmentation.py | 93 + .../data_augmentation/vae_augmentation.py | 94 + sledge/autoencoder/data_loader/__init__.py | 0 .../data_loader/autoencoder_datamodule.py | 130 + sledge/autoencoder/experiments/__init__.py | 0 .../experiments/feature_caching.py | 182 + .../autoencoder/experiments/latent_caching.py | 70 + sledge/autoencoder/experiments/training.py | 69 + sledge/autoencoder/modeling/__init__.py | 0 .../autoencoder_lightning_module_wrapper.py | 236 + .../autoencoder_torch_module_wrapper.py | 70 + .../autoencoder/modeling/matching/__init__.py | 0 .../modeling/matching/abstract_matching.py | 19 + .../modeling/matching/rvae_matching.py | 123 + .../autoencoder/modeling/metrics/__init__.py | 0 .../metrics/abstract_custom_metric.py | 28 + .../autoencoder/modeling/metrics/kl_metric.py | 28 + .../autoencoder/modeling/models/__init__.py | 0 .../modeling/models/rvae/__init__.py | 0 .../modeling/models/rvae/rvae_config.py | 119 + .../modeling/models/rvae/rvae_decoder.py | 424 + .../modeling/models/rvae/rvae_encoder.py | 89 + .../modeling/models/rvae/rvae_model.py | 66 + .../rvae/utils/rvae_position_encoding.py | 103 + .../models/rvae/utils/rvae_transformer.py | 349 + .../modeling/models/vae/__init__.py | 0 .../modeling/models/vae/vae_config.py | 56 + .../modeling/models/vae/vae_model.py | 107 + .../modeling/objectives/__init__.py | 0 .../objectives/abstract_custom_objective.py | 27 + .../modeling/objectives/kl_objective.py | 32 + .../modeling/objectives/rvae_objective.py | 116 + .../modeling/objectives/vae_objective.py | 57 + sledge/autoencoder/preprocessing/__init__.py | 0 .../feature_builders/__init__.py | 0 .../feature_builders/sledge/__init__.py | 0 .../sledge/sledge_agent_feature.py | 140 + .../sledge/sledge_feature_processing.py | 340 + .../sledge/sledge_line_feature.py | 266 + .../feature_builders/sledge/sledge_utils.py | 84 + .../sledge_raw_feature_builder.py | 168 + .../preprocessing/features/__init__.py | 0 .../preprocessing/features/latent_feature.py | 44 + .../preprocessing/features/map_id_feature.py | 54 + .../features/rvae_matching_feature.py | 34 + .../features/sledge_raster_feature.py | 253 + .../features/sledge_vector_feature.py | 437 + .../preprocessing/target_builders/__init__.py | 0 .../target_builders/map_id_target_builder.py | 28 + sledge/common/__init__.py | 0 sledge/common/helper/__init__.py | 0 sledge/common/helper/cache_helper.py | 24 + sledge/common/visualization/__init__.py | 0 sledge/common/visualization/sledge_colors.py | 288 + .../sledge_visualization_utils.py | 236 + sledge/diffusion/__init__.py | 0 sledge/diffusion/dataset/__init__.py | 0 .../dataset/rvae_latent_builder_config.py | 33 + .../diffusion/dataset/rvae_latent_dataset.py | 144 + sledge/diffusion/experiments/__init__.py | 0 .../diffusion/experiments/scenario_caching.py | 53 + sledge/diffusion/experiments/training.py | 306 + sledge/diffusion/modelling/__init__.py | 0 sledge/diffusion/modelling/ldm_pipeline.py | 107 + sledge/script/__init__.py | 0 sledge/script/builders/__init__.py | 0 sledge/script/builders/autoencoder_builder.py | 152 + sledge/script/builders/diffusion_builder.py | 171 + sledge/script/builders/matching_builder.py | 29 + sledge/script/builders/metric_builder.py | 56 + sledge/script/builders/model_builder.py | 28 + sledge/script/builders/scenario_builder.py | 0 sledge/script/builders/simulation_builder.py | 122 + sledge/script/builders/utils/__init__.py | 0 sledge/script/builders/utils/utils_config.py | 39 + sledge/script/config/__init__.py | 0 sledge/script/config/autoencoder/__init__.py | 0 .../callbacks/default_callbacks.yaml | 4 + .../learning_rate_monitor_callback.yaml | 6 + .../callbacks/model_checkpoint_callback.yaml | 11 + .../rvae_visualization_callback.yaml | 8 + .../callbacks/time_logging_callback.yaml | 3 + .../callbacks/vae_visualization_callback.yaml | 9 + .../data_augmentation/rvae_augmentation.yaml | 10 + .../rvae_no_augmentation.yaml | 10 + .../data_augmentation/vae_augmentation.yaml | 10 + .../data_loader/default_data_loader.yaml | 10 + .../autoencoder/default_autoencoder.yaml | 49 + .../lightning/default_lightning.yaml | 47 + .../lr_scheduler/multistep_lr.yaml | 5 + .../lr_scheduler/one_cycle_lr.yaml | 49 + .../matching/rvae_green_lights_matching.yaml | 6 + .../matching/rvae_lines_matching.yaml | 6 + .../matching/rvae_pedestrians_matching.yaml | 6 + .../matching/rvae_red_lights_matching.yaml | 6 + .../rvae_static_objects_matching.yaml | 6 + .../matching/rvae_vehicles_matching.yaml | 6 + .../autoencoder/objective/kl_objective.yaml | 4 + .../objective/rvae_ego_objective.yaml | 5 + .../rvae_green_lights_objective.yaml | 6 + .../objective/rvae_lines_objective.yaml | 6 + .../objective/rvae_pedestrians_objective.yaml | 6 + .../objective/rvae_red_lights_objective.yaml | 6 + .../rvae_static_objects_objective.yaml | 6 + .../objective/rvae_vehicles_objective.yaml | 6 + .../objective/vae_bce_objective.yaml | 4 + .../objective/vae_l1_objective.yaml | 4 + .../config/autoencoder/optimizer/adam.yaml | 6 + .../config/autoencoder/optimizer/adamw.yaml | 6 + .../config/autoencoder/optimizer/sgd.yaml | 8 + .../default_scenario_type_weights.yaml | 11 + .../training_metric/kl_metric.yaml | 3 + sledge/script/config/common/__init__.py | 0 .../common/autoencoder_model/rvae_model.yaml | 69 + .../common/autoencoder_model/vae_model.yaml | 39 + .../script/config/common/default_common.yaml | 17 + .../config/common/default_experiment.yaml | 24 + .../common/diffusion_model/dit_b_model.yaml | 19 + .../common/diffusion_model/dit_l_model.yaml | 19 + .../common/diffusion_model/dit_s_model.yaml | 19 + .../common/diffusion_model/dit_xl_model.yaml | 19 + .../mock_abstract_scenario_builder.yaml | 4 + .../common/scenario_builder/nuplan.yaml | 19 + .../scenario_builder/nuplan_challenge.yaml | 19 + .../common/scenario_builder/nuplan_mini.yaml | 19 + .../nuplan_challenge_scenario_mapping.yaml | 88 + .../nuplan_scenario_mapping.yaml | 88 + .../nuplan_vehicle_parameters.yaml | 10 + .../common/scenario_filter/filter_bos.yaml | 21 + .../common/scenario_filter/filter_lav.yaml | 21 + .../common/scenario_filter/filter_pgh.yaml | 21 + .../common/scenario_filter/filter_sgp.yaml | 21 + .../scenario_filter/one_continuous_log.yaml | 21 + .../scenario_filter/reduced_val14_split.yaml | 353 + .../common/scenario_filter/val14_split.yaml | 1153 ++ .../config/common/scenario_filter/vegas.yaml | 23 + .../simulation_metric/common_metrics.yaml | 5 + .../simulation_metric/default_metrics.yaml | 2 + .../ego_stop_at_stop_line_statistics.yaml | 8 + .../drivable_area_compliance_statistics.yaml | 12 + ...iving_direction_compliance_statistics.yaml | 14 + .../ego_is_comfortable_statistics.yaml | 15 + .../ego_is_making_progress_statistics.yaml | 11 + ...no_ego_at_fault_collisions_statistics.yaml | 14 + ...heading_error_within_bound_statistics.yaml | 11 + ...heading_error_within_bound_statistics.yaml | 11 + ...inal_l2_error_within_bound_statistics.yaml | 11 + ...ner_miss_rate_within_bound_statistics.yaml | 13 + .../speed_limit_compliance_statistics.yaml | 12 + ..._to_collision_within_bound_statistics.yaml | 15 + .../ego_acceleration_statistics.yaml | 5 + .../ego_expert_l2_error_statistics.yaml | 6 + ...o_expert_l2_error_with_yaw_statistics.yaml | 7 + .../low_level/ego_jerk_statistics.yaml | 7 + .../low_level/ego_lane_change_statistics.yaml | 6 + .../ego_lat_acceleration_statistics.yaml | 7 + .../low_level/ego_lat_jerk_statistics.yaml | 5 + .../ego_lon_acceleration_statistics.yaml | 8 + .../low_level/ego_lon_jerk_statistics.yaml | 7 + .../low_level/ego_mean_speed_statistics.yaml | 5 + ...rogress_along_expert_route_statistics.yaml | 6 + .../ego_yaw_acceleration_statistics.yaml | 7 + .../low_level/ego_yaw_rate_statistics.yaml | 7 + ...rage_l2_error_within_bound_statistics.yaml | 9 + ...lation_closed_loop_nonreactive_agents.yaml | 19 + ...imulation_closed_loop_reactive_agents.yaml | 19 + .../simulation_open_loop_boxes.yaml | 9 + .../script/config/common/splitter/nuplan.yaml | 15919 ++++++++++++++++ .../config/common/worker/ray_distributed.yaml | 8 + .../config/common/worker/sequential.yaml | 2 + .../worker/single_machine_thread_pool.yaml | 4 + sledge/script/config/diffusion/__init__.py | 0 .../accelerator/default_accelerator.yaml | 14 + .../config/diffusion/default_diffusion.yaml | 67 + .../noise_scheduler/ddpm_scheduler.yaml | 9 + .../config/diffusion/optimizer/adamw.yaml | 7 + sledge/script/config/simulation/__init__.py | 0 .../callback/serialization_callback.yaml | 8 + .../callback/simulation_log_callback.yaml | 7 + .../simulation/callback/timing_callback.yaml | 3 + .../config/simulation/default_simulation.yaml | 89 + .../log_play_back_controller.yaml | 2 + .../motion_model/kinematic_bicycle_model.yaml | 4 + .../perfect_tracking_controller.yaml | 2 + .../ego_controller/tracker/ilqr_tracker.yaml | 44 + .../ego_controller/tracker/lqr_tracker.yaml | 18 + .../ego_controller/two_stage_controller.yaml | 6 + .../main_callback/completion_callback.yaml | 6 + .../metric_aggregator_callback.yaml | 5 + .../main_callback/metric_file_callback.yaml | 8 + .../metric_summary_callback.yaml | 9 + .../main_callback/publisher_callback.yaml | 42 + .../main_callback/time_callback.yaml | 3 + .../main_callback/validation_callback.yaml | 6 + ...loop_reactive_agents_weighted_average.yaml | 19 + .../default_weighted_average.yaml | 11 + .../sledge_agents_observation.yaml | 10 + .../planner/pdm_closed_planner.yaml | 30 + .../step_simulation_time_controller.yaml | 2 + sledge/script/config/sledgeboard/__init__.py | 0 .../sledgeboard/default_sledgeboard.yaml | 27 + sledge/script/experiments/__init__.py | 0 .../autoencoder/training_rvae_model.yaml | 30 + .../autoencoder/training_vae_model.yaml | 17 + .../diffusion/training_dit_model.yaml | 9 + .../simulation/sledge_reactive_agents.yaml | 9 + sledge/script/run_autoencoder.py | 90 + sledge/script/run_diffusion.py | 62 + sledge/script/run_simulation.py | 95 + sledge/script/run_sledgeboard.py | 77 + sledge/simulation/__init__.py | 0 sledge/simulation/maps/__init__.py | 0 sledge/simulation/maps/sledge_map/__init__.py | 0 .../simulation/maps/sledge_map/sledge_lane.py | 134 + .../simulation/maps/sledge_map/sledge_map.py | 302 + .../maps/sledge_map/sledge_map_graph.py | 210 + .../simulation/maps/sledge_map/sledge_path.py | 102 + .../maps/sledge_map/sledge_polyline.py | 85 + .../maps/sledge_map/sledge_roadblock.py | 57 + sledge/simulation/observation/__init__.py | 0 .../observation/sledge_idm/__init__.py | 0 .../sledge_idm/sledge_idm_agent.py | 391 + .../sledge_idm/sledge_idm_agent_manager.py | 201 + .../sledge_idm/sledge_idm_agents_builder.py | 155 + .../observation/sledge_idm/utils.py | 222 + .../observation/sledge_idm_agents.py | 144 + sledge/simulation/planner/__init__.py | 0 .../planner/pdm_planner/__init__.py | 0 .../abstract_pdm_closed_planner.py | 159 + .../pdm_planner/abstract_pdm_planner.py | 162 + .../pdm_planner/observation/__init__.py | 0 .../observation/pdm_object_manager.py | 217 + .../observation/pdm_observation.py | 253 + .../observation/pdm_observation_utils.py | 42 + .../observation/pdm_occupancy_map.py | 99 + .../planner/pdm_planner/pdm_closed_planner.py | 80 + .../planner/pdm_planner/proposal/__init__.py | 0 .../pdm_planner/proposal/batch_idm_policy.py | 170 + .../pdm_planner/proposal/pdm_generator.py | 384 + .../pdm_planner/proposal/pdm_proposal.py | 96 + .../planner/pdm_planner/scoring/__init__.py | 0 .../scoring/pdm_comfort_metrics.py | 336 + .../planner/pdm_planner/scoring/pdm_scorer.py | 497 + .../pdm_planner/scoring/pdm_scorer_utils.py | 65 + .../pdm_planner/simulation/__init__.py | 0 .../simulation/batch_kinematic_bicycle.py | 182 + .../pdm_planner/simulation/batch_lqr.py | 464 + .../pdm_planner/simulation/batch_lqr_utils.py | 249 + .../pdm_planner/simulation/pdm_simulator.py | 79 + .../utils/graph_search/bfs_roadblock.py | 146 + .../utils/graph_search/dijkstra.py | 145 + .../utils/pdm_array_representation.py | 199 + .../pdm_planner/utils/pdm_emergency_brake.py | 136 + .../planner/pdm_planner/utils/pdm_enums.py | 171 + .../pdm_planner/utils/pdm_geometry_utils.py | 96 + .../planner/pdm_planner/utils/pdm_path.py | 93 + .../planner/pdm_planner/utils/route_utils.py | 216 + sledge/simulation/scenarios/__init__.py | 0 .../scenarios/sledge_scenario/__init__.py | 0 .../sledge_scenario/sledge_scenario.py | 335 + .../sledge_scenario/sledge_scenario_utils.py | 302 + sledge/sledgeboard/__init__.py | 0 sledge/sledgeboard/base/__init__.py | 0 sledge/sledgeboard/base/base_tab.py | 243 + sledge/sledgeboard/base/data_class.py | 131 + .../sledgeboard/base/experiment_file_data.py | 288 + sledge/sledgeboard/base/plot_data.py | 1002 + sledge/sledgeboard/base/simulation_tile.py | 963 + sledge/sledgeboard/resource/__init__.py | 0 sledge/sledgeboard/resource/css/cloud.css | 169 + sledge/sledgeboard/resource/css/histogram.css | 86 + sledge/sledgeboard/resource/css/overview.css | 77 + sledge/sledgeboard/resource/css/scenario.css | 273 + sledge/sledgeboard/resource/scripts/utils.js | 61 + .../resource/sledge_logo_transparent.png | Bin 0 -> 61902 bytes .../sledgeboard/resource/spectre-exp.min.css | 1241 ++ .../resource/spectre-icons.min.css | 597 + sledge/sledgeboard/resource/spectre.min.css | 3788 ++++ sledge/sledgeboard/resource/style.css | 274 + sledge/sledgeboard/sledgeboard.py | 181 + sledge/sledgeboard/style.py | 202 + sledge/sledgeboard/tabs/__init__.py | 0 sledge/sledgeboard/tabs/cloud_tab.py | 336 + sledge/sledgeboard/tabs/config/__init__.py | 0 .../tabs/config/cloud_tab_config.py | 234 + .../tabs/config/histogram_tab_config.py | 264 + .../tabs/config/overview_tab_config.py | 98 + .../tabs/config/scenario_tab_config.py | 72 + sledge/sledgeboard/tabs/configuration_tab.py | 124 + sledge/sledgeboard/tabs/histogram_tab.py | 746 + sledge/sledgeboard/tabs/js_code/__init__.py | 0 .../tabs/js_code/cloud_tab_js_code.py | 111 + .../tabs/js_code/histogram_tab_js_code.py | 54 + .../tabs/js_code/scenario_tab_js_code.py | 53 + sledge/sledgeboard/tabs/overview_tab.py | 207 + sledge/sledgeboard/tabs/scenario_tab.py | 1183 ++ sledge/sledgeboard/templates/index.html | 79 + sledge/sledgeboard/templates/tabs/cloud.html | 97 + .../sledgeboard/templates/tabs/histogram.html | 77 + .../sledgeboard/templates/tabs/overview.html | 26 + .../sledgeboard/templates/tabs/scenario.html | 162 + sledge/sledgeboard/utils/__init__.py | 0 .../utils/sledgeboard_cloud_utils.py | 269 + .../utils/sledgeboard_histogram_utils.py | 274 + sledge/sledgeboard/utils/utils.py | 75 + 331 files changed, 48364 insertions(+), 26 deletions(-) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 docs/autoencoder.md create mode 100644 docs/diffusion.md create mode 100644 docs/installation.md create mode 100644 docs/simulation.md create mode 100644 environment.yml create mode 100644 requirements.txt create mode 100644 scripts/autoencoder/rvae/feature_caching_rvae.sh create mode 100644 scripts/autoencoder/rvae/latent_caching_rvae.sh create mode 100644 scripts/autoencoder/rvae/training_rvae.sh create mode 100644 scripts/autoencoder/vae/training_vae.sh create mode 100644 scripts/diffusion/scenario_caching_diffusion.sh create mode 100644 scripts/diffusion/training_diffusion.sh create mode 100644 scripts/download/download_cache.sh create mode 100644 scripts/download/download_nuplan.sh create mode 100644 scripts/simulation/simple_simulation.sh create mode 100644 setup.py create mode 100644 sledge/__init__.py create mode 100644 sledge/autoencoder/__init__.py create mode 100644 sledge/autoencoder/callbacks/__init__.py create mode 100644 sledge/autoencoder/callbacks/rvae_visualization_callback.py create mode 100644 sledge/autoencoder/callbacks/vae_visualization_callback.py create mode 100644 sledge/autoencoder/data_augmentation/__init__.py create mode 100644 sledge/autoencoder/data_augmentation/augmentation_utils.py create mode 100644 sledge/autoencoder/data_augmentation/rvae_augmentation.py create mode 100644 sledge/autoencoder/data_augmentation/vae_augmentation.py create mode 100644 sledge/autoencoder/data_loader/__init__.py create mode 100644 sledge/autoencoder/data_loader/autoencoder_datamodule.py create mode 100644 sledge/autoencoder/experiments/__init__.py create mode 100644 sledge/autoencoder/experiments/feature_caching.py create mode 100644 sledge/autoencoder/experiments/latent_caching.py create mode 100644 sledge/autoencoder/experiments/training.py create mode 100644 sledge/autoencoder/modeling/__init__.py create mode 100644 sledge/autoencoder/modeling/autoencoder_lightning_module_wrapper.py create mode 100644 sledge/autoencoder/modeling/autoencoder_torch_module_wrapper.py create mode 100644 sledge/autoencoder/modeling/matching/__init__.py create mode 100644 sledge/autoencoder/modeling/matching/abstract_matching.py create mode 100644 sledge/autoencoder/modeling/matching/rvae_matching.py create mode 100644 sledge/autoencoder/modeling/metrics/__init__.py create mode 100644 sledge/autoencoder/modeling/metrics/abstract_custom_metric.py create mode 100644 sledge/autoencoder/modeling/metrics/kl_metric.py create mode 100644 sledge/autoencoder/modeling/models/__init__.py create mode 100644 sledge/autoencoder/modeling/models/rvae/__init__.py create mode 100644 sledge/autoencoder/modeling/models/rvae/rvae_config.py create mode 100644 sledge/autoencoder/modeling/models/rvae/rvae_decoder.py create mode 100644 sledge/autoencoder/modeling/models/rvae/rvae_encoder.py create mode 100644 sledge/autoencoder/modeling/models/rvae/rvae_model.py create mode 100644 sledge/autoencoder/modeling/models/rvae/utils/rvae_position_encoding.py create mode 100644 sledge/autoencoder/modeling/models/rvae/utils/rvae_transformer.py create mode 100644 sledge/autoencoder/modeling/models/vae/__init__.py create mode 100644 sledge/autoencoder/modeling/models/vae/vae_config.py create mode 100644 sledge/autoencoder/modeling/models/vae/vae_model.py create mode 100644 sledge/autoencoder/modeling/objectives/__init__.py create mode 100644 sledge/autoencoder/modeling/objectives/abstract_custom_objective.py create mode 100644 sledge/autoencoder/modeling/objectives/kl_objective.py create mode 100644 sledge/autoencoder/modeling/objectives/rvae_objective.py create mode 100644 sledge/autoencoder/modeling/objectives/vae_objective.py create mode 100644 sledge/autoencoder/preprocessing/__init__.py create mode 100644 sledge/autoencoder/preprocessing/feature_builders/__init__.py create mode 100644 sledge/autoencoder/preprocessing/feature_builders/sledge/__init__.py create mode 100644 sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_agent_feature.py create mode 100644 sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_feature_processing.py create mode 100644 sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_line_feature.py create mode 100644 sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_utils.py create mode 100644 sledge/autoencoder/preprocessing/feature_builders/sledge_raw_feature_builder.py create mode 100644 sledge/autoencoder/preprocessing/features/__init__.py create mode 100644 sledge/autoencoder/preprocessing/features/latent_feature.py create mode 100644 sledge/autoencoder/preprocessing/features/map_id_feature.py create mode 100644 sledge/autoencoder/preprocessing/features/rvae_matching_feature.py create mode 100644 sledge/autoencoder/preprocessing/features/sledge_raster_feature.py create mode 100644 sledge/autoencoder/preprocessing/features/sledge_vector_feature.py create mode 100644 sledge/autoencoder/preprocessing/target_builders/__init__.py create mode 100644 sledge/autoencoder/preprocessing/target_builders/map_id_target_builder.py create mode 100644 sledge/common/__init__.py create mode 100644 sledge/common/helper/__init__.py create mode 100644 sledge/common/helper/cache_helper.py create mode 100644 sledge/common/visualization/__init__.py create mode 100644 sledge/common/visualization/sledge_colors.py create mode 100644 sledge/common/visualization/sledge_visualization_utils.py create mode 100644 sledge/diffusion/__init__.py create mode 100644 sledge/diffusion/dataset/__init__.py create mode 100644 sledge/diffusion/dataset/rvae_latent_builder_config.py create mode 100644 sledge/diffusion/dataset/rvae_latent_dataset.py create mode 100644 sledge/diffusion/experiments/__init__.py create mode 100644 sledge/diffusion/experiments/scenario_caching.py create mode 100644 sledge/diffusion/experiments/training.py create mode 100644 sledge/diffusion/modelling/__init__.py create mode 100644 sledge/diffusion/modelling/ldm_pipeline.py create mode 100644 sledge/script/__init__.py create mode 100644 sledge/script/builders/__init__.py create mode 100644 sledge/script/builders/autoencoder_builder.py create mode 100644 sledge/script/builders/diffusion_builder.py create mode 100644 sledge/script/builders/matching_builder.py create mode 100644 sledge/script/builders/metric_builder.py create mode 100644 sledge/script/builders/model_builder.py create mode 100644 sledge/script/builders/scenario_builder.py create mode 100644 sledge/script/builders/simulation_builder.py create mode 100644 sledge/script/builders/utils/__init__.py create mode 100644 sledge/script/builders/utils/utils_config.py create mode 100644 sledge/script/config/__init__.py create mode 100644 sledge/script/config/autoencoder/__init__.py create mode 100644 sledge/script/config/autoencoder/callbacks/default_callbacks.yaml create mode 100644 sledge/script/config/autoencoder/callbacks/learning_rate_monitor_callback.yaml create mode 100644 sledge/script/config/autoencoder/callbacks/model_checkpoint_callback.yaml create mode 100644 sledge/script/config/autoencoder/callbacks/rvae_visualization_callback.yaml create mode 100644 sledge/script/config/autoencoder/callbacks/time_logging_callback.yaml create mode 100644 sledge/script/config/autoencoder/callbacks/vae_visualization_callback.yaml create mode 100644 sledge/script/config/autoencoder/data_augmentation/rvae_augmentation.yaml create mode 100644 sledge/script/config/autoencoder/data_augmentation/rvae_no_augmentation.yaml create mode 100644 sledge/script/config/autoencoder/data_augmentation/vae_augmentation.yaml create mode 100644 sledge/script/config/autoencoder/data_loader/default_data_loader.yaml create mode 100644 sledge/script/config/autoencoder/default_autoencoder.yaml create mode 100644 sledge/script/config/autoencoder/lightning/default_lightning.yaml create mode 100644 sledge/script/config/autoencoder/lr_scheduler/multistep_lr.yaml create mode 100644 sledge/script/config/autoencoder/lr_scheduler/one_cycle_lr.yaml create mode 100644 sledge/script/config/autoencoder/matching/rvae_green_lights_matching.yaml create mode 100644 sledge/script/config/autoencoder/matching/rvae_lines_matching.yaml create mode 100644 sledge/script/config/autoencoder/matching/rvae_pedestrians_matching.yaml create mode 100644 sledge/script/config/autoencoder/matching/rvae_red_lights_matching.yaml create mode 100644 sledge/script/config/autoencoder/matching/rvae_static_objects_matching.yaml create mode 100644 sledge/script/config/autoencoder/matching/rvae_vehicles_matching.yaml create mode 100644 sledge/script/config/autoencoder/objective/kl_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/rvae_ego_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/rvae_green_lights_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/rvae_lines_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/rvae_pedestrians_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/rvae_red_lights_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/rvae_static_objects_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/rvae_vehicles_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/vae_bce_objective.yaml create mode 100644 sledge/script/config/autoencoder/objective/vae_l1_objective.yaml create mode 100644 sledge/script/config/autoencoder/optimizer/adam.yaml create mode 100644 sledge/script/config/autoencoder/optimizer/adamw.yaml create mode 100644 sledge/script/config/autoencoder/optimizer/sgd.yaml create mode 100644 sledge/script/config/autoencoder/scenario_type_weights/default_scenario_type_weights.yaml create mode 100644 sledge/script/config/autoencoder/training_metric/kl_metric.yaml create mode 100644 sledge/script/config/common/__init__.py create mode 100644 sledge/script/config/common/autoencoder_model/rvae_model.yaml create mode 100644 sledge/script/config/common/autoencoder_model/vae_model.yaml create mode 100644 sledge/script/config/common/default_common.yaml create mode 100644 sledge/script/config/common/default_experiment.yaml create mode 100644 sledge/script/config/common/diffusion_model/dit_b_model.yaml create mode 100644 sledge/script/config/common/diffusion_model/dit_l_model.yaml create mode 100644 sledge/script/config/common/diffusion_model/dit_s_model.yaml create mode 100644 sledge/script/config/common/diffusion_model/dit_xl_model.yaml create mode 100644 sledge/script/config/common/scenario_builder/mock_abstract_scenario_builder.yaml create mode 100644 sledge/script/config/common/scenario_builder/nuplan.yaml create mode 100644 sledge/script/config/common/scenario_builder/nuplan_challenge.yaml create mode 100644 sledge/script/config/common/scenario_builder/nuplan_mini.yaml create mode 100644 sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_challenge_scenario_mapping.yaml create mode 100644 sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_scenario_mapping.yaml create mode 100644 sledge/script/config/common/scenario_builder/vehicle_parameters/nuplan_vehicle_parameters.yaml create mode 100644 sledge/script/config/common/scenario_filter/filter_bos.yaml create mode 100644 sledge/script/config/common/scenario_filter/filter_lav.yaml create mode 100644 sledge/script/config/common/scenario_filter/filter_pgh.yaml create mode 100644 sledge/script/config/common/scenario_filter/filter_sgp.yaml create mode 100644 sledge/script/config/common/scenario_filter/one_continuous_log.yaml create mode 100644 sledge/script/config/common/scenario_filter/reduced_val14_split.yaml create mode 100644 sledge/script/config/common/scenario_filter/val14_split.yaml create mode 100644 sledge/script/config/common/scenario_filter/vegas.yaml create mode 100644 sledge/script/config/common/simulation_metric/common_metrics.yaml create mode 100644 sledge/script/config/common/simulation_metric/default_metrics.yaml create mode 100644 sledge/script/config/common/simulation_metric/ego_in_stop_line/ego_stop_at_stop_line_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/drivable_area_compliance_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/driving_direction_compliance_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/ego_is_comfortable_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/ego_is_making_progress_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/no_ego_at_fault_collisions_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/planner_expert_average_heading_error_within_bound_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/planner_expert_final_heading_error_within_bound_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/planner_expert_final_l2_error_within_bound_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/planner_miss_rate_within_bound_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/speed_limit_compliance_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/high_level/time_to_collision_within_bound_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_acceleration_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_with_yaw_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_jerk_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_lane_change_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_lat_acceleration_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_lat_jerk_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_lon_acceleration_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_lon_jerk_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_mean_speed_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_progress_along_expert_route_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_yaw_acceleration_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/ego_yaw_rate_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/low_level/planner_expert_average_l2_error_within_bound_statistics.yaml create mode 100644 sledge/script/config/common/simulation_metric/simulation_closed_loop_nonreactive_agents.yaml create mode 100644 sledge/script/config/common/simulation_metric/simulation_closed_loop_reactive_agents.yaml create mode 100644 sledge/script/config/common/simulation_metric/simulation_open_loop_boxes.yaml create mode 100644 sledge/script/config/common/splitter/nuplan.yaml create mode 100644 sledge/script/config/common/worker/ray_distributed.yaml create mode 100644 sledge/script/config/common/worker/sequential.yaml create mode 100644 sledge/script/config/common/worker/single_machine_thread_pool.yaml create mode 100644 sledge/script/config/diffusion/__init__.py create mode 100644 sledge/script/config/diffusion/accelerator/default_accelerator.yaml create mode 100644 sledge/script/config/diffusion/default_diffusion.yaml create mode 100644 sledge/script/config/diffusion/noise_scheduler/ddpm_scheduler.yaml create mode 100644 sledge/script/config/diffusion/optimizer/adamw.yaml create mode 100644 sledge/script/config/simulation/__init__.py create mode 100644 sledge/script/config/simulation/callback/serialization_callback.yaml create mode 100644 sledge/script/config/simulation/callback/simulation_log_callback.yaml create mode 100644 sledge/script/config/simulation/callback/timing_callback.yaml create mode 100644 sledge/script/config/simulation/default_simulation.yaml create mode 100644 sledge/script/config/simulation/ego_controller/log_play_back_controller.yaml create mode 100644 sledge/script/config/simulation/ego_controller/motion_model/kinematic_bicycle_model.yaml create mode 100644 sledge/script/config/simulation/ego_controller/perfect_tracking_controller.yaml create mode 100644 sledge/script/config/simulation/ego_controller/tracker/ilqr_tracker.yaml create mode 100644 sledge/script/config/simulation/ego_controller/tracker/lqr_tracker.yaml create mode 100644 sledge/script/config/simulation/ego_controller/two_stage_controller.yaml create mode 100644 sledge/script/config/simulation/main_callback/completion_callback.yaml create mode 100644 sledge/script/config/simulation/main_callback/metric_aggregator_callback.yaml create mode 100644 sledge/script/config/simulation/main_callback/metric_file_callback.yaml create mode 100644 sledge/script/config/simulation/main_callback/metric_summary_callback.yaml create mode 100644 sledge/script/config/simulation/main_callback/publisher_callback.yaml create mode 100644 sledge/script/config/simulation/main_callback/time_callback.yaml create mode 100644 sledge/script/config/simulation/main_callback/validation_callback.yaml create mode 100644 sledge/script/config/simulation/metric_aggregator/closed_loop_reactive_agents_weighted_average.yaml create mode 100644 sledge/script/config/simulation/metric_aggregator/default_weighted_average.yaml create mode 100644 sledge/script/config/simulation/observation/sledge_agents_observation.yaml create mode 100644 sledge/script/config/simulation/planner/pdm_closed_planner.yaml create mode 100644 sledge/script/config/simulation/simulation_time_controller/step_simulation_time_controller.yaml create mode 100644 sledge/script/config/sledgeboard/__init__.py create mode 100644 sledge/script/config/sledgeboard/default_sledgeboard.yaml create mode 100644 sledge/script/experiments/__init__.py create mode 100644 sledge/script/experiments/autoencoder/training_rvae_model.yaml create mode 100644 sledge/script/experiments/autoencoder/training_vae_model.yaml create mode 100644 sledge/script/experiments/diffusion/training_dit_model.yaml create mode 100644 sledge/script/experiments/simulation/sledge_reactive_agents.yaml create mode 100644 sledge/script/run_autoencoder.py create mode 100644 sledge/script/run_diffusion.py create mode 100644 sledge/script/run_simulation.py create mode 100644 sledge/script/run_sledgeboard.py create mode 100644 sledge/simulation/__init__.py create mode 100644 sledge/simulation/maps/__init__.py create mode 100644 sledge/simulation/maps/sledge_map/__init__.py create mode 100644 sledge/simulation/maps/sledge_map/sledge_lane.py create mode 100644 sledge/simulation/maps/sledge_map/sledge_map.py create mode 100644 sledge/simulation/maps/sledge_map/sledge_map_graph.py create mode 100644 sledge/simulation/maps/sledge_map/sledge_path.py create mode 100644 sledge/simulation/maps/sledge_map/sledge_polyline.py create mode 100644 sledge/simulation/maps/sledge_map/sledge_roadblock.py create mode 100644 sledge/simulation/observation/__init__.py create mode 100644 sledge/simulation/observation/sledge_idm/__init__.py create mode 100644 sledge/simulation/observation/sledge_idm/sledge_idm_agent.py create mode 100644 sledge/simulation/observation/sledge_idm/sledge_idm_agent_manager.py create mode 100644 sledge/simulation/observation/sledge_idm/sledge_idm_agents_builder.py create mode 100644 sledge/simulation/observation/sledge_idm/utils.py create mode 100644 sledge/simulation/observation/sledge_idm_agents.py create mode 100644 sledge/simulation/planner/__init__.py create mode 100644 sledge/simulation/planner/pdm_planner/__init__.py create mode 100644 sledge/simulation/planner/pdm_planner/abstract_pdm_closed_planner.py create mode 100644 sledge/simulation/planner/pdm_planner/abstract_pdm_planner.py create mode 100644 sledge/simulation/planner/pdm_planner/observation/__init__.py create mode 100644 sledge/simulation/planner/pdm_planner/observation/pdm_object_manager.py create mode 100644 sledge/simulation/planner/pdm_planner/observation/pdm_observation.py create mode 100644 sledge/simulation/planner/pdm_planner/observation/pdm_observation_utils.py create mode 100644 sledge/simulation/planner/pdm_planner/observation/pdm_occupancy_map.py create mode 100644 sledge/simulation/planner/pdm_planner/pdm_closed_planner.py create mode 100644 sledge/simulation/planner/pdm_planner/proposal/__init__.py create mode 100644 sledge/simulation/planner/pdm_planner/proposal/batch_idm_policy.py create mode 100644 sledge/simulation/planner/pdm_planner/proposal/pdm_generator.py create mode 100644 sledge/simulation/planner/pdm_planner/proposal/pdm_proposal.py create mode 100644 sledge/simulation/planner/pdm_planner/scoring/__init__.py create mode 100644 sledge/simulation/planner/pdm_planner/scoring/pdm_comfort_metrics.py create mode 100644 sledge/simulation/planner/pdm_planner/scoring/pdm_scorer.py create mode 100644 sledge/simulation/planner/pdm_planner/scoring/pdm_scorer_utils.py create mode 100644 sledge/simulation/planner/pdm_planner/simulation/__init__.py create mode 100644 sledge/simulation/planner/pdm_planner/simulation/batch_kinematic_bicycle.py create mode 100644 sledge/simulation/planner/pdm_planner/simulation/batch_lqr.py create mode 100644 sledge/simulation/planner/pdm_planner/simulation/batch_lqr_utils.py create mode 100644 sledge/simulation/planner/pdm_planner/simulation/pdm_simulator.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/graph_search/bfs_roadblock.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/graph_search/dijkstra.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/pdm_array_representation.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/pdm_emergency_brake.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/pdm_enums.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/pdm_geometry_utils.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/pdm_path.py create mode 100644 sledge/simulation/planner/pdm_planner/utils/route_utils.py create mode 100644 sledge/simulation/scenarios/__init__.py create mode 100644 sledge/simulation/scenarios/sledge_scenario/__init__.py create mode 100644 sledge/simulation/scenarios/sledge_scenario/sledge_scenario.py create mode 100644 sledge/simulation/scenarios/sledge_scenario/sledge_scenario_utils.py create mode 100644 sledge/sledgeboard/__init__.py create mode 100644 sledge/sledgeboard/base/__init__.py create mode 100644 sledge/sledgeboard/base/base_tab.py create mode 100644 sledge/sledgeboard/base/data_class.py create mode 100644 sledge/sledgeboard/base/experiment_file_data.py create mode 100644 sledge/sledgeboard/base/plot_data.py create mode 100644 sledge/sledgeboard/base/simulation_tile.py create mode 100644 sledge/sledgeboard/resource/__init__.py create mode 100644 sledge/sledgeboard/resource/css/cloud.css create mode 100644 sledge/sledgeboard/resource/css/histogram.css create mode 100644 sledge/sledgeboard/resource/css/overview.css create mode 100644 sledge/sledgeboard/resource/css/scenario.css create mode 100644 sledge/sledgeboard/resource/scripts/utils.js create mode 100644 sledge/sledgeboard/resource/sledge_logo_transparent.png create mode 100644 sledge/sledgeboard/resource/spectre-exp.min.css create mode 100644 sledge/sledgeboard/resource/spectre-icons.min.css create mode 100644 sledge/sledgeboard/resource/spectre.min.css create mode 100644 sledge/sledgeboard/resource/style.css create mode 100644 sledge/sledgeboard/sledgeboard.py create mode 100644 sledge/sledgeboard/style.py create mode 100644 sledge/sledgeboard/tabs/__init__.py create mode 100644 sledge/sledgeboard/tabs/cloud_tab.py create mode 100644 sledge/sledgeboard/tabs/config/__init__.py create mode 100644 sledge/sledgeboard/tabs/config/cloud_tab_config.py create mode 100644 sledge/sledgeboard/tabs/config/histogram_tab_config.py create mode 100644 sledge/sledgeboard/tabs/config/overview_tab_config.py create mode 100644 sledge/sledgeboard/tabs/config/scenario_tab_config.py create mode 100644 sledge/sledgeboard/tabs/configuration_tab.py create mode 100644 sledge/sledgeboard/tabs/histogram_tab.py create mode 100644 sledge/sledgeboard/tabs/js_code/__init__.py create mode 100644 sledge/sledgeboard/tabs/js_code/cloud_tab_js_code.py create mode 100644 sledge/sledgeboard/tabs/js_code/histogram_tab_js_code.py create mode 100644 sledge/sledgeboard/tabs/js_code/scenario_tab_js_code.py create mode 100644 sledge/sledgeboard/tabs/overview_tab.py create mode 100644 sledge/sledgeboard/tabs/scenario_tab.py create mode 100644 sledge/sledgeboard/templates/index.html create mode 100644 sledge/sledgeboard/templates/tabs/cloud.html create mode 100644 sledge/sledgeboard/templates/tabs/histogram.html create mode 100644 sledge/sledgeboard/templates/tabs/overview.html create mode 100644 sledge/sledgeboard/templates/tabs/scenario.html create mode 100644 sledge/sledgeboard/utils/__init__.py create mode 100644 sledge/sledgeboard/utils/sledgeboard_cloud_utils.py create mode 100644 sledge/sledgeboard/utils/sledgeboard_histogram_utils.py create mode 100644 sledge/sledgeboard/utils/utils.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..c3fd080 --- /dev/null +++ b/.flake8 @@ -0,0 +1,17 @@ +[flake8] +ignore = + # Whitespace before ':' + E203, + # Module level import not at top of file + E402, + # Line break occurred before a binary operator + W503, + # Line break occurred after a binary operator + W504 + # line break before binary operator + E203 + # line too long + E501 + # No lambdas — too strict + E731 +max-line-length = 120 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8473d92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# python +*.pyc +**/__pycache__/ +.pytest_cache/* +.pydevproject + +# IDE +.vscode/* + +# Pip +*.egg-info + +# Log files +*.out +*.err +*.gz + +# Custom +*.ckpt +*.zip \ No newline at end of file diff --git a/README.md b/README.md index 43732f5..7ef85f3 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@

sledge -

Generative Simulation for Vehicle Motion Planning

+

Generative Simulation for Vehicle Motion Planning

Paper | Supplementary | Video


-> [**SLEDGE: Synthesizing Simulation Environments for Driving Agents with Generative Models**](https://arxiv.org/abs/2403.17933)
+> [**SLEDGE: Synthesizing Driving Environments with Generative Models and Rule-Based Traffic**](https://arxiv.org/abs/2403.17933)
> [Kashyap Chitta](https://kashyap7x.github.io/), [Daniel Dauner](https://danieldauner.github.io/), and [Andreas Geiger](https://www.cvlibs.net/)
> University of Tübingen, Tübingen AI Center +> +> European Conference on Computer Vision (ECCV), 2024 >
> @@ -19,48 +21,62 @@ This repo contains SLEDGE, the first generative simulator for vehicle motion pla https://github.com/autonomousvision/sledge/assets/50077664/1c653fda-6e44-4018-ae98-2ab3d0439cad -## News -* **`27 Mar, 2024`:** We released our paper on [arXiv](https://arxiv.org/abs/2403.17933). Code and models are coming soon, please stay tuned! +## News 📰 +* **`18 Aug, 2024`:** We released v0.1 of the SLEDGE code! +* **`01 Jul, 2024`:** Our paper was accepted at [ECCV 2024](https://eccv.ecva.net/) 🇮🇹 +* **`27 Mar, 2024`:** We released our paper on [arXiv](https://arxiv.org/abs/2403.17933)! -
-## Overview +## Table of Contents 📜 +1. [Overview](#overview) +2. [Getting started](#gettingstarted) +3. [Changelog](#changelog) +4. [Contact](#contact) +5. [Citation](#citation) +6. [Other resources](#otherresources) -- SLEDGE is a generative simulator, able to synthesize agent bounding boxes and lane graphs, which serve as an initial state for traffic simulation. -- The unique properties of the entities to be generated for SLEDGE, such as their connectivity and variable count per scene, render the naive application of most modern generative models to this task non-trivial. +## Getting started 🚀 -- Therefore, we introduce a novel raster-to-vector autoencoder (RVAE). It encodes agents and the lane graph into distinct channels in a rasterized latent map. This facilitates both lane-conditioned agent generation and -combined generation of lanes and agents with a Diffusion Transformer. +- [Installation and download](docs/installation.md) +- [Running the autoencoder](docs/autoencoder.md) +- [Running the diffusion model](docs/diffusion.md) +- [Simulation and visualization](docs/simulation.md) -- Using generated entities in SLEDGE enables greater control over the simulation, e.g. long routes, upsampling turns, or increasing traffic density. SLEDGE presents new challenges for planning algorithms, evidenced by failure rates of over 40% for PDM, when tested on hard routes and dense generated traffic. -- Compared to nuPlan, SLEDGE requires 500× less storage to set up (<4GB), making it a more accessible option and helping with democratizing future research in this field. +## Changelog 🛷 +- **`[2024/08/18]`** SLEDGE v0.1 release + - Scripts for pre-processing and downloads + - Raster-vector autoencoder (training & latent caching) + - Latent diffusion models (training & scenario generation) + - Simple simulations + - SledgeBoard -
-## TODO -- [ ] Code release -- [ ] Supplementary material & video +## TODO 📋 +- [ ] Add videos and talks +- [ ] Release checkpoints +- [ ] Metrics & complete simulation code +- [X] SLEDGE v0.1 & camera ready release - [x] Initial repository & preprint release -## Contact + +## Contact ✉ If you have any questions or suggestions, please feel free to open an issue or contact us (daniel.dauner@uni-tuebingen.de). -## Citation +## Citation 📎 If you find SLEDGE useful, please consider giving us a star 🌟 and citing our paper with the following BibTeX entry. ```BibTeX -@InProceedings{Chitta2024ARXIV, - title={SLEDGE: Synthesizing Simulation Environments for Driving Agents with Generative Models}, - author={Chitta, Kashyap and Dauner, Daniel and Geiger, Andreas}, - journal={arXiv}, - volume={2403.17933}, - year={2024} +@InProceedings{Chitta2024ECCV, + title = {SLEDGE: Synthesizing Driving Environments with Generative Models and Rule-Based Traffic}, + author = {Kashyap Chitta and Daniel Dauner and Andreas Geiger}, + booktitle = {European Conference on Computer Vision (ECCV)}, + year = {2024}, } ``` -## Other resources +## Other resources 💡 Twitter Follow @@ -72,7 +88,7 @@ If you find SLEDGE useful, please consider giving us a star 🌟 and citing Twitter Follow -- [NAVSIM](https://github.com/autonomousvision/navsim) | [tuPlan garage](https://github.com/autonomousvision/tuplan_garage) | [CARLA garage](https://github.com/autonomousvision/carla_garage) | [Survey on E2EAD](https://github.com/OpenDriveLab/End-to-end-Autonomous-Driving) +- [NAVSIM](https://github.com/autonomousvision/navsim) | [tuPlan garage](https://github.com/autonomousvision/sledge) | [CARLA garage](https://github.com/autonomousvision/carla_garage) | [Survey on E2EAD](https://github.com/OpenDriveLab/End-to-end-Autonomous-Driving) - [PlanT](https://github.com/autonomousvision/plant) | [KING](https://github.com/autonomousvision/king) | [TransFuser](https://github.com/autonomousvision/transfuser) | [NEAT](https://github.com/autonomousvision/neat)

(back to top)

diff --git a/docs/autoencoder.md b/docs/autoencoder.md new file mode 100644 index 0000000..7a5904a --- /dev/null +++ b/docs/autoencoder.md @@ -0,0 +1,31 @@ +# Autoencoder + +This guide provides instructions on how to run an autoencoder in SLEDGE. +The tutorial below shows the key functionalities of the raster-to-vector autoencoder (RVAE). + +### 1. Feature Caching +Similar to the [nuplan-devkit](https://github.com/motional/nuplan-devkit), + pre-processing of the training data is recommended. +The cache for the RVAE can be created by running: +```bash +cd $SLEDGE_DEVKIT_ROOT/scripts/autoencoder/rvae/ +bash feature_caching_rvae.sh +``` +This script pre-processes the vector features of several maps sequentially. The cached features only store the local map and agents in a general vector format. The features are further processed and rasterized on the fly during training. This two-step processing enables fast access to training data and allows data augmentation (e.g. random rotation and translation) for RVAE training. The feature cache is compatible with other autoencoders. + +### 2. Training Autoencoder +After creating or downloading the autoencoder cache, you can start the training. We provide an example script in the same folder. +```bash +bash training_rvae.sh +``` +You can find the experiment folder of training in `$SLEDGE_EXP_ROOT/exp` and monitor the run with tensorboard. + +### 3. Latent Caching +You must first cache the latent variables to run a latent diffusion model with the trained autoencoder. In SLEDGE, we cache the latent variables into the autoencoder cache directory (i.e. `$SLEDGE_EXP_ROOT/caches/autoencoder_cache`). The bash script is provided in the RVAE folder. +```bash +bash latent_caching_rvae.sh +``` +Importantly, data augmentation is disabled for latent caching. We also only cache the samples from the training split. + +### 4. Evaluating Autoencoder +Coming soon! \ No newline at end of file diff --git a/docs/diffusion.md b/docs/diffusion.md new file mode 100644 index 0000000..68660f0 --- /dev/null +++ b/docs/diffusion.md @@ -0,0 +1,23 @@ +# Diffusion + +This section provides instructions on how to utilize diffusion models within the SLEDGE framework. + +### 1. Training Diffusion +Before training a diffusion model, make sure you have a trained autoencoder checkpoint and latent cache as described in `docs/autoencoder.md`. +You can start a training experiment by running the script: +```bash +cd $SLEDGE_DEVKIT_ROOT/scripts/diffusion/ +bash training_diffusion.sh +``` +Please make sure you added the autoencoder checkpoint path to the bash script. Before training starts, the latent variables will be stored in a Hugging Face dataset format and saved to `$SLEDGE_EXP_ROOT/caches/diffusion_cache`. This format is compatible with the [`accelerate`](https://github.com/huggingface/accelerate) framework and has performance advantages. Read more [here](https://huggingface.co/docs/datasets/about_arrow) if you are interested. Our training pipeline supports [diffusion transformers (DiT)](https://arxiv.org/abs/2212.09748) in four sizes (S, B, L, XL). You can find the experiment folder and checkpoints in `$SLEDGE_EXP_ROOT/exp`. You can also monitor the training with tensorboard. + +### 2. Scenario Synthesis +Given the trained diffusion model, you can generate a set of samples used for driving simulation or the generative metrics. You can set the diffuser checkpoint path and run the following: +```bash +bash scenario_caching_diffusion.sh +``` +The samples are stored in `$SLEDGE_EXP_ROOT/caches/scenario_cache` by default. These samples can be simulated in the v0.1 release. +Additional options for route extrapolation by inpainting will be added in a future update. + +### 3. Evaluating Diffusion +Coming soon! \ No newline at end of file diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..1ee00ae --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,81 @@ +# Installation and download + +To get started with SLEDGE, we provide the main instructions on how to install the devkit and download the necessary data for training and simulation. + +### 1. Clone the sledge-devkit +Begin by cloning the SLEDGE repository and navigating to the repository directory: +```bash +git clone https://github.com/autonomousvision/sledge.git +cd sledge +``` + +### 2. Download the data +**Important:** Before downloading any data, please ensure you have read the [nuPlan license](https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/LICENSE). + +You can find the bash scripts to download the datasplits in `scripts/download/`. For complete usage in SLEDGE, you need to download the nuPlan dataset (without sensor data) with the `download_nuplan.sh` script (~2TiB). Each part of nuPlan has different roles in the SLEDGE framework: +- `train` and `val` contain the training and validation logs, which primarily serve for autoencoder feature caching. The autoencoder and diffusion training pipeline will operate on this pre-processed cache. +- `test` contains the test logs that are used in the metrics (reconstruction / generative) and for "Lane->Agent" simulation. +- `mini` has a few log files (~15GB) and can be used for simple experiments and demos. +- `maps` contains the maps of the four cities in nuPlan. These maps are necessary for autoencoder feature caching, the metrics, and "Lane->Agent" simulation. + +If you don't want to change the autoencoder features, you can directly download the pre-processed training cache with the `download_cache.sh` script (~15GiB). As such, the [license and conditions](https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/LICENSE) of the nuPlan dataset apply. + +After downloading, organize the decompressed files in the following directory structure: +```angular2html +~/sledge_workspace +├── sledge (devkit) +├── exp +│ ├── caches +│ │ ├── autoencoder_cache +│ │ │ └── +│ │ ├── diffusion_cache +│ │ │ └── +│ │ └── scenario_cache +│ │ └── +│ └── exp +│ └── +└── dataset + ├── maps + │   ├── nuplan-maps-v1.0.json + │   ├── sg-one-north + │   │   └── ... + │   ├── us-ma-boston + │   │   └── ... + │   ├── us-nv-las-vegas-strip + │   │   └── ... + │   └── us-pa-pittsburgh-hazelwood + │   └── ... + └── nuplan-v1.1 +    ├── splits +    │ ├── mini +    │ │ ├── 2021.05.12.22.00.38_veh-35_01008_01518.db +    │ │ ├── ... +    │ │ └── 2021.10.11.08.31.07_veh-50_01750_01948.db +    │ ├── test +    │ │ ├── 2021.05.25.12.30.39_veh-25_00005_00215.db +    │ │ ├── ... +    │ │ └── 2021.10.06.08.34.20_veh-53_01089_01868.db +    │ └── trainval +    │ ├── 2021.05.12.19.36.12_veh-35_00005_00204.db +    │ ├── ... +    │ └── 2021.10.22.18.45.52_veh-28_01175_01298.db +    └── sensor_blobs (empty) +``` +Several environment variables need to be added next to your `~/.bashrc` file. +For the above, the environment variables are defined as: +```bash +export NUPLAN_DATA_ROOT="$HOME/sledge_workspace/dataset" +export NUPLAN_MAPS_ROOT="$HOME/sledge_workspace/dataset/maps" + +export SLEDGE_EXP_ROOT="$HOME/sledge_workspace/exp" +export SLEDGE_DEVKIT_ROOT="$HOME/sledge_workspace/sledge" +``` + +### 3. Install the sledge-devkit +To install SLEDGE, create a new conda environment and install the necessary dependencies as follows: +```bash +conda env create --name sledge -f environment.yml +conda activate sledge +pip install -e . +``` +With these steps completed, SLEDGE should be ready to use. \ No newline at end of file diff --git a/docs/simulation.md b/docs/simulation.md new file mode 100644 index 0000000..1baf806 --- /dev/null +++ b/docs/simulation.md @@ -0,0 +1,21 @@ +# Simulation + +This section provides instructions for simulating scenarios in SLEDGE. + +### Simple Simulation + +For the v0.1 release, you can simulate simple 64mx64m patches. You first need to train a diffusion model and run scenario caching, as described in `docs/diffusion.md`. +Consequently, you can simulate the scenarios by running. +```bash +cd $SLEDGE_DEVKIT_ROOT/scripts/simulation/ +bash simple_simulation.sh +``` +By default, we simulate the [PDM-Closed](https://arxiv.org/abs/2306.07962) planner for 15 seconds. The experiment folder can be found in `$SLEDGE_EXP_ROOT/exp`. Further simulation modes and configurations will follow in future updates. + +### Visualization + +The simulated scenarios can be visualized with SledgeBoard. Simply run: +```bash +python $SLEDGE_DEVKIT_ROOT/sledge/script/run_sledgeboard.py +``` +Open the `.nuboard` file in the experiment folder, view simulations, and render videos of scenarios. Note that SledgeBoard is primarily of a skin of nuBoard, with small adaptations to view synthetic scenarios in SLEDGE. diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..e5a3ea3 --- /dev/null +++ b/environment.yml @@ -0,0 +1,9 @@ +name: sledge +channels: + - conda-forge +dependencies: + - python=3.9 + - pip=23.3.1 + - nb_conda_kernels + - pip: + - -r requirements.txt \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b803b31 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,54 @@ +nuplan-devkit @ git+https://github.com/motional/nuplan-devkit/@nuplan-devkit-v1.2 + +# nuplan requirements +aioboto3 +aiofiles +bokeh==2.4.3 +casadi +control==0.9.1 +Fiona +geopandas>=0.12.1 +guppy3==3.1.2 +hydra-core==1.1.0rc1 +joblib +matplotlib +nest_asyncio +numpy==1.23.4 +opencv-python==4.9.0.80 +pandas +Pillow +psutil +pyarrow +pyinstrument +pyogrio +pyquaternion>=0.9.5 +pytest +rasterio +ray +retry +rtree +scipy +selenium +setuptools==65.5.1 +Shapely>=2.0.0 +SQLAlchemy==1.4.27 +sympy +tornado +tqdm +ujson +notebook==7.2.1 + +# torch & lighting +torch==2.0.1 +torchvision==0.15.2 +pytorch-lightning==2.2.1 +tensorboard +protobuf==4.25.3 + +# hugging face +accelerate==0.33.0 +datasets==2.20.0 +transformers==4.43.3 +dataset==1.6.2 +diffusers==0.29.2 +timm==1.0.8 \ No newline at end of file diff --git a/scripts/autoencoder/rvae/feature_caching_rvae.sh b/scripts/autoencoder/rvae/feature_caching_rvae.sh new file mode 100644 index 0000000..eb1b3a9 --- /dev/null +++ b/scripts/autoencoder/rvae/feature_caching_rvae.sh @@ -0,0 +1,19 @@ +JOB_NAME=feature_caching +AUTOENCODER_CACHE_PATH=/path/to/exp/caches/autoencoder_cache +USE_CACHE_WITHOUT_DATASET=True +SEED=0 + + +for MAP in "filter_pgh" "filter_lav" "filter_sgp" "filter_bos" +do + python $SLEDGE_DEVKIT_ROOT/sledge/script/run_autoencoder.py \ + py_func=feature_caching \ + seed=$SEED \ + job_name=$JOB_NAME \ + +autoencoder=training_rvae_model \ + scenario_builder=nuplan \ + scenario_filter=$MAP \ + cache.autoencoder_cache_path=$AUTOENCODER_CACHE_PATH \ + cache.use_cache_without_dataset=$USE_CACHE_WITHOUT_DATASET \ + callbacks="[]" +done \ No newline at end of file diff --git a/scripts/autoencoder/rvae/latent_caching_rvae.sh b/scripts/autoencoder/rvae/latent_caching_rvae.sh new file mode 100644 index 0000000..b663f95 --- /dev/null +++ b/scripts/autoencoder/rvae/latent_caching_rvae.sh @@ -0,0 +1,18 @@ +JOB_NAME=latent_caching +AUTOENCODER_CACHE_PATH=/path/to/exp/caches/autoencoder_cache +AUTOENCODER_CHECKPOINT=/path/to/rvae_checkpoint.ckpt +USE_CACHE_WITHOUT_DATASET=True +SEED=0 + + +python $SLEDGE_DEVKIT_ROOT/sledge/script/run_autoencoder.py \ +py_func=latent_caching \ +seed=$SEED \ +job_name=$JOB_NAME \ ++autoencoder=training_rvae_model \ +data_augmentation=rvae_no_augmentation \ +autoencoder_checkpoint=$AUTOENCODER_CHECKPOINT \ +cache.autoencoder_cache_path=$AUTOENCODER_CACHE_PATH \ +cache.latent_name="rvae_latent" \ +cache.use_cache_without_dataset=$USE_CACHE_WITHOUT_DATASET \ +callbacks="[]" \ No newline at end of file diff --git a/scripts/autoencoder/rvae/training_rvae.sh b/scripts/autoencoder/rvae/training_rvae.sh new file mode 100644 index 0000000..f986422 --- /dev/null +++ b/scripts/autoencoder/rvae/training_rvae.sh @@ -0,0 +1,15 @@ +JOB_NAME=training_rvae_model +AUTOENCODER_CACHE_PATH=/path/to/exp/caches/autoencoder_cache +AUTOENCODER_CHECKPOINT=null # set for weight intialization / continue training +USE_CACHE_WITHOUT_DATASET=True +SEED=0 + +python $SLEDGE_DEVKIT_ROOT/sledge/script/run_autoencoder.py \ +py_func=training \ +seed=$SEED \ +job_name=$JOB_NAME \ ++autoencoder=training_rvae_model \ +autoencoder_checkpoint=$AUTOENCODER_CHECKPOINT \ +cache.autoencoder_cache_path=$AUTOENCODER_CACHE_PATH \ +cache.use_cache_without_dataset=$USE_CACHE_WITHOUT_DATASET \ +callbacks="[learning_rate_monitor_callback, model_checkpoint_callback, time_logging_callback, rvae_visualization_callback]" \ No newline at end of file diff --git a/scripts/autoencoder/vae/training_vae.sh b/scripts/autoencoder/vae/training_vae.sh new file mode 100644 index 0000000..5287136 --- /dev/null +++ b/scripts/autoencoder/vae/training_vae.sh @@ -0,0 +1,14 @@ +JOB_NAME=training_vae_model +AUTOENCODER_CACHE_PATH=/path/to/exp/caches/autoencoder_cache +USE_CACHE_WITHOUT_DATASET=True +SEED=0 + + +python $SLEDGE_DEVKIT_ROOT/sledge/script/run_autoencoder.py \ +py_func=training \ +seed=$SEED \ +job_name=$JOB_NAME \ ++autoencoder=training_vae_model \ +cache.autoencoder_cache_path=$AUTOENCODER_CACHE_PATH \ +cache.use_cache_without_dataset=$USE_CACHE_WITHOUT_DATASET \ +callbacks="[learning_rate_monitor_callback, model_checkpoint_callback, time_logging_callback, vae_visualization_callback]" \ No newline at end of file diff --git a/scripts/diffusion/scenario_caching_diffusion.sh b/scripts/diffusion/scenario_caching_diffusion.sh new file mode 100644 index 0000000..114cf21 --- /dev/null +++ b/scripts/diffusion/scenario_caching_diffusion.sh @@ -0,0 +1,16 @@ +JOB_NAME=scenario_caching +AUTOENCODER_CHECKPOINT=/path/to/rvae_checkpoint.ckpt +DIFFUSION_CHECKPOINT=/path/to/diffusion/checkpoint +DIFFUSION_MODEL=dit_b_model # [dit_s_model, dit_b_model, dit_l_model, dit_xl_model] +SEED=0 + + +python $SLEDGE_DEVKIT_ROOT/sledge/script/run_diffusion.py \ +py_func=scenario_caching \ +seed=$SEED \ +job_name=$JOB_NAME \ +py_func=scenario_cache \ ++diffusion=training_dit_model \ +diffusion_model=$DIFFUSION_MODEL \ +autoencoder_checkpoint=$AUTOENCODER_CHECKPOINT \ +diffusion_checkpoint=$DIFFUSION_CHECKPOINT \ No newline at end of file diff --git a/scripts/diffusion/training_diffusion.sh b/scripts/diffusion/training_diffusion.sh new file mode 100644 index 0000000..a158cdb --- /dev/null +++ b/scripts/diffusion/training_diffusion.sh @@ -0,0 +1,18 @@ +JOB_NAME=training_dit_diffusion +AUTOENCODER_CACHE_PATH=/path/to/exp/caches/autoencoder_cache +AUTOENCODER_CHECKPOINT=/path/to/rvae_checkpoint.ckpt +DIFFUSION_CHECKPOINT=null # set for weight intialization / continue training +DIFFUSION_MODEL=dit_b_model # [dit_s_model, dit_b_model, dit_l_model, dit_xl_model] +CLEANUP_DIFFUSION_CACHE=False +SEED=0 + +accelerate launch $SLEDGE_DEVKIT_ROOT/sledge/script/run_diffusion.py \ +py_func=training \ +seed=$SEED \ +job_name=$JOB_NAME \ ++diffusion=training_dit_model \ +diffusion_model=$DIFFUSION_MODEL \ +cache.autoencoder_cache_path=$AUTOENCODER_CACHE_PATH \ +cache.cleanup_diffusion_cache=$CLEANUP_DIFFUSION_CACHE \ +autoencoder_checkpoint=$AUTOENCODER_CHECKPOINT \ +diffusion_checkpoint=$DIFFUSION_CHECKPOINT \ No newline at end of file diff --git a/scripts/download/download_cache.sh b/scripts/download/download_cache.sh new file mode 100644 index 0000000..322e8ba --- /dev/null +++ b/scripts/download/download_cache.sh @@ -0,0 +1,2 @@ +# autoencoder cache +wget https://s3.eu-central-1.amazonaws.com/avg-projects-2/sledge/caches/autoencoder_cache.zip \ No newline at end of file diff --git a/scripts/download/download_nuplan.sh b/scripts/download/download_nuplan.sh new file mode 100644 index 0000000..19eaa32 --- /dev/null +++ b/scripts/download/download_nuplan.sh @@ -0,0 +1,22 @@ +# NOTE: Please check the LICENSE file when downloading the nuPlan dataset +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/LICENSE + +# maps +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-maps-v1.1.zip + +# train +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-v1.1_train_boston.zip +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-v1.1_train_pittsburgh.zip +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-v1.1_train_singapore.zip +for split in {1..6}; do + wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-v1.1_train_vegas_${split}.zip +done + +# val +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-v1.1_test.zip + +# test +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-v1.1_val.zip + +# mini +wget https://motional-nuplan.s3-ap-northeast-1.amazonaws.com/public/nuplan-v1.1/nuplan-v1.1_mini.zip \ No newline at end of file diff --git a/scripts/simulation/simple_simulation.sh b/scripts/simulation/simple_simulation.sh new file mode 100644 index 0000000..a5b9e5c --- /dev/null +++ b/scripts/simulation/simple_simulation.sh @@ -0,0 +1,7 @@ +CHALLENGE=sledge_reactive_agents + +python $SLEDGE_DEVKIT_ROOT/sledge/script/run_simulation.py \ ++simulation=$CHALLENGE \ +planner=pdm_closed_planner \ +observation=sledge_agents_observation \ +scenario_builder=nuplan \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3b723bd --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +import os +import setuptools + +# Change directory to allow installation from anywhere +script_folder = os.path.dirname(os.path.realpath(__file__)) +os.chdir(script_folder) + +with open("requirements.txt") as f: + requirements = f.read().splitlines() + +# Installs +setuptools.setup( + name="sledge", + version="0.1", + author="University of Tuebingen", + author_email="daniel.dauner@uni-tuebingen.de", + description="Simulation Environments for Vehicle Motion Planning with Generative Models.", + url="https://github.com/autonomousvision/sledge", + python_requires=">=3.9", + packages=["sledge"], + package_dir={"": "."}, + classifiers=[ + "Programming Language :: Python :: 3.9", + "Operating System :: OS Independent", + "License :: Free for non-commercial use", + ], + license="apache-2.0", + install_requires=requirements, +) diff --git a/sledge/__init__.py b/sledge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/__init__.py b/sledge/autoencoder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/callbacks/__init__.py b/sledge/autoencoder/callbacks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/callbacks/rvae_visualization_callback.py b/sledge/autoencoder/callbacks/rvae_visualization_callback.py new file mode 100644 index 0000000..9d3e076 --- /dev/null +++ b/sledge/autoencoder/callbacks/rvae_visualization_callback.py @@ -0,0 +1,224 @@ +from typing import Any, List, Optional + +import numpy as np +import numpy.typing as npt + +import torch +import torch.utils.data +import pytorch_lightning as pl + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType, move_features_type_to_device +from nuplan.planning.training.preprocessing.feature_collate import FeatureCollate + +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVector +from sledge.common.visualization.sledge_visualization_utils import get_sledge_raster, get_sledge_vector_as_raster + + +class RVAEVisualizationCallback(pl.Callback): + """ + Pytorch Lightning callback that visualizes the autoencoder inputs/outputs and logs them in Tensorboard. + """ + + def __init__( + self, + images_per_tile: int, + num_train_tiles: int, + num_val_tiles: int, + config: RVAEConfig, + ): + """ + Initializes the RVAE visualization callback. + :param images_per_tile: number of visualized samples (columns) + :param num_train_tiles: number of visualized tiles from training set + :param num_val_tiles: number of visualized tiles from validation set + :param config: RVAEConfig object storting plotting parameters + """ + super().__init__() + + self.custom_batch_size = images_per_tile + self.num_train_images = num_train_tiles * images_per_tile + self.num_val_images = num_val_tiles * images_per_tile + self.config = config + + # lazy loaded + self.train_dataloader: Optional[torch.utils.data.DataLoader] = None + self.val_dataloader: Optional[torch.utils.data.DataLoader] = None + + def _initialize_dataloaders(self, datamodule: pl.LightningDataModule) -> None: + """ + Initialize the dataloaders. This makes sure that the same examples are sampled + every time for comparison during visualization. + :param datamodule: lightning datamodule + """ + train_set = datamodule.train_dataloader().dataset + val_set = datamodule.val_dataloader().dataset + + self.train_dataloader = self._create_dataloader(train_set, self.num_train_images) + self.val_dataloader = self._create_dataloader(val_set, self.num_val_images) + + def _create_dataloader(self, dataset: torch.utils.data.Dataset, num_samples: int) -> torch.utils.data.DataLoader: + """ + Creates torch dataloader given dataset. + :param dataset: torch dataset + :param num_samples: size of random subset for visualization + :return: torch dataloader of sampler subset + """ + dataset_size = len(dataset) + num_keep = min(dataset_size, num_samples) + sampled_indices = np.random.choice(dataset_size, num_keep, replace=False) + subset = torch.utils.data.Subset(dataset=dataset, indices=sampled_indices) + return torch.utils.data.DataLoader( + dataset=subset, + batch_size=self.custom_batch_size, + collate_fn=FeatureCollate(), + ) + + def _log_from_dataloader( + self, + pl_module: pl.LightningModule, + dataloader: torch.utils.data.DataLoader, + loggers: List[Any], + training_step: int, + prefix: str, + ) -> None: + """ + Visualizes and logs all examples from the input dataloader. + :param pl_module: lightning module used for inference + :param dataloader: torch dataloader + :param loggers: list of loggers from the trainer + :param training_step: global step in training + :param prefix: prefix to add to the log tag + """ + for batch_idx, batch in enumerate(dataloader): + features: FeaturesType = batch[0] + targets: TargetsType = batch[1] + predictions = self._infer_model(pl_module, move_features_type_to_device(features, pl_module.device)) + + self._log_batch(loggers, features, targets, predictions, batch_idx, training_step, prefix) + + def _log_batch( + self, + loggers: List[Any], + features: FeaturesType, + targets: TargetsType, + predictions: TargetsType, + batch_idx: int, + training_step: int, + prefix: str, + ) -> None: + """ + Visualizes and logs a batch of data (features, targets, predictions) from the model. + :param loggers: list of loggers from the trainer + :param features: tensor of model features + :param targets: tensor of model targets + :param predictions: tensor of model predictions + :param batch_idx: index of total batches to visualize + :param training_step: global training step + :param prefix: prefix to add to the log tag + """ + + image_batch = self._get_images_from_features(features, targets, predictions) + tag = f"{prefix}_visualization_{batch_idx}" + + for logger in loggers: + if isinstance(logger.experiment, torch.utils.tensorboard.SummaryWriter): + logger.experiment.add_images( + tag=tag, + img_tensor=torch.from_numpy(image_batch), + global_step=training_step, + dataformats="NHWC", + ) + + def _get_images_from_features( + self, features: FeaturesType, targets: TargetsType, predictions: TargetsType + ) -> npt.NDArray[np.uint8]: + """ + Create a list of RGB raster images from a batch of model data of features. + :param features: tensor of model features + :param targets: tensor of model targets + :param predictions: tensor of model predictions + :return: list of raster images + """ + output_raster = [] + for map_id, sledge_raster, gt_sledge_vector, pred_sledge_vector in zip( + targets["map_id"].unpack(), + features["sledge_raster"].unpack(), + targets["sledge_vector"].unpack(), + predictions["sledge_vector"].unpack(), + ): + column = [] + pred_sledge_vector: SledgeVector + pred_sledge_vector_raster = get_sledge_vector_as_raster( + sledge_vector=pred_sledge_vector.torch_to_numpy(apply_sigmoid=True), + config=self.config, + map_id=map_id, + ) + column.append(pred_sledge_vector_raster) + + gt_sledge_vector: SledgeVector + gt_sledge_vector_raster = get_sledge_vector_as_raster( + sledge_vector=gt_sledge_vector.torch_to_numpy(apply_sigmoid=False), + config=self.config, + map_id=map_id, + ) + column.append(gt_sledge_vector_raster) + + map_raster_gt = get_sledge_raster(sledge_raster, self.config.pixel_frame) + column.append(map_raster_gt) + + output_raster.append(np.concatenate(column, axis=0)) + + return np.asarray(output_raster) + + def _infer_model(self, pl_module: pl.LightningModule, features: FeaturesType) -> TargetsType: + """ + Make an inference of the input batch features given a model. + :param pl_module: lightning model + :param features: model inputs + :return: model predictions + """ + with torch.no_grad(): + pl_module.eval() + predictions = move_features_type_to_device(pl_module(features), torch.device("cpu")) + pl_module.train() + + return predictions + + def on_train_epoch_end( + self, + trainer: pl.Trainer, + pl_module: pl.LightningModule, + unused: Optional = None, # type: ignore + ) -> None: + """ + Visualizes and logs training examples at the end of the epoch. + :param trainer: lightning trainer + :param pl_module: lightning module + """ + assert hasattr(trainer, "datamodule"), "Trainer missing datamodule attribute" + assert hasattr(trainer, "global_step"), "Trainer missing global_step attribute" + + if self.train_dataloader is None: + self._initialize_dataloaders(trainer.datamodule) + + self._log_from_dataloader(pl_module, self.train_dataloader, trainer.loggers, trainer.global_step, "train") + + def on_validation_epoch_end( + self, + trainer: pl.Trainer, + pl_module: pl.LightningModule, + unused: Optional = None, # type: ignore + ) -> None: + """ + Visualizes and logs validation examples at the end of the epoch. + :param trainer: lightning trainer + :param pl_module: lightning module + """ + assert hasattr(trainer, "datamodule"), "Trainer missing datamodule attribute" + assert hasattr(trainer, "global_step"), "Trainer missing global_step attribute" + + if self.val_dataloader is None: + self._initialize_dataloaders(trainer.datamodule) + + self._log_from_dataloader(pl_module, self.val_dataloader, trainer.loggers, trainer.global_step, "val") diff --git a/sledge/autoencoder/callbacks/vae_visualization_callback.py b/sledge/autoencoder/callbacks/vae_visualization_callback.py new file mode 100644 index 0000000..177e6a7 --- /dev/null +++ b/sledge/autoencoder/callbacks/vae_visualization_callback.py @@ -0,0 +1,210 @@ +from typing import Any, List, Optional + +import numpy as np +import numpy.typing as npt + +import torch +import torch.utils.data +import pytorch_lightning as pl + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType, move_features_type_to_device +from nuplan.planning.training.preprocessing.feature_collate import FeatureCollate +from sledge.common.visualization.sledge_visualization_utils import get_sledge_raster +from sledge.autoencoder.modeling.models.vae.vae_config import VAEConfig + + +class VAEVisualizationCallback(pl.Callback): + """ + Pytorch Lightning callback that visualizes the autoencoder inputs/outputs and logs them in Tensorboard. + """ + + def __init__( + self, + images_per_tile: int, + num_train_tiles: int, + num_val_tiles: int, + config: VAEConfig, + ): + """ + Initializes the RVAE visualization callback. + :param images_per_tile: number of visualized samples (columns) + :param num_train_tiles: number of visualized tiles from training set + :param num_val_tiles: number of visualized tiles from validation set + :param config: VAEConfig object storting plotting parameters + """ + super().__init__() + + self.custom_batch_size = images_per_tile + self.num_train_images = num_train_tiles * images_per_tile + self.num_val_images = num_val_tiles * images_per_tile + self.config = config + + # lazy loaded + self.train_dataloader: Optional[torch.utils.data.DataLoader] = None + self.val_dataloader: Optional[torch.utils.data.DataLoader] = None + + def _initialize_dataloaders(self, datamodule: pl.LightningDataModule) -> None: + """ + Initialize the dataloaders. This makes sure that the same examples are sampled + every time for comparison during visualization. + :param datamodule: lightning datamodule + """ + train_set = datamodule.train_dataloader().dataset + val_set = datamodule.val_dataloader().dataset + + self.train_dataloader = self._create_dataloader(train_set, self.num_train_images) + self.val_dataloader = self._create_dataloader(val_set, self.num_val_images) + + def _create_dataloader(self, dataset: torch.utils.data.Dataset, num_samples: int) -> torch.utils.data.DataLoader: + """ + Creates torch dataloader given dataset. + :param dataset: torch dataset + :param num_samples: size of random subset for visualization + :return: torch dataloader of sampler subset + """ + dataset_size = len(dataset) + num_keep = min(dataset_size, num_samples) + sampled_indices = np.random.choice(dataset_size, num_keep, replace=False) + subset = torch.utils.data.Subset(dataset=dataset, indices=sampled_indices) + return torch.utils.data.DataLoader( + dataset=subset, + batch_size=self.custom_batch_size, + collate_fn=FeatureCollate(), + ) + + def _log_from_dataloader( + self, + pl_module: pl.LightningModule, + dataloader: torch.utils.data.DataLoader, + loggers: List[Any], + training_step: int, + prefix: str, + ) -> None: + """ + Visualizes and logs all examples from the input dataloader. + :param pl_module: lightning module used for inference + :param dataloader: torch dataloader + :param loggers: list of loggers from the trainer + :param training_step: global step in training + :param prefix: prefix to add to the log tag + """ + for batch_idx, batch in enumerate(dataloader): + features: FeaturesType = batch[0] + targets: TargetsType = batch[1] + predictions = self._infer_model(pl_module, move_features_type_to_device(features, pl_module.device)) + self._log_batch(loggers, features, targets, predictions, batch_idx, training_step, prefix) + + def _log_batch( + self, + loggers: List[Any], + features: FeaturesType, + targets: TargetsType, + predictions: TargetsType, + batch_idx: int, + training_step: int, + prefix: str, + ) -> None: + """ + Visualizes and logs a batch of data (features, targets, predictions) from the model. + :param loggers: list of loggers from the trainer + :param features: tensor of model features + :param targets: tensor of model targets + :param predictions: tensor of model predictions + :param batch_idx: index of total batches to visualize + :param training_step: global training step + :param prefix: prefix to add to the log tag + """ + + image_batch = self._get_images_from_features(features, targets, predictions) + tag = f"{prefix}_visualization_{batch_idx}" + + for logger in loggers: + if isinstance(logger.experiment, torch.utils.tensorboard.SummaryWriter): + logger.experiment.add_images( + tag=tag, + img_tensor=torch.from_numpy(image_batch), + global_step=training_step, + dataformats="NHWC", + ) + + def _get_images_from_features( + self, features: FeaturesType, targets: TargetsType, predictions: TargetsType + ) -> npt.NDArray[np.uint8]: + """ + Create a list of RGB raster images from a batch of model data of features. + :param features: tensor of model features + :param targets: tensor of model targets + :param predictions: tensor of model predictions + :return: list of raster images + """ + output_raster = [] + + for gt_raster, pred_raster in zip( + features["sledge_raster"].unpack(), + predictions["sledge_raster"].unpack(), + ): + column = [] + + # FIXME: + map_raster_gt = get_sledge_raster(gt_raster, self.config.pixel_frame) + column.append(map_raster_gt) + + pred_raster.data = pred_raster.data.sigmoid() + map_raster_pred = get_sledge_raster(pred_raster, self.config.pixel_frame, self.config.threshold / 2) + column.append(map_raster_pred) + + output_raster.append(np.concatenate(column, axis=0)) + + return np.asarray(output_raster) + + def _infer_model(self, pl_module: pl.LightningModule, features: FeaturesType) -> TargetsType: + """ + Make an inference of the input batch features given a model. + :param pl_module: lightning model + :param features: model inputs + :return: model predictions + """ + with torch.no_grad(): + pl_module.eval() + predictions = move_features_type_to_device(pl_module(features), torch.device("cpu")) + pl_module.train() + + return predictions + + def on_train_epoch_end( + self, + trainer: pl.Trainer, + pl_module: pl.LightningModule, + unused: Optional = None, # type: ignore + ) -> None: + """ + Visualizes and logs training examples at the end of the epoch. + :param trainer: lightning trainer + :param pl_module: lightning module + """ + assert hasattr(trainer, "datamodule"), "Trainer missing datamodule attribute" + assert hasattr(trainer, "global_step"), "Trainer missing global_step attribute" + + if self.train_dataloader is None: + self._initialize_dataloaders(trainer.datamodule) + + self._log_from_dataloader(pl_module, self.train_dataloader, trainer.loggers, trainer.global_step, "train") + + def on_validation_epoch_end( + self, + trainer: pl.Trainer, + pl_module: pl.LightningModule, + unused: Optional = None, # type: ignore + ) -> None: + """ + Visualizes and logs validation examples at the end of the epoch. + :param trainer: lightning trainer + :param pl_module: lightning module + """ + assert hasattr(trainer, "datamodule"), "Trainer missing datamodule attribute" + assert hasattr(trainer, "global_step"), "Trainer missing global_step attribute" + + if self.val_dataloader is None: + self._initialize_dataloaders(trainer.datamodule) + + self._log_from_dataloader(pl_module, self.val_dataloader, trainer.loggers, trainer.global_step, "val") diff --git a/sledge/autoencoder/data_augmentation/__init__.py b/sledge/autoencoder/data_augmentation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/data_augmentation/augmentation_utils.py b/sledge/autoencoder/data_augmentation/augmentation_utils.py new file mode 100644 index 0000000..ae9d053 --- /dev/null +++ b/sledge/autoencoder/data_augmentation/augmentation_utils.py @@ -0,0 +1,123 @@ +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.state_representation import StateSE2 + +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeVector, + SledgeVectorElement, + LineIndex, + AgentIndex, + StaticObjectIndex, +) + + +def random_se2(scales: StateSE2) -> StateSE2: + """ + Samples random SE(2) state space, given the scales from the input + :param scales: scales for (x,y,θ) + :return: random SE(2) state + """ + + noise_x = np.random.normal(loc=0, scale=scales.x) + noise_y = np.random.normal(loc=0, scale=scales.y) + noise_heading = np.random.normal(loc=0, scale=scales.heading) + + return StateSE2(noise_x, noise_y, noise_heading) + + +def transform_sledge_vector(sledge_vector: SledgeVector, origin: StateSE2) -> SledgeVector: + """ + Transforms entire sledge vector dataclass into local coordinate from of origin + TODO: move somewhere for general usage + :param sledge_vector: Vector dataclass for sledge + :param origin: SE(2) state as origin + :return: transformed sledge vector dataclass + """ + sledge_vector.lines.states[..., LineIndex.POINT] = transform_points_to_origin( + sledge_vector.lines.states[..., LineIndex.POINT], origin + ) + sledge_vector.vehicles.states[..., AgentIndex.STATE_SE2] = transform_se2_to_origin( + sledge_vector.vehicles.states[..., AgentIndex.STATE_SE2], origin + ) + sledge_vector.pedestrians.states[..., AgentIndex.STATE_SE2] = transform_se2_to_origin( + sledge_vector.pedestrians.states[..., AgentIndex.STATE_SE2], origin + ) + sledge_vector.static_objects.states[..., StaticObjectIndex.STATE_SE2] = transform_se2_to_origin( + sledge_vector.static_objects.states[..., StaticObjectIndex.STATE_SE2], origin + ) + sledge_vector.green_lights.states[..., LineIndex.POINT] = transform_points_to_origin( + sledge_vector.green_lights.states[..., LineIndex.POINT], origin + ) + sledge_vector.red_lights.states[..., LineIndex.POINT] = transform_points_to_origin( + sledge_vector.red_lights.states[..., LineIndex.POINT], origin + ) + return sledge_vector + + +def element_dropout(element: SledgeVectorElement, p: float) -> SledgeVectorElement: + """ + Random dropout of entities in vector element dataclass + :param element: sledge vector element dataclass (e.g. lines, vehicles) + :param p: probability of removing entity from vector element collection + :return: augmented vector element dataclass + """ + + num_entities = len(element.states) + if num_entities == 0: + return element + + dropout_mask = np.random.choice([True, False], size=num_entities, p=[1 - p, p]) + states = element.states[dropout_mask] + mask = element.mask[dropout_mask] + + return SledgeVectorElement(states, mask) + + +def transform_points_to_origin(points: npt.NDArray[np.float64], origin: StateSE2) -> npt.NDArray[np.float64]: + """ + Transforms (x,y)-array to origin frame + TODO: move somewhere for general usage + :param points: array with (x,y) in last axis + :param origin: SE(2) state as origin + :return: transformed points + """ + + if len(points) == 0: + return points + + assert points.shape[-1] == 2 + theta = -origin.heading + origin_array = np.array([[origin.x, origin.y, origin.heading]], dtype=np.float64) + + R = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) + points_rel = points - origin_array[..., :2] + points_rel[..., :2] = points_rel[..., :2] @ R.T + + return points_rel + + +def transform_se2_to_origin(state_se2_array: npt.NDArray[np.float64], origin: StateSE2) -> npt.NDArray[np.float64]: + """ + Transforms (x,y,θ)-array to origin frame + TODO: move somewhere for general usage + :param points: array with (x,y,θ) in last axis + :param origin: SE(2) state as origin + :return: transformed (x,y,θ)-array + """ + if len(state_se2_array) == 0: + return state_se2_array + + assert state_se2_array.shape[-1] == 3 + + theta = -origin.heading + origin_array = np.array([[origin.x, origin.y, origin.heading]], dtype=np.float64) + + R = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) + + points_rel = state_se2_array - origin_array + points_rel[..., :2] = points_rel[..., :2] @ R.T + points_rel[:, 2] = normalize_angle(points_rel[:, 2]) + + return points_rel diff --git a/sledge/autoencoder/data_augmentation/rvae_augmentation.py b/sledge/autoencoder/data_augmentation/rvae_augmentation.py new file mode 100644 index 0000000..17a7158 --- /dev/null +++ b/sledge/autoencoder/data_augmentation/rvae_augmentation.py @@ -0,0 +1,93 @@ +from typing import List, Optional, Tuple + +import numpy as np + +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.training.data_augmentation.abstract_data_augmentation import AbstractAugmentor +from nuplan.planning.training.data_augmentation.data_augmentation_util import ParameterToScale, ScalingDirection +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType +from nuplan.common.actor_state.state_representation import StateSE2 + +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVectorRaw +from sledge.autoencoder.data_augmentation.augmentation_utils import random_se2, transform_sledge_vector, element_dropout +from sledge.autoencoder.preprocessing.feature_builders.sledge.sledge_feature_processing import ( + sledge_raw_feature_processing, +) + + +class RVAEAugmenter(AbstractAugmentor): + def __init__( + self, + config: RVAEConfig, + se2_noise: Optional[Tuple[float, float, float]] = None, + p_vehicle_dropout: Optional[float] = None, + p_pedestrian_dropout: Optional[float] = None, + p_static_dropout: Optional[float] = None, + ) -> None: + """ + Initialize the augmenter for RVAE. + NOTE: + - Object pre-processes and rasterizes the features + - Enables augmentation and config changes with the same autoencoder cache. + :param config: config dataclass of RVAE + :param se2_noise: tuple of (x,y,θ) noise scale, defaults to None + :param p_vehicle_dropout: probability of removing vehicle, defaults to None + :param p_pedestrian_dropout: probability of removing pedestrian, defaults to None + :param p_static_dropout: probability of removing static objects, defaults to None + """ + self._config = config + + self._se2_augmentation = StateSE2(se2_noise[0], se2_noise[1], np.deg2rad(se2_noise[2])) if se2_noise else None + self._p_vehicle_dropout = p_vehicle_dropout + self._p_pedestrian_dropout = p_pedestrian_dropout + self._p_static_dropout = p_static_dropout + + def augment( + self, + features: FeaturesType, + targets: TargetsType, + scenario: Optional[AbstractScenario] = None, + ) -> Tuple[FeaturesType, TargetsType]: + """Inherited, see superclass.""" + + sledge_vector_raw: SledgeVectorRaw = features["sledge_raw"] + + # TODO: Refactor / Add new augmentations + if self._se2_augmentation: + origin = random_se2(self._se2_augmentation) + sledge_vector_raw = transform_sledge_vector(sledge_vector_raw, origin) + + if self._p_vehicle_dropout: + sledge_vector_raw.vehicles = element_dropout(sledge_vector_raw.vehicles, self._p_vehicle_dropout) + if self._p_pedestrian_dropout: + sledge_vector_raw.pedestrians = element_dropout(sledge_vector_raw.pedestrians, self._p_pedestrian_dropout) + if self._p_static_dropout: + sledge_vector_raw.static_objects = element_dropout(sledge_vector_raw.static_objects, self._p_static_dropout) + + frame_vector, frame_raster = sledge_raw_feature_processing(sledge_vector_raw, self._config) + + del features["sledge_raw"] + features["sledge_raster"] = frame_raster + targets["sledge_vector"] = frame_vector + + return features, targets + + @property + def required_features(self) -> List[str]: + """Inherited, see superclass.""" + return [] + + @property + def required_targets(self) -> List[str]: + """Inherited, see superclass.""" + return [] + + @property + def augmentation_probability(self) -> ParameterToScale: + """Inherited, see superclass.""" + return ParameterToScale( + param=1.0, + param_name=f"{self._augment_prob=}".partition("=")[0].split(".")[1], + scaling_direction=ScalingDirection.MAX, + ) diff --git a/sledge/autoencoder/data_augmentation/vae_augmentation.py b/sledge/autoencoder/data_augmentation/vae_augmentation.py new file mode 100644 index 0000000..8d5dfb5 --- /dev/null +++ b/sledge/autoencoder/data_augmentation/vae_augmentation.py @@ -0,0 +1,94 @@ +from typing import List, Optional, Tuple + +import numpy as np + +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.training.data_augmentation.abstract_data_augmentation import AbstractAugmentor +from nuplan.planning.training.data_augmentation.data_augmentation_util import ParameterToScale, ScalingDirection +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType +from nuplan.common.actor_state.state_representation import StateSE2 + +from sledge.autoencoder.modeling.models.vae.vae_config import VAEConfig +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVectorRaw +from sledge.autoencoder.data_augmentation.augmentation_utils import random_se2, transform_sledge_vector, element_dropout +from sledge.autoencoder.preprocessing.feature_builders.sledge.sledge_feature_processing import ( + sledge_raw_feature_processing, +) + + +class VAEAugmenter(AbstractAugmentor): + def __init__( + self, + config: VAEConfig, + se2_noise: Optional[Tuple[float, float, float]] = None, + p_vehicle_dropout: Optional[float] = None, + p_pedestrian_dropout: Optional[float] = None, + p_static_dropout: Optional[float] = None, + ) -> None: + """ + Initialize the augmenter for VAE. + NOTE: + - Object pre-processes and rasterizes the features + - Enables augmentation and config changes with the same autoencoder cache. + :param config: config dataclass of VAE + :param se2_noise: tuple of (x,y,θ) noise scale, defaults to None + :param p_vehicle_dropout: probability of removing vehicle, defaults to None + :param p_pedestrian_dropout: probability of removing pedestrian, defaults to None + :param p_static_dropout: probability of removing static objects, defaults to None + """ + self._config = config + + self._se2_augmentation = StateSE2(se2_noise[0], se2_noise[1], np.deg2rad(se2_noise[2])) if se2_noise else None + + self._p_vehicle_dropout = p_vehicle_dropout + self._p_pedestrian_dropout = p_pedestrian_dropout + self._p_static_dropout = p_static_dropout + + def augment( + self, + features: FeaturesType, + targets: TargetsType, + scenario: Optional[AbstractScenario] = None, + ) -> Tuple[FeaturesType, TargetsType]: + """Inherited, see superclass.""" + + sledge_vector_raw: SledgeVectorRaw = features["sledge_raw"] + + # TODO: Refactor / Add new augmentations + if self._se2_augmentation: + origin = random_se2(self._se2_augmentation) + sledge_vector_raw = transform_sledge_vector(sledge_vector_raw, origin) + + if self._p_vehicle_dropout: + sledge_vector_raw.vehicles = element_dropout(sledge_vector_raw.vehicles, self._p_vehicle_dropout) + if self._p_pedestrian_dropout: + sledge_vector_raw.pedestrians = element_dropout(sledge_vector_raw.pedestrians, self._p_pedestrian_dropout) + if self._p_static_dropout: + sledge_vector_raw.static_objects = element_dropout(sledge_vector_raw.static_objects, self._p_static_dropout) + + _, frame_raster = sledge_raw_feature_processing(sledge_vector_raw, self._config) + + del features["sledge_raw"] + features["sledge_raster"] = frame_raster + targets["sledge_raster"] = frame_raster + + return features, targets + + @property + def required_features(self) -> List[str]: + """Inherited, see superclass.""" + return [] + + @property + def required_targets(self) -> List[str]: + """Inherited, see superclass.""" + return [] + + @property + def augmentation_probability(self) -> ParameterToScale: + """Inherited, see superclass.""" + return ParameterToScale( + param=1.0, + param_name=f"{self._augment_prob=}".partition("=")[0].split(".")[1], + scaling_direction=ScalingDirection.MAX, + ) diff --git a/sledge/autoencoder/data_loader/__init__.py b/sledge/autoencoder/data_loader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/data_loader/autoencoder_datamodule.py b/sledge/autoencoder/data_loader/autoencoder_datamodule.py new file mode 100644 index 0000000..96232db --- /dev/null +++ b/sledge/autoencoder/data_loader/autoencoder_datamodule.py @@ -0,0 +1,130 @@ +import logging +from typing import Any, Dict, List, Optional, Tuple +from omegaconf import DictConfig + +import torch + +from nuplan.planning.training.modeling.types import FeaturesType, move_features_type_to_device +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.training.preprocessing.feature_preprocessor import FeaturePreprocessor +from nuplan.planning.utils.multithreading.worker_pool import WorkerPool +from nuplan.planning.training.data_loader.datamodule import create_dataset, DataModule +from nuplan.planning.training.data_loader.splitter import AbstractSplitter +from nuplan.planning.training.data_augmentation.abstract_data_augmentation import AbstractAugmentor + +logger = logging.getLogger(__name__) + + +class AutoencoderDataModule(DataModule): + """ + Autoencoder Datamodule in SLEDGE wrapping all and datasets for distributed pre-processing. + NOTE: This wrapper ensures that + - augmentation (with augmentation/pre-processing) is applied on all subsets. + - Wrapper is compatible with updated lightning version + """ + + def __init__( + self, + feature_preprocessor: FeaturePreprocessor, + splitter: AbstractSplitter, + all_scenarios: List[AbstractScenario], + train_fraction: float, + val_fraction: float, + test_fraction: float, + dataloader_params: Dict[str, Any], + scenario_type_sampling_weights: DictConfig, + worker: WorkerPool, + augmentors: Optional[List[AbstractAugmentor]] = None, + ) -> None: + """ + Initialize the class. + :param feature_preprocessor: Feature preprocessor object. + :param splitter: Splitter object used to retrieve lists of samples to construct train/val/test sets. + :param train_fraction: Fraction of training examples to load. + :param val_fraction: Fraction of validation examples to load. + :param test_fraction: Fraction of test examples to load. + :param dataloader_params: Parameter dictionary passed to the dataloaders. + :param augmentors: Augmentor object for providing data augmentation to data samples. + """ + super().__init__( + feature_preprocessor, + splitter=splitter, + all_scenarios=all_scenarios, + train_fraction=train_fraction, + val_fraction=val_fraction, + test_fraction=test_fraction, + dataloader_params=dataloader_params, + scenario_type_sampling_weights=scenario_type_sampling_weights, + worker=worker, + augmentors=augmentors, + ) + + def setup(self, stage: Optional[str] = None) -> None: + """ + Set up the dataset for each target set depending on the training stage. + This is called by every process in distributed training. + :param stage: Stage of training, can be "fit" or "test". + """ + if stage is None: + return + + # TODO: Refactor stage str in SLEDGE + if stage == "fit": + # Training Dataset + train_samples = self._splitter.get_train_samples(self._all_samples, self._worker) + assert len(train_samples) > 0, "Splitter returned no training samples" + + self._train_set = create_dataset( + train_samples, + self._feature_preprocessor, + self._train_fraction, + "train", + self._augmentors, + ) + + # Validation Dataset + val_samples = self._splitter.get_val_samples(self._all_samples, self._worker) + assert len(val_samples) > 0, "Splitter returned no validation samples" + + self._val_set = create_dataset( + val_samples, + self._feature_preprocessor, + self._val_fraction, + "validation", + self._augmentors, + ) + elif stage == "test": + # Testing Dataset + test_samples = self._splitter.get_test_samples(self._all_samples, self._worker) + assert len(test_samples) > 0, "Splitter returned no test samples" + + self._test_set = create_dataset( + test_samples, + self._feature_preprocessor, + self._test_fraction, + "test", + self._augmentors, + ) + else: + raise ValueError(f'Stage must be one of ["fit", "test"], got ${stage}.') + + def transfer_batch_to_device( + self, + batch: Tuple[FeaturesType, ...], + device: torch.device, + dataloader_idx: int, + ) -> Tuple[FeaturesType, ...]: + """ + Transfer a batch to device. + :param batch: Batch on origin device. + :param device: Desired device. + :param dataloader_idx: The index of the dataloader to which the batch belongs. (ignored) + :return: Batch in new device. + """ + return tuple( + ( + move_features_type_to_device(batch[0], device), + move_features_type_to_device(batch[1], device), + batch[2], + ) + ) diff --git a/sledge/autoencoder/experiments/__init__.py b/sledge/autoencoder/experiments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/experiments/feature_caching.py b/sledge/autoencoder/experiments/feature_caching.py new file mode 100644 index 0000000..2016192 --- /dev/null +++ b/sledge/autoencoder/experiments/feature_caching.py @@ -0,0 +1,182 @@ +import gc +import itertools +import logging +import os +import uuid +from pathlib import Path +from typing import Dict, List, Optional, Union + +from omegaconf import DictConfig + +from nuplan.common.utils.distributed_scenario_filter import DistributedMode, DistributedScenarioFilter +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.scenario_builder.abstract_scenario_builder import AbstractScenarioBuilder, RepartitionStrategy +from nuplan.planning.script.builders.scenario_building_builder import build_scenario_builder +from nuplan.planning.script.builders.scenario_filter_builder import build_scenario_filter +from nuplan.planning.utils.multithreading.worker_pool import WorkerPool +from nuplan.planning.utils.multithreading.worker_utils import chunk_list, worker_map +from nuplan.planning.training.preprocessing.feature_preprocessor import FeaturePreprocessor +from nuplan.planning.training.experiments.cache_metadata_entry import ( + CacheMetadataEntry, + CacheResult, + save_cache_metadata, +) + +from sledge.script.builders.model_builder import build_autoencoder_torch_module_wrapper + +logger = logging.getLogger(__name__) + + +def cache_scenarios(args: List[Dict[str, Union[List[str], DictConfig]]]) -> List[CacheResult]: + """ + Performs the caching of scenario DB files in parallel. + :param args: A list of dicts containing the following items: + "scenario": the scenario as built by scenario_builder + "cfg": the DictConfig to use to process the file. + :return: A dict with the statistics of the job. Contains the following keys: + "successes": The number of successfully processed scenarios. + "failures": The number of scenarios that couldn't be processed. + """ + + # Define a wrapper method to help with memory garbage collection. + # This way, everything will go out of scope, allowing the python GC to clean up after the function. + # + # This is necessary to save memory when running on large datasets. + def cache_scenarios_internal(args: List[Dict[str, Union[List[AbstractScenario], DictConfig]]]) -> List[CacheResult]: + node_id = int(os.environ.get("NODE_RANK", 0)) + thread_id = str(uuid.uuid4()) + + scenarios: List[AbstractScenario] = [a["scenario"] for a in args] + cfg: DictConfig = args[0]["cfg"] + + # NOTE: Changed from nuPlan for compatibility with autoencoder wrapper + autoencoder = build_autoencoder_torch_module_wrapper(cfg) + feature_builders = autoencoder.get_list_of_required_feature() + target_builders = autoencoder.get_list_of_computed_target() + + # Now that we have the feature and target builders, we do not need the model any more. + # Delete it so it gets gc'd and we can save a few system resources. + del autoencoder + + # Create feature preprocessor + assert ( + cfg.cache.autoencoder_cache_path is not None + ), f"Cache path cannot be None when caching, got {cfg.cache.autoencoder_cache_path}" + preprocessor = FeaturePreprocessor( + cache_path=cfg.cache.autoencoder_cache_path, + force_feature_computation=cfg.cache.force_feature_computation, + feature_builders=feature_builders, + target_builders=target_builders, + ) + + logger.info("Extracted %s scenarios for thread_id=%s, node_id=%s.", str(len(scenarios)), thread_id, node_id) + num_failures = 0 + num_successes = 0 + all_file_cache_metadata: List[Optional[CacheMetadataEntry]] = [] + for idx, scenario in enumerate(scenarios): + logger.info( + "Processing scenario %s / %s in thread_id=%s, node_id=%s", + idx + 1, + len(scenarios), + thread_id, + node_id, + ) + + features, targets, file_cache_metadata = preprocessor.compute_features(scenario) + + scenario_num_failures = sum( + 0 if feature.is_valid else 1 for feature in itertools.chain(features.values(), targets.values()) + ) + scenario_num_successes = len(features.values()) + len(targets.values()) - scenario_num_failures + num_failures += scenario_num_failures + num_successes += scenario_num_successes + all_file_cache_metadata += file_cache_metadata + + logger.info("Finished processing scenarios for thread_id=%s, node_id=%s", thread_id, node_id) + return [CacheResult(failures=num_failures, successes=num_successes, cache_metadata=all_file_cache_metadata)] + + result = cache_scenarios_internal(args) + + # Force a garbage collection to clean up any unused resources + gc.collect() + + return result + + +def build_scenarios_from_config( + cfg: DictConfig, scenario_builder: AbstractScenarioBuilder, worker: WorkerPool +) -> List[AbstractScenario]: + """ + Build scenarios from config file. + :param cfg: Omegaconf dictionary + :param scenario_builder: Scenario builder. + :param worker: Worker to submit tasks which can be executed in parallel + :return: A list of scenarios + """ + scenario_filter = build_scenario_filter(cfg.scenario_filter) + return scenario_builder.get_scenarios(scenario_filter, worker) # type: ignore + + +def cache_feature(cfg: DictConfig, worker: WorkerPool) -> None: + """ + Build the lightning datamodule and cache all samples. + :param cfg: omegaconf dictionary + :param worker: Worker to submit tasks which can be executed in parallel + """ + assert ( + cfg.cache.autoencoder_cache_path is not None + ), f"Cache path cannot be None when caching, got {cfg.cache.autoencoder_cache_path}" + + scenario_builder = build_scenario_builder(cfg) + if int(os.environ.get("NUM_NODES", 1)) > 1 and cfg.distribute_by_scenario: + # Partition differently based on how the scenario builder loads the data + repartition_strategy = scenario_builder.repartition_strategy + if repartition_strategy == RepartitionStrategy.REPARTITION_FILE_DISK: + scenario_filter = DistributedScenarioFilter( + cfg=cfg, + worker=worker, + node_rank=int(os.environ.get("NODE_RANK", 0)), + num_nodes=int(os.environ.get("NUM_NODES", 1)), + synchronization_path=cfg.cache.autoencoder_cache_path, + timeout_seconds=cfg.get("distributed_timeout_seconds", 3600), + distributed_mode=cfg.get("distributed_mode", DistributedMode.LOG_FILE_BASED), + ) + scenarios = scenario_filter.get_scenarios() + elif repartition_strategy == RepartitionStrategy.INLINE: + scenarios = build_scenarios_from_config(cfg, scenario_builder, worker) + num_nodes = int(os.environ.get("NUM_NODES", 1)) + node_id = int(os.environ.get("NODE_RANK", 0)) + scenarios = chunk_list(scenarios, num_nodes)[node_id] + else: + expected_repartition_strategies = [e.value for e in RepartitionStrategy] + raise ValueError( + f"Expected repartition strategy to be in {expected_repartition_strategies}, got {repartition_strategy}." + ) + else: + logger.debug( + "Building scenarios without distribution, if you're running on a multi-node system, make sure you aren't" + "accidentally caching each scenario multiple times!" + ) + scenarios = build_scenarios_from_config(cfg, scenario_builder, worker) + + data_points = [{"scenario": scenario, "cfg": cfg} for scenario in scenarios] + logger.info("Starting dataset caching of %s files...", str(len(data_points))) + + cache_results = worker_map(worker, cache_scenarios, data_points) + + num_success = sum(result.successes for result in cache_results) + num_fail = sum(result.failures for result in cache_results) + num_total = num_success + num_fail + logger.info("Completed dataset caching! Failed features and targets: %s out of %s", str(num_fail), str(num_total)) + + cached_metadata = [ + cache_metadata_entry + for cache_result in cache_results + for cache_metadata_entry in cache_result.cache_metadata + if cache_metadata_entry is not None + ] + + node_id = int(os.environ.get("NODE_RANK", 0)) + logger.info(f"Node {node_id}: Storing metadata csv file containing cache paths for valid features and targets...") + save_cache_metadata(cached_metadata, Path(cfg.cache.autoencoder_cache_path), node_id) + logger.info("Done storing metadata csv file.") diff --git a/sledge/autoencoder/experiments/latent_caching.py b/sledge/autoencoder/experiments/latent_caching.py new file mode 100644 index 0000000..630e40e --- /dev/null +++ b/sledge/autoencoder/experiments/latent_caching.py @@ -0,0 +1,70 @@ +import logging +from pathlib import Path +from omegaconf import DictConfig + +import torch +from tqdm import tqdm + +from nuplan.planning.utils.multithreading.worker_pool import WorkerPool +from nuplan.planning.training.preprocessing.utils.feature_cache import FeatureCachePickle + +from sledge.script.builders.model_builder import build_autoencoder_torch_module_wrapper +from sledge.script.builders.autoencoder_builder import build_autoencoder_lightning_datamodule +from sledge.autoencoder.preprocessing.features.latent_feature import Latent +from sledge.autoencoder.modeling.autoencoder_lightning_module_wrapper import AutoencoderLightningModuleWrapper + +logger = logging.getLogger(__name__) + + +def cache_latent(cfg: DictConfig, worker: WorkerPool) -> None: + """ + Build the lightning datamodule and cache the latent of all training samples. + :param cfg: omegaconf dictionary + :param worker: Worker to submit tasks which can be executed in parallel + """ + + assert cfg.autoencoder_checkpoint is not None, "cfg.autoencoder_checkpoint is not specified for latent caching!" + + # Create model + logger.info("Building Autoencoder Module...") + torch_module_wrapper = build_autoencoder_torch_module_wrapper(cfg) + torch_module_wrapper = AutoencoderLightningModuleWrapper.load_from_checkpoint( + cfg.autoencoder_checkpoint, model=torch_module_wrapper + ).model + logger.info("Building Autoencoder Module...DONE!") + + # Build the datamodule + logger.info("Building Datamodule Module...") + datamodule = build_autoencoder_lightning_datamodule(cfg, worker, torch_module_wrapper) + datamodule.setup("fit") + dataloader = datamodule.train_dataloader() + logger.info("Building Datamodule Module...DONE!") + + autoencoder_cache_path = Path(cfg.cache.autoencoder_cache_path) + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + storing_mechanism = FeatureCachePickle() + torch_module_wrapper = torch_module_wrapper.to(device) + + # Perform inference + with torch.no_grad(): + for batch in tqdm(dataloader, total=len(dataloader), desc="Cache Latents (batch-wise)"): + # Assuming batch is a tuple of (inputs, labels, indices) where indices track sample order + features, targets, scenarios = datamodule.transfer_batch_to_device(batch, device, 0) + predictions = torch_module_wrapper.forward(features, encode_only=True) + assert "latent" in predictions + + latents: Latent = predictions["latent"] + latents = latents.torch_to_numpy() + + for latent, scenario in zip(latents.unpack(), scenarios): + file_name = ( + autoencoder_cache_path + / scenario.log_name + / scenario.scenario_type + / scenario.token + / cfg.cache.latent_name + ) + storing_mechanism.store_computed_feature_to_folder(file_name, latent) + + return None diff --git a/sledge/autoencoder/experiments/training.py b/sledge/autoencoder/experiments/training.py new file mode 100644 index 0000000..3711fae --- /dev/null +++ b/sledge/autoencoder/experiments/training.py @@ -0,0 +1,69 @@ +import logging +from omegaconf import DictConfig + +from torch.optim.lr_scheduler import OneCycleLR +import pytorch_lightning as pl + +from nuplan.planning.utils.multithreading.worker_pool import WorkerPool +from nuplan.planning.training.experiments.training import TrainingEngine +from nuplan.planning.script.builders.utils.utils_config import get_num_gpus_used +from nuplan.planning.script.builders.utils.utils_type import is_target_type + +from sledge.script.builders.model_builder import build_autoencoder_torch_module_wrapper +from sledge.script.builders.autoencoder_builder import ( + build_autoencoder_lightning_datamodule, + build_autoencoder_lightning_module, + build_autoencoder_trainer, +) + +logger = logging.getLogger(__name__) + + +def build_training_engine(cfg: DictConfig, worker: WorkerPool) -> TrainingEngine: + """ + Build the three core lightning modules: LightningDataModule, LightningModule and Trainer + :param cfg: omegaconf dictionary + :param worker: Worker to submit tasks which can be executed in parallel + :return: TrainingEngine + """ + logger.info("Building training engine...") + + # Create model + torch_module_wrapper = build_autoencoder_torch_module_wrapper(cfg) + + # Build the datamodule + datamodule = build_autoencoder_lightning_datamodule(cfg, worker, torch_module_wrapper) + + cfg = scale_cfg_for_distributed_training(cfg, datamodule=datamodule, worker=worker) + + # Build lightning module + model = build_autoencoder_lightning_module(cfg, torch_module_wrapper) + + # Build trainer + trainer = build_autoencoder_trainer(cfg) + + engine = TrainingEngine(trainer=trainer, datamodule=datamodule, model=model) + + return engine + + +def scale_cfg_for_distributed_training( + cfg: DictConfig, datamodule: pl.LightningDataModule, worker: WorkerPool +) -> DictConfig: + """ + Adjusts parameters in cfg for ddp. + :param cfg: Config with parameters for instantiation. + :param datamodule: Datamodule which will be used for updating the lr_scheduler parameters. + :return cfg: Updated config. + """ + number_gpus = get_num_gpus_used(cfg) + + # Setup learning rate and momentum schedulers + if is_target_type(cfg.lr_scheduler, OneCycleLR): + num_train_samples = int( + len(datamodule._splitter.get_train_samples(datamodule._all_samples, worker)) * datamodule._train_fraction + ) + + cfg.lr_scheduler.steps_per_epoch = (num_train_samples // cfg.data_loader.params.batch_size) // number_gpus + + return cfg diff --git a/sledge/autoencoder/modeling/__init__.py b/sledge/autoencoder/modeling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/modeling/autoencoder_lightning_module_wrapper.py b/sledge/autoencoder/modeling/autoencoder_lightning_module_wrapper.py new file mode 100644 index 0000000..ae85d7d --- /dev/null +++ b/sledge/autoencoder/modeling/autoencoder_lightning_module_wrapper.py @@ -0,0 +1,236 @@ +import logging +from typing import Any, Dict, List, Optional, Tuple, Union +from hydra.utils import instantiate +from omegaconf import DictConfig + +import torch +from torch.optim import Optimizer +from torch.optim.lr_scheduler import _LRScheduler +import pytorch_lightning as pl + +from nuplan.planning.script.builders.lr_scheduler_builder import build_lr_scheduler +from nuplan.planning.training.modeling.objectives.abstract_objective import aggregate_objectives +from nuplan.planning.training.modeling.types import FeaturesType, ScenarioListType, TargetsType + +from sledge.autoencoder.modeling.autoencoder_torch_module_wrapper import AutoencoderTorchModuleWrapper +from sledge.autoencoder.modeling.matching.abstract_matching import AbstractMatching +from sledge.autoencoder.modeling.metrics.abstract_custom_metric import AbstractCustomMetric +from sledge.autoencoder.modeling.objectives.abstract_custom_objective import AbstractCustomObjective + +logger = logging.getLogger(__name__) + + +class AutoencoderLightningModuleWrapper(pl.LightningModule): + """ + Custom lightning module that wraps the training/validation/testing procedure and handles the objective/metric computation. + """ + + def __init__( + self, + model: AutoencoderTorchModuleWrapper, + objectives: List[AbstractCustomObjective], + metrics: Optional[List[AbstractCustomMetric]], + matchings: Optional[List[AbstractMatching]], + optimizer: Optional[DictConfig] = None, + lr_scheduler: Optional[DictConfig] = None, + warm_up_lr_scheduler: Optional[DictConfig] = None, + objective_aggregate_mode: str = "sum", + ) -> None: + """ + Initialize lightning autoencoder wrapper. + :param model: autoencoder torch module wrapper. + :param objectives: list of autoencoder objectives computed at each step + :param metrics: optional list of metrics to track + :param matchings: optional list of matching objects (e.g. for hungarian objectives) + :param optimizer: config for instantiating optimizer. Can be 'None' for older models + :param lr_scheduler: config for instantiating lr_scheduler. Can be 'None' for older models and when an lr_scheduler is not being used. + :param warm_up_lr_scheduler: _description_, defaults to None + :param objective_aggregate_mode: how should different objectives be combined, can be 'sum', 'mean', and 'max'. + """ + super().__init__() + self.save_hyperparameters(ignore=["model"]) + + self.model = model + self.objectives = objectives + self.metrics = metrics + self.matchings = matchings + self.optimizer = optimizer + self.lr_scheduler = lr_scheduler + self.warm_up_lr_scheduler = warm_up_lr_scheduler + self.objective_aggregate_mode = objective_aggregate_mode + + def _step(self, batch: Tuple[FeaturesType, TargetsType, ScenarioListType], prefix: str) -> torch.Tensor: + """ + Propagates the model forward and backwards and computes/logs losses and metrics. + + This is called either during training, validation or testing stage. + + :param batch: input batch consisting of features and targets + :param prefix: prefix prepended at each artifact's name during logging + :return: model's scalar loss + """ + features, targets, scenarios = batch + + predictions = self.forward(features) + matchings = self._compute_matchings(predictions, targets) + objectives = self._compute_objectives(predictions, targets, matchings, scenarios) + metrics = self._compute_metrics(predictions, targets, matchings, scenarios) + loss = aggregate_objectives(objectives, agg_mode=self.objective_aggregate_mode) + + self._log_step(loss, objectives, metrics, prefix) + + return loss + + def _compute_objectives( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + """ + Computes a set of learning objectives used for supervision given the model's predictions and targets. + + :param predictions: dictionary of predicted dataclasses. + :param targets: dictionary of target dataclasses. + :param matchings: dictionary of prediction-target matching. + :param scenarios: list of scenario types (for adaptive weighting) + :return: dictionary of objective names and values + """ + objectives_dict: Dict[str, torch.Tensor] = {} + for objective in self.objectives: + objectives_dict.update(objective.compute(predictions, targets, matchings, scenarios)) + return objectives_dict + + def _compute_metrics( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + """ + Computes a set of metrics used for logging. + + :param predictions: dictionary of predicted dataclasses. + :param targets: dictionary of target dataclasses. + :param matchings: dictionary of prediction-target matching. + :param scenarios: list of scenario types (for adaptive weighting) + :return: dictionary of metrics names and values + """ + metrics_dict: Dict[str, torch.Tensor] = {} + if self.metrics: + for metric in self.metrics: + metrics_dict.update(metric.compute(predictions, targets, matchings, scenarios)) + return metrics_dict + + def _compute_matchings(self, predictions: FeaturesType, targets: TargetsType) -> FeaturesType: + """ + Computes a the matchings (e.g. for hungarian loss) between prediction and targets. + + :param predictions: dictionary of predicted dataclasses. + :param targets: dictionary of target dataclasses. + :return: dictionary of matching names and matching dataclasses + """ + matchings_dict: Dict[str, torch.Tensor] = {} + if self.matchings: + for matching in self.matchings: + matchings_dict.update(matching.compute(predictions, targets)) + return matchings_dict + + def _log_step( + self, + loss: torch.Tensor, + objectives: Dict[str, torch.Tensor], + metrics: Dict[str, torch.Tensor], + prefix: str, + loss_name: str = "loss", + ) -> None: + """ + Logs the artifacts from a training/validation/test step. + + :param loss: scalar loss value + :type objectives: [type] + :param metrics: dictionary of metrics names and values + :param prefix: prefix prepended at each artifact's name + :param loss_name: name given to the loss for logging + """ + self.log(f"loss/{prefix}_{loss_name}", loss) + + for key, value in objectives.items(): + self.log(f"objectives/{prefix}_{key}", value) + + if self.metrics: + for key, value in metrics.items(): + self.log(f"metrics/{prefix}_{key}", value) + + def training_step(self, batch: Tuple[FeaturesType, TargetsType, ScenarioListType], batch_idx: int) -> torch.Tensor: + """ + Step called for each batch example during training. + + :param batch: example batch + :param batch_idx: batch's index (unused) + :return: model's loss tensor + """ + return self._step(batch, "train") + + def validation_step( + self, batch: Tuple[FeaturesType, TargetsType, ScenarioListType], batch_idx: int + ) -> torch.Tensor: + """ + Step called for each batch example during validation. + + :param batch: example batch + :param batch_idx: batch's index (unused) + :return: model's loss tensor + """ + return self._step(batch, "val") + + def test_step(self, batch: Tuple[FeaturesType, TargetsType, ScenarioListType], batch_idx: int) -> torch.Tensor: + """ + Step called for each batch example during testing. + + :param batch: example batch + :param batch_idx: batch's index (unused) + :return: model's loss tensor + """ + return self._step(batch, "test") + + def forward(self, features: FeaturesType) -> TargetsType: + """ + Propagates a batch of features through the model. + + :param features: features batch + :return: model's predictions + """ + return self.model(features) + + def configure_optimizers( + self, + ) -> Union[Optimizer, Dict[str, Union[Optimizer, _LRScheduler]]]: + """ + Configures the optimizers and learning schedules for the training. + + :return: optimizer or dictionary of optimizers and schedules + """ + if self.optimizer is None: + raise RuntimeError("To train, optimizer must not be None.") + + # Get optimizer + optimizer: Optimizer = instantiate( + config=self.optimizer, + params=self.parameters(), + lr=self.optimizer.lr, # Use lr found from lr finder; otherwise use optimizer config + ) + # Log the optimizer used + logger.info(f"Using optimizer: {self.optimizer._target_}") + + # Get lr_scheduler + lr_scheduler_params: Dict[str, Union[_LRScheduler, str, int]] = build_lr_scheduler( + optimizer=optimizer, + lr=self.optimizer.lr, + warm_up_lr_scheduler_cfg=self.warm_up_lr_scheduler, + lr_scheduler_cfg=self.lr_scheduler, + ) + lr_scheduler_params["interval"] = "step" + lr_scheduler_params["frequency"] = 1 + + optimizer_dict: Dict[str, Any] = {} + optimizer_dict["optimizer"] = optimizer + if lr_scheduler_params: + logger.info(f"Using lr_schedulers {lr_scheduler_params}") + optimizer_dict["lr_scheduler"] = lr_scheduler_params + + return optimizer_dict if "lr_scheduler" in optimizer_dict else optimizer_dict["optimizer"] diff --git a/sledge/autoencoder/modeling/autoencoder_torch_module_wrapper.py b/sledge/autoencoder/modeling/autoencoder_torch_module_wrapper.py new file mode 100644 index 0000000..f871167 --- /dev/null +++ b/sledge/autoencoder/modeling/autoencoder_torch_module_wrapper.py @@ -0,0 +1,70 @@ +import abc +from typing import List + +import torch.nn as nn + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType +from nuplan.planning.training.modeling.torch_module_wrapper import TorchModuleWrapper +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import AbstractFeatureBuilder +from nuplan.planning.training.preprocessing.target_builders.abstract_target_builder import AbstractTargetBuilder + + +class AutoencoderTorchModuleWrapper(TorchModuleWrapper): + """Torch module wrapper that encapsulates builders for constructing model features and targets.""" + + def __init__( + self, + feature_builders: List[AbstractFeatureBuilder], + target_builders: List[AbstractTargetBuilder], + ): + """ + Construct a model with feature and target builders. + :param feature_builders: The list of builders which will compute features for this model. + :param target_builders: The list of builders which will compute targets for this model. + """ + super().__init__( + future_trajectory_sampling=None, # dummy value + feature_builders=feature_builders, + target_builders=target_builders, + ) + + self.feature_builders = feature_builders + self.target_builders = target_builders + + @abc.abstractmethod + def forward(self, features: FeaturesType, encoder_only: bool) -> TargetsType: + """ + The main inference call for the model. + :param features: _description_ + :param encoder_only: whether to only encode input in autoencoder + :return: The results of the inference as a TargetsType. + """ + pass + + @abc.abstractmethod + def encode(self, features: FeaturesType) -> FeaturesType: + """ + TODO: + """ + pass + + @abc.abstractmethod + def decode(self, features: FeaturesType) -> TargetsType: + """ + TODO: + """ + pass + + @abc.abstractmethod + def get_encoder(self, features: FeaturesType) -> nn.Module: + """ + TODO: + """ + pass + + @abc.abstractmethod + def get_decoder(self, features: FeaturesType) -> nn.Module: + """ + TODO: + """ + pass diff --git a/sledge/autoencoder/modeling/matching/__init__.py b/sledge/autoencoder/modeling/matching/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/modeling/matching/abstract_matching.py b/sledge/autoencoder/modeling/matching/abstract_matching.py new file mode 100644 index 0000000..2133e47 --- /dev/null +++ b/sledge/autoencoder/modeling/matching/abstract_matching.py @@ -0,0 +1,19 @@ +from abc import ABC, abstractmethod +import torch + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType + + +class AbstractMatching(ABC): + """Matching interface used in for hungarian matching losses during training.""" + + @abstractmethod + @torch.no_grad() + def compute(self, predictions: FeaturesType, targets: TargetsType) -> TargetsType: + """ + Run matching between model predictions and targets for loss computation. + :param predictions: Predicted feature tensors for matching. + :param targets: Target feature tensors for matching + :return: Matching formulation between prediction and target + """ + pass diff --git a/sledge/autoencoder/modeling/matching/rvae_matching.py b/sledge/autoencoder/modeling/matching/rvae_matching.py new file mode 100644 index 0000000..1fdb0cb --- /dev/null +++ b/sledge/autoencoder/modeling/matching/rvae_matching.py @@ -0,0 +1,123 @@ +import torch +from scipy.optimize import linear_sum_assignment + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType + +from sledge.autoencoder.modeling.matching.abstract_matching import AbstractMatching +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.preprocessing.features.rvae_matching_feature import RVAEMatchingFeature +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeVectorElement, + SledgeVectorElementType, + BoundingBoxIndex, +) + + +class RVAEHungarianMatching(AbstractMatching): + """Object for hungarian matching of RVAE model""" + + def __init__(self, key: str, config: RVAEConfig): + """ + Initialize matching object of RVAE + :param key: string identifier if sledge vector dataclass + :param config: config dataclass of RVAE + """ + + self._key = key + self._config = config + + @torch.no_grad() + def compute(self, predictions: FeaturesType, targets: TargetsType) -> TargetsType: + """Inherited from superclass.""" + + pred_vector_element: SledgeVectorElement = getattr(predictions["sledge_vector"], self._key) + gt_vector_element: SledgeVectorElement = getattr(targets["sledge_vector"], self._key) + + element_type = pred_vector_element.get_element_type() + assert element_type == gt_vector_element.get_element_type() + assert element_type in [ + SledgeVectorElementType.LINE, + SledgeVectorElementType.AGENT, + SledgeVectorElementType.STATIC, + ] + + gt_states, gt_mask = gt_vector_element.states, gt_vector_element.mask + pred_states, pred_logits = pred_vector_element.states, pred_vector_element.mask + + ce_cost = _get_ce_cost(gt_mask, pred_logits) + + if element_type == SledgeVectorElementType.LINE: + l1_cost = _get_line_l1_cost(gt_states, pred_states, gt_mask) + ce_weight, reconstruction_weight = self._config.line_ce_weight, self._config.line_reconstruction_weight + else: + l1_cost = _get_box_l1_cost(gt_states, pred_states, gt_mask, pred_vector_element.get_element_index()) + ce_weight, reconstruction_weight = self._config.box_ce_weight, self._config.box_reconstruction_weight + + cost = ce_weight * ce_cost + reconstruction_weight * l1_cost + cost = cost.cpu() # NOTE: This unfortunately is the runtime bottleneck + + indices = [linear_sum_assignment(c) for i, c in enumerate(cost)] + matching = [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices] + + return {f"{self._key}_matching": RVAEMatchingFeature(matching)} + + +@torch.no_grad() +def _get_ce_cost(gt_mask: torch.Tensor, pred_logits: torch.Tensor) -> torch.Tensor: + """ + Calculated cross-entropy matching cost based on numerically stable PyTorch version, see: + https://github.com/pytorch/pytorch/blob/c64e006fc399d528bb812ae589789d0365f3daf4/aten/src/ATen/native/Loss.cpp#L214 + :param gt_mask: ground-truth binary existence labels, shape: (batch, num_gt) + :param pred_logits: predicted (normalized) logits of existence, shape: (batch, num_pred) + :return: cross-entropy cost tensor of shape (batch, num_pred, num_gt) + """ + + gt_mask_expanded = gt_mask[:, :, None].detach().float() # (b, ng, 1) + pred_logits_expanded = pred_logits[:, None, :].detach() # (b, 1, np) + + max_val = torch.relu(-pred_logits_expanded) + helper_term = max_val + torch.log(torch.exp(-max_val) + torch.exp(-pred_logits_expanded - max_val)) + ce_cost = (1 - gt_mask_expanded) * pred_logits_expanded + helper_term # (b, ng, np) + ce_cost = ce_cost.permute(0, 2, 1) # (b, np, ng) + + return ce_cost + + +@torch.no_grad() +def _get_line_l1_cost(gt_states: torch.Tensor, pred_states: torch.Tensor, gt_mask: torch.Tensor) -> torch.Tensor: + """ + Calculates the L1 matching cost for line state tensors. + :param gt_states: ground-truth line tensor, shape: (batch, num_gt, state_size) + :param pred_states: predicted line tensor, shape: (batch, num_pred, state_size) + :param gt_mask: ground-truth binary existence labels for masking, shape: (batch, num_gt) + :return: L1 cost tensor of shape (batch, num_pred, num_gt) + """ + + gt_states_expanded = gt_states[:, :, None].detach() # (b, ng, 1, *s) + pred_states_expanded = pred_states[:, None].detach() # (b, 1, np, *s) + l1_cost = gt_mask[..., None] * (gt_states_expanded - pred_states_expanded).abs().sum(dim=-1).mean(dim=-1) + l1_cost = l1_cost.permute(0, 2, 1) # (b, np, ng) + + return l1_cost + + +@torch.no_grad() +def _get_box_l1_cost( + gt_states: torch.Tensor, pred_states: torch.Tensor, gt_mask: torch.Tensor, object_indexing: BoundingBoxIndex +) -> torch.Tensor: + """ + Calculates the L1 matching cost for bounding box state tensors, based on the (x,y) position. + :param gt_states: ground-truth box tensor, shape: (batch, num_gt, state_size) + :param pred_states: predicted box tensor, shape: (batch, num_pred, state_size) + :param gt_mask: ground-truth binary existence labels for masking, shape: (batch, num_gt) + :param object_indexing: index enum of object type. + :return: L1 cost tensor of shape (batch, num_pred, num_gt) + """ + + # NOTE: Bounding Box L1 matching only considers position, ignoring irrelevant attr. (e.g. box extent) + gt_states_expanded = gt_states[:, :, None, object_indexing.POINT].detach() # (b, ng, 1, 2) + pred_states_expanded = pred_states[:, None, :, object_indexing.POINT].detach() # (b, 1, np, 2) + l1_cost = gt_mask[..., None] * (gt_states_expanded - pred_states_expanded).abs().sum(dim=-1) # (b, ng, np) + l1_cost = l1_cost.permute(0, 2, 1) # (b, np, ng) + + return l1_cost diff --git a/sledge/autoencoder/modeling/metrics/__init__.py b/sledge/autoencoder/modeling/metrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/modeling/metrics/abstract_custom_metric.py b/sledge/autoencoder/modeling/metrics/abstract_custom_metric.py new file mode 100644 index 0000000..fb6c721 --- /dev/null +++ b/sledge/autoencoder/modeling/metrics/abstract_custom_metric.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod +from typing import Dict + +import torch + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType, ScenarioListType + + +class AbstractCustomMetric(ABC): + """Custom abstract class for metric definition. Allows to multiple metric in one objects.""" + + @abstractmethod + def compute( + self, + predictions: FeaturesType, + targets: TargetsType, + matchings: TargetsType, + scenarios: ScenarioListType, + ) -> Dict[str, torch.Tensor]: + """ + Computes the metric given the ground truth targets and the model's predictions. + :param predictions: dictionary of model's predictions + :param targets: dictionary of ground-truth targets from the dataset + :param matchings: dictionary of matchings between targets and predictions + :param scenarios: list if scenario types (for type-specific weighting) + :return: dictionary of metric name and scalar. + """ + pass diff --git a/sledge/autoencoder/modeling/metrics/kl_metric.py b/sledge/autoencoder/modeling/metrics/kl_metric.py new file mode 100644 index 0000000..cfbf01e --- /dev/null +++ b/sledge/autoencoder/modeling/metrics/kl_metric.py @@ -0,0 +1,28 @@ +from typing import Dict + +import torch + +from nuplan.planning.training.modeling.types import FeaturesType, ScenarioListType, TargetsType + +from sledge.autoencoder.preprocessing.features.latent_feature import Latent +from sledge.autoencoder.modeling.metrics.abstract_custom_metric import AbstractCustomMetric + + +class KLMetric(AbstractCustomMetric): + def __init__(self, scenario_type_loss_weighting: Dict[str, float]): + """ + Initializes the class + :param scenario_type_loss_weighting: loss weight per scenario (ignored) + """ + self._scenario_type_loss_weighting = scenario_type_loss_weighting + + def compute( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + """Inherited, see superclass.""" + + pred_latent: Latent = predictions["latent"] + mu, log_var = pred_latent.mu, pred_latent.log_var + kl_metric = -0.5 * torch.mean(1 + log_var - mu**2 - log_var.exp()) + + return {"kl_metric": kl_metric} diff --git a/sledge/autoencoder/modeling/models/__init__.py b/sledge/autoencoder/modeling/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/modeling/models/rvae/__init__.py b/sledge/autoencoder/modeling/models/rvae/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/modeling/models/rvae/rvae_config.py b/sledge/autoencoder/modeling/models/rvae/rvae_config.py new file mode 100644 index 0000000..7e4a57b --- /dev/null +++ b/sledge/autoencoder/modeling/models/rvae/rvae_config.py @@ -0,0 +1,119 @@ +from typing import List, Tuple +from dataclasses import dataclass + +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeConfig + + +@dataclass +class RVAEConfig(SledgeConfig): + """Configuration dataclass for Raster-Vector-Autoencoder.""" + + # 1. features raw + radius: int = 100 + pose_interval: int = 1.0 + + # 2. features raster & vector + frame: Tuple[int, int] = (64, 64) + + num_lines: int = 50 + num_vehicles: int = 50 + num_pedestrians: int = 20 + num_static_objects: int = 30 + num_green_lights: int = 20 + num_red_lights: int = 20 + + num_line_poses: int = 20 + vehicle_max_velocity: float = 15 + pedestrian_max_velocity: float = 2 + + pixel_size: float = 0.25 + line_dots_radius: int = 0 + + # 3. raster encoder π + model_name: str = "resnet50" + down_factor: int = 32 # NOTE: specific to resnet + num_input_channels: int = 12 + latent_channel: int = 64 + + # 4. vector decoder φ + num_encoder_layers: int = 0 + num_decoder_layers: int = 3 + + patch_size: int = 1 + dropout: float = 0.1 + num_head: int = 8 + d_model: int = 512 + d_ffn: int = 1024 + activation: str = "relu" + normalize_before: bool = False + positional_embedding: str = "sine" + split_latent: bool = True + + head_d_ffn: int = 1024 + head_num_layers: int = 2 + + num_line_queries: int = 30 + num_vehicle_queries: int = 30 + num_pedestrian_queries: int = 10 + num_static_object_queries: int = 20 + num_green_light_queries: int = 10 + num_red_light_queries: int = 10 + + # matching & loss + line_reconstruction_weight: float = 2.0 + line_ce_weight: float = 5.0 + + box_reconstruction_weight: float = 2.0 + box_ce_weight: float = 5.0 + + ego_reconstruction_weight: float = 1.0 + kl_weight: float = 0.1 + + norm_by_count: bool = False + + # output + threshold: float = 0.3 + + # augmentation + def __post_init__(self): + super().__post_init__() + assert self.positional_embedding in ["learned", "sine"] + assert self.activation in ["relu", "gelu", "glu"] + + @property + def pixel_frame(self) -> Tuple[int, int]: + frame_width, frame_height = self.frame + return int(frame_width / self.pixel_size), int(frame_height / self.pixel_size) + + @property + def latent_frame(self) -> Tuple[int, int]: + pixel_width, pixel_height = self.pixel_frame + return int(pixel_width / self.down_factor), int(pixel_height / self.down_factor) + + @property + def patches_frame(self) -> Tuple[int, int]: + latent_width, latent_height = self.latent_frame + return int(latent_width / self.patch_size), int(latent_height / self.patch_size) + + @property + def num_patches(self) -> int: + patches_width, patches_height = self.patches_frame + return patches_width * patches_height + + @property + def d_patches(self) -> int: + + num_channels = self.latent_channel // 2 if self.split_latent else self.latent_channel + return int(self.patch_size**2) * num_channels + + @property + def num_queries_list(self) -> List[int]: + return [ + self.num_line_queries, + self.num_vehicle_queries, + self.num_pedestrian_queries, + self.num_static_object_queries, + self.num_green_light_queries, + self.num_red_light_queries, + 1, # add ego query + ] diff --git a/sledge/autoencoder/modeling/models/rvae/rvae_decoder.py b/sledge/autoencoder/modeling/models/rvae/rvae_decoder.py new file mode 100644 index 0000000..a6bab0b --- /dev/null +++ b/sledge/autoencoder/modeling/models/rvae/rvae_decoder.py @@ -0,0 +1,424 @@ +from __future__ import annotations + +import json +from pathlib import PosixPath +from typing import Any, Dict, Optional, Tuple, Union +import dataclasses +import numpy as np + +import torch +import torch.nn as nn +import torch.nn.functional as F + +import diffusers +from diffusers.models.modeling_utils import ModelMixin +from diffusers.configuration_utils import ConfigMixin, FrozenDict, register_to_config + +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.modeling.models.rvae.utils.rvae_position_encoding import build_position_encoding +from sledge.autoencoder.modeling.models.rvae.utils.rvae_transformer import Transformer +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeVector, + SledgeVectorElement, + LineIndex, + AgentIndex, + StaticObjectIndex, + BoundingBoxIndex, +) + + +class RVAEDecoder(ModelMixin, ConfigMixin): + """Decoder module of Raster-Vector-Autoencoder.""" + + @register_to_config + def __init__(self, config: RVAEConfig): + """ + Initialize decoder module. + :param config: config dataclass of RVAE + """ + + super(RVAEDecoder, self).__init__() + + self._config = config + self._num_queries_list = config.num_queries_list + + self._transformer = Transformer( + d_model=config.d_model, + nhead=config.num_head, + num_encoder_layers=config.num_encoder_layers, + num_decoder_layers=config.num_decoder_layers, + dim_feedforward=config.d_ffn, + dropout=config.dropout, + activation=config.activation, + normalize_before=config.normalize_before, + return_intermediate_dec=False, + ) + self._line_head = LineHead( + d_input=config.d_model, + d_ffn=config.head_d_ffn, + num_layers=config.head_num_layers, + num_line_poses=config.num_line_poses, + frame=config.frame, + ) + self._vehicle_head = BoundingBoxHead( + d_input=config.d_model, + d_ffn=config.head_d_ffn, + num_layers=config.head_num_layers, + frame=config.frame, + enum=AgentIndex, + max_velocity=config.vehicle_max_velocity, + ) + self._pedestrian_head = BoundingBoxHead( + d_input=config.d_model, + d_ffn=config.head_d_ffn, + num_layers=config.head_num_layers, + frame=config.frame, + enum=AgentIndex, + max_velocity=config.pedestrian_max_velocity, + ) + self._static_object_head = BoundingBoxHead( + d_input=config.d_model, + d_ffn=config.head_d_ffn, + num_layers=config.head_num_layers, + frame=config.frame, + enum=StaticObjectIndex, + ) + self._green_line_head = LineHead( + d_input=config.d_model, + d_ffn=config.head_d_ffn, + num_layers=config.head_num_layers, + num_line_poses=config.num_line_poses, + frame=config.frame, + ) + self._red_line_head = LineHead( + d_input=config.d_model, + d_ffn=config.head_d_ffn, + num_layers=config.head_num_layers, + num_line_poses=config.num_line_poses, + frame=config.frame, + ) + self._ego_head = EgoHead(config.d_model) + + self._patch_projection = nn.Linear(config.d_patches, config.d_model) + self._query_embedding = nn.Embedding(sum(self._num_queries_list), config.d_model) + self._position_encoding = build_position_encoding(config) + + if config.split_latent: + self._type_encoding = nn.Embedding(2, config.d_model) + + def to_json_string(self) -> str: + """ + Create string of RVAEConfig. Overwrites to_json_string in ConfigMixin. + :return: string description of RVAEDecoder configuration. + """ + # NOTE: this function is necessary to save RVAConfig as json in diffusion pipeline. + config_dict = dataclasses.asdict(self._config) + config_dict["_class_name"] = self.__class__.__name__ + config_dict["_diffusers_version"] = diffusers.__version__ + + def to_json_saveable(value): + if isinstance(value, np.ndarray): + value = value.tolist() + elif isinstance(value, PosixPath): + value = str(value) + return value + + config_dict = {k: to_json_saveable(v) for k, v in config_dict.items()} + return json.dumps(config_dict, indent=2, sort_keys=True) + "\n" + + @classmethod + def from_config( + cls, + config: Union[FrozenDict, Dict[str, Any]] = None, + return_unused_kwarg: bool = False, + **kwargs, + ) -> RVAEDecoder: + """ + :param config: RVAE config as dictionary from json, defaults to None + :param return_unused_kwargs: ignored, defaults to False + :return: RVAE decoder module from config dictionary + """ + # NOTE: this function to load RVAEDecoder from as json config in diffusion pipeline. + filtered_config_dict = {k: v for k, v in config.items() if k in RVAEConfig.__annotations__} + config_dataclass = RVAEConfig(**filtered_config_dict) + return RVAEDecoder(config=config_dataclass) + + def forward(self, latent: torch.Tensor) -> SledgeVector: + """ + Forward pass of decoder module. + :param latent: tensor of latent variable (after reparameterization) + :return: sledge vector dataclass + """ + b, device = latent.shape[0], latent.device + if self._config.split_latent: + static_latent, dynamic_latent = torch.chunk(latent, 2, dim=1) + + static_patches, dynamic_patches = ( + patchify(static_latent, self._config.patch_size), + patchify(dynamic_latent, self._config.patch_size), + ) + + num_patches = self._config.num_patches + decoder_mask = create_mask( + num_patches, + num_patches, + sum(self._num_queries_list[:1]), + sum(self._num_queries_list[1:]), + device, + ) + + type_embed = self._type_encoding.weight[None, ...].repeat(b, 1, 1) # (b, 2, d_model) + type_embed = type_embed.repeat_interleave(num_patches, 1) # (b, 2*p*p, d_model) + type_embed = type_embed.permute(1, 0, 2) # (2*p*p, b, d_model) + + pos_embed = self._position_encoding(static_patches) # (b, d_model, p, p) + pos_embed = pos_embed.flatten(-2).permute(2, 0, 1) # (p*p, b, d_model) + pos_embed = pos_embed.repeat(2, 1, 1) # (2*p*p, b, d_model) + pos_embed += type_embed + + static_patches = static_patches.flatten(-2).permute(2, 0, 1) # (p*p, b, d_patches) + dynamic_patches = dynamic_patches.flatten(-2).permute(2, 0, 1) # (p*p, b, d_patches) + patches = torch.cat([static_patches, dynamic_patches], dim=0) # (2*p*p, b, d_patches) + projected_patches = self._patch_projection(patches) # (2*p*p, b, d_model) + + else: + decoder_mask = None + patches = patchify(static_latent, self._config.patch_size) + patches = patches.flatten(-2).permute(2, 0, 1) # (p*p, b, d_patches) + projected_patches = self._patch_projection(patches) # (p*p, b, d_model) + + pos_embed = self._position_encoding(static_patches) # (b, d_model, p, p) + pos_embed = pos_embed.flatten(-2).permute(2, 0, 1) # (p*p, b, d_model) + + query_embed = self._query_embedding.weight[:, None].repeat(1, b, 1) + + hs = self._transformer( + src=projected_patches, + query_embed=query_embed, + pos_embed=pos_embed, + memory_mask=decoder_mask, + )[0].permute(1, 0, 2) + + hs_line, hs_vehicle, hs_pedestrian, hs_static, hs_green, hs_red, hs_ego = hs.split( + self._num_queries_list, dim=1 + ) + line_element = self._line_head(hs_line) + vehicle_element = self._vehicle_head(hs_vehicle) + pedestrian_element = self._pedestrian_head(hs_pedestrian) + static_object_element = self._static_object_head(hs_static) + green_line_element = self._green_line_head(hs_green) + red_line_element = self._red_line_head(hs_red) + ego_element = self._ego_head(hs_ego) + + return SledgeVector( + line_element, + vehicle_element, + pedestrian_element, + static_object_element, + green_line_element, + red_line_element, + ego_element, + ) + + def decode(self, latent: torch.Tensor) -> SledgeVector: + """Alias for .forward()""" + return self.forward(latent) + + +class FFN(nn.Module): + """Feed-forward network implementation.""" + + def __init__( + self, + d_input: int, + d_ffn: int, + d_output: int, + num_layers: int, + ): + """ + Initialize FNN. + :param d_input: dimensionality of input + :param d_ffn: dimensionality hidden layers + :param d_output: dimensionality of output + :param num_layers: number of hidden layers + """ + super(FFN, self).__init__() + + self._num_layers = num_layers + h = [d_ffn] * (num_layers - 1) + self.layers = nn.ModuleList(nn.Linear(n, k) for n, k in zip([d_input] + h, h + [d_output])) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + """Forward pass of FNN.""" + for i, layer in enumerate(self.layers): + x = F.relu(layer(x)) if i < self._num_layers - 1 else layer(x) + return x + + +class LineHead(nn.Module): + """Decoder head for lines in RVAEDecoder (lines, traffic lights).""" + + def __init__( + self, + d_input: int, + d_ffn: int, + num_layers: int, + num_line_poses: int, + frame: Tuple[float, float], + ): + """ + Initialize line head. + :param d_input: dimensionality of input. + :param d_ffn: dimensionality of ffn hidden layers. + :param num_layers: number of hidden layers in fnn. + :param num_line_poses: number of output poses of lines. + :param frame: output frame of predicted patch in meter. + """ + super(LineHead, self).__init__() + + self._ffn_states = FFN(d_input, d_ffn, num_line_poses * LineIndex.size(), num_layers) + self._ffn_mask = nn.Linear(d_input, 1) + + self._num_line_poses = num_line_poses + + frame_transform = torch.tensor(frame, dtype=torch.float32, requires_grad=False) + frame_transform = frame_transform[None, :] / 2 + self.register_buffer("_frame_transform", frame_transform) + + def forward(self, queries: torch.Tensor) -> SledgeVectorElement: + """Forward pass of LineHead.""" + + batch_size, num_queries = queries.shape[:2] + states = ( + self._ffn_states(queries).tanh().reshape(batch_size, num_queries, self._num_line_poses, LineIndex.size()) + * self._frame_transform + ) + + states = states + mask = self._ffn_mask(queries).squeeze(dim=-1) + + return SledgeVectorElement(states, mask) + + +class BoundingBoxHead(nn.Module): + """Decoder head for bounding boxes in RVAEDecoder (vehicles, pedestrian, static objects).""" + + def __init__( + self, + d_input: int, + d_ffn: int, + num_layers: int, + frame: Tuple[float, float], + enum: BoundingBoxIndex, + max_velocity: Optional[float] = None, + ): + """ + Initialize bounding box head. + :param d_input: dimensionality of input. + :param d_ffn: dimensionality of ffn hidden layers. + :param num_layers: number of hidden layers in fnn. + :param frame: output frame of predicted patch in meter. + :param enum: integer enum class of bounding box states. + :param max_velocity: max velocity of predicted bounding box, defaults to None. + """ + super(BoundingBoxHead, self).__init__() + + if enum == AgentIndex: + assert max_velocity is not None + + self._ffn_states = FFN(d_input, d_ffn, enum.size(), num_layers) + self._ffn_mask = nn.Linear(d_input, 1) + + self._enum = enum + self._max_velocity = max_velocity + + frame_transform = torch.tensor(frame, dtype=torch.float32, requires_grad=False) + frame_transform = frame_transform[None, :] / 2 + self.register_buffer("_frame_transform", frame_transform) + + def forward(self, queries: torch.Tensor) -> SledgeVectorElement: + """Forward pass of BoundingBoxHead.""" + + states = self._ffn_states(queries) + + states[..., self._enum.POINT] = states[..., self._enum.POINT].tanh() * self._frame_transform + states[..., self._enum.HEADING] = states[..., self._enum.HEADING].tanh() * np.pi + + if self._max_velocity is not None: + states[..., self._enum.VELOCITY] = states[..., self._enum.VELOCITY].sigmoid() * self._max_velocity + + mask = self._ffn_mask(queries).squeeze(dim=-1) + return SledgeVectorElement(states, mask) + + +class EgoHead(nn.Module): + """Simple decoder head for ego vehicle attributes.""" + + def __init__( + self, + d_input: int, + ): + """ + Initialize ego head. + :param d_input: dimensionality of input. + """ + + super(EgoHead, self).__init__() + + # TODO: Maybe refactor this class + self._ffn_states = nn.Linear(d_input, 1) + + def forward(self, ego_query) -> SledgeVectorElement: + + states = self._ffn_states(ego_query).squeeze() + mask = torch.ones_like(states) # dummy + + return SledgeVectorElement(states, mask) + + +# TODO: as class method in decoder object? +def create_mask( + num_static_patches: int, + num_dynamic_patches: int, + num_static_queries: int, + num_dynamic_queries: int, + device: torch.device, +) -> torch.Tensor: + """ + Create lane-agent mask for transformer in RVAEDecoder. + :param num_static_patches: number of patches (key-val) of static latent. + :param num_dynamic_patches: number of patches (key-val) of dynamic latent. + :param num_static_queries: number of queries for static elements. + :param num_dynamic_queries: number of queries for dynamic elements. + :param device: torch device where to init mask. + :return: mask for RVAEDecoder + """ + num_patches = num_static_patches + num_dynamic_patches + num_queries = num_static_queries + num_dynamic_queries + + mask = torch.full((num_queries, num_patches), float("-inf"), device=device) + + mask[:num_static_queries, :num_static_patches] = 0 + mask[num_static_queries:, num_static_patches:] = 0 + + return mask + + +# TODO: as class method in decoder object? +def patchify(latent: torch.Tensor, patch_size: int) -> torch.Tensor: + """ + Create patches of latent tensor, similar to a vision transformer. + :param latent: tensor of the latent variable. + :param patch_size: size of patches. + :return: patched latent tensor. + """ + assert latent.ndim == 4, "Latent tensor should be 4-dimensional" + batch, channels, height, width = latent.shape + + num_patches = height // patch_size + patches = latent.unfold(dimension=2, size=patch_size, step=patch_size) + patches = patches.unfold(dimension=3, size=patch_size, step=patch_size) + patches = patches.permute(0, 1, 4, 5, 2, 3) + patches = patches.reshape(batch, -1, num_patches, num_patches) + + return patches diff --git a/sledge/autoencoder/modeling/models/rvae/rvae_encoder.py b/sledge/autoencoder/modeling/models/rvae/rvae_encoder.py new file mode 100644 index 0000000..2ecf0ad --- /dev/null +++ b/sledge/autoencoder/modeling/models/rvae/rvae_encoder.py @@ -0,0 +1,89 @@ +# Code mainly from: https://github.com/facebookresearch/detr (Apache-2.0 license) +# TODO: Refactor & add docstring's + +from typing import Union +import torch +import torchvision + +from torch import nn +from torchvision.models._utils import IntermediateLayerGetter + +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.modeling.models.vae.vae_config import VAEConfig +from sledge.autoencoder.preprocessing.features.latent_feature import Latent + + +class FrozenBatchNorm2d(torch.nn.Module): + """ + BatchNorm2d where the batch statistics and the affine parameters are fixed. + + Copy-paste from torchvision.misc.ops with added eps before rqsrt, + without which any other models than torchvision.models.resnet[18,34,50,101] + produce nans. + """ + + def __init__(self, n): + super(FrozenBatchNorm2d, self).__init__() + self.register_buffer("weight", torch.ones(n)) + self.register_buffer("bias", torch.zeros(n)) + self.register_buffer("running_mean", torch.zeros(n)) + self.register_buffer("running_var", torch.ones(n)) + + def _load_from_state_dict( + self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs + ): + num_batches_tracked_key = prefix + "num_batches_tracked" + if num_batches_tracked_key in state_dict: + del state_dict[num_batches_tracked_key] + + super(FrozenBatchNorm2d, self)._load_from_state_dict( + state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs + ) + + def forward(self, x): + # move reshapes to the beginning + # to make it user-friendly + w = self.weight.reshape(1, -1, 1, 1) + b = self.bias.reshape(1, -1, 1, 1) + rv = self.running_var.reshape(1, -1, 1, 1) + rm = self.running_mean.reshape(1, -1, 1, 1) + eps = 1e-5 + scale = w * (rv + eps).rsqrt() + bias = b - rm * scale + return x * scale + bias + + +class RVAEEncoder(nn.Module): + """ResNet backbone with frozen BatchNorm.""" + + def __init__(self, config: Union[RVAEConfig, VAEConfig]): + """ + Initialize encoder module. + :param config: config of RVAE or VAE. + """ + super().__init__() + + # TODO: support more backbones. + backbone = getattr(torchvision.models, config.model_name)( + replace_stride_with_dilation=[False, False, False], + weights="DEFAULT", + norm_layer=FrozenBatchNorm2d, + ) + if config.num_input_channels != 3: + backbone.conv1 = nn.Conv2d(config.num_input_channels, 64, 7, stride=2, padding=3, bias=False) + self._backbone = IntermediateLayerGetter(backbone, return_layers={"layer4": "0"}) + output_channels = 512 if config.model_name in ["resnet18", "resnet34"] else 2048 + + # TODO: add params to config + self._group_norm = nn.GroupNorm(num_groups=32, num_channels=output_channels, eps=1e-6, affine=True) + self._latent = nn.Conv2d(output_channels, 2 * config.latent_channel, kernel_size=3, stride=1, padding=1) + + def forward(self, raster: torch.Tensor) -> Latent: + + cnn_features = self._backbone(raster)["0"] + normed_features = self._group_norm(cnn_features) + + latent = self._latent(normed_features) + mu, log_var = torch.chunk(latent, 2, dim=1) + + return Latent(mu, log_var) diff --git a/sledge/autoencoder/modeling/models/rvae/rvae_model.py b/sledge/autoencoder/modeling/models/rvae/rvae_model.py new file mode 100644 index 0000000..9bf0c4c --- /dev/null +++ b/sledge/autoencoder/modeling/models/rvae/rvae_model.py @@ -0,0 +1,66 @@ +import torch +import torch.nn as nn + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType + +from sledge.autoencoder.modeling.autoencoder_torch_module_wrapper import AutoencoderTorchModuleWrapper +from sledge.autoencoder.modeling.models.rvae.rvae_encoder import RVAEEncoder +from sledge.autoencoder.modeling.models.rvae.rvae_decoder import RVAEDecoder +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.preprocessing.features.latent_feature import Latent +from sledge.autoencoder.preprocessing.feature_builders.sledge_raw_feature_builder import SledgeRawFeatureBuilder +from sledge.autoencoder.preprocessing.target_builders.map_id_target_builder import MapIDTargetBuilder + + +class RVAEModel(AutoencoderTorchModuleWrapper): + """Raster-Vector Autoencoder in of SLEDGE.""" + + def __init__(self, config: RVAEConfig): + """ + Initialize Raster-Vector Autoencoder. + :param config: configuration dataclass of RVAE. + """ + feature_builders = [SledgeRawFeatureBuilder(config)] + target_builders = [MapIDTargetBuilder()] + + super().__init__(feature_builders=feature_builders, target_builders=target_builders) + + self._config = config + + self._raster_encoder = RVAEEncoder(config) + self._vector_decoder = RVAEDecoder(config) + + @staticmethod + def _reparameterize(latent: Latent) -> torch.Tensor: + """ + Reparameterization method for variational autoencoder's. + :param latent: dataclass for mu, logvar tensors. + :return: combined latent tensor. + """ + mu, log_var = latent.mu, latent.log_var + assert mu.shape == log_var.shape + std = torch.exp(0.5 * log_var) + eps = torch.randn_like(std) + return mu + eps * std + + def forward(self, features: FeaturesType, encode_only: bool = False) -> TargetsType: + """Inherited, see superclass.""" + predictions: TargetsType = {} + + # encoding + predictions["latent"] = self._raster_encoder(features["sledge_raster"].data) + latent = self._reparameterize(predictions["latent"]) + if encode_only: + return predictions + + # decoding + predictions["sledge_vector"] = self._vector_decoder(latent) + return predictions + + def get_encoder(self) -> nn.Module: + """Inherited, see superclass.""" + return self._raster_encoder + + def get_decoder(self) -> nn.Module: + """Inherited, see superclass.""" + return self._vector_decoder diff --git a/sledge/autoencoder/modeling/models/rvae/utils/rvae_position_encoding.py b/sledge/autoencoder/modeling/models/rvae/utils/rvae_position_encoding.py new file mode 100644 index 0000000..155ef54 --- /dev/null +++ b/sledge/autoencoder/modeling/models/rvae/utils/rvae_position_encoding.py @@ -0,0 +1,103 @@ +# Code mainly from: https://github.com/facebookresearch/detr (Apache-2.0 license) +# TODO: Refactor & add docstring's + +from typing import Optional, Union +import numpy as np +import torch +from torch import nn + +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig + + +class PositionEmbeddingSine(nn.Module): + """ + This is a more standard version of the position embedding, very similar to the one + used by the Attention is all you need paper, generalized to work on images. + """ + + def __init__( + self, + num_pos_feats: int = 512, + temperature: float = 10000, + normalize: bool = True, + scale: Optional[float] = None, + ): + super().__init__() + self.num_pos_feats = num_pos_feats + self.temperature = temperature + self.normalize = normalize + if scale is not None and normalize is False: + raise ValueError("normalize should be True if scale is passed") + if scale is None: + scale = 2 * np.pi + self.scale = scale + + def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None) -> torch.Tensor: + not_mask = ~mask if mask else torch.ones(x.shape[0], *x.shape[-2:], device=x.device) + + y_embed = not_mask.cumsum(1, dtype=torch.float32) + x_embed = not_mask.cumsum(2, dtype=torch.float32) + if self.normalize: + eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale + + dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device) + dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats) + + pos_x = x_embed[:, :, :, None] / dim_t + pos_y = y_embed[:, :, :, None] / dim_t + pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3) + pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + return pos + + +class PositionEmbeddingLearned(nn.Module): + """ + Absolute pos embedding, learned. + """ + + def __init__(self, num_pos_feats: int = 256): + super().__init__() + self.row_embed = nn.Embedding(50, num_pos_feats) + self.col_embed = nn.Embedding(50, num_pos_feats) + self._reset_parameters() + + def _reset_parameters(self) -> None: + nn.init.uniform_(self.row_embed.weight) + nn.init.uniform_(self.col_embed.weight) + + def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None) -> torch.Tensor: + h, w = x.shape[-2:] + i = torch.arange(w, device=x.device) + j = torch.arange(h, device=x.device) + x_emb = self.col_embed(i) + y_emb = self.row_embed(j) + pos = ( + torch.cat( + [ + x_emb.unsqueeze(0).repeat(h, 1, 1), + y_emb.unsqueeze(1).repeat(1, w, 1), + ], + dim=-1, + ) + .permute(2, 0, 1) + .unsqueeze(0) + .repeat(x.shape[0], 1, 1, 1) + ) + return pos + + +def build_position_encoding(config: RVAEConfig) -> Union[PositionEmbeddingSine, PositionEmbeddingLearned]: + + num_features = config.d_model // 2 + # FIXME: add positional embedding parameters in config + if config.positional_embedding == "sine": + position_embedding = PositionEmbeddingSine(num_features, normalize=True) + elif config.positional_embedding == "learned": + position_embedding = PositionEmbeddingLearned(num_features) + else: + raise ValueError(f"not supported {config.positional_embedding}") + + return position_embedding diff --git a/sledge/autoencoder/modeling/models/rvae/utils/rvae_transformer.py b/sledge/autoencoder/modeling/models/rvae/utils/rvae_transformer.py new file mode 100644 index 0000000..ddee066 --- /dev/null +++ b/sledge/autoencoder/modeling/models/rvae/utils/rvae_transformer.py @@ -0,0 +1,349 @@ +# Code mainly from: https://github.com/facebookresearch/detr (Apache-2.0 license) +# TODO: Refactor & add docstring's + +import copy +from typing import Callable, Optional +import torch +import torch.nn.functional as F +from torch import nn + + +class Transformer(nn.Module): + + def __init__( + self, + d_model: int = 512, + nhead: int = 8, + num_encoder_layers: int = 6, + num_decoder_layers: int = 6, + dim_feedforward: int = 2048, + dropout: float = 0.1, + activation: str = "relu", + normalize_before: bool = False, + return_intermediate_dec: bool = False, + ): + super().__init__() + + encoder_layer = TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout, activation, normalize_before) + encoder_norm = nn.LayerNorm(d_model) if normalize_before else None + self.encoder = TransformerEncoder(encoder_layer, num_encoder_layers, encoder_norm) + + decoder_layer = TransformerDecoderLayer(d_model, nhead, dim_feedforward, dropout, activation, normalize_before) + decoder_norm = nn.LayerNorm(d_model) + self.decoder = TransformerDecoder( + decoder_layer, num_decoder_layers, decoder_norm, return_intermediate=return_intermediate_dec + ) + + self._reset_parameters() + + self.d_model = d_model + self.nhead = nhead + + def _reset_parameters(self): + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + + def forward( + self, + src: torch.Tensor, + query_embed: torch.Tensor, + pos_embed: torch.Tensor, + memory_mask: Optional[torch.Tensor] = None, + ): + + tgt = torch.zeros_like(query_embed) + memory = self.encoder(src, pos_embed=pos_embed) + hs = self.decoder(tgt, memory, pos_embed=pos_embed, query_pos=query_embed, memory_mask=memory_mask) + return hs + + +class TransformerEncoder(nn.Module): + + def __init__(self, encoder_layer, num_layers, norm=None): + super().__init__() + self.layers = _get_clones(encoder_layer, num_layers) + self.num_layers = num_layers + self.norm = norm + + def forward( + self, + src, + mask: Optional[torch.Tensor] = None, + src_key_padding_mask: Optional[torch.Tensor] = None, + pos_embed: Optional[torch.Tensor] = None, + ): + output = src + + for layer in self.layers: + output = layer( + output, + src_mask=mask, + src_key_padding_mask=src_key_padding_mask, + pos_embed=pos_embed, + ) + + if self.norm is not None: + output = self.norm(output) + + return output + + +class TransformerDecoder(nn.Module): + + def __init__(self, decoder_layer, num_layers, norm=None, return_intermediate=False): + super().__init__() + self.layers = _get_clones(decoder_layer, num_layers) + self.num_layers = num_layers + self.norm = norm + self.return_intermediate = return_intermediate + + def forward( + self, + tgt, + memory, + tgt_mask: Optional[torch.Tensor] = None, + memory_mask: Optional[torch.Tensor] = None, + tgt_key_padding_mask: Optional[torch.Tensor] = None, + memory_key_padding_mask: Optional[torch.Tensor] = None, + pos_embed: Optional[torch.Tensor] = None, + query_pos: Optional[torch.Tensor] = None, + ): + output = tgt + intermediate = [] + for layer in self.layers: + output = layer( + output, + memory, + tgt_mask=tgt_mask, + memory_mask=memory_mask, + tgt_key_padding_mask=tgt_key_padding_mask, + memory_key_padding_mask=memory_key_padding_mask, + pos_embed=pos_embed, + query_pos=query_pos, + ) + if self.return_intermediate: + intermediate.append(self.norm(output)) + + if self.norm is not None: + output = self.norm(output) + if self.return_intermediate: + intermediate.pop() + intermediate.append(output) + + if self.return_intermediate: + return torch.stack(intermediate) + + return output.unsqueeze(0) + + +class TransformerEncoderLayer(nn.Module): + + def __init__( + self, + d_model: int, + nhead: int, + dim_feedforward: int = 2048, + dropout: int = 0.1, + activation="relu", + normalize_before=False, + ): + super().__init__() + self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout) + # Implementation of Feedforward model + self.linear1 = nn.Linear(d_model, dim_feedforward) + self.dropout = nn.Dropout(dropout) + self.linear2 = nn.Linear(dim_feedforward, d_model) + + self.norm1 = nn.LayerNorm(d_model) + self.norm2 = nn.LayerNorm(d_model) + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + + self.activation = _get_activation_fn(activation) + self.normalize_before = normalize_before + + def with_pos_embed(self, tensor, pos_embed: Optional[torch.Tensor]): + return tensor if pos_embed is None else tensor + pos_embed + + def forward_post( + self, + src, + src_mask: Optional[torch.Tensor] = None, + src_key_padding_mask: Optional[torch.Tensor] = None, + pos_embed: Optional[torch.Tensor] = None, + ): + q = k = self.with_pos_embed(src, pos_embed) + src2 = self.self_attn(q, k, value=src, attn_mask=src_mask, key_padding_mask=src_key_padding_mask)[0] + src = src + self.dropout1(src2) + src = self.norm1(src) + src2 = self.linear2(self.dropout(self.activation(self.linear1(src)))) + src = src + self.dropout2(src2) + src = self.norm2(src) + return src + + def forward_pre( + self, + src, + src_mask: Optional[torch.Tensor] = None, + src_key_padding_mask: Optional[torch.Tensor] = None, + pos_embed: Optional[torch.Tensor] = None, + ): + src2 = self.norm1(src) + q = k = self.with_pos_embed(src2, pos_embed) + src2 = self.self_attn(q, k, value=src2, attn_mask=src_mask, key_padding_mask=src_key_padding_mask)[0] + src = src + self.dropout1(src2) + src2 = self.norm2(src) + src2 = self.linear2(self.dropout(self.activation(self.linear1(src2)))) + src = src + self.dropout2(src2) + return src + + def forward( + self, + src, + src_mask: Optional[torch.Tensor] = None, + src_key_padding_mask: Optional[torch.Tensor] = None, + pos_embed: Optional[torch.Tensor] = None, + ): + if self.normalize_before: + return self.forward_pre(src, src_mask, src_key_padding_mask, pos_embed) + return self.forward_post(src, src_mask, src_key_padding_mask, pos_embed) + + +class TransformerDecoderLayer(nn.Module): + + def __init__( + self, + d_model, + nhead, + dim_feedforward=2048, + dropout=0.1, + activation="relu", + normalize_before=False, + ): + super().__init__() + self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout) + self.multihead_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout) + # Implementation of Feedforward model + self.linear1 = nn.Linear(d_model, dim_feedforward) + self.dropout = nn.Dropout(dropout) + self.linear2 = nn.Linear(dim_feedforward, d_model) + + self.norm1 = nn.LayerNorm(d_model) + self.norm2 = nn.LayerNorm(d_model) + self.norm3 = nn.LayerNorm(d_model) + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.dropout3 = nn.Dropout(dropout) + + self.activation = _get_activation_fn(activation) + self.normalize_before = normalize_before + + def with_pos_embed(self, tensor, pos: Optional[torch.Tensor]): + return tensor if pos is None else tensor + pos + + def forward_post( + self, + tgt, + memory, + tgt_mask: Optional[torch.Tensor] = None, + memory_mask: Optional[torch.Tensor] = None, + tgt_key_padding_mask: Optional[torch.Tensor] = None, + memory_key_padding_mask: Optional[torch.Tensor] = None, + pos: Optional[torch.Tensor] = None, + query_pos: Optional[torch.Tensor] = None, + ): + q = k = self.with_pos_embed(tgt, query_pos) + tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask, key_padding_mask=tgt_key_padding_mask)[0] + tgt = tgt + self.dropout1(tgt2) + tgt = self.norm1(tgt) + tgt2 = self.multihead_attn( + query=self.with_pos_embed(tgt, query_pos), + key=self.with_pos_embed(memory, pos), + value=memory, + attn_mask=memory_mask, + key_padding_mask=memory_key_padding_mask, + )[0] + tgt = tgt + self.dropout2(tgt2) + tgt = self.norm2(tgt) + tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt)))) + tgt = tgt + self.dropout3(tgt2) + tgt = self.norm3(tgt) + return tgt + + def forward_pre( + self, + tgt, + memory, + tgt_mask: Optional[torch.Tensor] = None, + memory_mask: Optional[torch.Tensor] = None, + tgt_key_padding_mask: Optional[torch.Tensor] = None, + memory_key_padding_mask: Optional[torch.Tensor] = None, + pos_embed: Optional[torch.Tensor] = None, + query_pos: Optional[torch.Tensor] = None, + ): + tgt2 = self.norm1(tgt) + q = k = self.with_pos_embed(tgt2, query_pos) + tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask, key_padding_mask=tgt_key_padding_mask)[0] + tgt = tgt + self.dropout1(tgt2) + tgt2 = self.norm2(tgt) + tgt2 = self.multihead_attn( + query=self.with_pos_embed(tgt2, query_pos), + key=self.with_pos_embed(memory, pos_embed), + value=memory, + attn_mask=memory_mask, + key_padding_mask=memory_key_padding_mask, + )[0] + tgt = tgt + self.dropout2(tgt2) + tgt2 = self.norm3(tgt) + tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2)))) + tgt = tgt + self.dropout3(tgt2) + return tgt + + def forward( + self, + tgt, + memory, + tgt_mask: Optional[torch.Tensor] = None, + memory_mask: Optional[torch.Tensor] = None, + tgt_key_padding_mask: Optional[torch.Tensor] = None, + memory_key_padding_mask: Optional[torch.Tensor] = None, + pos_embed: Optional[torch.Tensor] = None, + query_pos: Optional[torch.Tensor] = None, + ): + if self.normalize_before: + return self.forward_pre( + tgt, + memory, + tgt_mask, + memory_mask, + tgt_key_padding_mask, + memory_key_padding_mask, + pos_embed, + query_pos, + ) + return self.forward_post( + tgt, + memory, + tgt_mask, + memory_mask, + tgt_key_padding_mask, + memory_key_padding_mask, + pos_embed, + query_pos, + ) + + +def _get_clones(module, N): + return nn.ModuleList([copy.deepcopy(module) for i in range(N)]) + + +def _get_activation_fn(activation: str) -> Callable: + """Return an activation function given a string""" + if activation == "relu": + return F.relu + if activation == "gelu": + return F.gelu + if activation == "glu": + return F.glu + raise RuntimeError(f"activation should be relu/gelu, not {activation}.") diff --git a/sledge/autoencoder/modeling/models/vae/__init__.py b/sledge/autoencoder/modeling/models/vae/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/modeling/models/vae/vae_config.py b/sledge/autoencoder/modeling/models/vae/vae_config.py new file mode 100644 index 0000000..ebd1a0f --- /dev/null +++ b/sledge/autoencoder/modeling/models/vae/vae_config.py @@ -0,0 +1,56 @@ +from typing import Tuple +from dataclasses import dataclass + +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeConfig + + +@dataclass +class VAEConfig(SledgeConfig): + """Configuration dataclass for Raster VAE.""" + + # 1. features raw + radius: int = 100 + pose_interval: int = 1.0 + + # 2. features raster & vector + frame: Tuple[int, int] = (64, 64) + + num_lines: int = 50 + num_vehicles: int = 50 + num_pedestrians: int = 20 + num_static_objects: int = 30 + num_green_lights: int = 20 + num_red_lights: int = 20 + + num_line_poses: int = 20 + vehicle_max_velocity: float = 15 + pedestrian_max_velocity: float = 2 + + pixel_size: float = 0.25 + line_dots_radius: int = 0 + + # 3. raster encoder π + model_name: str = "resnet50" + down_factor: int = 32 # NOTE: specific to resnet + num_input_channels: int = 12 + latent_channel: int = 64 + + # loss + reconstruction_weight: float = 1.0 + kl_weight: float = 0.1 + + # output + threshold: float = 0.3 + + def __post_init__(self): + super().__post_init__() + + @property + def pixel_frame(self) -> Tuple[int, int]: + frame_width, frame_height = self.frame + return int(frame_width / self.pixel_size), int(frame_height / self.pixel_size) + + @property + def latent_frame(self) -> Tuple[int, int]: + pixel_width, pixel_height = self.pixel_frame + return int(pixel_width / self.down_factor), int(pixel_height / self.down_factor) diff --git a/sledge/autoencoder/modeling/models/vae/vae_model.py b/sledge/autoencoder/modeling/models/vae/vae_model.py new file mode 100644 index 0000000..c4e0b61 --- /dev/null +++ b/sledge/autoencoder/modeling/models/vae/vae_model.py @@ -0,0 +1,107 @@ +import torch +import torch.nn as nn + +from nuplan.planning.training.modeling.types import FeaturesType, TargetsType + +from sledge.autoencoder.modeling.autoencoder_torch_module_wrapper import AutoencoderTorchModuleWrapper +from sledge.autoencoder.modeling.models.rvae.rvae_encoder import RVAEEncoder +from sledge.autoencoder.modeling.models.vae.vae_config import VAEConfig + +from sledge.autoencoder.preprocessing.features.latent_feature import Latent +from sledge.autoencoder.preprocessing.features.sledge_raster_feature import SledgeRaster +from sledge.autoencoder.preprocessing.feature_builders.sledge_raw_feature_builder import SledgeRawFeatureBuilder +from sledge.autoencoder.preprocessing.target_builders.map_id_target_builder import MapIDTargetBuilder + + +class VAEModel(AutoencoderTorchModuleWrapper): + """Raster Variation Autoencoder.""" + + def __init__(self, config: VAEConfig): + """ + Initialize Raster VAE. + :param config: configuration dataclass of VAE. + """ + feature_builders = [SledgeRawFeatureBuilder(config)] + target_builders = [MapIDTargetBuilder()] + + super().__init__(feature_builders=feature_builders, target_builders=target_builders) + + self._config = config + + self._raster_encoder = RVAEEncoder(config) + self._raster_decoder = RasterDecoder(config.latent_channel, config.num_input_channels) + + @staticmethod + def _reparameterize(latent: Latent) -> torch.Tensor: + """ + Reparameterization method for variational autoencoder's. + :param latent: dataclass for mu, logvar tensors. + :return: combined latent tensor. + """ + + mu, log_var = latent.mu, latent.log_var + assert mu.shape == log_var.shape + std = torch.exp(0.5 * log_var) + eps = torch.randn_like(std) + return mu + eps * std + + def forward(self, features: FeaturesType, encode_only: bool = False) -> TargetsType: + """Inherited, see superclass.""" + predictions: TargetsType = {} + + # encoding + predictions["latent"] = self._raster_encoder(features["sledge_raster"].data) + latent = self._reparameterize(predictions["latent"]) + if encode_only: + return predictions + + # decoding + predictions["sledge_raster"] = self._raster_decoder(latent) + return predictions + + def get_encoder(self) -> nn.Module: + """Inherited, see superclass.""" + return self._raster_encoder + + def get_decoder(self) -> nn.Module: + """Inherited, see superclass.""" + return self._raster_decoder + + +class RasterDecoder(nn.Module): + """Simple Raster Decoder from latent.""" + + def __init__(self, latent_channel: int, num_output_channels: int = 2): + super(RasterDecoder, self).__init__() + + self.deconv1 = nn.ConvTranspose2d(latent_channel, 256, kernel_size=4, stride=2, padding=1) # Output: 128x16x16 + self.bn1 = nn.BatchNorm2d(256) + self.relu1 = nn.ReLU() + + self.deconv2 = nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1) # Output: 64x32x32 + self.bn2 = nn.BatchNorm2d(128) + self.relu2 = nn.ReLU() + + self.deconv3 = nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1) # Output: 32x64x64 + self.bn3 = nn.BatchNorm2d(64) + self.relu3 = nn.ReLU() + + self.deconv4 = nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1) # Output: 16x128x128 + self.bn4 = nn.BatchNorm2d(32) + self.relu4 = nn.ReLU() + + self.deconv5 = nn.ConvTranspose2d(32, 16, kernel_size=4, stride=2, padding=1) # Output: 8x256x256 + self.bn5 = nn.BatchNorm2d(16) + self.relu5 = nn.ReLU() + + # Final layer to adjust channels from 8 to 12 + self.final_conv = nn.Conv2d(16, num_output_channels, kernel_size=3, stride=1, padding=1) # Output: 12x256x256 + + def forward(self, x): + x = self.relu1(self.bn1(self.deconv1(x))) + x = self.relu2(self.bn2(self.deconv2(x))) + x = self.relu3(self.bn3(self.deconv3(x))) + x = self.relu4(self.bn4(self.deconv4(x))) + x = self.relu5(self.bn5(self.deconv5(x))) + x = self.final_conv(x) + return SledgeRaster(data=x) diff --git a/sledge/autoencoder/modeling/objectives/__init__.py b/sledge/autoencoder/modeling/objectives/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/modeling/objectives/abstract_custom_objective.py b/sledge/autoencoder/modeling/objectives/abstract_custom_objective.py new file mode 100644 index 0000000..fb4360b --- /dev/null +++ b/sledge/autoencoder/modeling/objectives/abstract_custom_objective.py @@ -0,0 +1,27 @@ +from abc import ABC, abstractmethod +from typing import Dict +import torch + +from nuplan.planning.training.modeling.types import FeaturesType, ScenarioListType, TargetsType + + +class AbstractCustomObjective(ABC): + """Abstract class for custom objectives in sledge package. Allows to multiple objectives via dict.""" + + @abstractmethod + def compute( + self, + predictions: FeaturesType, + targets: TargetsType, + matchings: TargetsType, + scenarios: ScenarioListType, + ) -> Dict[str, torch.Tensor]: + """ + Computes the objective given the ground truth targets and the model's predictions. + :param predictions: dictionary of model's predictions + :param targets: dictionary of ground-truth targets from the dataset + :param matchings: dictionary of matchings between targets and predictions + :param scenarios: list if scenario types (for type-specific weighting) + :return: dictionary of metric name and scalar. + """ + pass diff --git a/sledge/autoencoder/modeling/objectives/kl_objective.py b/sledge/autoencoder/modeling/objectives/kl_objective.py new file mode 100644 index 0000000..5589f24 --- /dev/null +++ b/sledge/autoencoder/modeling/objectives/kl_objective.py @@ -0,0 +1,32 @@ +from typing import Dict + +import torch + +from nuplan.planning.training.modeling.types import FeaturesType, ScenarioListType, TargetsType + +from sledge.autoencoder.preprocessing.features.latent_feature import Latent +from sledge.autoencoder.modeling.objectives.abstract_custom_objective import AbstractCustomObjective + + +class KLObjective(AbstractCustomObjective): + """Kullback-Leibler divergence objective for VAEs.""" + + def __init__(self, weight: float, scenario_type_loss_weighting: Dict[str, float]): + """ + Initialize KL objective. + :param weight: scalar for loss weighting (aka. β) + :param scenario_type_loss_weighting: scenario-type specific loss weights (ignored). + """ + self._weight = weight + self._scenario_type_loss_weighting = scenario_type_loss_weighting + + def compute( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + """Inherited, see superclass.""" + + pred_latent: Latent = predictions["latent"] + mu, log_var = pred_latent.mu, pred_latent.log_var + kl_loss = -0.5 * torch.mean(1 + log_var - mu**2 - log_var.exp()) + + return {"kl_latent": self._weight * kl_loss} diff --git a/sledge/autoencoder/modeling/objectives/rvae_objective.py b/sledge/autoencoder/modeling/objectives/rvae_objective.py new file mode 100644 index 0000000..3410d9a --- /dev/null +++ b/sledge/autoencoder/modeling/objectives/rvae_objective.py @@ -0,0 +1,116 @@ +from typing import Dict, Tuple + +import torch +import torch.nn.functional as F + +from nuplan.planning.training.modeling.types import FeaturesType, ScenarioListType, TargetsType + +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.modeling.objectives.abstract_custom_objective import AbstractCustomObjective +from sledge.autoencoder.preprocessing.features.rvae_matching_feature import RVAEMatchingFeature +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVectorElement, SledgeVectorElementType + + +class RVAEHungarianObjective(AbstractCustomObjective): + """Object for hungarian loss (ie. lw + bce) for RVAE model.""" + + def __init__(self, key: str, config: RVAEConfig, scenario_type_loss_weighting: Dict[str, float]): + """ + Initialize hungarian loss object. + :param key: string identifier if sledge vector dataclass + :param config: config dataclass of RVAE + :param scenario_type_loss_weighting: scenario-type specific loss weights (ignored) + """ + self._key = key + self._config = config + self._scenario_type_loss_weighting = scenario_type_loss_weighting + + def compute( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + """Inherited, see superclass.""" + + # Retrieve all relevant predictions, targets, matchings + pred_vector_element: SledgeVectorElement = getattr(predictions["sledge_vector"], self._key) + gt_vector_element: SledgeVectorElement = getattr(targets["sledge_vector"], self._key) + + element_type = pred_vector_element.get_element_type() + assert element_type == gt_vector_element.get_element_type() + assert element_type in [ + SledgeVectorElementType.LINE, + SledgeVectorElementType.AGENT, + SledgeVectorElementType.STATIC, + ] + + matching: RVAEMatchingFeature = matchings[f"{self._key}_matching"] + + # Unpack predictions and targets + gt_states, gt_mask = gt_vector_element.states, gt_vector_element.mask + pred_states, pred_logits = pred_vector_element.states, pred_vector_element.mask + + # Arrange predictions and targets according to matching + indices, permutation_indices = matching.indices, _get_src_permutation_idx(matching.indices) + + pred_states_idx = pred_states[permutation_indices] + gt_states_idx = torch.cat([t[i] for t, (_, i) in zip(gt_states, indices)], dim=0) + + pred_logits_idx = pred_logits[permutation_indices] + gt_mask_idx = torch.cat([t[i] for t, (_, i) in zip(gt_mask, indices)], dim=0).float() + + # calculate CE and L1 Loss + l1_loss = F.l1_loss(pred_states_idx, gt_states_idx, reduction="none") + if element_type == SledgeVectorElementType.LINE: + l1_loss = l1_loss.sum(-1).mean(-1) * gt_mask_idx + ce_weight, reconstruction_weight = self._config.line_ce_weight, self._config.line_reconstruction_weight + else: + l1_loss = l1_loss.sum(-1) * gt_mask_idx + ce_weight, reconstruction_weight = self._config.box_ce_weight, self._config.box_reconstruction_weight + + ce_loss = F.binary_cross_entropy_with_logits(pred_logits_idx, gt_mask_idx, reduction="none") + + # Whether to average by batch size or entity count + bs = gt_mask.shape[0] + if self._config.norm_by_count: + num_gt_instances = gt_mask.float().sum(-1) + num_gt_instances = num_gt_instances if num_gt_instances > 0 else num_gt_instances + 1 + l1_loss = l1_loss.view(bs, -1).sum() / num_gt_instances + ce_loss = ce_loss.view(bs, -1).sum() / num_gt_instances + else: + l1_loss = l1_loss.view(bs, -1).mean() + ce_loss = ce_loss.view(bs, -1).mean() + + return {f"l1_{self._key}": reconstruction_weight * l1_loss, f"ce_{self._key}": ce_weight * ce_loss} + + +class RVAEEgoObjective(AbstractCustomObjective): + """Simple regression loss of ego attributes (ie. lw + bce).""" + + def __init__(self, weight: float, scenario_type_loss_weighting: Dict[str, float]): + """ + Initialize ego objective. + :param weight: scalar for loss weighting + :param scenario_type_loss_weighting: scenario-type specific loss weights (ignored) + """ + self._weight = weight + self._scenario_type_loss_weighting = scenario_type_loss_weighting + + def compute( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + """Inherited, see superclass.""" + + pred_ego_element: SledgeVectorElement = predictions["sledge_vector"].ego + gt_ego_element: SledgeVectorElement = targets["sledge_vector"].ego + l1_loss = F.l1_loss(pred_ego_element.states, gt_ego_element.states[..., 0]) + + return {"l1_ego": self._weight * l1_loss} + + +def _get_src_permutation_idx(indices) -> Tuple[torch.Tensor, torch.Tensor]: + """Helper function for permutation of matched indices.""" + + # permute predictions following indices + batch_idx = torch.cat([torch.full_like(src, i) for i, (src, _) in enumerate(indices)]) + src_idx = torch.cat([src for (src, _) in indices]) + + return batch_idx, src_idx diff --git a/sledge/autoencoder/modeling/objectives/vae_objective.py b/sledge/autoencoder/modeling/objectives/vae_objective.py new file mode 100644 index 0000000..6cc0771 --- /dev/null +++ b/sledge/autoencoder/modeling/objectives/vae_objective.py @@ -0,0 +1,57 @@ +from typing import Dict + +import torch +import torch.nn.functional as F + +from nuplan.planning.training.modeling.types import FeaturesType, ScenarioListType, TargetsType + +from sledge.autoencoder.preprocessing.features.sledge_raster_feature import SledgeRaster +from sledge.autoencoder.modeling.objectives.abstract_custom_objective import AbstractCustomObjective + + +class VAEL1Objective(AbstractCustomObjective): + """Object for image reconstruction loss (ie. l1).""" + + def __init__(self, weight: float, scenario_type_loss_weighting: Dict[str, float]): + """ + Initialize l1 objective for raster reconstruction. + :param weight: scalar for loss weighting + :param scenario_type_loss_weighting: scenario-type specific loss weights (ignored) + """ + self._weight = weight + self._scenario_type_loss_weighting = scenario_type_loss_weighting + + def compute( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + + gt_raster: SledgeRaster = targets["sledge_raster"] + pred_raster: SledgeRaster = predictions["sledge_raster"] + + l1_loss = F.l1_loss(gt_raster.data, pred_raster.data) + + return {"l1_loss": self._weight * l1_loss} + + +class VAEBCEObjective(AbstractCustomObjective): + """Object for image reconstruction loss (ie. binary cross-entropy).""" + + def __init__(self, weight: float, scenario_type_loss_weighting: Dict[str, float]): + """ + Initialize binary cross-entropy objective for raster reconstruction. + :param weight: scalar for loss weighting + :param scenario_type_loss_weighting: scenario-type specific loss weights (ignored) + """ + self._weight = weight + self._scenario_type_loss_weighting = scenario_type_loss_weighting + + def compute( + self, predictions: FeaturesType, targets: TargetsType, matchings: TargetsType, scenarios: ScenarioListType + ) -> Dict[str, torch.Tensor]: + """Inherited, see superclass.""" + + gt_raster: SledgeRaster = targets["sledge_raster"] + pred_raster: SledgeRaster = predictions["sledge_raster"] + bce_loss = F.binary_cross_entropy_with_logits(gt_raster.data, pred_raster.data) + + return {"bce_loss": self._weight * bce_loss} diff --git a/sledge/autoencoder/preprocessing/__init__.py b/sledge/autoencoder/preprocessing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/preprocessing/feature_builders/__init__.py b/sledge/autoencoder/preprocessing/feature_builders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/preprocessing/feature_builders/sledge/__init__.py b/sledge/autoencoder/preprocessing/feature_builders/sledge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_agent_feature.py b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_agent_feature.py new file mode 100644 index 0000000..580b9da --- /dev/null +++ b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_agent_feature.py @@ -0,0 +1,140 @@ +from typing import List, Optional + +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.agent import Agent +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks + +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import convert_absolute_to_relative_se2_array +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import state_se2_to_array +from sledge.simulation.planner.pdm_planner.observation.pdm_occupancy_map import PDMOccupancyMap +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import ego_state_to_state_array +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import StateIndex +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeVectorElement, + StaticObjectIndex, + AgentIndex, + EgoIndex, +) + + +def compute_ego_features(ego_state: EgoState) -> SledgeVectorElement: + """ + Compute raw sledge vector features for ego agents + :param ego_state: object of ego vehicle state in nuPlan + :return: sledge vector element of raw ego attributes. + """ + + state_array = ego_state_to_state_array(ego_state) + ego_states = np.zeros((EgoIndex.size()), dtype=np.float64) + ego_mask = np.ones((1), dtype=bool) # dummy value + ego_states[EgoIndex.VELOCITY_2D] = state_array[StateIndex.VELOCITY_2D] + ego_states[EgoIndex.ACCELERATION_2D] = state_array[StateIndex.ACCELERATION_2D] + + return SledgeVectorElement(ego_states, ego_mask) + + +# TODO: Refactor +def compute_agent_features( + ego_state: EgoState, + detections: DetectionsTracks, + agent_type: TrackedObjectType, + radius: float, + drivable_area_map: Optional[PDMOccupancyMap] = None, +) -> SledgeVectorElement: + """ + Computes raw sledge vector features for agents (ie. vehicles, pedestrians) + :param ego_state: object of ego vehicle state in nuPlan + :param detections: dataclass for detected objects in nuPlan + :param agent_type: enum of agent type (ie. vehicles, pedestrians) + :param radius: radius around the ego vehicle to extract objects + :param drivable_area_map: drivable area map for filtering if provided, defaults to None + :return: raw sledge vector element of agent_type + """ + + tracked_objects = detections.tracked_objects + agents_list: List[Agent] = tracked_objects.get_tracked_objects_of_type(agent_type) + + agents_states_list: List[npt.NDArray[np.float64]] = [] + for agent in agents_list: + agent_states_ = np.zeros(AgentIndex.size(), dtype=np.float64) + agent_states_[AgentIndex.STATE_SE2] = state_se2_to_array(agent.center) + agent_states_[AgentIndex.WIDTH] = agent.box.width + agent_states_[AgentIndex.LENGTH] = agent.box.length + agent_states_[AgentIndex.VELOCITY] = agent.velocity.magnitude() + agents_states_list.append(agent_states_) + agents_states_all = np.array(agents_states_list) + + # convert to local coords and filter out of box + if len(agents_states_all) > 0: + if drivable_area_map is not None: + in_drivable_area = drivable_area_map.points_in_polygons(agents_states_all[..., AgentIndex.POINT]).any( + axis=0 + ) + agents_states_all = agents_states_all[in_drivable_area] + + # convert to local coordinates + agents_states_all[..., AgentIndex.STATE_SE2] = convert_absolute_to_relative_se2_array( + ego_state.center, agents_states_all[..., AgentIndex.STATE_SE2] + ) + + # filter detections + within_radius = np.linalg.norm(agents_states_all[..., AgentIndex.POINT], axis=-1) <= radius + agents_states_all = agents_states_all[within_radius] + + agent_states = np.array(agents_states_all, dtype=np.float32) + agent_mask = np.zeros(len(agents_states_all), dtype=bool) + + return SledgeVectorElement(agent_states, agent_mask) + + +def compute_static_object_features( + ego_state: EgoState, + detections: DetectionsTracks, + radius: float, + drivable_area_map: Optional[PDMOccupancyMap] = None, +) -> SledgeVectorElement: + """ + Computes raw sledge vector features for static objects (ie. barriers, generic) + :param ego_state: object of ego vehicle state in nuPlan + :param detections: dataclass for detected objects in nuPlan + :param radius: radius around the ego vehicle to extract objects + :param drivable_area_map: drivable area map for filtering if provided, defaults to None + :return: raw sledge vector element of all static object classes + """ + + tracked_objects = detections.tracked_objects + objects_list: List[Agent] = tracked_objects.get_static_objects() + + objects_states_list: List[npt.NDArray[np.float64]] = [] + for object in objects_list: + object_states_ = np.zeros(StaticObjectIndex.size(), dtype=np.float64) + object_states_[StaticObjectIndex.STATE_SE2] = state_se2_to_array(object.center) + object_states_[StaticObjectIndex.WIDTH] = object.box.width + object_states_[StaticObjectIndex.LENGTH] = object.box.length + objects_states_list.append(object_states_) + objects_states_all = np.array(objects_states_list) + + # convert to local coords and filter out of box + if len(objects_states_all) > 0: + if drivable_area_map is not None: + in_drivable_area = drivable_area_map.points_in_polygons( + objects_states_all[..., StaticObjectIndex.POINT] + ).any(axis=0) + objects_states_all = objects_states_all[in_drivable_area] + + # convert to local coordinates + objects_states_all[..., StaticObjectIndex.STATE_SE2] = convert_absolute_to_relative_se2_array( + ego_state.center, objects_states_all[..., StaticObjectIndex.STATE_SE2] + ) + # filter detections + within_radius = np.linalg.norm(objects_states_all[..., StaticObjectIndex.POINT], axis=-1) <= radius + objects_states_all = objects_states_all[within_radius] + + object_states = np.array(objects_states_all, dtype=np.float32) + object_mask = np.zeros(len(objects_states_all), dtype=bool) + + return SledgeVectorElement(object_states, object_mask) diff --git a/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_feature_processing.py b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_feature_processing.py new file mode 100644 index 0000000..5079efa --- /dev/null +++ b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_feature_processing.py @@ -0,0 +1,340 @@ +import copy +from typing import List, Optional, Tuple + +import cv2 +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.oriented_box import OrientedBox +from nuplan.common.actor_state.vehicle_parameters import get_pacifica_parameters + +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import array_to_states_se2, array_to_state_se2 +from sledge.simulation.planner.pdm_planner.utils.pdm_path import PDMPath +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.autoencoder.preprocessing.features.sledge_raster_feature import SledgeRaster, SledgeRasterIndex +from sledge.autoencoder.preprocessing.feature_builders.sledge.sledge_utils import ( + coords_in_frame, + coords_to_pixel, + pixel_in_frame, + raster_mask_oriented_box, +) +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeConfig, + SledgeVector, + SledgeVectorRaw, + SledgeVectorElement, + StaticObjectIndex, + AgentIndex, + EgoIndex, +) + + +def sledge_raw_feature_processing( + sledge_vector_raw: SledgeVectorRaw, config: SledgeConfig +) -> Tuple[SledgeVector, SledgeRaster]: + """ + Computes sledge vector from raw format on-the-fly, ie. during augmentation. + - processes the raw vector format to the frame and settings in the configuration. + - rasterize the processed vector representation + + :param sledge_vector_raw: raw representation of a vector in sledge, see dataclass. + :param config: configuration for sledge autoencoder, incl. frame and raster config + :return: tuple for processed sledge vector and raster dataclasses + """ + + pixel_height, pixel_width = config.pixel_frame + raster = np.zeros((pixel_height, pixel_width, SledgeRasterIndex.size()), dtype=np.float32) + + vector_lines, raster_lines = process_lines( + sledge_vector_raw.lines, + config, + config.num_lines, + ) + vector_vehicles, raster_vehicles = process_agents( + sledge_vector_raw.vehicles, + config, + config.num_vehicles, + config.vehicle_max_velocity, + ego_element=sledge_vector_raw.ego, + ) + vector_pedestrians, raster_pedestrians = process_agents( + sledge_vector_raw.pedestrians, + config, + config.num_pedestrians, + config.pedestrian_max_velocity, + ) + vector_static, raster_static = process_static_objects( + sledge_vector_raw.static_objects, + config, + ) + vector_green_lines, raster_green_lines = process_lines( + sledge_vector_raw.green_lights, + config, + config.num_green_lights, + ) + vector_red_lines, raster_red_lines = process_lines( + sledge_vector_raw.red_lights, + config, + config.num_red_lights, + ) + + raster[..., SledgeRasterIndex.LINE] = raster_lines + raster[..., SledgeRasterIndex.VEHICLE] = raster_vehicles + raster[..., SledgeRasterIndex.PEDESTRIAN] = raster_pedestrians + raster[..., SledgeRasterIndex.STATIC_OBJECT] = raster_static + raster[..., SledgeRasterIndex.GREEN_LIGHT] = raster_green_lines + raster[..., SledgeRasterIndex.RED_LIGHT] = raster_red_lines + + return ( + SledgeVector( + vector_lines, + vector_vehicles, + vector_pedestrians, + vector_static, + vector_green_lines, + vector_red_lines, + copy.deepcopy(sledge_vector_raw.ego), + ), + SledgeRaster(raster), + ) + + +def process_lines( + lines: SledgeVectorElement, config: SledgeConfig, num_lines: int +) -> Tuple[SledgeVectorElement, npt.NDArray[np.float32]]: + """ + TODO: Refactor + Processes the lines entities from raw vector format + - sort and interpolate nearest lines for fixed sized array + - rasterize lines in the two image channels + + :param lines: raw line vector element + :param config: dataclass for sledge autoencoder + :param num_lines: max number of lines in output representations + :return: tuple of processed line elements and line channels + """ + + # 1. preprocess lines (e.g. check if in frame) + lines_in_frame = [] + for line_states, line_mask in zip(lines.states, lines.mask): + line_in_mask = line_states[line_mask] # (n, 3) + if len(line_in_mask) < 2: + continue + + path = PDMPath(array_to_states_se2(line_in_mask)) + distances = np.arange( + 0, + path.length + config.pixel_size, + config.pixel_size, + ) + line = path.interpolate(distances, as_array=True) + frame_mask = coords_in_frame(line[..., :2], config.frame) + indices_segments = find_consecutive_true_indices(frame_mask) + + for indices_segment in indices_segments: + line_segment = line[indices_segment] + if len(line_segment) < 3: + continue + lines_in_frame.append(line_segment) + + # sort out nearest num_lines elements + lines_distances = [np.linalg.norm(line[..., :2], axis=-1).min() for line in lines_in_frame] + lines_in_frame = [lines_in_frame[idx] for idx in np.argsort(lines_distances)[:num_lines]] + + # 2. rasterize preprocessed lines + pixel_height, pixel_width = config.pixel_frame + raster_lines = np.zeros((pixel_height, pixel_width, 2), dtype=np.float32) + for line in lines_in_frame: + + # encode orientation as color value + dxy = np.concatenate([np.cos(line[..., 2, None]), np.sin(line[..., 2, None])], axis=-1) + values = 0.5 * (dxy + 1) + pixel_coords = coords_to_pixel(line[..., :2], config.frame, config.pixel_size) + pixel_mask = pixel_in_frame(pixel_coords, config.pixel_frame) + + pixel_coords, values = pixel_coords[pixel_mask], values[pixel_mask] + raster_lines[pixel_coords[..., 0], pixel_coords[..., 1]] = values + + if config.line_dots_radius > 0: + thickness = -1 + if len(values) > 1: + + # NOTE: OpenCV has origin on top-left corner + cv2.circle( + raster_lines, + (pixel_coords[0, 1], pixel_coords[0, 0]), + radius=config.line_dots_radius, + color=values[0], + thickness=thickness, + ) + cv2.circle( + raster_lines, + (pixel_coords[-1, 1], pixel_coords[-1, 0]), + radius=config.line_dots_radius, + color=values[-1], + thickness=thickness, + ) + + # 3. vectorized preprocessed lines + vector_states = np.zeros((num_lines, config.num_line_poses, 2), dtype=np.float32) + vector_labels = np.zeros((num_lines), dtype=bool) + vector_labels[: len(lines_in_frame)] = True + + for line_idx, line in enumerate(lines_in_frame): + path = PDMPath(array_to_states_se2(line)) + distances = np.linspace(0, path.length, num=config.num_line_poses, endpoint=True) + vector_states[line_idx] = path.interpolate(distances, as_array=True)[..., :2] + + return SledgeVectorElement(vector_states, vector_labels), raster_lines + + +def process_agents( + agents: SledgeVectorElement, + config: SledgeConfig, + num_agents: int, + max_velocity: float, + ego_element: Optional[SledgeVectorElement] = None, +) -> Tuple[SledgeVectorElement, npt.NDArray[np.float32]]: + """ + TODO: Refactor + Processes the agent entities from raw vector format + - sort and interpolate nearest agents for fixed sized array + - rasterize agents in the two image channels + + :param agents: raw sledge vector element of agent category + :param config: config of sledge autoencoder + :param num_agents: max number of agent type in output representations + :param max_velocity: max velocity of agent type + :param ego_element: optional ego vehicle feature (ie. rasterize in vehicle channel), defaults to None + :return: tuple of processed agent elements and agent channels + """ + + # 1. vectorized raw agents (e.g. cap max number) + agents_states_all = agents.states + vector_states = np.zeros((num_agents, AgentIndex.size()), dtype=np.float32) + vector_labels = np.zeros(num_agents, dtype=bool) + if len(agents_states_all) > 0: + frame_mask = coords_in_frame(agents_states_all[..., AgentIndex.POINT], config.frame) + agents_states_frame = agents_states_all[frame_mask] + distances = np.linalg.norm(agents_states_frame[..., AgentIndex.POINT], axis=-1) + argsort = np.argsort(distances)[:num_agents] + agents_states_nearest = agents_states_frame[argsort] + vector_states[: len(agents_states_nearest)] = agents_states_nearest + vector_labels[: len(agents_states_nearest)] = True + vector_states[..., AgentIndex.VELOCITY] = np.minimum(vector_states[..., AgentIndex.VELOCITY], max_velocity) + + # 2. rasterize agent bounding boxes + pixel_height, pixel_width = config.pixel_frame + raster_agents = np.zeros((pixel_height, pixel_width, 2), dtype=np.float32) + for agent_state in vector_states[vector_labels]: + # Get the 2D coordinate of the detected agents. + oriented_box = OrientedBox( + array_to_state_se2(agent_state[AgentIndex.STATE_SE2]), + agent_state[AgentIndex.LENGTH], + agent_state[AgentIndex.WIDTH], + 1.0, # NOTE: dummy height + ) + raster_mask = raster_mask_oriented_box(oriented_box, config) + + # Calculate change in position + dx = agent_state[AgentIndex.VELOCITY] * np.cos(agent_state[AgentIndex.HEADING]) + dy = agent_state[AgentIndex.VELOCITY] * np.sin(agent_state[AgentIndex.HEADING]) + + raster_agents[raster_mask, 0] = 0.5 * (dx / max_velocity + 1) + raster_agents[raster_mask, 1] = 0.5 * (dy / max_velocity + 1) + + # Optional 3. rasterize ego bounding box (ie. for vehicles channels) + if ego_element: + ego_car_footprint = get_pacifica_parameters() + ego_center_array = np.zeros(3, dtype=np.float32) + oriented_box = OrientedBox( + array_to_state_se2(ego_center_array), + ego_car_footprint.length, + ego_car_footprint.width, + 1.0, # NOTE: dummy height + ) + + raster_mask = raster_mask_oriented_box(oriented_box, config) + velocity = np.linalg.norm(ego_element.states[EgoIndex.VELOCITY_2D], axis=-1) + velocity = np.minimum(velocity, max_velocity) + + # Calculate change in position + dx = velocity * np.cos(ego_center_array[AgentIndex.HEADING]) + dy = velocity * np.sin(ego_center_array[AgentIndex.HEADING]) + + raster_agents[raster_mask, 0] = 0.5 * (dx / max_velocity + 1) + raster_agents[raster_mask, 1] = 0.5 * (dy / max_velocity + 1) + + return SledgeVectorElement(vector_states, vector_labels), raster_agents + + +def process_static_objects( + static_objects: SledgeVectorElement, config: RVAEConfig +) -> Tuple[SledgeVectorElement, npt.NDArray[np.float32]]: + """ + TODO: Refactor + Processes the static object entities from raw vector format + - sort and interpolate nearest objects for fixed sized array + - rasterize objects in the two image channels + + :param static_objects: raw sledge vector element of static bounding boxes + :param config: config of sledge autoencoder + :return: tuple of processed objects elements and object channels + """ + + # 1. vectorized raw static objects (e.g. cap max number) + states_all = static_objects.states + vector_states = np.zeros((config.num_static_objects, StaticObjectIndex.size()), dtype=np.float32) + vector_labels = np.zeros(config.num_static_objects, dtype=bool) + + if len(states_all) > 0: + frame_mask = coords_in_frame(states_all[..., StaticObjectIndex.POINT], config.frame) + static_states_frame = states_all[frame_mask] + + distances = np.linalg.norm(static_states_frame[..., StaticObjectIndex.POINT], axis=-1) + argsort = np.argsort(distances)[: config.num_static_objects] + static_states_nearest = static_states_frame[argsort] + + vector_states[: len(static_states_nearest)] = static_states_nearest + vector_labels[: len(static_states_nearest)] = True + + # 2. rasterize static object bounding boxes + pixel_height, pixel_width = config.pixel_frame + raster_objects = np.zeros((pixel_height, pixel_width, 2), dtype=np.float32) + for agent_state in vector_states[vector_labels]: + # Get the 2D coordinate of the detected objects. + oriented_box = OrientedBox( + array_to_state_se2(agent_state[StaticObjectIndex.STATE_SE2]), + agent_state[StaticObjectIndex.LENGTH], + agent_state[StaticObjectIndex.WIDTH], + 1.0, # NOTE: dummy height + ) + raster_mask = raster_mask_oriented_box(oriented_box, config) + + # Calculate change in position + dx = np.cos(agent_state[StaticObjectIndex.HEADING]) + dy = np.sin(agent_state[StaticObjectIndex.HEADING]) + + raster_objects[raster_mask, 0] = 0.5 * (dx + 1) + raster_objects[raster_mask, 1] = 0.5 * (dy + 1) + + return SledgeVectorElement(vector_states, vector_labels), raster_objects + + +def find_consecutive_true_indices(mask: npt.NDArray[np.bool_]) -> List[npt.NDArray[np.int32]]: + """ + Helper function for line preprocessing. + For example, lines might exceed or return into frame. + Find regions in mask where line is consecutively in frame (ie. to split line) + + :param mask: 1D numpy array of booleans + :return: List of int32 arrays, where mask is consecutively true. + """ + + padded_mask = np.pad(np.asarray(mask), (1, 1), "constant", constant_values=False) + + changes = np.diff(padded_mask.astype(int)) + starts = np.where(changes == 1)[0] # indices of False -> True + ends = np.where(changes == -1)[0] # indices of True -> False + + return [np.arange(start, end) for start, end in zip(starts, ends)] diff --git a/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_line_feature.py b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_line_feature.py new file mode 100644 index 0000000..cebd9fd --- /dev/null +++ b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_line_feature.py @@ -0,0 +1,266 @@ +# TODO: Refactor +from typing import Dict, List, Tuple + +import numpy as np +import numpy.typing as npt +import networkx as nx + +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject +from nuplan.common.maps.abstract_map import AbstractMap, SemanticMapLayer +from nuplan.common.maps.maps_datatypes import TrafficLightStatusData, TrafficLightStatusType +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateSE2 +from nuplan.planning.training.preprocessing.features.trajectory_utils import convert_absolute_to_relative_poses + +from sledge.simulation.planner.pdm_planner.utils.pdm_path import PDMPath +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import array_to_states_se2 +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVectorElement + + +def get_lane_objects(ego_state: EgoState, map_api: AbstractMap, radius: float) -> List[LaneGraphEdgeMapObject]: + """ + Load all lanes in radius from map api. + :param ego_state: object of ego vehicle state in nuPlan + :param map_api: object of map in nuPlan + :param radius: radius of lanes to load + :return: list of lane objects + """ + layers = [SemanticMapLayer.LANE, SemanticMapLayer.LANE_CONNECTOR] + nearby_map_objects = map_api.get_proximal_map_objects( + layers=layers, + point=ego_state.center.point, + radius=radius, + ) + nearby_lanes = [] + for layer in layers: + nearby_lanes.extend(nearby_map_objects[layer]) + return nearby_lanes + + +def get_lane_graph( + ego_state: EgoState, map_api: AbstractMap, radius: float +) -> Tuple[nx.DiGraph, Dict[str, LaneGraphEdgeMapObject]]: + """ + Extract lane graph from map api as networkx directed graph + :param ego_state: object of ego vehicle state in nuPlan + :param map_api: object of map in nuPlan + :param radius: radius of lanes to load + :return: tuple of directed lane graph and dictionary of id's and lanes + """ + + lane_objects = get_lane_objects(ego_state, map_api, radius) + lane_dict = {lane.id: lane for lane in lane_objects} + + G = nx.DiGraph() + for lane_id, lane in lane_dict.items(): + G.add_node(lane_id) + + for lane_id, lane in lane_dict.items(): + for outgoing_lane in lane.outgoing_edges: + outgoing_lane_id = outgoing_lane.id + if outgoing_lane_id in lane_dict.keys(): + G.add_edge(lane_id, outgoing_lane_id) + + return G, lane_dict + + +def find_path_to_targets(graph: nx.DiGraph, start: str, targets: List[str]) -> List[str]: + """ + Find path from start to any target node (without crossing another target node). + :param graph: directed graph in networkx + :param start: id of start node + :param targets: list of id's as target nodes + :return: path from start to closest target + """ + + def _find_path_to_target(graph: nx.DiGraph, start: str, target: str) -> List[str]: + """Helper function to return path between start and target, if exists.""" + try: + path = nx.shortest_path(graph, start, target) + return path + except nx.NetworkXNoPath: + return [] + + for target in targets: + path_to_target = _find_path_to_target(graph, start, target) + if path_to_target: + # check if other target node was crossed) + if len(set(path_to_target) & set(targets)) == 1: + return path_to_target + + return [] + + +def get_discrete_path(lane_dict: Dict[str, LaneGraphEdgeMapObject], lane_ids: List[str]) -> List[StateSE2]: + """ + Helper function to convert lane path to discrete sequence of SE(2) samples. + :param lane_dict: dictionary of lane id and lane objects + :param lane_ids: list of lane id's in path + :return: list of SE(2) objects + """ + discrete_path = [] + for lane_id in lane_ids: + discrete_path.extend(lane_dict[lane_id].baseline_path.discrete_path) + return discrete_path + + +def get_edge_graph(ego_state: EgoState, map_api: AbstractMap, radius: float) -> List[PDMPath]: + """ + Lane graph preprocessing method, as described in the supplementary material. + Simplifies the formulation of a lane, by summarizing consecutive nodes, + that don't have multiple ingoing our outgoing lanes. + :param ego_state: object of ego vehicle state in nuPlan + :param map_api: object of map in nuPlan + :param radius: radius around ego for lanes to preprocess + :return: list of summarized lanes, as interpolatable paths + """ + + G_lane, lane_dict = get_lane_graph(ego_state, map_api, radius) + + # collect start and end lanes of edges + start_dict, stop_dict = {}, {} + for lane_id, lane in lane_dict.items(): + in_degree = G_lane.in_degree(lane_id) + out_degree = G_lane.out_degree(lane_id) + + if in_degree == 0 or in_degree > 1: + start_dict[lane_id] = lane + + if out_degree == 0 or out_degree > 1: + stop_dict[lane_id] = lane + + for lane_id, lane in lane_dict.items(): + is_predecessor_stop = len(set(G_lane.predecessors(lane_id)) & set(stop_dict.keys())) > 0 + if is_predecessor_stop: + start_dict[lane_id] = lane + + is_successors_start = len(set(G_lane.successors(lane_id)) & set(start_dict.keys())) > 0 + if is_successors_start: + stop_dict[lane_id] = lane + + paths_list: List[PDMPath] = [] + + # add nodes to line graph + for start_lane_id, start_lane in start_dict.items(): + path_to_targets = find_path_to_targets(G_lane, start_lane_id, list(stop_dict.keys())) + if path_to_targets: + discrete_path = get_discrete_path(lane_dict, path_to_targets) + local_discrete_path: npt.NDArray[np.float32] = convert_absolute_to_relative_poses( + ego_state.center, discrete_path + ) + within_radius = np.linalg.norm(local_discrete_path[..., :2], axis=-1) <= radius + local_discrete_path = local_discrete_path[within_radius] + + if len(local_discrete_path) < 2: + continue + + paths_list.append(PDMPath(list(array_to_states_se2(local_discrete_path)))) + + return paths_list + + +def get_traffic_light_discrete_paths( + traffic_light_data: TrafficLightStatusData, traffic_light_status: TrafficLightStatusType, map_api: AbstractMap +) -> List[List[StateSE2]]: + """ + Load traffic lights lines from map api. + :param traffic_light_data: dataclass of current traffic lights + :param traffic_light_status: status type to extract (ie. red or green) + :param map_api: object of map in nuPlan + :return: list of paths, as SE(2) sequence + """ + traffic_light_lanes: List[LaneGraphEdgeMapObject] = [] + for data in traffic_light_data: + if data.status == traffic_light_status: + lane_connector = map_api.get_map_object(str(data.lane_connector_id), SemanticMapLayer.LANE_CONNECTOR) + traffic_light_lanes.append(lane_connector) + + discrete_paths = [] + for lane in traffic_light_lanes: + discrete_paths.append(lane.baseline_path.discrete_path) + + return discrete_paths + + +def compute_path_features(interpolatable_paths: List[PDMPath], pose_interval: float) -> SledgeVectorElement: + """ + Compute raw vector element of lines. + :param interpolatable_paths: lines as interpolatable SE(2) paths + :param pose_interval: interval of poses to sample in meter + :return: raw sledge vector element for feature caching. + """ + line_poses_list: List[npt.NDArray[np.float32]] = [] + for edge_path in interpolatable_paths: + limit = ( + edge_path.length + pose_interval + if edge_path.length % pose_interval > pose_interval / 2 + else edge_path.length + ) + distances = np.arange(0, limit, step=pose_interval) + line_poses = edge_path.interpolate(distances, as_array=True) + line_poses_list.append(line_poses) + + if len(line_poses_list) > 0: + num_lines = len(line_poses_list) + max_poses = max([len(poses) for poses in line_poses_list]) + + lines = np.zeros((num_lines, max_poses, 3), dtype=np.float32) # (line, points, 3) + mask = np.zeros((num_lines, max_poses), dtype=bool) # (line, points) + + for line_idx, line_poses in enumerate(line_poses_list): + lines[line_idx, : len(line_poses)] = line_poses + mask[line_idx, : len(line_poses)] = True + else: + + lines = np.array([], dtype=np.float32) # (line, points, 3) + mask = np.array([], dtype=bool) # (line, points) + + return SledgeVectorElement(lines, mask) + + +def compute_line_features( + ego_state: EgoState, map_api: AbstractMap, radius: float, pose_interval: float +) -> SledgeVectorElement: + """ + Compute raw vector element of lines (ie. lanes in map). + :param ego_state: object of ego vehicle in nuPlan + :param map_api: object of map in nuPlan + :param radius: radius around ego for lanes to preprocess + :param pose_interval: interval of poses to sample in meter + :return: raw sledge vector element for feature caching. + """ + interpolatable_paths = get_edge_graph(ego_state, map_api, radius) + return compute_path_features(interpolatable_paths, pose_interval) + + +def compute_traffic_light_features( + ego_state: EgoState, + map_api: AbstractMap, + traffic_light_data: List[TrafficLightStatusData], + traffic_light_status: TrafficLightStatusType, + radius: float, + pose_interval: float, +) -> SledgeVectorElement: + """ + Compute raw vector element of traffic lights, given status type. + :param ego_state: object of ego vehicle in nuPlan + :param map_api: object of map in nuPlan + :param traffic_light_data: dataclass of traffic lights in nuPlan + :param traffic_light_status: status type to preprocess + :param radius: radius around ego for lanes to preprocess + :param pose_interval: interval of poses to sample in meter + :return: raw sledge vector element for feature caching + """ + discrete_paths = get_traffic_light_discrete_paths(traffic_light_data, traffic_light_status, map_api) + interpolatable_paths: List[PDMPath] = [] + for discrete_path in discrete_paths: + local_discrete_path: npt.NDArray[np.float32] = convert_absolute_to_relative_poses( + ego_state.center, discrete_path + ) + within_radius = np.linalg.norm(local_discrete_path[..., :2], axis=-1) <= radius + local_discrete_path = local_discrete_path[within_radius] + if len(local_discrete_path) < 2: + continue + interpolatable_paths.append(PDMPath(list(array_to_states_se2(local_discrete_path)))) + + return compute_path_features(interpolatable_paths, pose_interval) diff --git a/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_utils.py b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_utils.py new file mode 100644 index 0000000..f9f09e7 --- /dev/null +++ b/sledge/autoencoder/preprocessing/feature_builders/sledge/sledge_utils.py @@ -0,0 +1,84 @@ +# TODO: Move these functions for general use. +# eg. sledge.common.visualization + +from typing import Tuple + +import cv2 +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.oriented_box import OrientedBox +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeConfig + + +def raster_mask_oriented_box(oriented_box: OrientedBox, config: SledgeConfig) -> npt.NDArray[np.bool_]: + """ + Create raster mask if a oriented bounding box in BEV + :param oriented_box: class of a bounding box with heading + :param config: config dataclass of a sledge autoencoder + :return: creates raster mask of oriented bounding box + """ + + pixel_width, pixel_height = config.pixel_frame + corners = np.asarray([[corner.x, corner.y] for corner in oriented_box.all_corners()]) + corner_idcs = coords_to_pixel(corners, config.frame, config.pixel_size) + + # TODO: check if float32 is really necessary here. + raster_mask = np.zeros((pixel_width, pixel_height), dtype=np.float32) + cv2.fillPoly(raster_mask, [corner_idcs], color=1.0, lineType=cv2.LINE_AA) + + # NOTE: OpenCV has origin on top-left corner + raster_mask = np.rot90(raster_mask)[::-1] + return raster_mask > 0 + + +def coords_in_frame(coords: npt.NDArray[np.float32], frame: Tuple[float, float]) -> npt.NDArray[np.bool_]: + """ + Checks which coordinates are within the given 2D frame extend. + :param coords: coordinate array in numpy (x,y) in last axis + :param frame: tuple of frame extend in meter + :return: numpy array of boolean's + """ + assert coords.shape[-1] == 2, "Coordinate array must have last dim size of 2 (ie. x,y)" + width, height = frame + + within_width = np.logical_and(-width / 2 <= coords[..., 0], coords[..., 0] <= width / 2) + within_height = np.logical_and(-height / 2 <= coords[..., 1], coords[..., 1] <= height / 2) + + return np.logical_and(within_width, within_height) + + +def pixel_in_frame(pixel: npt.NDArray[np.int32], pixel_frame: Tuple[int, int]) -> npt.NDArray[np.bool_]: + """ + Checks if pixels indices are within the image. + :param pixel: pixel indices as numpy array + :param pixel_frame: tuple of raster width and height + :return: numpy array of boolean's + """ + assert pixel.shape[-1] == 2, "Coordinate array must have last dim size of 2 (ie. x,y)" + pixel_width, pixel_height = pixel_frame + + within_width = np.logical_and(0 <= pixel[..., 0], pixel[..., 0] < pixel_width) + within_height = np.logical_and(0 <= pixel[..., 1], pixel[..., 1] < pixel_height) + + return np.logical_and(within_width, within_height) + + +def coords_to_pixel( + coords: npt.NDArray[np.float32], frame: Tuple[float, float], pixel_size: float +) -> npt.NDArray[np.int32]: + """ + Converts ego-centric coordinates into pixel coordinates (ie. indices) + :param coords: coordinate array in numpy (x,y) in last axis + :param frame: tuple of frame extend in meter + :param pixel_size: size of a pixel + :return: indices of pixel coordinates + """ + assert coords.shape[-1] == 2 + + width, height = frame + pixel_width, pixel_height = int(width / pixel_size), int(height / pixel_size) + pixel_center = np.array([[pixel_width / 2.0, pixel_height / 2.0]]) + coords_idcs = (coords / pixel_size) + pixel_center + + return coords_idcs.astype(np.int32) diff --git a/sledge/autoencoder/preprocessing/feature_builders/sledge_raw_feature_builder.py b/sledge/autoencoder/preprocessing/feature_builders/sledge_raw_feature_builder.py new file mode 100644 index 0000000..fe25618 --- /dev/null +++ b/sledge/autoencoder/preprocessing/feature_builders/sledge_raw_feature_builder.py @@ -0,0 +1,168 @@ +from typing import List, Type +from shapely.geometry import Polygon + +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.planner.abstract_planner import PlannerInitialization, PlannerInput +from nuplan.common.maps.maps_datatypes import TrafficLightStatusData, TrafficLightStatusType, SemanticMapLayer +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import ( + AbstractFeatureBuilder, + AbstractModelFeature, +) + +from sledge.simulation.planner.pdm_planner.observation.pdm_occupancy_map import PDMOccupancyMap +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVectorRaw, SledgeConfig +from sledge.autoencoder.preprocessing.feature_builders.sledge.sledge_agent_feature import ( + compute_ego_features, + compute_agent_features, + compute_static_object_features, +) +from sledge.autoencoder.preprocessing.feature_builders.sledge.sledge_line_feature import ( + compute_line_features, + compute_traffic_light_features, +) + + +class SledgeRawFeatureBuilder(AbstractFeatureBuilder): + """Feature builder object for raw vector representation to train autoencoder in sledge""" + + def __init__(self, config: SledgeConfig): + """ + Initializes the feature builder. + :param config: configuration dataclass of autoencoder in sledge. + """ + self._config = config + + @classmethod + def get_feature_unique_name(cls) -> str: + """Inherited, see superclass.""" + return "sledge_raw" + + @classmethod + def get_feature_type(cls) -> Type[AbstractModelFeature]: + """Inherited, see superclass.""" + return SledgeVectorRaw + + def get_features_from_simulation( + self, current_input: PlannerInput, initialization: PlannerInitialization + ) -> SledgeVectorRaw: + """Inherited, see superclass.""" + + history = current_input.history + ego_state = history.ego_states[-1] + detections = history.observations[-1] + map_api = initialization.map_api + traffic_light_data = current_input.traffic_light_data + + return self._compute_features(ego_state, map_api, detections, traffic_light_data) + + def get_features_from_scenario(self, scenario: AbstractScenario) -> SledgeVectorRaw: + """Inherited, see superclass.""" + + ego_state = scenario.initial_ego_state + detections = scenario.initial_tracked_objects + map_api = scenario.map_api + traffic_light_data = list(scenario.get_traffic_light_status_at_iteration(0)) + + return self._compute_features(ego_state, map_api, detections, traffic_light_data) + + def _compute_features( + self, + ego_state: EgoState, + map_api: AbstractMap, + detections: DetectionsTracks, + traffic_light_data: List[TrafficLightStatusData], + ) -> SledgeVectorRaw: + """ + Compute raw vector feature for autoencoder training. + :param ego_state: object of ego vehicle state in nuPlan + :param map_api: object of map in nuPlan + :param detections: dataclass of detected objects in scenario + :param traffic_light_data: dataclass of traffic lights in nuPlan + :return: raw vector representation of lines, agents, objects, etc. + """ + + drivable_area_map = get_drivable_area_map(map_api, ego_state, self._config.radius) + + lines = compute_line_features( + ego_state, + map_api, + self._config.radius, + self._config.pose_interval, + ) + vehicles = compute_agent_features( + ego_state, + detections, + TrackedObjectType.VEHICLE, + self._config.radius, + drivable_area_map, + ) + pedestrians = compute_agent_features( + ego_state, + detections, + TrackedObjectType.PEDESTRIAN, + self._config.radius, + ) + static_objects = compute_static_object_features( + ego_state, + detections, + self._config.radius, + # drivable_area_map, + ) + green_lights = compute_traffic_light_features( + ego_state, + map_api, + traffic_light_data, + TrafficLightStatusType.GREEN, + self._config.radius, + self._config.pose_interval, + ) + red_lights = compute_traffic_light_features( + ego_state, + map_api, + traffic_light_data, + TrafficLightStatusType.RED, + self._config.radius, + self._config.pose_interval, + ) + ego = compute_ego_features(ego_state) + + return SledgeVectorRaw(lines, vehicles, pedestrians, static_objects, green_lights, red_lights, ego) + + +def get_drivable_area_map( + map_api: AbstractMap, + ego_state: EgoState, + map_radius: float, + map_layers: List[SemanticMapLayer] = [ + SemanticMapLayer.ROADBLOCK, + SemanticMapLayer.INTERSECTION, + ], +) -> PDMOccupancyMap: + """ + Helper function to create occupancy map of road polygons. + :param map_api: object of map in nuPlan + :param ego_state: object of ego vehicle state in nuPlan + :param map_radius: radius around the ego vehicle to load polygons + :param map_layers: layers to load, defaults to [ SemanticMapLayer.ROADBLOCK, SemanticMapLayer.INTERSECTION, ] + :return: occupancy map from PDM + """ + # query all drivable map elements around ego position + drivable_area = map_api.get_proximal_map_objects(ego_state.center.point, map_radius, map_layers) + + # collect lane polygons in list, save on-route indices + drivable_polygons: List[Polygon] = [] + drivable_polygon_ids: List[str] = [] + + for layer in [SemanticMapLayer.ROADBLOCK, SemanticMapLayer.INTERSECTION]: + for layer_object in drivable_area[layer]: + drivable_polygons.append(layer_object.polygon) + drivable_polygon_ids.append(layer_object.id) + + # create occupancy map with lane polygons + drivable_area_map = PDMOccupancyMap(drivable_polygon_ids, drivable_polygons) + + return drivable_area_map diff --git a/sledge/autoencoder/preprocessing/features/__init__.py b/sledge/autoencoder/preprocessing/features/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/preprocessing/features/latent_feature.py b/sledge/autoencoder/preprocessing/features/latent_feature.py new file mode 100644 index 0000000..5e80e29 --- /dev/null +++ b/sledge/autoencoder/preprocessing/features/latent_feature.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import Any, Dict, List +from dataclasses import dataclass +import torch + +from nuplan.planning.script.builders.utils.utils_type import validate_type +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import AbstractModelFeature +from nuplan.planning.training.preprocessing.features.abstract_model_feature import FeatureDataType, to_tensor + + +@dataclass +class Latent(AbstractModelFeature): + """Feature class of latent variable.""" + + mu: FeatureDataType + log_var: FeatureDataType + + def to_device(self, device: torch.device) -> Latent: + """Implemented. See interface.""" + validate_type(self.mu, torch.Tensor) + validate_type(self.log_var, torch.Tensor) + return Latent(mu=self.mu.to(device=device), log_var=self.log_var.to(device=device)) + + def to_feature_tensor(self) -> Latent: + """Inherited, see superclass.""" + return Latent(mu=to_tensor(self.mu), log_var=to_tensor(self.log_var)) + + @classmethod + def deserialize(cls, data: Dict[str, Any]) -> Latent: + """Implemented. See interface.""" + return Latent(mu=data["mu"], log_var=data["log_var"]) + + def unpack(self) -> List[Latent]: + """Implemented. See interface.""" + return [Latent(mu, log_var) for mu, log_var in zip(self.mu, self.log_var)] + + def torch_to_numpy(self) -> Latent: + """Helper method to convert feature from torch tensor to numpy array.""" + return Latent(mu=self.mu.detach().cpu().numpy(), log_var=self.log_var.detach().cpu().numpy()) + + def squeeze(self) -> Latent: + """Helper method to apply .squeeze() on features.""" + return Latent(mu=self.mu.squeeze(0), log_var=self.log_var.squeeze(0)) diff --git a/sledge/autoencoder/preprocessing/features/map_id_feature.py b/sledge/autoencoder/preprocessing/features/map_id_feature.py new file mode 100644 index 0000000..1551fb6 --- /dev/null +++ b/sledge/autoencoder/preprocessing/features/map_id_feature.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Dict, List + +import torch + +from nuplan.planning.script.builders.utils.utils_type import validate_type +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import AbstractModelFeature +from nuplan.planning.training.preprocessing.features.abstract_model_feature import FeatureDataType, to_tensor + +MAP_NAME_ID_ABBR = [ + (0, "us-nv-las-vegas-strip", "LAV"), + (1, "us-pa-pittsburgh-hazelwood", "PGH"), + (2, "sg-one-north", "SGP"), + (3, "us-ma-boston", "BOS"), +] + +MAP_NAME_TO_ID = {name: id for id, name, abbr in MAP_NAME_ID_ABBR} +MAP_ID_TO_NAME = {id: name for id, name, abbr in MAP_NAME_ID_ABBR} +MAP_ID_TO_ABBR = {id: abbr for id, name, abbr in MAP_NAME_ID_ABBR} + + +@dataclass +class MapID(AbstractModelFeature): + """Feature class of to store map id.""" + + id: FeatureDataType + + def to_device(self, device: torch.device) -> MapID: + """Implemented. See interface.""" + validate_type(self.id, torch.Tensor) + return MapID(id=self.id.to(device=device)) + + def to_feature_tensor(self) -> MapID: + """Inherited, see superclass.""" + return MapID(id=to_tensor(self.id)) + + @classmethod + def deserialize(cls, data: Dict[str, Any]) -> MapID: + """Implemented. See interface.""" + return MapID(id=data["id"]) + + def unpack(self) -> List[MapID]: + """Implemented. See interface.""" + return [MapID(id) for id in zip(self.id)] + + def torch_to_numpy(self) -> MapID: + """Helper method to convert feature from torch tensor to numpy array.""" + return MapID(id=self.id.detach().cpu().numpy()) + + def squeeze(self) -> MapID: + """Helper method to apply .squeeze() on features.""" + return MapID(id=self.squeeze(0)) diff --git a/sledge/autoencoder/preprocessing/features/rvae_matching_feature.py b/sledge/autoencoder/preprocessing/features/rvae_matching_feature.py new file mode 100644 index 0000000..39b75bd --- /dev/null +++ b/sledge/autoencoder/preprocessing/features/rvae_matching_feature.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Dict, List +import torch + +from nuplan.planning.script.builders.utils.utils_type import validate_type +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import AbstractModelFeature +from nuplan.planning.training.preprocessing.features.abstract_model_feature import FeatureDataType, to_tensor + + +@dataclass +class RVAEMatchingFeature(AbstractModelFeature): + """Feature class to score matched entities during RVAE training.""" + + indices: FeatureDataType + + def to_device(self, device: torch.device) -> RVAEMatchingFeature: + """Implemented. See interface.""" + validate_type(self.indices, torch.Tensor) + return RVAEMatchingFeature(indices=self.indices.to(device=device)) + + def to_feature_tensor(self) -> RVAEMatchingFeature: + """Inherited, see superclass.""" + return RVAEMatchingFeature(indices=to_tensor(self.indices)) + + @classmethod + def deserialize(cls, data: Dict[str, Any]) -> RVAEMatchingFeature: + """Implemented. See interface.""" + return RVAEMatchingFeature(indices=data["indices"]) + + def unpack(self) -> List[RVAEMatchingFeature]: + """Implemented. See interface.""" + return [RVAEMatchingFeature(indices) for indices in zip(self.indices)] diff --git a/sledge/autoencoder/preprocessing/features/sledge_raster_feature.py b/sledge/autoencoder/preprocessing/features/sledge_raster_feature.py new file mode 100644 index 0000000..8270a6a --- /dev/null +++ b/sledge/autoencoder/preprocessing/features/sledge_raster_feature.py @@ -0,0 +1,253 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Dict, List, Union, Optional + +import torch +import torchvision +from torch import Tensor + +import numpy as np +from numpy import ndarray + +from nuplan.planning.script.builders.utils.utils_type import validate_type +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import AbstractModelFeature +from nuplan.planning.training.preprocessing.features.abstract_model_feature import FeatureDataType + + +@dataclass +class SledgeRaster(AbstractModelFeature): + """Feature class of raster in sledge.""" + + data: FeatureDataType + + @property + def num_batches(self) -> Optional[int]: + """Number of batches in the feature.""" + return None if len(self.data.shape) < 4 else self.data.shape[0] + + def to_feature_tensor(self) -> AbstractModelFeature: + """Implemented. See interface.""" + to_tensor_torchvision = torchvision.transforms.ToTensor() + data = to_tensor_torchvision(np.asarray(self.data)) + return SledgeRaster(data=data) + + def to_device(self, device: torch.device) -> SledgeRaster: + """Implemented. See interface.""" + validate_type(self.data, torch.Tensor) + return SledgeRaster(data=self.data.to(device=device)) + + @classmethod + def deserialize(cls, data: Dict[str, Any]) -> SledgeRaster: + """Implemented. See interface.""" + return SledgeRaster(data=data["data"]) + + def unpack(self) -> List[SledgeRaster]: + """Implemented. See interface.""" + return [SledgeRaster(data[None]) for data in self.data] + + @staticmethod + def from_feature_tensor(tensor: torch.Tensor) -> SledgeRaster: + """Implemented. See interface.""" + array = tensor.numpy() + + # So can assume that the torch tensor will always be channels first + # and the numpy array will always be channels last. + # So this moves the channels to last when reading the torch tensor + if len(array.shape) == 4: + array = array.transpose(0, 2, 3, 1) + else: + array = array.transpose(1, 2, 0) + + return SledgeRaster(array) + + @property + def width(self) -> int: + """ + :return: the width of a raster + """ + return self.data.shape[-2] if self._is_channels_last() else self.data.shape[-1] # type: ignore + + @property + def height(self) -> int: + """ + :return: the height of a raster + """ + return self.data.shape[-3] if self._is_channels_last() else self.data.shape[-2] # type: ignore + + def num_channels(self) -> int: + """ + Number of raster channels. + """ + return self.data.shape[-1] if self._is_channels_last() else self.data.shape[-3] # type: ignore + + @property + def lines_layer(self) -> FeatureDataType: + return self._get_data_channel(SledgeRasterIndex.LINE) + + @property + def vehicles_layer(self) -> FeatureDataType: + return self._get_data_channel(SledgeRasterIndex.VEHICLE) + + @property + def pedestrians_layer(self) -> FeatureDataType: + return self._get_data_channel(SledgeRasterIndex.PEDESTRIAN) + + @property + def static_objects_layer(self) -> FeatureDataType: + return self._get_data_channel(SledgeRasterIndex.STATIC_OBJECT) + + @property + def green_lights_layer(self) -> FeatureDataType: + return self._get_data_channel(SledgeRasterIndex.GREEN_LIGHT) + + @property + def red_lights_layer(self) -> FeatureDataType: + return self._get_data_channel(SledgeRasterIndex.RED_LIGHT) + + def _is_channels_last(self) -> bool: + """ + Check location of channel dimension + :return True if position [-1] is the number of channels + """ + # For tensor, channel is put right before the spatial dimention. + if isinstance(self.data, Tensor): + return False + + # The default numpy array data format is channel last. + elif isinstance(self.data, ndarray): + return True + else: + raise RuntimeError( + f"The data needs to be either numpy array or torch Tensor, but got type(data): {type(self.data)}" + ) + + def _get_data_channel(self, index: Union[int, slice]) -> FeatureDataType: + """ + Extract channel data + :param index: of layer + :return: data corresponding to layer + """ + if self._is_channels_last(): + return self.data[..., index] + else: + return self.data[..., index, :, :] + + +class SledgeRasterIndex: + """Index Enum of raster channel in SledgeRaster.""" + + _LINE_X = 0 + _LINE_Y = 1 + _VEHICLE_X = 2 + _VEHICLE_Y = 3 + _PEDESTRIAN_X = 4 + _PEDESTRIAN_Y = 5 + _STATIC_OBJECT_X = 6 + _STATIC_OBJECT_Y = 7 + _GREEN_LIGHT_X = 8 + _GREEN_LIGHT_Y = 9 + _RED_LIGHT_X = 10 + _RED_LIGHT_Y = 11 + + @classmethod + def size(cls) -> int: + """ + :return: number of channels + """ + valid_attributes = [ + attribute + for attribute in dir(cls) + if attribute.startswith("_") and not attribute.startswith("__") and not callable(getattr(cls, attribute)) + ] + return len(valid_attributes) + + @classmethod + @property + def LINE_X(cls): + return cls._LINE_X + + @classmethod + @property + def LINE_Y(cls): + return cls._LINE_Y + + @classmethod + @property + def LINE(cls): + return slice(cls._LINE_X, cls._LINE_Y + 1) + + @classmethod + @property + def VEHICLE_X(cls): + return cls._VEHICLE_X + + @classmethod + @property + def VEHICLE_Y(cls): + return cls._VEHICLE_Y + + @classmethod + @property + def VEHICLE(cls): + return slice(cls._VEHICLE_X, cls._VEHICLE_Y + 1) + + @classmethod + @property + def PEDESTRIAN_X(cls): + return cls._PEDESTRIAN_X + + @classmethod + @property + def PEDESTRIAN_Y(cls): + return cls._PEDESTRIAN_Y + + @classmethod + @property + def PEDESTRIAN(cls): + return slice(cls._PEDESTRIAN_X, cls._PEDESTRIAN_Y + 1) + + @classmethod + @property + def STATIC_OBJECT_X(cls): + return cls._STATIC_OBJECT_X + + @classmethod + @property + def STATIC_OBJECT_Y(cls): + return cls._STATIC_OBJECT_Y + + @classmethod + @property + def STATIC_OBJECT(cls): + return slice(cls._STATIC_OBJECT_X, cls._STATIC_OBJECT_Y + 1) + + @classmethod + @property + def GREEN_LIGHT_X(cls): + return cls._GREEN_LIGHT_X + + @classmethod + @property + def GREEN_LIGHT_Y(cls): + return cls._GREEN_LIGHT_Y + + @classmethod + @property + def GREEN_LIGHT(cls): + return slice(cls._GREEN_LIGHT_X, cls._GREEN_LIGHT_Y + 1) + + @classmethod + @property + def RED_LIGHT_X(cls): + return cls._RED_LIGHT_X + + @classmethod + @property + def RED_LIGHT_Y(cls): + return cls._RED_LIGHT_Y + + @classmethod + @property + def RED_LIGHT(cls): + return slice(cls._RED_LIGHT_X, cls._RED_LIGHT_Y + 1) diff --git a/sledge/autoencoder/preprocessing/features/sledge_vector_feature.py b/sledge/autoencoder/preprocessing/features/sledge_vector_feature.py new file mode 100644 index 0000000..5589097 --- /dev/null +++ b/sledge/autoencoder/preprocessing/features/sledge_vector_feature.py @@ -0,0 +1,437 @@ +from __future__ import annotations + +import torch + +from dataclasses import dataclass +from typing import Any, Dict, List, Tuple, Union +from enum import Enum + +from nuplan.planning.script.builders.utils.utils_type import validate_type +from nuplan.planning.training.preprocessing.features.abstract_model_feature import FeatureDataType, to_tensor +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import AbstractModelFeature + + +@dataclass +class SledgeConfig: + """General autoencoder config, for SledgeVector(Raw) features.""" + + # 1. features raw + radius: int = 100 + pose_interval: int = 1.0 + + # 2. features raster & vector + frame: Tuple[int, int] = (64, 64) + + num_lines: int = 50 + num_vehicles: int = 50 + num_pedestrians: int = 20 + num_static_objects: int = 30 + num_green_lights: int = 20 + num_red_lights: int = 20 + + num_line_poses: int = 20 + vehicle_max_velocity: float = 15 + pedestrian_max_velocity: float = 2 + + pixel_size: float = 0.25 + line_dots_radius: int = 0 + + # output + threshold: float = 0.3 + + def __post_init__(self): + assert 0 <= self.threshold <= 1, "Config threshold must be in [0,1]" + + @property + def pixel_frame(self) -> Tuple[int, int]: + frame_width, frame_height = self.frame + return int(frame_width / self.pixel_size), int(frame_height / self.pixel_size) + + +@dataclass +class SledgeVector(AbstractModelFeature): + """Feature class of complete vector representation in sledge.""" + + lines: SledgeVectorElement + vehicles: SledgeVectorElement + pedestrians: SledgeVectorElement + static_objects: SledgeVectorElement + green_lights: SledgeVectorElement + red_lights: SledgeVectorElement + ego: SledgeVectorElement + + def to_device(self, device: torch.device) -> SledgeVector: + """Implemented. See interface.""" + return SledgeVector( + lines=self.lines.to_device(device=device), + vehicles=self.vehicles.to_device(device=device), + pedestrians=self.pedestrians.to_device(device=device), + static_objects=self.static_objects.to_device(device=device), + green_lights=self.green_lights.to_device(device=device), + red_lights=self.red_lights.to_device(device=device), + ego=self.ego.to_device(device=device), + ) + + def to_feature_tensor(self) -> SledgeVector: + """Inherited, see superclass.""" + return SledgeVector( + lines=self.lines.to_feature_tensor(), + vehicles=self.vehicles.to_feature_tensor(), + pedestrians=self.pedestrians.to_feature_tensor(), + static_objects=self.static_objects.to_feature_tensor(), + green_lights=self.green_lights.to_feature_tensor(), + red_lights=self.red_lights.to_feature_tensor(), + ego=self.ego.to_feature_tensor(), + ) + + @classmethod + def deserialize(cls, data: Dict[str, Any]) -> SledgeVector: + """Implemented. See interface.""" + return SledgeVector( + lines=SledgeVectorElement.deserialize(data["lines"]), + vehicles=SledgeVectorElement.deserialize(data["vehicles"]), + pedestrians=SledgeVectorElement.deserialize(data["pedestrians"]), + static_objects=SledgeVectorElement.deserialize(data["static_objects"]), + green_lights=SledgeVectorElement.deserialize(data["green_lights"]), + red_lights=SledgeVectorElement.deserialize(data["red_lights"]), + ego=SledgeVectorElement.deserialize(data["ego"]), + ) + + def unpack(self) -> List[SledgeVector]: + """Implemented. See interface.""" + return [ + SledgeVector(lines, vehicles, pedestrians, static_objects, green_lights, red_lights, ego) + for lines, vehicles, pedestrians, static_objects, green_lights, red_lights, ego in zip( + self.lines.unpack(), + self.vehicles.unpack(), + self.pedestrians.unpack(), + self.static_objects.unpack(), + self.green_lights.unpack(), + self.red_lights.unpack(), + self.ego.unpack(), + ) + ] + + @classmethod + def collate(cls, batch: List[SledgeVector]) -> SledgeVector: + """ + Implemented. See interface. + Collates a list of features that each have batch size of 1. + """ + return SledgeVector( + lines=SledgeVectorElement.collate([item.lines for item in batch]), + vehicles=SledgeVectorElement.collate([item.vehicles for item in batch]), + pedestrians=SledgeVectorElement.collate([item.pedestrians for item in batch]), + static_objects=SledgeVectorElement.collate([item.static_objects for item in batch]), + green_lights=SledgeVectorElement.collate([item.green_lights for item in batch]), + red_lights=SledgeVectorElement.collate([item.red_lights for item in batch]), + ego=SledgeVectorElement.collate([item.ego for item in batch]), + ) + + def torch_to_numpy(self, apply_sigmoid: bool = True) -> SledgeVector: + """Helper method to convert feature from torch tensor to numpy array.""" + return SledgeVector( + lines=self.lines.torch_to_numpy(apply_sigmoid), + vehicles=self.vehicles.torch_to_numpy(apply_sigmoid), + pedestrians=self.pedestrians.torch_to_numpy(apply_sigmoid), + static_objects=self.static_objects.torch_to_numpy(apply_sigmoid), + green_lights=self.green_lights.torch_to_numpy(apply_sigmoid), + red_lights=self.red_lights.torch_to_numpy(apply_sigmoid), + ego=self.ego.torch_to_numpy(apply_sigmoid), + ) + + +@dataclass +class SledgeVectorRaw(SledgeVector): + """Feature class of raw vector representation, for feature caching.""" + + # NOTE: Placeholder class for type hints + pass + + +@dataclass +class SledgeVectorElement(AbstractModelFeature): + """Feature class individual vector element, eg. line, vehicle, etc.""" + + states: FeatureDataType + mask: FeatureDataType + + def to_device(self, device: torch.device) -> SledgeVectorElement: + """Implemented. See interface.""" + validate_type(self.states, torch.Tensor) + validate_type(self.mask, torch.Tensor) + return SledgeVectorElement(states=self.states.to(device=device), mask=self.mask.to(device=device)) + + def to_feature_tensor(self) -> SledgeVectorElement: + """Inherited, see superclass.""" + return SledgeVectorElement(states=to_tensor(self.states), mask=to_tensor(self.mask)) + + @classmethod + def deserialize(cls, data: Dict[str, Any]) -> SledgeVectorElement: + """Implemented. See interface.""" + return SledgeVectorElement(states=data["states"], mask=data["mask"]) + + def unpack(self) -> List[SledgeVectorElement]: + """Implemented. See interface.""" + return [SledgeVectorElement(states[None], mask[None]) for states, mask in zip(self.states, self.mask)] + + def torch_to_numpy(self, apply_sigmoid: bool = True) -> SledgeVectorElement: + """Helper method to convert feature from torch tensor to numpy array.""" + _mask = self.mask.sigmoid() if apply_sigmoid else self.mask + return SledgeVectorElement( + states=self.states.squeeze(dim=0).detach().cpu().numpy(), + mask=_mask.squeeze(dim=0).detach().cpu().numpy(), + ) + + def get_element_type(self) -> SledgeVectorElementType: + """Helper method to get type of vector element.""" + # NOTE: assumes types have different state sizes. + n_dim = self.states.shape[-1] + if n_dim == LineIndex.size(): + return SledgeVectorElementType.LINE + elif n_dim == AgentIndex.size(): + return SledgeVectorElementType.AGENT + elif n_dim == StaticObjectIndex.size(): + return SledgeVectorElementType.STATIC + elif n_dim == EgoIndex.size(): + return SledgeVectorElementType.EGO + else: + raise ValueError("SledgeVectorElement cannot be matched to types in SledgeVectorElementType!") + + def get_element_index(self) -> Union[LineIndex, AgentIndex, StaticObjectIndex, EgoIndex]: + """Helper method to get index enum of vector element.""" + element_type = self.get_element_type() + if element_type == SledgeVectorElementType.LINE: + return LineIndex + elif element_type == SledgeVectorElementType.AGENT: + return AgentIndex + elif element_type == SledgeVectorElementType.STATIC: + return StaticObjectIndex + elif element_type == SledgeVectorElementType.EGO: + return EgoIndex + else: + raise ValueError("SledgeVectorElement cannot be matched to types in SledgeVectorElementType!") + + +class SledgeVectorElementType(Enum): + """Enum of vector element types.""" + + LINE = 0 + AGENT = 1 + STATIC = 2 + EGO = 3 + + +class LineIndex: + """Index Enum of line states in SledgeVectorElement.""" + + _X = 0 + _Y = 1 + + @classmethod + def size(cls) -> int: + """ + :return: number of channels + """ + valid_attributes = [ + attribute + for attribute in dir(cls) + if attribute.startswith("_") and not attribute.startswith("__") and not callable(getattr(cls, attribute)) + ] + return len(valid_attributes) + + @classmethod + @property + def X(cls): + return cls._X + + @classmethod + @property + def Y(cls): + return cls._Y + + @classmethod + @property + def POINT(cls): + # NOTE: assumes X, Y have subsequent indices + return slice(cls._X, cls._Y + 1) + + +class AgentIndex: + """Index Enum of vehicles and pedestrian states in SledgeVectorElement.""" + + _X = 0 + _Y = 1 + _HEADING = 2 + _WIDTH = 3 + _LENGTH = 4 + _VELOCITY = 5 + + @classmethod + def size(cls) -> int: + """ + :return: number of channels + """ + valid_attributes = [ + attribute + for attribute in dir(cls) + if attribute.startswith("_") and not attribute.startswith("__") and not callable(getattr(cls, attribute)) + ] + return len(valid_attributes) + + @classmethod + @property + def X(cls): + return cls._X + + @classmethod + @property + def Y(cls): + return cls._Y + + @classmethod + @property + def HEADING(cls): + return cls._HEADING + + @classmethod + @property + def WIDTH(cls): + return cls._WIDTH + + @classmethod + @property + def LENGTH(cls): + return cls._LENGTH + + @classmethod + @property + def VELOCITY(cls): + return cls._VELOCITY + + @classmethod + @property + def POINT(cls): + # NOTE: assumes X, Y have subsequent indices + return slice(cls._X, cls._Y + 1) + + @classmethod + @property + def STATE_SE2(cls): + # NOTE: assumes X, Y, HEADING have subsequent indices + return slice(cls._X, cls._HEADING + 1) + + +class StaticObjectIndex: + """Index Enum of static object states in SledgeVectorElement.""" + + _X = 0 + _Y = 1 + _HEADING = 2 + _WIDTH = 3 + _LENGTH = 4 + + @classmethod + def size(cls) -> int: + """ + :return: number of channels + """ + valid_attributes = [ + attribute + for attribute in dir(cls) + if attribute.startswith("_") and not attribute.startswith("__") and not callable(getattr(cls, attribute)) + ] + return len(valid_attributes) + + @classmethod + @property + def X(cls): + return cls._X + + @classmethod + @property + def Y(cls): + return cls._Y + + @classmethod + @property + def HEADING(cls): + return cls._HEADING + + @classmethod + @property + def WIDTH(cls): + return cls._WIDTH + + @classmethod + @property + def LENGTH(cls): + return cls._LENGTH + + @classmethod + @property + def POINT(cls): + # NOTE: assumes X, Y have subsequent indices + return slice(cls._X, cls._Y + 1) + + @classmethod + @property + def STATE_SE2(cls): + # NOTE: assumes X, Y, HEADING have subsequent indices + return slice(cls._X, cls._HEADING + 1) + + +class EgoIndex: + """Index Enum of ego state in SledgeVectorElement.""" + + _VELOCITY_X = 0 + _VELOCITY_Y = 1 + _ACCELERATION_X = 2 + _ACCELERATION_Y = 3 + + @classmethod + def size(cls) -> int: + """ + :return: number of channels + """ + valid_attributes = [ + attribute + for attribute in dir(cls) + if attribute.startswith("_") and not attribute.startswith("__") and not callable(getattr(cls, attribute)) + ] + return len(valid_attributes) + + @classmethod + @property + def VELOCITY_X(cls): + return cls._VELOCITY_X + + @classmethod + @property + def VELOCITY_Y(cls): + return cls._VELOCITY_Y + + @classmethod + @property + def ACCELERATION_X(cls): + return cls._ACCELERATION_X + + @classmethod + @property + def ACCELERATION_Y(cls): + return cls._ACCELERATION_Y + + @classmethod + @property + def VELOCITY_2D(cls): + # assumes velocity X, Y have subsequent indices + return slice(cls._VELOCITY_X, cls._VELOCITY_Y + 1) + + @classmethod + @property + def ACCELERATION_2D(cls): + # assumes acceleration X, Y have subsequent indices + return slice(cls._ACCELERATION_X, cls._ACCELERATION_Y + 1) + + +BoundingBoxIndex = Union[AgentIndex, StaticObjectIndex] diff --git a/sledge/autoencoder/preprocessing/target_builders/__init__.py b/sledge/autoencoder/preprocessing/target_builders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/autoencoder/preprocessing/target_builders/map_id_target_builder.py b/sledge/autoencoder/preprocessing/target_builders/map_id_target_builder.py new file mode 100644 index 0000000..4f3a493 --- /dev/null +++ b/sledge/autoencoder/preprocessing/target_builders/map_id_target_builder.py @@ -0,0 +1,28 @@ +from typing import Type +import numpy as np + +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.training.preprocessing.feature_builders.abstract_feature_builder import AbstractModelFeature +from nuplan.planning.training.preprocessing.target_builders.abstract_target_builder import AbstractTargetBuilder + +from sledge.autoencoder.preprocessing.features.map_id_feature import MapID, MAP_NAME_TO_ID + + +class MapIDTargetBuilder(AbstractTargetBuilder): + def __init__(self) -> None: + pass + + @classmethod + def get_feature_unique_name(cls) -> str: + """Inherited, see superclass.""" + return "map_id" + + @classmethod + def get_feature_type(cls) -> Type[AbstractModelFeature]: + """Inherited, see superclass.""" + return MapID + + def get_targets(self, scenario: AbstractScenario) -> MapID: + """Inherited, see superclass.""" + id = np.array(MAP_NAME_TO_ID[scenario.map_api.map_name], dtype=np.int64) + return MapID(id) diff --git a/sledge/common/__init__.py b/sledge/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/common/helper/__init__.py b/sledge/common/helper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/common/helper/cache_helper.py b/sledge/common/helper/cache_helper.py new file mode 100644 index 0000000..ae14038 --- /dev/null +++ b/sledge/common/helper/cache_helper.py @@ -0,0 +1,24 @@ +from typing import List +from pathlib import Path + + +def find_feature_paths(root_path: Path, feature_name: str) -> List[Path]: + """ + Simple helper function, collecting all available gzip files in a cache. + :param root_path: path of feature cache + :param feature_name: name of feature, excluding file ending + :return: list of paths + """ + + # TODO: move somewhere else + file_paths: List[Path] = [] + for log_path in root_path.iterdir(): + if log_path.name == "metadata": + continue + for scenario_type_path in log_path.iterdir(): + for token_path in scenario_type_path.iterdir(): + feature_path = token_path / f"{feature_name}.gz" + if feature_path.is_file(): + file_paths.append(token_path / feature_name) + + return file_paths diff --git a/sledge/common/visualization/__init__.py b/sledge/common/visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/common/visualization/sledge_colors.py b/sledge/common/visualization/sledge_colors.py new file mode 100644 index 0000000..8c455d9 --- /dev/null +++ b/sledge/common/visualization/sledge_colors.py @@ -0,0 +1,288 @@ +from dataclasses import dataclass +from typing import Any, Dict, Tuple +from PIL import ImageColor + +from nuplan.common.maps.abstract_map import SemanticMapLayer +from nuplan.common.maps.maps_datatypes import TrafficLightStatusType +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType + + +@dataclass +class Color: + """Dataclass for storing hex colors.""" + + hex: str + + @property + def rgb(self) -> Tuple[int, int, int]: + """ + :return: hex color as RBG tuple + """ + return ImageColor.getcolor(self.hex, "RGB") + + @property + def rgba(self) -> Tuple[int, int, int]: + """ + :return: hex color as RBGA tuple + """ + return ImageColor.getcolor(self.hex, "RGBA") + + +BLACK: Color = Color("#000000") +WHITE: Color = Color("#FFFFFF") +LIGHT_GREY: Color = Color("#D3D3D3") + +TAB_10: Dict[int, Color] = { + 0: Color("#1f77b4"), # blue + 1: Color("#ff7f0e"), # orange + 2: Color("#2ca02c"), # green + 3: Color("#d62728"), # red + 4: Color("#9467bd"), # violet + 5: Color("#8c564b"), # brown + 6: Color("#e377c2"), # pink + 7: Color("#7f7f7f"), # grey + 8: Color("#bcbd22"), # yellow + 9: Color("#17becf"), # cyan +} + + +NEW_TAB_10: Dict[int, str] = { + 0: Color("#4e79a7"), # blue + 1: Color("#f28e2b"), # orange + 2: Color("#e15759"), # red + 3: Color("#76b7b2"), # cyan + 4: Color("#59a14f"), # green + 5: Color("#edc948"), # yellow + 6: Color("#b07aa1"), # violet + 7: Color("#ff9da7"), # pink-ish + 8: Color("#9c755f"), # brown + 9: Color("#bab0ac"), # grey +} + + +ELLIS_5: Dict[int, str] = { + 0: Color("#DE7061"), # red + 1: Color("#B0E685"), # green + 2: Color("#4AC4BD"), # cyan + 3: Color("#E38C47"), # orange + 4: Color("#699CDB"), # blue +} + + +SLEDGE_ELEMENTS: Dict[SemanticMapLayer, Color] = { + "lines": Color("#666666"), + "vehicles": ELLIS_5[4], + "pedestrians": NEW_TAB_10[6], + "static_objects": NEW_TAB_10[5], + "green_lights": TAB_10[2], + "red_lights": TAB_10[3], + "ego": ELLIS_5[0], +} + +MAP_LAYER_CONFIG: Dict[SemanticMapLayer, Any] = { + SemanticMapLayer.LANE: { + "fill_color": LIGHT_GREY, + "fill_color_alpha": 1.0, + "line_color": LIGHT_GREY, + "line_color_alpha": 0.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.WALKWAYS: { + "fill_color": "#d4d19e", + "fill_color_alpha": 1.0, + "line_color": "#d4d19e", + "line_color_alpha": 0.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.CARPARK_AREA: { + "fill_color": Color("#b9d3b4"), + "fill_color_alpha": 1.0, + "line_color": Color("#b9d3b4"), + "line_color_alpha": 0.0, + "line_width": 0.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.PUDO: { + "fill_color": Color("#AF75A7"), + "fill_color_alpha": 0.3, + "line_color": Color("#AF75A7"), + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.INTERSECTION: { + "fill_color": Color("#D3D3D3"), + "fill_color_alpha": 1.0, + "line_color": Color("#D3D3D3"), + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.STOP_LINE: { + "fill_color": Color("#FF0101"), + "fill_color_alpha": 0.0, + "line_color": Color("#FF0101"), + "line_color_alpha": 0.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.CROSSWALK: { + "fill_color": NEW_TAB_10[6], + "fill_color_alpha": 0.3, + "line_color": NEW_TAB_10[6], + "line_color_alpha": 0.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.ROADBLOCK: { + "fill_color": Color("#0000C0"), + "fill_color_alpha": 0.2, + "line_color": Color("#0000C0"), + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, + SemanticMapLayer.BASELINE_PATHS: { + "line_color": Color("#666666"), + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "--", + "zorder": 1, + }, + SemanticMapLayer.LANE_CONNECTOR: { + "line_color": Color("#CBCBCB"), + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 1, + }, +} + +AGENT_CONFIG: Dict[TrackedObjectType, Any] = { + TrackedObjectType.VEHICLE: { + "fill_color": ELLIS_5[4], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, + TrackedObjectType.PEDESTRIAN: { + "fill_color": NEW_TAB_10[6], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, + TrackedObjectType.BICYCLE: { + "fill_color": ELLIS_5[3], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, + TrackedObjectType.TRAFFIC_CONE: { + "fill_color": NEW_TAB_10[5], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, + TrackedObjectType.BARRIER: { + "fill_color": NEW_TAB_10[5], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, + TrackedObjectType.CZONE_SIGN: { + "fill_color": NEW_TAB_10[5], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, + TrackedObjectType.GENERIC_OBJECT: { + "fill_color": NEW_TAB_10[5], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, + TrackedObjectType.EGO: { + "fill_color": ELLIS_5[0], + "fill_color_alpha": 1.0, + "line_color": "black", + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "-", + "zorder": 2, + }, +} + +TRAFFIC_LIGHT_CONFIG: Dict[TrafficLightStatusType, Any] = { + TrafficLightStatusType.RED: { + "line_color": TAB_10[3], + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "--", + "zorder": 1, + }, + TrafficLightStatusType.GREEN: { + "line_color": TAB_10[2], + "line_color_alpha": 1.0, + "line_width": 1.0, + "line_style": "--", + "zorder": 1, + }, +} + +TRAJECTORY_CONFIG: Dict[str, Any] = { + "human": { + "fill_color": NEW_TAB_10[4], + "fill_color_alpha": 1.0, + "line_color": NEW_TAB_10[4], + "line_color_alpha": 1.0, + "line_width": 2.0, + "line_style": "-", + "marker": "o", + "marker_size": 5, + "marker_edge_color": "black", + "zorder": 3, + }, + "agent": { + "fill_color": ELLIS_5[0], + "fill_color_alpha": 1.0, + "line_color": ELLIS_5[0], + "line_color_alpha": 1.0, + "line_width": 2.0, + "line_style": "-", + "marker": "o", + "marker_size": 5, + "marker_edge_color": "black", + "zorder": 3, + }, +} diff --git a/sledge/common/visualization/sledge_visualization_utils.py b/sledge/common/visualization/sledge_visualization_utils.py new file mode 100644 index 0000000..4cc8532 --- /dev/null +++ b/sledge/common/visualization/sledge_visualization_utils.py @@ -0,0 +1,236 @@ +from typing import Optional, Tuple +import cv2 + +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.oriented_box import OrientedBox + +from sledge.common.visualization.sledge_colors import Color, BLACK, WHITE, SLEDGE_ELEMENTS +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import array_to_state_se2 +from sledge.autoencoder.preprocessing.features.map_id_feature import MapID, MAP_ID_TO_ABBR +from sledge.autoencoder.preprocessing.feature_builders.sledge.sledge_utils import coords_to_pixel +from sledge.autoencoder.preprocessing.features.sledge_raster_feature import SledgeRaster +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeConfig, + SledgeVector, + SledgeVectorElement, + SledgeVectorElementType, + BoundingBoxIndex, +) + + +def add_border_to_raster( + image: npt.NDArray[np.uint8], border_size: int = 1, border_color: Color = BLACK +) -> npt.NDArray[np.uint8]: + """ + Add boarder to numpy array / image. + :param image: image as numpy array + :param border_size: size of border in pixels, defaults to 1 + :param border_color: color of border, defaults to BLACK + :return: image with border. + """ + bordered_image = cv2.copyMakeBorder( + image, + border_size, + border_size, + border_size, + border_size, + cv2.BORDER_CONSTANT, + value=border_color.rgb, + ) + return bordered_image + + +def get_sledge_raster( + raster: SledgeRaster, pixel_frame: Tuple[int, int], threshold: float = 0.0, add_border: bool = True +) -> npt.NDArray[np.uint8]: + """ + Convert sledge raster dataclass to numpy RGB image. + :param raster: sledge raster dataclass + :param pixel_frame: sizel of pixel in meter + :param threshold: threshold for color channels, defaults to 0.0 + :param add_border: whether to add border, defaults to True + :return: numpy RGB image + """ + # FIXME: method only works reliably for ground-trough rasters + pixel_width, pixel_height = pixel_frame + image: npt.NDArray[np.uint8] = np.full((pixel_width, pixel_height, 3), WHITE.rgb, dtype=np.uint8) + image[raster.lines_layer[0].mean(0) > threshold] = SLEDGE_ELEMENTS["lines"].rgb + image[raster.vehicles_layer[0].mean(0) > threshold] = SLEDGE_ELEMENTS["vehicles"].rgb + image[raster.pedestrians_layer[0].mean(0) > threshold] = SLEDGE_ELEMENTS["pedestrians"].rgb + image[raster.static_objects_layer[0].mean(0) > threshold] = SLEDGE_ELEMENTS["static_objects"].rgb + image[raster.green_lights_layer[0].mean(0) > threshold] = SLEDGE_ELEMENTS["green_lights"].rgb + image[raster.red_lights_layer[0].mean(0) > threshold] = SLEDGE_ELEMENTS["red_lights"].rgb + image = image[::-1, ::-1] + + if add_border: + image = add_border_to_raster(image) + + return image + + +def get_sledge_vector_as_raster( + sledge_vector: SledgeVector, config: SledgeConfig, map_id: Optional[MapID] = None +) -> npt.NDArray[np.uint8]: + """ + Convert sledge vector into RGB numpy array for visualization. + :param sledge_vector: dataclass of vector representation + :param config: config dataclass of sledge autoencoder + :param map_id: map identifier to draw if provided, defaults to None + :return: numpy RGB image + """ + + pixel_width, pixel_height = config.pixel_frame + image: npt.NDArray[np.uint8] = np.full((pixel_width, pixel_height, 3), WHITE.rgb, dtype=np.uint8) + draw_dict = { + "L": {"elem": sledge_vector.lines, "color": SLEDGE_ELEMENTS["lines"], "count": 0}, + "V": {"elem": sledge_vector.vehicles, "color": SLEDGE_ELEMENTS["vehicles"], "count": 0}, + "P": {"elem": sledge_vector.pedestrians, "color": SLEDGE_ELEMENTS["pedestrians"], "count": 0}, + "S": {"elem": sledge_vector.static_objects, "color": SLEDGE_ELEMENTS["static_objects"], "count": 0}, + "G": {"elem": sledge_vector.green_lights, "color": SLEDGE_ELEMENTS["green_lights"], "count": 0}, + "R": {"elem": sledge_vector.red_lights, "color": SLEDGE_ELEMENTS["red_lights"], "count": 0}, + } + + for key, elem_dict in draw_dict.items(): + image, counter = draw_sledge_vector_element(image, elem_dict["elem"], config, elem_dict["color"]) + draw_dict[key]["count"] = counter + + # TODO: adapt to autoencoder config + font = cv2.FONT_HERSHEY_SIMPLEX + font_scale = 0.4 + thickness = 1 + line_height = 15 + width_offset = pixel_width - 39 + height_offset = pixel_height - 99 + + for i, (key, elem_dict) in enumerate(draw_dict.items()): + count = elem_dict["count"] + cv2.putText( + image, + f"{key}={count}", + (width_offset, height_offset + (i + 1) * line_height), + font, + font_scale, + elem_dict["color"].rgb, + thickness, + cv2.LINE_AA, + ) + + draw_dict[key]["count"] = counter + + if map_id: + map_abbreviation = MAP_ID_TO_ABBR[int(map_id.id[0])] + cv2.putText( + image, + f"{map_abbreviation}", + (width_offset, height_offset), + font, + font_scale, + BLACK.rgb, + thickness, + cv2.LINE_AA, + ) + + image = add_border_to_raster(image) + return image + + +def draw_sledge_vector_element( + image: npt.NDArray[np.uint8], sledge_vector_element: SledgeVectorElement, config: SledgeConfig, color: Color +) -> Tuple[npt.NDArray[np.uint8], int]: + """ + Draws vector element on numpy RGB image. + :param image: numpy RGB image + :param sledge_vector_element: vector element to draw + :param config: dataclass config of autoencoder in sledge + :param color: color helper class + :return: tuple of numpy RGB image and element count + """ + + element_counter = 0 + element_type = sledge_vector_element.get_element_type() + element_index = sledge_vector_element.get_element_index() + + for states, p in zip(sledge_vector_element.states, sledge_vector_element.mask): + draw_element = False + if type(p) is np.bool_: + draw_element = p + else: + draw_element = p > config.threshold + if not draw_element: + continue + + if element_type == SledgeVectorElementType.LINE: + image = draw_line_element(image, states, config, color) + else: + image = draw_bounding_box_element(image, states, config, color, element_index) + element_counter += 1 + + return image, element_counter + + +def draw_line_element( + image: npt.NDArray[np.uint8], state: npt.NDArray[np.float32], config: SledgeConfig, color: Color +) -> npt.NDArray[np.uint8]: + """ + Draws a line state (eg. of lane or traffic light) onto numpy RGB image. + :param image: numpy RGB image + :param state: coordinate array of line + :param config: dataclass config of autoencoder in sledge + :param color: color helper class + :return: numpy RGB image + """ + assert state.shape[-1] == 2 + line_mask = np.zeros(config.pixel_frame, dtype=np.float32) + indices = coords_to_pixel(state, config.frame, config.pixel_size) + coords_x, coords_y = indices[..., 0], indices[..., 1] + + # NOTE: OpenCV has origin on top-left corner + for point_1, point_2 in zip(zip(coords_x[:-1], coords_y[:-1]), zip(coords_x[1:], coords_y[1:])): + cv2.line(line_mask, point_1, point_2, color=1.0, thickness=1) + + cv2.circle(line_mask, (coords_x[0], coords_y[0]), radius=3, color=1.0, thickness=-1) + cv2.circle(line_mask, (coords_x[-1], coords_y[-1]), radius=3, color=1.0, thickness=-1) + line_mask = np.rot90(line_mask)[:, ::-1] + + image[line_mask > 0] = color.rgb + return image + + +def draw_bounding_box_element( + image: npt.NDArray[np.uint8], + state: npt.NDArray[np.float32], + config: SledgeConfig, + color: Color, + object_indexing: BoundingBoxIndex, +) -> npt.NDArray[np.uint8]: + """ + Draws a bounding box (eg. of vehicle) onto numpy RGB image. + :param image: numpy RGB image + :param state: state array of bounding box + :param config: dataclass config of autoencoder in sledge + :param color: color helper class + :param object_indexing: index enum of state array + :return: numpy RGB image + """ + + # Get the 2D coordinate of the detected agents. + raster_oriented_box = OrientedBox( + array_to_state_se2(state[object_indexing.STATE_SE2]), + state[object_indexing.LENGTH], + state[object_indexing.WIDTH], + 1.0, # NOTE: dummy height + ) + box_bottom_corners = raster_oriented_box.all_corners() + corners = np.asarray([[corner.x, corner.y] for corner in box_bottom_corners]) # type: ignore + corner_indices = coords_to_pixel(corners, config.frame, config.pixel_size) + + bounding_box_mask = np.zeros(config.pixel_frame, dtype=np.float32) + cv2.fillPoly(bounding_box_mask, [corner_indices], color=1.0, lineType=cv2.LINE_AA) + + # NOTE: OpenCV has origin on top-left corner + bounding_box_mask = np.rot90(bounding_box_mask)[:, ::-1] + + image[bounding_box_mask > 0] = color.rgb + return image diff --git a/sledge/diffusion/__init__.py b/sledge/diffusion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/diffusion/dataset/__init__.py b/sledge/diffusion/dataset/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/diffusion/dataset/rvae_latent_builder_config.py b/sledge/diffusion/dataset/rvae_latent_builder_config.py new file mode 100644 index 0000000..05e3f4c --- /dev/null +++ b/sledge/diffusion/dataset/rvae_latent_builder_config.py @@ -0,0 +1,33 @@ +from dataclasses import dataclass +from pathlib import Path +from typing import List +from datasets.builder import BuilderConfig +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig + + +@dataclass +class RVAELatentBuilderConfig(BuilderConfig): + + feature_name: str = "rvae_latent" + label_name: str = "map_id" + rvae_config: RVAEConfig = RVAEConfig() + + def find_file_paths(self, root_path: Path) -> List[Path]: + """ + Search for latent features in cache. + :param root_path: root path of cache + :return: list of latent file paths + """ + + # TODO: move somewhere else + file_paths: List[Path] = [] + for log_path in root_path.iterdir(): + if log_path.name == "metadata": + continue + for scenario_type_path in log_path.iterdir(): + for token_path in scenario_type_path.iterdir(): + if (token_path / f"{self.feature_name}.gz").is_file() and ( + token_path / f"{self.label_name}.gz" + ).is_file(): + file_paths.append(token_path) + return file_paths diff --git a/sledge/diffusion/dataset/rvae_latent_dataset.py b/sledge/diffusion/dataset/rvae_latent_dataset.py new file mode 100644 index 0000000..5e124f4 --- /dev/null +++ b/sledge/diffusion/dataset/rvae_latent_dataset.py @@ -0,0 +1,144 @@ +# Copyright 2020 The HuggingFace Datasets Authors and the current dataset script contributor. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +import warnings + +import os +import gzip +import pickle +from tqdm import tqdm +import datasets + +from sledge.autoencoder.modeling.models.rvae.rvae_config import RVAEConfig +from sledge.diffusion.dataset.rvae_latent_builder_config import RVAELatentBuilderConfig + +_CITATION = """ +@inproceedings{Chitta2024ECCV, + author = {Kashyap Chitta and Daniel Dauner and Andreas Geiger}, + title = {SLEDGE: Synthesizing Driving Environments with Generative Models and Rule-Based Traffic}, + booktitle = {European Conference on Computer Vision (ECCV)}, + year = {2024}, +} +""" + +_DESCRIPTION = """Apache-2.0 license""" +_HOMEPAGE = "https://github.com/autonomousvision/sledge" +_LICENSE = "" + + +class RVAELatentDataset(datasets.GeneratorBasedBuilder): + """TODO: Short description of my dataset.""" + + VERSION = datasets.Version("1.0.0") + + # This is an example of a dataset with multiple configurations. + # If you don't want/need to define several sub-sets in your dataset, + # just remove the BUILDER_CONFIG_CLASS and the BUILDER_CONFIGS attributes. + + # If you need to make complex sub-parts in the datasets with configurable options + # You can create your own builder configuration class to store attribute, inheriting from datasets.BuilderConfig + # BUILDER_CONFIG_CLASS = MyBuilderConfig + + # You will be able to load one or the other configurations in the following list with + # data = datasets.load_dataset('my_dataset', 'default') + BUILDER_CONFIGS = [ + RVAELatentBuilderConfig( + name="rvae_latent", + version=VERSION, + description="This part of my dataset covers a first domain", + feature_name="rvae_latent", + label_name="map_id", + rvae_config=RVAEConfig(), + ), + ] + + DEFAULT_CONFIG_NAME = ( + "rvae_latent" # It's not mandatory to have a default configuration. Just use one if it make sense. + ) + + def _info(self): + # TODO: This method specifies the datasets.DatasetInfo object which contains informations and typings for the dataset + + warnings.warn("Considering standard parameters from RVAEConfig for latent shape!") + + c = self.config.rvae_config.latent_channel + w, h = self.config.rvae_config.latent_frame + + features = datasets.Features( + { + "features": datasets.Array3D(shape=(c, w, h), dtype="float32"), + "label": datasets.Value("int64"), + } + ) + + return datasets.DatasetInfo( + # This is the description that will appear on the datasets page. + description=_DESCRIPTION, + # This defines the different columns of the dataset and their types + features=features, # Here we define them above because they are different between the two configurations + # If there's a common (input, target) tuple from the features, uncomment supervised_keys line below and + # specify them. They'll be used if as_supervised=True in builder.as_dataset. + # supervised_keys=("sentence", "label"), + # Homepage of the dataset for documentation + homepage=_HOMEPAGE, + # License for the dataset if available + license=_LICENSE, + # Citation for the dataset + citation=_CITATION, + ) + + def _split_generators(self, dl_manager): + # TODO: This method is tasked with downloading/extracting the data and defining the splits depending on the configuration + # If several configurations are possible (listed in BUILDER_CONFIGS), the configuration selected by the user is in self.config.name + + # dl_manager is a datasets.download.DownloadManager that can be used to download and extract URLS + # It can accept any type or nested list/dict and will give back the same structure with the url replaced with path to local files. + # By default the archives will be extracted and a path to a cached folder where they are extracted is returned instead of the archive + + # TODO: sort tokens according to train / val splits + + file_paths = self.config.find_file_paths(Path(dl_manager._data_dir)) + + return [ + datasets.SplitGenerator( + name=datasets.Split.TRAIN, + # These kwargs will be passed to _generate_examples + gen_kwargs={ + "filepath": file_paths, + "split": "train", + }, + ), + ] + + # method parameters are unpacked from `gen_kwargs` as given in `_split_generators` + def _generate_examples(self, filepath, split): + # TODO: This method handles input defined in _split_generators to yield (key, example) tuples from the dataset. + # The `key` is for legacy reasons (tfds) and is not important in itself, but must be unique for each example. + + for key, file_path in tqdm(enumerate(filepath)): + + with gzip.open(os.path.join(file_path, f"{self.config.feature_name}.gz"), "rb") as f: + data = pickle.load(f) + + with gzip.open(os.path.join(file_path, f"{self.config.label_name}.gz"), "rb") as f: + id = pickle.load(f) + + array = data["mu"] + label = id["id"] + + yield key, { + "features": array, + "label": label, + } diff --git a/sledge/diffusion/experiments/__init__.py b/sledge/diffusion/experiments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/diffusion/experiments/scenario_caching.py b/sledge/diffusion/experiments/scenario_caching.py new file mode 100644 index 0000000..2d5266c --- /dev/null +++ b/sledge/diffusion/experiments/scenario_caching.py @@ -0,0 +1,53 @@ +from pathlib import Path +from tqdm import tqdm +from omegaconf import DictConfig +from accelerate.logging import get_logger + +from nuplan.planning.training.preprocessing.utils.feature_cache import FeatureCachePickle + +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVector +from sledge.autoencoder.preprocessing.features.map_id_feature import MAP_ID_TO_NAME +from sledge.script.builders.diffusion_builder import build_pipeline_from_checkpoint + +logger = get_logger(__name__, log_level="INFO") + + +def run_scenario_caching(cfg: DictConfig) -> None: + """ + Applies the diffusion model generate and cache scenarios. + :param cfg: DictConfig. Configuration that is used to run the experiment. + """ + + logger.info("Building pipeline from checkpoint...") + pipeline = build_pipeline_from_checkpoint(cfg) + pipeline.to("cuda") + logger.info("Building pipeline from checkpoint...DONE!") + + logger.info("Scenario caching...") + storing_mechanism = FeatureCachePickle() + current_cache_size: int = 0 + class_labels = list(range(cfg.num_classes)) * (cfg.inference_batch_size // cfg.num_classes) + num_total_batches = (cfg.cache.scenario_cache_size // cfg.inference_batch_size) + 1 + for _ in tqdm(range(num_total_batches), desc="Load cache files..."): + sledge_vector_list = pipeline( + class_labels=class_labels, + num_inference_timesteps=cfg.num_inference_timesteps, + guidance_scale=cfg.guidance_scale, + num_classes=cfg.num_classes, + ) + for sledge_vector, map_id in zip(sledge_vector_list, class_labels): + sledge_vector_numpy: SledgeVector = sledge_vector.torch_to_numpy() + file_name = ( + Path(cfg.cache.scenario_cache_path) + / "log" + / MAP_ID_TO_NAME[map_id] + / str(current_cache_size) + / "sledge_vector" + ) + file_name.parent.mkdir(parents=True, exist_ok=True) + storing_mechanism.store_computed_feature_to_folder(file_name, sledge_vector_numpy) + current_cache_size += 1 + if current_cache_size >= cfg.cache.scenario_cache_size: + break + logger.info("Scenario caching...DONE!") + return None diff --git a/sledge/diffusion/experiments/training.py b/sledge/diffusion/experiments/training.py new file mode 100644 index 0000000..c7ee3f3 --- /dev/null +++ b/sledge/diffusion/experiments/training.py @@ -0,0 +1,306 @@ +import math +import os +from pathlib import Path +import numpy as np + +import torch +import torch.nn.functional as F + +from packaging import version +from tqdm.auto import tqdm + +import accelerate +from accelerate.logging import get_logger + +import datasets +import diffusers +from diffusers import DiTTransformer2DModel +from diffusers.training_utils import EMAModel +import pyarrow_hotfix + +from sledge.diffusion.modelling.ldm_pipeline import LDMPipeline +from sledge.autoencoder.callbacks.rvae_visualization_callback import get_sledge_vector_as_raster +from sledge.script.builders.diffusion_builder import ( + build_accelerator, + build_diffusion_model, + build_noise_scheduler, + build_optimizer, + build_dataset, + build_dataloader, + build_lr_scheduler, + build_decoder, + build_pipeline_from_checkpoint, +) + +pyarrow_hotfix.uninstall() +logger = get_logger(__name__, log_level="INFO") + + +def run_training_diffusion(cfg): + + # Build accelerator + accelerator = build_accelerator(cfg) + + if cfg.diffusion_checkpoint is not None: + # Build complete ldm pipeline + pipeline = build_pipeline_from_checkpoint(cfg) + diffusion_model, noise_scheduler, decoder = pipeline.transformer, pipeline.scheduler, pipeline.decoder + + else: + # Build diffusion model + diffusion_model = build_diffusion_model(cfg) + + # Build Noise Scheduler + noise_scheduler = build_noise_scheduler(cfg) + + # build decoder + decoder = build_decoder(cfg) + + # build optimizer + optimizer = build_optimizer(cfg, diffusion_model) + + # build dataset + dataset = build_dataset(cfg) + + # build dataloader + dataloader = build_dataloader(cfg, dataset) + + # build lr_scheduler + lr_scheduler = build_lr_scheduler(cfg, optimizer, len(dataloader) * cfg.num_epochs) + + # `accelerate` 0.16.0 will have better support for customized saving + if version.parse(accelerate.__version__) >= version.parse("0.16.0"): + # create custom saving & loading hooks so that `accelerator.save_state(...)` serializes in a nice format + def save_model_hook(models, weights, output_dir): + if cfg.use_ema: + ema_model.save_pretrained(os.path.join(output_dir, "transformer_ema")) + + for i, model in enumerate(models): + model.save_pretrained(os.path.join(output_dir, "transformer")) + + # make sure to pop weight so that corresponding model is not saved again + weights.pop() + + def load_model_hook(models, input_dir): + if cfg.use_ema: + load_model = EMAModel.from_pretrained(os.path.join(input_dir, "transformer_ema"), DiTTransformer2DModel) + ema_model.load_state_dict(load_model.state_dict()) + ema_model.to(accelerator.device) + del load_model + + for i in range(len(models)): + # pop models so that they are not loaded again + model = models.pop() + + # load diffusers style into model + load_model = DiTTransformer2DModel.from_pretrained(input_dir, subfolder="transformer") + model.register_to_config(**load_model.config) + + model.load_state_dict(load_model.state_dict()) + del load_model + + accelerator.register_save_state_pre_hook(save_model_hook) + accelerator.register_load_state_pre_hook(load_model_hook) + + logger.info(accelerator.state, main_process_only=False) + if accelerator.is_local_main_process: + datasets.utils.logging.set_verbosity_warning() + diffusers.utils.logging.set_verbosity_info() + else: + datasets.utils.logging.set_verbosity_error() + diffusers.utils.logging.set_verbosity_error() + + # Handle the repository creation + if accelerator.is_main_process: + if cfg.output_dir is not None: + os.makedirs(cfg.output_dir, exist_ok=True) + + # Create EMA for the model. + if cfg.ema.use_ema: + ema_model = EMAModel( + diffusion_model.parameters(), + decay=cfg.ema.max_decay, + use_ema_warmup=True, + inv_gamma=cfg.ema.inv_gamma, + power=cfg.ema.power, + model_cls=DiTTransformer2DModel, + model_config=diffusion_model.config, + ) + + # Prepare everything with our `accelerator`. + diffusion_model, optimizer, dataloader, lr_scheduler = accelerator.prepare( + diffusion_model, optimizer, dataloader, lr_scheduler + ) + + # prepare decoder + decoder.requires_grad_(False) + decoder.eval() + decoder.to(accelerator.device) + + if cfg.ema.use_ema: + ema_model.to(accelerator.device) + + # We need to initialize the trackers we use, and also store our configuration. + # The trackers initializes automatically on the main process. + if accelerator.is_main_process: + run = os.path.split(__file__)[-1].split(".")[0] + accelerator.init_trackers(run) + + total_batch_size = ( + cfg.data_loader.params.batch_size * accelerator.num_processes * accelerator.gradient_accumulation_steps + ) + num_update_steps_per_epoch = math.ceil(len(dataloader) / accelerator.gradient_accumulation_steps) + max_train_steps = cfg.num_epochs * num_update_steps_per_epoch + + num_diffusion_params = sum(p.numel() for p in diffusion_model.parameters()) + num_decoder_params = sum(p.numel() for p in decoder.parameters()) + + logger.info("***** Running training *****") + logger.info(f" Num examples = {len(dataset)}") + logger.info(f" Num Epochs = {cfg.num_epochs}") + logger.info(f" Num Steps = {max_train_steps}") + logger.info(f" Batch size per device = {cfg.data_loader.params.batch_size}") + logger.info(f" Batch size total (w. parallel, distributed & accumulation) = {total_batch_size}") + logger.info(f" Gradient Accumulation steps = {accelerator.gradient_accumulation_steps}\n") + + logger.info("***** #Parameters *****") + logger.info(f" Diffusion Model = {num_diffusion_params}") + logger.info(f" Decoder = {num_decoder_params}") + logger.info(f" Total = {num_diffusion_params+num_decoder_params}") + + global_step = 0 + first_epoch = 0 + + # Train! + for epoch in range(first_epoch, cfg.num_epochs): + diffusion_model.train() # enables dropout + + progress_bar = tqdm(total=num_update_steps_per_epoch, disable=not accelerator.is_local_main_process) + progress_bar.set_description(f"Epoch {epoch}") + + for step, batch in enumerate(dataloader): + # Load latents and labels + map_ids = batch["label"] + latents = batch["input"] + + # Sample noise that we'll add to the latents + noise = torch.randn_like(latents) + bsz = latents.shape[0] + + # Sample a random timestep for each latent + timesteps = torch.randint( + 0, noise_scheduler.config.num_train_timesteps, (bsz,), device=latents.device + ).long() + + # Add noise to the clean latents according to the noise magnitude at each timestep + # (this is the forward diffusion process) + noisy_latents = noise_scheduler.add_noise(latents, noise, timesteps) + with accelerator.accumulate(diffusion_model): + # Predict the noise residual + model_output = diffusion_model( + hidden_states=noisy_latents, + class_labels=map_ids, + timestep=timesteps, + ).sample + loss = F.mse_loss(model_output, noise) # TODO: add to config + + accelerator.backward(loss) + + if accelerator.sync_gradients: + accelerator.clip_grad_norm_(diffusion_model.parameters(), 1.0) # TODO: add to config + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + + # Checks if the accelerator has performed an optimization step behind the scenes + if accelerator.sync_gradients: + if cfg.ema.use_ema: + ema_model.step(diffusion_model.parameters()) + progress_bar.update(1) + global_step += 1 + + logs = { + "loss": loss.detach().item(), + "lr": lr_scheduler.get_last_lr()[0], + "step": global_step, + } + if cfg.ema.use_ema: + logs["ema_decay"] = ema_model.cur_decay_value + progress_bar.set_postfix(**logs) + accelerator.log(logs, step=global_step) + + if cfg.debug_mode: + break + + progress_bar.close() + accelerator.wait_for_everyone() + + # Generate sample images for visual inspection + if accelerator.is_main_process: + if epoch % cfg.inference_epochs == 0 or epoch == cfg.num_epochs - 1: + transformer = accelerator.unwrap_model(diffusion_model).eval() + decoder = accelerator.unwrap_model(decoder) + + if cfg.ema.use_ema: + ema_model.store(transformer.parameters()) + ema_model.copy_to(transformer.parameters()) + + pipeline = LDMPipeline( + decoder=decoder, + transformer=transformer, + scheduler=noise_scheduler, + ) + + generator = torch.Generator(device=pipeline.device).manual_seed(cfg.seed) + class_labels = list(range(cfg.num_classes)) * (cfg.inference_batch_size // cfg.num_classes) + + # (1) Create some synthetic images from the LDMPipeline + sledge_vectors_generated = pipeline( + class_labels=class_labels, + num_inference_timesteps=cfg.num_inference_timesteps, + guidance_scale=cfg.guidance_scale, + generator=generator, + num_classes=cfg.num_classes, + ) + generated_images = [] + for sledge_vector in sledge_vectors_generated: + generated_images.append( + get_sledge_vector_as_raster(sledge_vector.torch_to_numpy(), decoder._config) + ) + generated_images = np.array(generated_images).transpose(0, 3, 1, 2) + + # (2) Decode some latents from train set (for debugging) + latents_norm = latents[: cfg.inference_batch_size] + sledge_vectors_training = decoder.decode(latents_norm).unpack() + training_images = [] + for sledge_vector in sledge_vectors_training: + training_images.append(get_sledge_vector_as_raster(sledge_vector.torch_to_numpy(), decoder._config)) + training_images = np.array(training_images).transpose(0, 3, 1, 2) + + # Add (1) + (2) to tensorboard + tracker = accelerator.get_tracker("tensorboard", unwrap=True) + tracker.add_images("diff_generated", generated_images, epoch) + tracker.add_images("diff_training", training_images, epoch) + + if cfg.ema.use_ema: + ema_model.restore(transformer.parameters()) + + # save the model + transformer = accelerator.unwrap_model(diffusion_model).eval() + decoder = accelerator.unwrap_model(decoder) + + if cfg.ema.use_ema: + ema_model.store(transformer.parameters()) + ema_model.copy_to(transformer.parameters()) + + pipeline = LDMPipeline( + decoder=decoder, + transformer=transformer, + scheduler=noise_scheduler, + ) + pipeline.save_pretrained(Path(cfg.output_dir) / "checkpoint") + + if cfg.ema.use_ema: + ema_model.restore(transformer.parameters()) + + accelerator.end_training() diff --git a/sledge/diffusion/modelling/__init__.py b/sledge/diffusion/modelling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/diffusion/modelling/ldm_pipeline.py b/sledge/diffusion/modelling/ldm_pipeline.py new file mode 100644 index 0000000..8198eda --- /dev/null +++ b/sledge/diffusion/modelling/ldm_pipeline.py @@ -0,0 +1,107 @@ +import inspect +from typing import List, Optional, Union + +import torch + +from diffusers.models import DiTTransformer2DModel +from diffusers.schedulers import DDPMScheduler +from diffusers import DiffusionPipeline + +from sledge.autoencoder.modeling.models.rvae.rvae_decoder import RVAEDecoder +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVector + + +class LDMPipeline(DiffusionPipeline): + """Latent Diffusion Model pipeline for generation.""" + + def __init__(self, decoder: RVAEDecoder, transformer: DiTTransformer2DModel, scheduler: DDPMScheduler): + """ + Initializes diffusion pipeline. + :param decoder: decoder module of raster-vector autoencoder + :param transformer: diffusion transformer + :param scheduler: noise schedular + """ + super().__init__() + self.register_modules(decoder=decoder, transformer=transformer, scheduler=scheduler) + + @torch.no_grad() + def __call__( + self, + class_labels: List[int], + num_inference_timesteps: int = 50, + guidance_scale: float = 4.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + eta: float = 0.0, + num_classes: int = 4, + ) -> List[SledgeVector]: + """ + Generates a batch of sledge vectors. + :param class_labels: list of integers for classes to generate. + :param num_inference_timesteps: iterative diffusion steps, defaults to 50 + :param guidance_scale: scale for classifier-free guidance, defaults to 4.0 + :param generator: optional torch generator, defaults to None + :param eta: noise multiplier, defaults to 0.0 + :param num_classes: number of classes, defaults to 4 + :return: list of sledge vector dataclass + """ + + batch_size = len(class_labels) + class_labels = torch.tensor(class_labels, device=self.device).reshape(-1) + class_null = torch.tensor([num_classes] * batch_size, device=self.device) + class_labels_input = torch.cat([class_labels, class_null], 0) + + latents = torch.randn( + ( + batch_size, + self.transformer.config.in_channels, + self.transformer.config.sample_size, + self.transformer.config.sample_size, + ), + generator=generator, + device=self.device, + ) + + # scale the initial noise by the standard deviation required by the scheduler + latent_model_input = torch.cat([latents] * 2) + latent_model_input = latent_model_input * self.scheduler.init_noise_sigma + + self.scheduler.set_timesteps(num_inference_timesteps, device=self.device) + + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + + extra_kwargs = {} + if accepts_eta: + extra_kwargs["eta"] = eta + + for t in self.progress_bar(self.scheduler.timesteps): + + # scale the model input + half = latent_model_input[: len(latent_model_input) // 2] + latent_model_input = torch.cat([half, half], dim=0) + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + noise_prediction = self.transformer( + hidden_states=latent_model_input, + class_labels=class_labels_input, + timestep=t.unsqueeze(0), + ).sample + + # perform guidance + cond_eps, uncond_eps = torch.split(noise_prediction, len(noise_prediction) // 2, dim=0) + half_eps = uncond_eps + guidance_scale * (cond_eps - uncond_eps) + noise_prediction = torch.cat([half_eps, half_eps], dim=0) + + # compute the previous noisy sample x_t -> x_t-1 + latent_model_input = self.scheduler.step( + noise_prediction, t, latent_model_input, **extra_kwargs + ).prev_sample + + # split the latent into the two halves + latent_model_input, _ = latent_model_input.chunk(2, dim=0) + + # convert the vectors to images + vector_output = self.decoder.decode(latent_model_input).unpack() + + return vector_output diff --git a/sledge/script/__init__.py b/sledge/script/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/builders/__init__.py b/sledge/script/builders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/builders/autoencoder_builder.py b/sledge/script/builders/autoencoder_builder.py new file mode 100644 index 0000000..a900044 --- /dev/null +++ b/sledge/script/builders/autoencoder_builder.py @@ -0,0 +1,152 @@ +import logging +from typing import List, cast + +import pytorch_lightning as pl + +from omegaconf import DictConfig +from hydra.utils import instantiate + +from nuplan.planning.training.preprocessing.feature_preprocessor import FeaturePreprocessor +from nuplan.planning.script.builders.data_augmentation_builder import build_agent_augmentor +from nuplan.planning.script.builders.scenario_builder import build_scenarios +from nuplan.planning.script.builders.splitter_builder import build_splitter +from nuplan.planning.script.builders.utils.utils_type import validate_type +from nuplan.planning.utils.multithreading.worker_pool import WorkerPool + +from sledge.autoencoder.modeling.autoencoder_torch_module_wrapper import AutoencoderTorchModuleWrapper +from sledge.autoencoder.modeling.autoencoder_lightning_module_wrapper import AutoencoderLightningModuleWrapper +from sledge.autoencoder.data_loader.autoencoder_datamodule import AutoencoderDataModule +from sledge.script.builders.metric_builder import build_custom_training_metrics, build_custom_objectives +from sledge.script.builders.matching_builder import build_matching + +logger = logging.getLogger(__name__) + + +def build_autoencoder_lightning_datamodule( + cfg: DictConfig, worker: WorkerPool, model: AutoencoderTorchModuleWrapper +) -> pl.LightningDataModule: + """ + Build the autoencoder lightning datamodule for SLEDGE from the config. + :param cfg: Omegaconf dictionary. + :param model: NN model used for training. + :param worker: Worker to submit tasks which can be executed in parallel. + :return: Instantiated datamodule object. + """ + # Build features and targets + feature_builders = model.get_list_of_required_feature() + target_builders = model.get_list_of_computed_target() + + # Build splitter + splitter = build_splitter(cfg.splitter) + + # Create feature preprocessor + feature_preprocessor = FeaturePreprocessor( + cache_path=cfg.cache.autoencoder_cache_path, + force_feature_computation=cfg.cache.force_feature_computation, + feature_builders=feature_builders, + target_builders=target_builders, + ) + + # Create data augmentation + augmentors = build_agent_augmentor(cfg.data_augmentation) if "data_augmentation" in cfg else None + + # Build dataset scenarios + scenarios = build_scenarios(cfg, worker, model) + + # Create custom datamodule (always applies data augmentation for pre-processing) + datamodule: pl.LightningDataModule = AutoencoderDataModule( + feature_preprocessor=feature_preprocessor, + splitter=splitter, + all_scenarios=scenarios, + dataloader_params=cfg.data_loader.params, + augmentors=augmentors, + worker=worker, + scenario_type_sampling_weights=cfg.scenario_type_weights.scenario_type_sampling_weights, + **cfg.data_loader.datamodule, + ) + + return datamodule + + +def build_autoencoder_lightning_module( + cfg: DictConfig, torch_module_wrapper: AutoencoderTorchModuleWrapper +) -> pl.LightningModule: + """ + Builds the lightning module from the config. + :param cfg: omegaconf dictionary + :param torch_module_wrapper: NN model used for training + :return: built object. + """ + # Build loss + objectives = build_custom_objectives(cfg) + + # Build metrics to evaluate the performance of predictions + metrics = build_custom_training_metrics(cfg) if "training_metric" in cfg else None + + # Build matcher, e.g. used for DETR-style autoencoder in SLEDGE + matchings = build_matching(cfg.matching) if "matching" in cfg else None + + # Create the complete Module + model = AutoencoderLightningModuleWrapper( + model=torch_module_wrapper, + objectives=objectives, + metrics=metrics, + matchings=matchings, + optimizer=cfg.optimizer, + lr_scheduler=cfg.lr_scheduler if "lr_scheduler" in cfg else None, + warm_up_lr_scheduler=cfg.warm_up_lr_scheduler if "warm_up_lr_scheduler" in cfg else None, + objective_aggregate_mode=cfg.objective_aggregate_mode, + ) + + return cast(pl.LightningModule, model) + + +def build_callbacks(cfg: DictConfig) -> List[pl.Callback]: + """ + Build callbacks based on config. + :param cfg: Dict config. + :return List of callbacks. + """ + logger.info("Building callbacks...") + + instantiated_callbacks = [] + for callback_type in cfg.callbacks.values(): + callback: pl.Callback = instantiate(callback_type) + validate_type(callback, pl.Callback) + instantiated_callbacks.append(callback) + + logger.info("Building callbacks...DONE!") + + return instantiated_callbacks + + +def build_autoencoder_trainer(cfg: DictConfig) -> pl.Trainer: + """ + Builds the lightning trainer from the config. + :param cfg: omegaconf dictionary + :return: built object. + """ + params = cfg.lightning.trainer.params + + callbacks = build_callbacks(cfg) + + loggers = [ + pl.loggers.TensorBoardLogger( + save_dir=cfg.group, + name=cfg.experiment, + log_graph=False, + version="", + prefix="", + ), + ] + + # TODO: remove this workaround + del cfg.lightning.trainer.params.gpus + + trainer = pl.Trainer( + callbacks=callbacks, + logger=loggers, + **params, + ) + + return trainer diff --git a/sledge/script/builders/diffusion_builder.py b/sledge/script/builders/diffusion_builder.py new file mode 100644 index 0000000..c28e958 --- /dev/null +++ b/sledge/script/builders/diffusion_builder.py @@ -0,0 +1,171 @@ +import logging +import os +from shutil import rmtree +from pathlib import Path +from omegaconf import DictConfig +from hydra.utils import instantiate + +import torch +import torch.nn as nn + +import accelerate +import diffusers +from datasets import load_dataset + +from nuplan.planning.script.builders.utils.utils_type import validate_type + +from sledge.diffusion.modelling.ldm_pipeline import LDMPipeline +from sledge.script.builders.model_builder import build_autoencoder_torch_module_wrapper + + +logger = logging.getLogger(__name__) + + +def build_accelerator(cfg: DictConfig) -> accelerate.Accelerator: + """ + Build accelerator from config file. + :param cfg: Omegaconf dictionary + :return: accelerator + """ + kwargs_handlers = [] + logger.info("Building Accelerator...") + accelerator: accelerate.Accelerator = instantiate(config=cfg.accelerator, kwargs_handlers=kwargs_handlers) + validate_type(accelerator, accelerate.Accelerator) + logger.info("Building Accelerator...DONE!") + return accelerator + + +def build_diffusion_model(cfg: DictConfig) -> diffusers.DiTTransformer2DModel: + """ + Build diffusion model from config file. + :param cfg: Omegaconf dictionary + :return: diffusion model + """ + logger.info("Building Diffusion Model...") + diffusion_model: diffusers.DiTTransformer2DModel = instantiate(cfg.diffusion_model) + validate_type(diffusion_model, diffusers.DiTTransformer2DModel) + logger.info("Building Diffusion Model...DONE!") + return diffusion_model + + +def build_noise_scheduler(cfg: DictConfig) -> diffusers.SchedulerMixin: + """ + Build noise scheduler for diffusion training from config file. + :param cfg: Omegaconf dictionary + :return: noise scheduler + """ + logger.info("Building Noise Scheduler...") + noise_scheduler: diffusers.SchedulerMixin = instantiate(cfg.noise_scheduler) + validate_type(noise_scheduler, diffusers.SchedulerMixin) + logger.info("Building Noise Scheduler...DONE!") + return noise_scheduler + + +def build_optimizer(cfg: DictConfig, diffusion_model: nn.Module) -> torch.optim.Optimizer: + """ + Build torch optimizer from config file. + :param cfg: Omegaconf dictionary + :return: torch optimizer + """ + logger.info("Building Optimizer...") + optimizer: torch.optim.Optimizer = instantiate(config=cfg.optimizer, params=diffusion_model.parameters()) + validate_type(optimizer, torch.optim.Optimizer) + logger.info("Building Optimizer...DONE!") + return optimizer + + +def build_dataset(cfg: DictConfig) -> torch.utils.data.Dataset: + """ + Build torch dataset for diffusion training from config file. + :param cfg: Omegaconf dictionary + :return: torch dataset + """ + + logger.info("Building Dataset...") + if cfg.cache.cleanup_diffusion_cache and Path(cfg.cache.diffusion_cache_path).exists(): + logger.info("Deleting existing dataset...") + rmtree(cfg.cache.diffusion_cache_path) + logger.info("Deleting existing dataset...DONE!") + + dataset_file_path = Path(os.getenv("SLEDGE_DEVKIT_ROOT")) / "sledge/diffusion/dataset/rvae_latent_dataset.py" + dataset = load_dataset( + path=str(dataset_file_path), + data_dir=cfg.cache.autoencoder_cache_path, + cache_dir=cfg.cache.diffusion_cache_path, + split="train", + ) + + def transform_images(examples): + images = [torch.as_tensor(image, dtype=torch.float32) for image in examples["features"]] + labels = examples["label"] + return {"input": images, "label": labels} + + dataset.set_transform(transform_images) + logger.info("Building Dataset...DONE!") + return dataset + + +def build_dataloader(cfg: DictConfig, dataset: torch.utils.data.Dataset) -> torch.utils.data.DataLoader: + """ + Build torch dataloader for diffusion training from config file. + :param cfg: Omegaconf dictionary + :return: torch dataloader + """ + + # TODO: remove + logger.info("Building Dataloader...") + dataloader = torch.utils.data.DataLoader(dataset, **cfg.data_loader.params) + logger.info("Building Dataloader...DONE!") + return dataloader + + +def build_lr_scheduler( + cfg: DictConfig, optimizer: torch.optim.Optimizer, num_training_steps: int +) -> torch.optim.lr_scheduler.LRScheduler: + """ + Build learning rate scheduler for diffusion training from config file. + :param cfg: Omegaconf dictionary + :return: torch learning rate scheduler + """ + + logger.info("Building LR Scheduler...DONE!") + lr_scheduler = diffusers.optimization.get_scheduler( + cfg.lr_scheduler.name, + optimizer=optimizer, + num_warmup_steps=cfg.lr_scheduler.num_warmup_steps * cfg.accelerator.gradient_accumulation_steps, + num_training_steps=num_training_steps, + ) + logger.info("Building LR Scheduler...DONE!") + return lr_scheduler + + +def build_decoder(cfg: DictConfig) -> nn.Module: + """ + Build decoder from autoencoder for diffusion training from config file. + :param cfg: Omegaconf dictionary + :return: decoder as torch module + """ + + logger.info("Building Decoder...DONE!") + assert cfg.autoencoder_checkpoint is not None, "cfg.autoencoder_checkpoint is not specified!" + torch_module_wrapper = build_autoencoder_torch_module_wrapper(cfg) + decoder = torch_module_wrapper.get_decoder() + logger.info("Building Decoder...DONE!") + + return decoder + + +def build_pipeline_from_checkpoint(cfg: DictConfig) -> LDMPipeline: + """ + Build latent diffusion pipeline from config file. + :param cfg: Omegaconf dictionary + :return: latent diffusion pipeline + """ + + logger.info("Building LDMPipeline...") + assert cfg.diffusion_checkpoint is not None, "cfg.diffusion_checkpoint is not specified!" + pipeline = LDMPipeline.from_pretrained(cfg.diffusion_checkpoint, use_safetensors=True) + logger.info(f"Load from checkpoint {cfg.autoencoder_checkpoint}...DONE!") + logger.info("Building LDMPipeline...DONE!") + + return pipeline diff --git a/sledge/script/builders/matching_builder.py b/sledge/script/builders/matching_builder.py new file mode 100644 index 0000000..fc55cae --- /dev/null +++ b/sledge/script/builders/matching_builder.py @@ -0,0 +1,29 @@ +import logging +from typing import List + +from hydra.utils import instantiate +from omegaconf import DictConfig + +from nuplan.planning.script.builders.utils.utils_type import validate_type + +from sledge.autoencoder.modeling.matching.abstract_matching import AbstractMatching + +logger = logging.getLogger(__name__) + + +def build_matching(cfg: DictConfig) -> List[AbstractMatching]: + """ + Build list of matchings based on config. + :param cfg: Dict config. + :return List of augmentor objects. + """ + logger.info("Building matchings...") + + instantiated_matchings = [] + for matching_type in cfg.values(): + matching: AbstractMatching = instantiate(matching_type) + validate_type(matching, AbstractMatching) + instantiated_matchings.append(matching) + + logger.info("Building matchings...DONE!") + return instantiated_matchings diff --git a/sledge/script/builders/metric_builder.py b/sledge/script/builders/metric_builder.py new file mode 100644 index 0000000..4b3de66 --- /dev/null +++ b/sledge/script/builders/metric_builder.py @@ -0,0 +1,56 @@ +import logging +from typing import List + +from hydra.utils import instantiate +from omegaconf import DictConfig + +from nuplan.planning.script.builders.utils.utils_type import validate_type + +from sledge.autoencoder.modeling.metrics.abstract_custom_metric import AbstractCustomMetric +from sledge.autoencoder.modeling.objectives.abstract_custom_objective import AbstractCustomObjective + +logger = logging.getLogger(__name__) + + +def build_custom_training_metrics(cfg: DictConfig) -> List[AbstractCustomMetric]: + """ + Build objectives based on config + :param cfg: config + :return list of objectives. + """ + instantiated_metrics = [] + + scenario_type_loss_weighting = ( + cfg.scenario_type_weights.scenario_type_loss_weights + if ("scenario_type_weights" in cfg and "scenario_type_loss_weights" in cfg.scenario_type_weights) + else {} + ) + for metric_name, metric_type in cfg.training_metric.items(): + new_metric: AbstractCustomMetric = instantiate( + metric_type, scenario_type_loss_weighting=scenario_type_loss_weighting + ) + validate_type(new_metric, AbstractCustomMetric) + instantiated_metrics.append(new_metric) + return instantiated_metrics + + +def build_custom_objectives(cfg: DictConfig) -> List[AbstractCustomObjective]: + """ + Build objectives based on config + :param cfg: config + :return list of objectives. + """ + instantiated_objectives = [] + + scenario_type_loss_weighting = ( + cfg.scenario_type_weights.scenario_type_loss_weights + if ("scenario_type_weights" in cfg and "scenario_type_loss_weights" in cfg.scenario_type_weights) + else {} + ) + for objective_name, objective_type in cfg.objective.items(): + new_objective: AbstractCustomObjective = instantiate( + objective_type, scenario_type_loss_weighting=scenario_type_loss_weighting + ) + validate_type(new_objective, AbstractCustomObjective) + instantiated_objectives.append(new_objective) + return instantiated_objectives diff --git a/sledge/script/builders/model_builder.py b/sledge/script/builders/model_builder.py new file mode 100644 index 0000000..517ddac --- /dev/null +++ b/sledge/script/builders/model_builder.py @@ -0,0 +1,28 @@ +import logging + +from hydra.utils import instantiate +from omegaconf import DictConfig + +from nuplan.planning.script.builders.utils.utils_type import validate_type + +from sledge.autoencoder.modeling.autoencoder_torch_module_wrapper import AutoencoderTorchModuleWrapper +from sledge.autoencoder.modeling.autoencoder_lightning_module_wrapper import AutoencoderLightningModuleWrapper + +logger = logging.getLogger(__name__) + + +def build_autoencoder_torch_module_wrapper(cfg: DictConfig) -> AutoencoderTorchModuleWrapper: + """ + Builds the autoencoder module. + :param cfg: DictConfig. Configuration that is used to run the experiment. + :return: Instance of AutoencoderTorchModuleWrapper. + """ + logger.info("Building AutoencoderTorchModuleWrapper...") + model = instantiate(cfg.autoencoder_model) + validate_type(model, AutoencoderTorchModuleWrapper) + if cfg.autoencoder_checkpoint: + model = AutoencoderLightningModuleWrapper.load_from_checkpoint(cfg.autoencoder_checkpoint, model=model).model + logger.info(f"Load from checkpoint {cfg.autoencoder_checkpoint}...DONE!") + logger.info("Building AutoencoderTorchModuleWrapper...DONE!") + + return model diff --git a/sledge/script/builders/scenario_builder.py b/sledge/script/builders/scenario_builder.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/builders/simulation_builder.py b/sledge/script/builders/simulation_builder.py new file mode 100644 index 0000000..788307b --- /dev/null +++ b/sledge/script/builders/simulation_builder.py @@ -0,0 +1,122 @@ +import logging +from pathlib import Path +from typing import List, Optional + +from hydra.utils import instantiate +from omegaconf import DictConfig + +from nuplan.planning.utils.multithreading.worker_pool import WorkerPool +from nuplan.planning.script.builders.metric_builder import build_metrics_engines +from nuplan.planning.script.builders.observation_builder import build_observations +from nuplan.planning.script.builders.planner_builder import build_planners +from nuplan.planning.simulation.callback.abstract_callback import AbstractCallback +from nuplan.planning.simulation.callback.metric_callback import MetricCallback +from nuplan.planning.simulation.callback.multi_callback import MultiCallback +from nuplan.planning.simulation.controller.abstract_controller import AbstractEgoController +from nuplan.planning.simulation.observation.abstract_observation import AbstractObservation +from nuplan.planning.simulation.planner.abstract_planner import AbstractPlanner +from nuplan.planning.simulation.runner.simulations_runner import SimulationRunner +from nuplan.planning.simulation.simulation import Simulation +from nuplan.planning.simulation.simulation_setup import SimulationSetup +from nuplan.planning.simulation.simulation_time_controller.abstract_simulation_time_controller import ( + AbstractSimulationTimeController, +) + +from sledge.simulation.scenarios.sledge_scenario.sledge_scenario import SledgeScenario +from sledge.common.helper.cache_helper import find_feature_paths + + +logger = logging.getLogger(__name__) + + +def build_simulations( + cfg: DictConfig, + worker: WorkerPool, + callbacks: List[AbstractCallback], + callbacks_worker: Optional[WorkerPool] = None, + pre_built_planners: Optional[List[AbstractPlanner]] = None, +) -> List[SimulationRunner]: + """ + Build simulations. + :param cfg: DictConfig. Configuration that is used to run the experiment. + :param callbacks: Callbacks for simulation. + :param worker: Worker for job execution. + :param callbacks_worker: worker pool to use for callbacks from sim + :param pre_built_planners: List of pre-built planners to run in simulation. + :return A dict of simulation engines with challenge names. + """ + logger.info("Building test...") + + # Create Simulation object container + simulations = list() + + # Retrieve scenarios + logger.info("Extracting Sledge scenarios...") + cache_path = Path(cfg.cache.scenario_cache_path) + sledge_vector_paths = find_feature_paths(cache_path, feature_name="sledge_vector") + scenarios = [SledgeScenario(path) for path in sledge_vector_paths] + logger.info("Extracting Sledge scenarios...DONE!") + + metric_engines_map = {} + if cfg.run_metric: + logger.info("Building metric engines...") + metric_engines_map = build_metrics_engines(cfg=cfg, scenarios=scenarios) + logger.info("Building metric engines...DONE") + else: + logger.info("Metric engine is disable") + + logger.info(f"Building simulations from {len(scenarios)} scenarios...") + + # Build a metric metadata file + for scenario in scenarios: + + # Build planners + if pre_built_planners is None: + if "planner" not in cfg.keys(): + raise KeyError('Planner not specified in config. Please specify a planner using "planner" field.') + + planners = build_planners(cfg.planner, scenario) + else: + planners = pre_built_planners + + for planner in planners: + # Ego Controller + ego_controller: AbstractEgoController = instantiate(cfg.ego_controller, scenario=scenario) + + # Simulation Manager + simulation_time_controller: AbstractSimulationTimeController = instantiate( + cfg.simulation_time_controller, scenario=scenario + ) + + # Perception + observations: AbstractObservation = build_observations(cfg.observation, scenario=scenario) + + # Metric Engine + metric_engine = metric_engines_map.get(scenario.scenario_type, None) + if metric_engine is not None: + stateful_callbacks = [MetricCallback(metric_engine=metric_engine, worker_pool=callbacks_worker)] + else: + stateful_callbacks = [] + + if "simulation_log_callback" in cfg.callback: + stateful_callbacks.append( + instantiate(cfg.callback["simulation_log_callback"], worker_pool=callbacks_worker) + ) + + # Construct simulation and manager + simulation_setup = SimulationSetup( + time_controller=simulation_time_controller, + observations=observations, + ego_controller=ego_controller, + scenario=scenario, + ) + + simulation = Simulation( + simulation_setup=simulation_setup, + callback=MultiCallback(callbacks + stateful_callbacks), + simulation_history_buffer_duration=cfg.simulation_history_buffer_duration, + ) + simulations.append(SimulationRunner(simulation, planner)) + + logger.info("Building simulations...DONE!") + return simulations diff --git a/sledge/script/builders/utils/__init__.py b/sledge/script/builders/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/builders/utils/utils_config.py b/sledge/script/builders/utils/utils_config.py new file mode 100644 index 0000000..317f7b7 --- /dev/null +++ b/sledge/script/builders/utils/utils_config.py @@ -0,0 +1,39 @@ +import logging +from pathlib import Path +from shutil import rmtree + +from omegaconf import DictConfig, OmegaConf + +logger = logging.getLogger(__name__) + + +# TODO: maybe remove this function +def update_config_for_autoencoder_training(cfg: DictConfig) -> None: + """ + Updates the config based on some conditions. + :param cfg: omegaconf dictionary that is used to run the experiment. + """ + # Make the configuration editable. + OmegaConf.set_struct(cfg, False) + + if cfg.cache.autoencoder_cache_path is None: + logger.warning("Parameter autoencoder_cache_path is not set, caching is disabled") + else: + if cfg.cache.cleanup_autoencoder_cache and Path(cfg.cache.autoencoder_cache_path).exists(): + rmtree(cfg.cache.autoencoder_cache_path) + + Path(cfg.cache.autoencoder_cache_path).mkdir(parents=True, exist_ok=True) + + cfg.cache.cache_path = cfg.cache.autoencoder_cache_path # TODO: remove this workaround + cfg.lightning.trainer.params.gpus = -1 # TODO: remove this workaround + + # Save all interpolations and remove keys that were only used for interpolation and have no further use. + OmegaConf.resolve(cfg) + + # Finalize the configuration and make it non-editable. + OmegaConf.set_struct(cfg, True) + + # Log the final configuration after all overrides, interpolations and updates. + if cfg.log_config: + logger.info(f"Creating experiment name [{cfg.experiment}] in group [{cfg.group}] with config...") + logger.info("\n" + OmegaConf.to_yaml(cfg)) diff --git a/sledge/script/config/__init__.py b/sledge/script/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/config/autoencoder/__init__.py b/sledge/script/config/autoencoder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/config/autoencoder/callbacks/default_callbacks.yaml b/sledge/script/config/autoencoder/callbacks/default_callbacks.yaml new file mode 100644 index 0000000..023949b --- /dev/null +++ b/sledge/script/config/autoencoder/callbacks/default_callbacks.yaml @@ -0,0 +1,4 @@ +defaults: + - learning_rate_monitor_callback + - model_checkpoint_callback + - time_logging_callback diff --git a/sledge/script/config/autoencoder/callbacks/learning_rate_monitor_callback.yaml b/sledge/script/config/autoencoder/callbacks/learning_rate_monitor_callback.yaml new file mode 100644 index 0000000..30579b3 --- /dev/null +++ b/sledge/script/config/autoencoder/callbacks/learning_rate_monitor_callback.yaml @@ -0,0 +1,6 @@ +learning_rate_monitor_callback: + _target_: pytorch_lightning.callbacks.LearningRateMonitor + _convert_: 'all' + + logging_interval: 'step' + log_momentum: true diff --git a/sledge/script/config/autoencoder/callbacks/model_checkpoint_callback.yaml b/sledge/script/config/autoencoder/callbacks/model_checkpoint_callback.yaml new file mode 100644 index 0000000..b6ccb2f --- /dev/null +++ b/sledge/script/config/autoencoder/callbacks/model_checkpoint_callback.yaml @@ -0,0 +1,11 @@ +model_checkpoint_callback: + _target_: nuplan.planning.training.callbacks.checkpoint_callback.ModelCheckpointAtEpochEnd + _convert_: 'all' + + save_last: false + dirpath: ${output_dir}/best_model + + # see default_lightning.yaml + save_top_k: ${lightning.trainer.checkpoint.save_top_k} + monitor: ${lightning.trainer.checkpoint.monitor} + mode: ${lightning.trainer.checkpoint.mode} diff --git a/sledge/script/config/autoencoder/callbacks/rvae_visualization_callback.yaml b/sledge/script/config/autoencoder/callbacks/rvae_visualization_callback.yaml new file mode 100644 index 0000000..8d2b84f --- /dev/null +++ b/sledge/script/config/autoencoder/callbacks/rvae_visualization_callback.yaml @@ -0,0 +1,8 @@ +rvae_visualization_callback: + _target_: sledge.autoencoder.callbacks.rvae_visualization_callback.RVAEVisualizationCallback + _convert_: 'all' + + images_per_tile: 6 # number of images per row + num_train_tiles: 5 # number of rows of training images + num_val_tiles: 5 # number of rows of validation images + config: ${autoencoder_model.config} diff --git a/sledge/script/config/autoencoder/callbacks/time_logging_callback.yaml b/sledge/script/config/autoencoder/callbacks/time_logging_callback.yaml new file mode 100644 index 0000000..7e62971 --- /dev/null +++ b/sledge/script/config/autoencoder/callbacks/time_logging_callback.yaml @@ -0,0 +1,3 @@ +time_logging_callback: + _target_: nuplan.planning.training.callbacks.time_logging_callback.TimeLoggingCallback + _convert_: 'all' diff --git a/sledge/script/config/autoencoder/callbacks/vae_visualization_callback.yaml b/sledge/script/config/autoencoder/callbacks/vae_visualization_callback.yaml new file mode 100644 index 0000000..69a3305 --- /dev/null +++ b/sledge/script/config/autoencoder/callbacks/vae_visualization_callback.yaml @@ -0,0 +1,9 @@ +vae_visualization_callback: + _target_: sledge.autoencoder.callbacks.vae_visualization_callback.VAEVisualizationCallback + _convert_: 'all' + + images_per_tile: 6 # number of images per row + num_train_tiles: 5 # number of rows of training images + num_val_tiles: 5 # number of rows of validation images + config: ${autoencoder_model.config} + diff --git a/sledge/script/config/autoencoder/data_augmentation/rvae_augmentation.yaml b/sledge/script/config/autoencoder/data_augmentation/rvae_augmentation.yaml new file mode 100644 index 0000000..9476c12 --- /dev/null +++ b/sledge/script/config/autoencoder/data_augmentation/rvae_augmentation.yaml @@ -0,0 +1,10 @@ +rvae_augmentation: + _target_: sledge.autoencoder.data_augmentation.rvae_augmentation.RVAEAugmenter + _convert_: 'all' + + config: ${autoencoder_model.config} + + se2_noise: [1,1,2.5] # meter, meter, degree + p_vehicle_dropout: 0.1 + p_pedestrian_dropout: 0.1 + p_static_dropout: 0.1 \ No newline at end of file diff --git a/sledge/script/config/autoencoder/data_augmentation/rvae_no_augmentation.yaml b/sledge/script/config/autoencoder/data_augmentation/rvae_no_augmentation.yaml new file mode 100644 index 0000000..0769bf2 --- /dev/null +++ b/sledge/script/config/autoencoder/data_augmentation/rvae_no_augmentation.yaml @@ -0,0 +1,10 @@ +rvae_augmentation: + _target_: sledge.autoencoder.data_augmentation.rvae_augmentation.RVAEAugmenter + _convert_: 'all' + + config: ${autoencoder_model.config} + + se2_noise: null + p_vehicle_dropout: null + p_pedestrian_dropout: null + p_static_dropout: null diff --git a/sledge/script/config/autoencoder/data_augmentation/vae_augmentation.yaml b/sledge/script/config/autoencoder/data_augmentation/vae_augmentation.yaml new file mode 100644 index 0000000..9201455 --- /dev/null +++ b/sledge/script/config/autoencoder/data_augmentation/vae_augmentation.yaml @@ -0,0 +1,10 @@ +vae_augmentation: + _target_: sledge.autoencoder.data_augmentation.vae_augmentation.VAEAugmenter + _convert_: 'all' + + config: ${autoencoder_model.config} + + se2_noise: [1,1,2.5] # meter, meter, degree + p_vehicle_dropout: 0.1 + p_pedestrian_dropout: 0.1 + p_static_dropout: 0.1 \ No newline at end of file diff --git a/sledge/script/config/autoencoder/data_loader/default_data_loader.yaml b/sledge/script/config/autoencoder/data_loader/default_data_loader.yaml new file mode 100644 index 0000000..e8b542f --- /dev/null +++ b/sledge/script/config/autoencoder/data_loader/default_data_loader.yaml @@ -0,0 +1,10 @@ +datamodule: + train_fraction: 1.0 # [%] fraction of training samples to use + val_fraction: 1.0 # [%] fraction of validation samples to use + test_fraction: 1.0 # [%] fraction of test samples to use + +params: + batch_size: 64 # batch size per GPU + num_workers: 8 # number of dataloader workers + pin_memory: ${gpu} # allocate dataloader examples in a page-locked memory for faster host-to-device transfer + drop_last: true # drop the last examples if the batch is not complete diff --git a/sledge/script/config/autoencoder/default_autoencoder.yaml b/sledge/script/config/autoencoder/default_autoencoder.yaml new file mode 100644 index 0000000..9a072da --- /dev/null +++ b/sledge/script/config/autoencoder/default_autoencoder.yaml @@ -0,0 +1,49 @@ +hydra: + run: + dir: ${output_dir} + output_subdir: ${output_dir}/code/hydra # Store hydra's config breakdown here for debugging + searchpath: # Only in these paths are discoverable + - pkg://sledge.script.config.common + - pkg://sledge.script.experiments # Put experiments configs in script/experiments/ + +defaults: + - default_experiment + - default_common + + # Trainer and callbacks + - lightning: default_lightning + - callbacks: default_callbacks + + # Optimizer settings + - optimizer: adamw # [adam, adamw] supported optimizers + - lr_scheduler: one_cycle_lr # [one_cycle_lr, multistep_lr] supported lr_schedulers + - warm_up_lr_scheduler: null # [linear_warm_up, constant_warm_up] supported warm up lr schedulers + + # Data Loading + - data_loader: default_data_loader + - splitter: ??? + + # Objectives and metrics + - objective: ??? + - training_metric: null + - matching: null + - data_augmentation: null + - data_augmentation_scheduler: null # [default_augmentation_schedulers, stepwise_augmentation_probability_scheduler, stepwise_noise_parameter_scheduler] supported data augmentation schedulers + - scenario_type_weights: default_scenario_type_weights + +experiment_name: ${py_func}_autoencoder +objective_aggregate_mode: ??? # How to aggregate multiple objectives, can be 'mean', 'max', 'sum' + +autoencoder_checkpoint: null + +# Cache parameters +cache: + autoencoder_cache_path: ${oc.env:SLEDGE_EXP_ROOT}/caches/autoencoder_cache # Local/remote path to store all preprocessed artifacts from the data pipeline + latent_name: null + use_cache_without_dataset: false # Load all existing features from a local/remote cache without loading the dataset + force_feature_computation: false # Recompute features even if a cache exists + + cleanup_autoencoder_cache: false # Cleanup cached data in the cache_path, this ensures that new data are generated if the same cache_path is passed + +# Mandatory parameters +py_func: ??? # Function to be run inside main (can be "train", "test", "cache") diff --git a/sledge/script/config/autoencoder/lightning/default_lightning.yaml b/sledge/script/config/autoencoder/lightning/default_lightning.yaml new file mode 100644 index 0000000..505142f --- /dev/null +++ b/sledge/script/config/autoencoder/lightning/default_lightning.yaml @@ -0,0 +1,47 @@ +distributed_training: + equal_variance_scaling_strategy: true # scales lr and betas either linearly if false (multiply by num GPUs) or with equal_variance if true (multiply by sqaure root of num GPUs) + +trainer: + checkpoint: + resume_training: false # load the model from the last epoch and resume training + save_top_k: -1 # save the top K models in terms of performance + monitor: loss/val_loss # metric to monitor for performance + mode: min # minimize/maximize metric + + params: + max_time: "04:00:00:00" # training time before the process is terminated + + max_epochs: 50 # maximum number of training epochs + check_val_every_n_epoch: 1 # run validation set every n training epochs + val_check_interval: 1.0 # [%] run validation set every X% of training set + + limit_train_batches: 1.0 # how much of training dataset to check (float = fraction, int = num_batches) + limit_val_batches: 1.0 # how much of validation dataset to check (float = fraction, int = num_batches) + limit_test_batches: 1.0 # how much of test dataset to check (float = fraction, int = num_batches) + + devices: -1 # The devices to use. -1 to indicate all available devices + # accelerator: ddp # distribution method + precision: 32-true # floating point precision + # amp_level: O2 # AMP optimization level + num_nodes: 1 # Number of nodes used for training + + # auto_scale_batch_size: false + # auto_lr_find: false # tunes LR before beginning training + # terminate_on_nan: true # terminates training if a nan is encountered in loss/weights + + num_sanity_val_steps: 0 # number of validation steps to run before training begins + fast_dev_run: false # runs 1 batch of train/val/test for sanity + + accumulate_grad_batches: 1 # accumulates gradients every n batches + # track_grad_norm: -1 # logs the p-norm for inspection + gradient_clip_val: 0.0 # value to clip gradients + gradient_clip_algorithm: norm # [value, norm] method to clip gradients + + # checkpoint_callback: true # enab le default checkpoint + + overfitting: + enable: false # run an overfitting test instead of training + + params: + max_epochs: 150 # number of epochs to overfit the same batches + overfit_batches: 1 # number of batches to overfit diff --git a/sledge/script/config/autoencoder/lr_scheduler/multistep_lr.yaml b/sledge/script/config/autoencoder/lr_scheduler/multistep_lr.yaml new file mode 100644 index 0000000..3ccbfd8 --- /dev/null +++ b/sledge/script/config/autoencoder/lr_scheduler/multistep_lr.yaml @@ -0,0 +1,5 @@ +_target_: torch.optim.lr_scheduler.MultiStepLR +_convert_: all + +milestones: [100, 170] # decays the learning rate of each parameter group by gamma once the number of epochs equals one of the milestones +gamma: 0.1 # multiplicative factor of learning rate decay diff --git a/sledge/script/config/autoencoder/lr_scheduler/one_cycle_lr.yaml b/sledge/script/config/autoencoder/lr_scheduler/one_cycle_lr.yaml new file mode 100644 index 0000000..b99fdf0 --- /dev/null +++ b/sledge/script/config/autoencoder/lr_scheduler/one_cycle_lr.yaml @@ -0,0 +1,49 @@ +_target_: torch.optim.lr_scheduler.OneCycleLR +_convert_: 'all' + +# Complete details found here: https://pytorch.org/docs/master/generated/torch.optim.lr_scheduler.OneCycleLR.html +# Either total_steps OR (epochs AND steps_per_epoch) must be provided. + +# Updated through code in the model with configure_optimizers() +optimizer: null + +# Upper learning rate boundaries in the cycle for each parameter group. +max_lr: 1e-4 + +# The number of epochs to train for. +epochs: ${lightning.trainer.params.max_epochs} + +# The number of steps per epoch to train for. This is used along with epochs in order to infer the total number of steps +# in the cycle if a value for total_steps is not provided. +# Updated through code in update_distributed_lr_scheduler_config(). +steps_per_epoch: null + +# The percentage of the cycle (in number of steps) spent increasing the learning rate. +pct_start: 0.0 + +# {‘cos’, ‘linear’} Specifies the annealing strategy: “cos” for cosine annealing, “linear” for linear annealing. +anneal_strategy: cos + +# If True, momentum is cycled inversely to learning rate between ‘base_momentum’ and ‘max_momentum’. +cycle_momentum: true + +# Lower momentum boundaries in the cycle for each parameter group. Note that momentum is cycled inversely to +# learning rate; at the peak of a cycle, momentum is ‘base_momentum’ and learning rate is ‘max_lr’. +base_momentum: 0.85 + +# Upper momentum boundaries in the cycle for each parameter group. Functionally, it defines the cycle amplitude +# (max_momentum - base_momentum). Note that momentum is cycled inversely to learning rate; at the start of a cycle, +# momentum is ‘max_momentum’ and learning rate is ‘base_lr’ +max_momentum: 0.95 + +# Determines the initial learning rate via initial_lr = max_lr/div_factor +div_factor: 10 + +# Determines the final initial learning rate to be used via final_initial_lr = initial_lr/final_div_factor +final_div_factor: 10 + +# The index of the last batch. This parameter is used when resuming a training job. Since step() should be invoked after +# each batch instead of after each epoch, this number represents the total number of batches computed, not the total +# number of epochs computed. When last_epoch=-1, the schedule is started from the beginning. + +last_epoch: -1 # Unclear if lightning uses for step-level checkpoint resume but kept for completion. KIV. diff --git a/sledge/script/config/autoencoder/matching/rvae_green_lights_matching.yaml b/sledge/script/config/autoencoder/matching/rvae_green_lights_matching.yaml new file mode 100644 index 0000000..0c41292 --- /dev/null +++ b/sledge/script/config/autoencoder/matching/rvae_green_lights_matching.yaml @@ -0,0 +1,6 @@ +rvae_green_lights_matching: + _target_: sledge.autoencoder.modeling.matching.rvae_matching.RVAEHungarianMatching + _convert_: "all" + + key: "green_lights" + config: ${autoencoder_model.config} diff --git a/sledge/script/config/autoencoder/matching/rvae_lines_matching.yaml b/sledge/script/config/autoencoder/matching/rvae_lines_matching.yaml new file mode 100644 index 0000000..5fd2c6e --- /dev/null +++ b/sledge/script/config/autoencoder/matching/rvae_lines_matching.yaml @@ -0,0 +1,6 @@ +rvae_lines_matching: + _target_: sledge.autoencoder.modeling.matching.rvae_matching.RVAEHungarianMatching + _convert_: "all" + + key: "lines" + config: ${autoencoder_model.config} diff --git a/sledge/script/config/autoencoder/matching/rvae_pedestrians_matching.yaml b/sledge/script/config/autoencoder/matching/rvae_pedestrians_matching.yaml new file mode 100644 index 0000000..ff1ce0e --- /dev/null +++ b/sledge/script/config/autoencoder/matching/rvae_pedestrians_matching.yaml @@ -0,0 +1,6 @@ +rvae_pedestrians_matching: + _target_: sledge.autoencoder.modeling.matching.rvae_matching.RVAEHungarianMatching + _convert_: "all" + + key: "pedestrians" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/matching/rvae_red_lights_matching.yaml b/sledge/script/config/autoencoder/matching/rvae_red_lights_matching.yaml new file mode 100644 index 0000000..81a9e3a --- /dev/null +++ b/sledge/script/config/autoencoder/matching/rvae_red_lights_matching.yaml @@ -0,0 +1,6 @@ +rvae_red_lights_matching: + _target_: sledge.autoencoder.modeling.matching.rvae_matching.RVAEHungarianMatching + _convert_: "all" + + key: "red_lights" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/matching/rvae_static_objects_matching.yaml b/sledge/script/config/autoencoder/matching/rvae_static_objects_matching.yaml new file mode 100644 index 0000000..47df101 --- /dev/null +++ b/sledge/script/config/autoencoder/matching/rvae_static_objects_matching.yaml @@ -0,0 +1,6 @@ +rvae_static_objects_matching: + _target_: sledge.autoencoder.modeling.matching.rvae_matching.RVAEHungarianMatching + _convert_: "all" + + key: "static_objects" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/matching/rvae_vehicles_matching.yaml b/sledge/script/config/autoencoder/matching/rvae_vehicles_matching.yaml new file mode 100644 index 0000000..c5c4a18 --- /dev/null +++ b/sledge/script/config/autoencoder/matching/rvae_vehicles_matching.yaml @@ -0,0 +1,6 @@ +rvae_vehicles_matching: + _target_: sledge.autoencoder.modeling.matching.rvae_matching.RVAEHungarianMatching + _convert_: "all" + + key: "vehicles" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/kl_objective.yaml b/sledge/script/config/autoencoder/objective/kl_objective.yaml new file mode 100644 index 0000000..327e068 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/kl_objective.yaml @@ -0,0 +1,4 @@ +kl_objective: + _target_: sledge.autoencoder.modeling.objectives.kl_objective.KLObjective + _convert_: 'all' + weight: ${autoencoder_model.config.kl_weight} diff --git a/sledge/script/config/autoencoder/objective/rvae_ego_objective.yaml b/sledge/script/config/autoencoder/objective/rvae_ego_objective.yaml new file mode 100644 index 0000000..afcfeb2 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/rvae_ego_objective.yaml @@ -0,0 +1,5 @@ +rvae_ego_objective: + _target_: sledge.autoencoder.modeling.objectives.rvae_objective.RVAEEgoObjective + _convert_: "all" + + weight: ${autoencoder_model.config.ego_reconstruction_weight} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/rvae_green_lights_objective.yaml b/sledge/script/config/autoencoder/objective/rvae_green_lights_objective.yaml new file mode 100644 index 0000000..7faa9e5 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/rvae_green_lights_objective.yaml @@ -0,0 +1,6 @@ +rvae_green_lights_objective: + _target_: sledge.autoencoder.modeling.objectives.rvae_objective.RVAEHungarianObjective + _convert_: "all" + + key: "green_lights" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/rvae_lines_objective.yaml b/sledge/script/config/autoencoder/objective/rvae_lines_objective.yaml new file mode 100644 index 0000000..24a80b8 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/rvae_lines_objective.yaml @@ -0,0 +1,6 @@ +rvae_lines_objective: + _target_: sledge.autoencoder.modeling.objectives.rvae_objective.RVAEHungarianObjective + _convert_: "all" + + key: "lines" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/rvae_pedestrians_objective.yaml b/sledge/script/config/autoencoder/objective/rvae_pedestrians_objective.yaml new file mode 100644 index 0000000..fea8e15 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/rvae_pedestrians_objective.yaml @@ -0,0 +1,6 @@ +rvae_pedestrians_objective: + _target_: sledge.autoencoder.modeling.objectives.rvae_objective.RVAEHungarianObjective + _convert_: "all" + + key: "pedestrians" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/rvae_red_lights_objective.yaml b/sledge/script/config/autoencoder/objective/rvae_red_lights_objective.yaml new file mode 100644 index 0000000..1eca9b1 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/rvae_red_lights_objective.yaml @@ -0,0 +1,6 @@ +rvae_red_lights_objective: + _target_: sledge.autoencoder.modeling.objectives.rvae_objective.RVAEHungarianObjective + _convert_: "all" + + key: "red_lights" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/rvae_static_objects_objective.yaml b/sledge/script/config/autoencoder/objective/rvae_static_objects_objective.yaml new file mode 100644 index 0000000..c717c09 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/rvae_static_objects_objective.yaml @@ -0,0 +1,6 @@ +rvae_static_objects_objective: + _target_: sledge.autoencoder.modeling.objectives.rvae_objective.RVAEHungarianObjective + _convert_: "all" + + key: "static_objects" + config: ${autoencoder_model.config} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/rvae_vehicles_objective.yaml b/sledge/script/config/autoencoder/objective/rvae_vehicles_objective.yaml new file mode 100644 index 0000000..27edfa0 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/rvae_vehicles_objective.yaml @@ -0,0 +1,6 @@ +rvae_vehicles_objective: + _target_: sledge.autoencoder.modeling.objectives.rvae_objective.RVAEHungarianObjective + _convert_: "all" + + key: "vehicles" + config: ${autoencoder_model.config} diff --git a/sledge/script/config/autoencoder/objective/vae_bce_objective.yaml b/sledge/script/config/autoencoder/objective/vae_bce_objective.yaml new file mode 100644 index 0000000..36264f5 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/vae_bce_objective.yaml @@ -0,0 +1,4 @@ +vae_bce_objective: + _target_: sledge.autoencoder.modeling.objectives.vae_objective.VAEBCEObjective + _convert_: 'all' + weight: ${autoencoder_model.config.reconstruction_weight} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/objective/vae_l1_objective.yaml b/sledge/script/config/autoencoder/objective/vae_l1_objective.yaml new file mode 100644 index 0000000..6dbc642 --- /dev/null +++ b/sledge/script/config/autoencoder/objective/vae_l1_objective.yaml @@ -0,0 +1,4 @@ +vae_bce_objective: + _target_: sledge.autoencoder.modeling.objectives.vae_objective.VAEL1Objective + _convert_: 'all' + weight: ${autoencoder_model.config.reconstruction_weight} \ No newline at end of file diff --git a/sledge/script/config/autoencoder/optimizer/adam.yaml b/sledge/script/config/autoencoder/optimizer/adam.yaml new file mode 100644 index 0000000..1994345 --- /dev/null +++ b/sledge/script/config/autoencoder/optimizer/adam.yaml @@ -0,0 +1,6 @@ +_target_: torch.optim.Adam +_convert_: 'all' + +lr: 5e-5 # learning rate +weight_decay: 5e-4 # l2 norm penalty +betas: [0.9, 0.999] # coefficients used for computing running averages of gradient and its square diff --git a/sledge/script/config/autoencoder/optimizer/adamw.yaml b/sledge/script/config/autoencoder/optimizer/adamw.yaml new file mode 100644 index 0000000..742e57a --- /dev/null +++ b/sledge/script/config/autoencoder/optimizer/adamw.yaml @@ -0,0 +1,6 @@ +_target_: torch.optim.AdamW +_convert_: 'all' + +lr: 5e-5 # learning rate +weight_decay: 5e-4 # weight decay coefficient +betas: [0.9, 0.999] # coefficients used for computing running averages of gradient and its square diff --git a/sledge/script/config/autoencoder/optimizer/sgd.yaml b/sledge/script/config/autoencoder/optimizer/sgd.yaml new file mode 100644 index 0000000..38ab858 --- /dev/null +++ b/sledge/script/config/autoencoder/optimizer/sgd.yaml @@ -0,0 +1,8 @@ +_target_: torch.optim.SGD +_convert_: 'all' + +lr: 5e-5 # learning rate +momentum: 0.95 # momentum factor +weight_decay: 1e-5 # l2 penalty +dampening: 0.0 # dampening used for momentum +nesterov: true # enables nesterov momentum if true, otherwise nesterov momentum is not used diff --git a/sledge/script/config/autoencoder/scenario_type_weights/default_scenario_type_weights.yaml b/sledge/script/config/autoencoder/scenario_type_weights/default_scenario_type_weights.yaml new file mode 100644 index 0000000..ee1962b --- /dev/null +++ b/sledge/script/config/autoencoder/scenario_type_weights/default_scenario_type_weights.yaml @@ -0,0 +1,11 @@ +scenario_type_sampling_weights: + # scenario_name: scenario_type_weights + # If a scenario types weight is not specified by default its weight is 1.0. + # To sample with a lower probability set a weight lower than 1.0 + # To sample with a higher probability set a weight higher than 1.0 + enable: false + scenario_type_weights: + unknown: 1.0 + +scenario_type_loss_weights: + unknown: 1.0 diff --git a/sledge/script/config/autoencoder/training_metric/kl_metric.yaml b/sledge/script/config/autoencoder/training_metric/kl_metric.yaml new file mode 100644 index 0000000..499bf29 --- /dev/null +++ b/sledge/script/config/autoencoder/training_metric/kl_metric.yaml @@ -0,0 +1,3 @@ +kl_metric: + _target_: sledge.autoencoder.modeling.metrics.kl_metric.KLMetric + _convert_: 'all' diff --git a/sledge/script/config/common/__init__.py b/sledge/script/config/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/config/common/autoencoder_model/rvae_model.yaml b/sledge/script/config/common/autoencoder_model/rvae_model.yaml new file mode 100644 index 0000000..794d4a7 --- /dev/null +++ b/sledge/script/config/common/autoencoder_model/rvae_model.yaml @@ -0,0 +1,69 @@ +_target_: sledge.autoencoder.modeling.models.rvae.rvae_model.RVAEModel +_convert_: 'all' + +config: + _target_: sledge.autoencoder.modeling.models.rvae.rvae_config.RVAEConfig + + # 1. features raw + radius: 100 + pose_interval: 1.0 + + # 2. features in frame + frame: [64, 64] + num_lines: 50 + num_vehicles: 50 + num_pedestrians: 20 + num_static_objects: 30 + num_green_lights: 20 + num_red_lights: 20 + + num_line_poses: 20 + vehicle_max_velocity: 15 + pedestrian_max_velocity: 2 + + pixel_size: 0.25 + line_dots_radius: 0 + + # 3. raster encoder π + model_name: "resnet50" + down_factor: 32 # NOTE: specific to resnet + num_input_channels: 12 + latent_channel: 64 + + # 4. vector decoder φ + num_encoder_layers: 0 + num_decoder_layers: 6 + + patch_size: 1 + dropout: 0.1 + num_head: 8 + d_model: 512 + d_ffn: 2048 + activation: "relu" + normalize_before: False + positional_embedding: "sine" + split_latent: True + + head_d_ffn: 1024 + head_num_layers: 1 + + num_line_queries: 50 + num_vehicle_queries: 50 + num_pedestrian_queries: 20 + num_static_object_queries: 30 + num_green_light_queries: 20 + num_red_light_queries: 20 + + # matching & loss + line_reconstruction_weight: 2 + line_ce_weight: 5 + + box_reconstruction_weight: 2 + box_ce_weight: 5 + + ego_reconstruction_weight: 1 + kl_weight: 0.1 + + norm_by_count: False + + threshold: 0.3 diff --git a/sledge/script/config/common/autoencoder_model/vae_model.yaml b/sledge/script/config/common/autoencoder_model/vae_model.yaml new file mode 100644 index 0000000..fc44a62 --- /dev/null +++ b/sledge/script/config/common/autoencoder_model/vae_model.yaml @@ -0,0 +1,39 @@ +_target_: sledge.autoencoder.modeling.models.vae.vae_model.VAEModel +_convert_: 'all' + + +config: + _target_: sledge.autoencoder.modeling.models.vae.vae_config.VAEConfig + + # 1. features raw + radius: 100 + pose_interval: 1.0 + + # 2. features in frame + frame: [64, 64] + num_lines: 50 + num_vehicles: 50 + num_pedestrians: 20 + num_static_objects: 30 + num_green_lights: 20 + num_red_lights: 20 + + num_line_poses: 20 + vehicle_max_velocity: 15 + pedestrian_max_velocity: 2 + + pixel_size: 0.25 + line_dots_radius: 0 + + # 3. raster encoder π + model_name: "resnet50" + down_factor: 32 # NOTE: specific to resnet + num_input_channels: 12 + latent_channel: 64 + + # loss + reconstruction_weight: 1.0 + kl_weight: 0.1 + + # output + threshold: 0.3 \ No newline at end of file diff --git a/sledge/script/config/common/default_common.yaml b/sledge/script/config/common/default_common.yaml new file mode 100644 index 0000000..5f957b6 --- /dev/null +++ b/sledge/script/config/common/default_common.yaml @@ -0,0 +1,17 @@ +# Default common configs +defaults: + # Add common items + - scenario_builder: nuplan_mini + - scenario_filter: one_continuous_log + - model: null + - autoencoder_model: null + - diffusion_model: null + # Worker that is used to run simulations + - worker: ray_distributed + +distribute_by_scenario: true +distributed_timeout_seconds: 7200 # Sets how long to wait while synchronizing across worker nodes in a distributed context. +selected_simulation_metrics: null + +# Sets verbosity level, in particular determines if progress bars are shown or not. +verbose: false diff --git a/sledge/script/config/common/default_experiment.yaml b/sledge/script/config/common/default_experiment.yaml new file mode 100644 index 0000000..89a78d5 --- /dev/null +++ b/sledge/script/config/common/default_experiment.yaml @@ -0,0 +1,24 @@ +# Common experiment configs +group: ${oc.env:SLEDGE_EXP_ROOT}/exp # This is where results, logs, config, etc. are saved +experiment_name: ??? # Experiment name, by default 'simulation' or 'training' +job_name: ??? # Job name, as defined in the specific yaml files. + +# Directory structure +date_format: '%Y.%m.%d.%H.%M.%S' +experiment_uid: ${now:${date_format}} # Unique Id of the experiment, default to timestamp +experiment: ${experiment_name}/${job_name}/${experiment_uid} # Unique name of the experiment +output_dir: ${group}/${experiment} # Output directory to save all training artifacts +metric_dir: metrics # Metric dir name to save metric results. +aggregator_metric_dir: aggregator_metric # Aggregator metric dir name to save aggregated metrics. +runner_report_file: runner_report.parquet # Name of the parquet file the RunnerReport will be stored to +log_config: false # Whether to log the final config after all overrides and interpolations + +# Execution +max_number_of_workers: null # Set null to disable threading for simulation execution +seed: 0 # Random seed value. +enable_profiling: false # Whether to enable profiler which will be dumped to "profiling" folder +gpu: true # Whether to use available GPUs during training/simulation + +# Logger +logger_level: info # Level of logger +logger_format_string: null # Logger format string, set null to use the default format string diff --git a/sledge/script/config/common/diffusion_model/dit_b_model.yaml b/sledge/script/config/common/diffusion_model/dit_b_model.yaml new file mode 100644 index 0000000..0def637 --- /dev/null +++ b/sledge/script/config/common/diffusion_model/dit_b_model.yaml @@ -0,0 +1,19 @@ +_target_: diffusers.models.DiTTransformer2DModel +_convert_: 'all' + +activation_fn: "gelu-approximate" +attention_bias: true +attention_head_dim: 64 +dropout: 0.0 +in_channels: 64 +norm_elementwise_affine: false +norm_eps: 1e-05 +norm_num_groups: 32 +norm_type: "ada_norm_zero" +num_attention_heads: 12 +num_embeds_ada_norm: ${num_classes} +num_layers: 12 +out_channels: 64 +patch_size: 1 +sample_size: 8 +upcast_attention: false diff --git a/sledge/script/config/common/diffusion_model/dit_l_model.yaml b/sledge/script/config/common/diffusion_model/dit_l_model.yaml new file mode 100644 index 0000000..ab347ae --- /dev/null +++ b/sledge/script/config/common/diffusion_model/dit_l_model.yaml @@ -0,0 +1,19 @@ +_target_: diffusers.models.DiTTransformer2DModel +_convert_: 'all' + +activation_fn: "gelu-approximate" +attention_bias: true +attention_head_dim: 64 +dropout: 0.0 +in_channels: 64 +norm_elementwise_affine: false +norm_eps: 1e-05 +norm_num_groups: 32 +norm_type: "ada_norm_zero" +num_attention_heads: 16 +num_embeds_ada_norm: ${num_classes} +num_layers: 24 +out_channels: 64 +patch_size: 1 +sample_size: 8 +upcast_attention: false diff --git a/sledge/script/config/common/diffusion_model/dit_s_model.yaml b/sledge/script/config/common/diffusion_model/dit_s_model.yaml new file mode 100644 index 0000000..a8d4dd1 --- /dev/null +++ b/sledge/script/config/common/diffusion_model/dit_s_model.yaml @@ -0,0 +1,19 @@ +_target_: diffusers.models.DiTTransformer2DModel +_convert_: 'all' + +activation_fn: "gelu-approximate" +attention_bias: true +attention_head_dim: 64 +dropout: 0.0 +in_channels: 64 +norm_elementwise_affine: false +norm_eps: 1e-05 +norm_num_groups: 32 +norm_type: "ada_norm_zero" +num_attention_heads: 16 +num_embeds_ada_norm: ${num_classes} +num_layers: 6 +out_channels: 64 +patch_size: 1 +sample_size: 8 +upcast_attention: false diff --git a/sledge/script/config/common/diffusion_model/dit_xl_model.yaml b/sledge/script/config/common/diffusion_model/dit_xl_model.yaml new file mode 100644 index 0000000..f5e31c5 --- /dev/null +++ b/sledge/script/config/common/diffusion_model/dit_xl_model.yaml @@ -0,0 +1,19 @@ +_target_: diffusers.models.DiTTransformer2DModel +_convert_: 'all' + +activation_fn: "gelu-approximate" +attention_bias: true +attention_head_dim: 64 +dropout: 0.0 +in_channels: 64 +norm_elementwise_affine: false +norm_eps: 1e-05 +norm_num_groups: 32 +norm_type: "ada_norm_zero" +num_attention_heads: 16 +num_embeds_ada_norm: ${num_classes} +num_layers: 28 +out_channels: 64 +patch_size: 1 +sample_size: 8 +upcast_attention: false diff --git a/sledge/script/config/common/scenario_builder/mock_abstract_scenario_builder.yaml b/sledge/script/config/common/scenario_builder/mock_abstract_scenario_builder.yaml new file mode 100644 index 0000000..4e4c34f --- /dev/null +++ b/sledge/script/config/common/scenario_builder/mock_abstract_scenario_builder.yaml @@ -0,0 +1,4 @@ +_target_: nuplan.planning.scenario_builder.test.mock_abstract_scenario_builder.MockAbstractScenarioBuilder +_convert_: 'all' + +num_scenarios: 5 # The number of scenarios to return from get_scenarios() diff --git a/sledge/script/config/common/scenario_builder/nuplan.yaml b/sledge/script/config/common/scenario_builder/nuplan.yaml new file mode 100644 index 0000000..834d8c9 --- /dev/null +++ b/sledge/script/config/common/scenario_builder/nuplan.yaml @@ -0,0 +1,19 @@ +_target_: nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_builder.NuPlanScenarioBuilder +_convert_: 'all' + +data_root: ${oc.env:NUPLAN_DATA_ROOT}/nuplan-v1.1/splits/trainval +map_root: ${oc.env:NUPLAN_MAPS_ROOT} +sensor_root: ${oc.env:NUPLAN_DATA_ROOT}/nuplan-v1.1/sensor_blobs + +db_files: null # if db file(s) exist locally, the data_root is ignored + +map_version: nuplan-maps-v1.0 + +include_cameras: false # Include camera data in the scenarios. + +max_workers: null +verbose: ${verbose} + +defaults: + - vehicle_parameters: nuplan_vehicle_parameters + - scenario_mapping: nuplan_scenario_mapping diff --git a/sledge/script/config/common/scenario_builder/nuplan_challenge.yaml b/sledge/script/config/common/scenario_builder/nuplan_challenge.yaml new file mode 100644 index 0000000..f95b484 --- /dev/null +++ b/sledge/script/config/common/scenario_builder/nuplan_challenge.yaml @@ -0,0 +1,19 @@ +_target_: nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_builder.NuPlanScenarioBuilder +_convert_: 'all' + +data_root: ${oc.env:NUPLAN_DATA_ROOT}/nuplan-v1.1/test/ +map_root: ${oc.env:NUPLAN_MAPS_ROOT} +sensor_root: ${oc.env:NUPLAN_DATA_ROOT}/nuplan-v1.1/sensor_blobs + +db_files: null # if db file(s) exist locally, the data_root is ignored + +map_version: nuplan-maps-v1.0 + +include_cameras: false # Include camera data in the scenarios. + +max_workers: null +verbose: ${verbose} + +defaults: + - vehicle_parameters: nuplan_vehicle_parameters + - scenario_mapping: nuplan_challenge_scenario_mapping diff --git a/sledge/script/config/common/scenario_builder/nuplan_mini.yaml b/sledge/script/config/common/scenario_builder/nuplan_mini.yaml new file mode 100644 index 0000000..33bbfc6 --- /dev/null +++ b/sledge/script/config/common/scenario_builder/nuplan_mini.yaml @@ -0,0 +1,19 @@ +_target_: nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_builder.NuPlanScenarioBuilder +_convert_: 'all' + +data_root: ${oc.env:NUPLAN_DATA_ROOT}/nuplan-v1.1/splits/mini +map_root: ${oc.env:NUPLAN_MAPS_ROOT} +sensor_root: ${oc.env:NUPLAN_DATA_ROOT}/nuplan-v1.1/sensor_blobs + +db_files: null # if db file(s) exist locally, the data_root is ignored + +map_version: nuplan-maps-v1.0 + +include_cameras: false # Include camera data in the scenarios. + +max_workers: null +verbose: ${verbose} + +defaults: + - vehicle_parameters: nuplan_vehicle_parameters + - scenario_mapping: nuplan_scenario_mapping diff --git a/sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_challenge_scenario_mapping.yaml b/sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_challenge_scenario_mapping.yaml new file mode 100644 index 0000000..8ed22ca --- /dev/null +++ b/sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_challenge_scenario_mapping.yaml @@ -0,0 +1,88 @@ +_target_: nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_utils.ScenarioMapping +_convert_: 'all' + +# The default sampling rate for scenarios. +# Will be used for all unknown scenarios as well as those with subsample ratio not specified +subsample_ratio_override: 0.5 + +# List of scenarios and their extraction instructions: +# scenario_name: name of scenario (e.g. ego overtaking) +# scenario_duration: duration of the scenario (e.g. extract 20s from when the event occurred) +# extraction_offset: [s] offset of the scenario (e.g. start at -5s from when the event occurred) +# subsample_ratio: ratio used sample the scenario (e.g. a 0.1 ratio means sample from 20Hz to 2Hz). If not provided, then `subsample_ratio_override` will be used. + +scenario_map: + # scenario_name: [scenario_duration, extraction_offset, subsample_ratio] + accelerating_at_crosswalk: [15.0, -3.0] + accelerating_at_stop_sign: [15.0, -3.0] + accelerating_at_stop_sign_no_crosswalk: [15.0, -3.0] + accelerating_at_traffic_light: [15.0, -3.0] + accelerating_at_traffic_light_with_lead: [15.0, -3.0] + accelerating_at_traffic_light_without_lead: [15.0, -3.0] + behind_bike: [15.0, -3.0] + behind_long_vehicle: [15.0, -3.0] + behind_pedestrian_on_driveable: [15.0, -3.0] + behind_pedestrian_on_pickup_dropoff: [15.0, -3.0] + changing_lane: [15.0, -3.0] + changing_lane_to_left: [15.0, -3.0] + changing_lane_to_right: [15.0, -3.0] + changing_lane_with_lead: [15.0, -3.0] + changing_lane_with_trail: [15.0, -3.0] + crossed_by_bike: [15.0, -3.0] + crossed_by_vehicle: [15.0, -3.0] + following_lane_with_lead: [15.0, -3.0] + following_lane_with_slow_lead: [15.0, -3.0] + following_lane_without_lead: [15.0, -3.0] + high_lateral_acceleration: [15.0, -3.0] + high_magnitude_jerk: [15.0, -3.0] + high_magnitude_speed: [15.0, -3.0] + low_magnitude_speed: [15.0, -3.0] + medium_magnitude_speed: [15.0, -3.0] + near_barrier_on_driveable: [15.0, -3.0] + near_construction_zone_sign: [15.0, -3.0] + near_high_speed_vehicle: [15.0, -3.0] + near_long_vehicle: [15.0, -3.0] + near_multiple_bikes: [15.0, -3.0] + near_multiple_pedestrians: [15.0, -3.0] + near_multiple_vehicles: [15.0, -3.0] + near_pedestrian_at_pickup_dropoff: [15.0, -3.0] + near_pedestrian_on_crosswalk: [15.0, -3.0] + near_pedestrian_on_crosswalk_with_ego: [15.0, -3.0] + near_trafficcone_on_driveable: [15.0, -3.0] + on_all_way_stop_intersection: [15.0, -3.0] + on_carpark: [15.0, -3.0] + on_intersection: [15.0, -3.0] + on_pickup_dropoff: [15.0, -3.0] + on_stopline_crosswalk: [15.0, -3.0] + on_stopline_stop_sign: [15.0, -3.0] + on_stopline_traffic_light: [15.0, -3.0] + on_traffic_light_intersection: [15.0, -3.0] + starting_high_speed_turn: [15.0, -3.0] + starting_left_turn: [15.0, -3.0] + starting_low_speed_turn: [15.0, -3.0] + starting_protected_cross_turn: [15.0, -3.0] + starting_protected_noncross_turn: [15.0, -3.0] + starting_right_turn: [15.0, -3.0] + starting_straight_stop_sign_intersection_traversal: [15.0, -3.0] + starting_straight_traffic_light_intersection_traversal: [15.0, -3.0] + starting_u_turn: [15.0, -3.0] + starting_unprotected_cross_turn: [15.0, -3.0] + starting_unprotected_noncross_turn: [15.0, -3.0] + stationary: [15.0, -3.0] + stationary_at_crosswalk: [15.0, -3.0] + stationary_at_traffic_light_with_lead: [15.0, -3.0] + stationary_at_traffic_light_without_lead: [15.0, -3.0] + stationary_in_traffic: [15.0, -3.0] + stopping_at_crosswalk: [15.0, -3.0] + stopping_at_stop_sign_no_crosswalk: [15.0, -3.0] + stopping_at_stop_sign_with_lead: [15.0, -3.0] + stopping_at_stop_sign_without_lead: [15.0, -3.0] + stopping_at_traffic_light_with_lead: [15.0, -3.0] + stopping_at_traffic_light_without_lead: [15.0, -3.0] + stopping_with_lead: [15.0, -3.0] + traversing_crosswalk: [15.0, -3.0] + traversing_intersection: [15.0, -3.0] + traversing_narrow_lane: [15.0, -3.0] + traversing_pickup_dropoff: [15.0, -3.0] + traversing_traffic_light_intersection: [15.0, -3.0] + waiting_for_pedestrian_to_cross: [15.0, -3.0] diff --git a/sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_scenario_mapping.yaml b/sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_scenario_mapping.yaml new file mode 100644 index 0000000..8ed22ca --- /dev/null +++ b/sledge/script/config/common/scenario_builder/scenario_mapping/nuplan_scenario_mapping.yaml @@ -0,0 +1,88 @@ +_target_: nuplan.planning.scenario_builder.nuplan_db.nuplan_scenario_utils.ScenarioMapping +_convert_: 'all' + +# The default sampling rate for scenarios. +# Will be used for all unknown scenarios as well as those with subsample ratio not specified +subsample_ratio_override: 0.5 + +# List of scenarios and their extraction instructions: +# scenario_name: name of scenario (e.g. ego overtaking) +# scenario_duration: duration of the scenario (e.g. extract 20s from when the event occurred) +# extraction_offset: [s] offset of the scenario (e.g. start at -5s from when the event occurred) +# subsample_ratio: ratio used sample the scenario (e.g. a 0.1 ratio means sample from 20Hz to 2Hz). If not provided, then `subsample_ratio_override` will be used. + +scenario_map: + # scenario_name: [scenario_duration, extraction_offset, subsample_ratio] + accelerating_at_crosswalk: [15.0, -3.0] + accelerating_at_stop_sign: [15.0, -3.0] + accelerating_at_stop_sign_no_crosswalk: [15.0, -3.0] + accelerating_at_traffic_light: [15.0, -3.0] + accelerating_at_traffic_light_with_lead: [15.0, -3.0] + accelerating_at_traffic_light_without_lead: [15.0, -3.0] + behind_bike: [15.0, -3.0] + behind_long_vehicle: [15.0, -3.0] + behind_pedestrian_on_driveable: [15.0, -3.0] + behind_pedestrian_on_pickup_dropoff: [15.0, -3.0] + changing_lane: [15.0, -3.0] + changing_lane_to_left: [15.0, -3.0] + changing_lane_to_right: [15.0, -3.0] + changing_lane_with_lead: [15.0, -3.0] + changing_lane_with_trail: [15.0, -3.0] + crossed_by_bike: [15.0, -3.0] + crossed_by_vehicle: [15.0, -3.0] + following_lane_with_lead: [15.0, -3.0] + following_lane_with_slow_lead: [15.0, -3.0] + following_lane_without_lead: [15.0, -3.0] + high_lateral_acceleration: [15.0, -3.0] + high_magnitude_jerk: [15.0, -3.0] + high_magnitude_speed: [15.0, -3.0] + low_magnitude_speed: [15.0, -3.0] + medium_magnitude_speed: [15.0, -3.0] + near_barrier_on_driveable: [15.0, -3.0] + near_construction_zone_sign: [15.0, -3.0] + near_high_speed_vehicle: [15.0, -3.0] + near_long_vehicle: [15.0, -3.0] + near_multiple_bikes: [15.0, -3.0] + near_multiple_pedestrians: [15.0, -3.0] + near_multiple_vehicles: [15.0, -3.0] + near_pedestrian_at_pickup_dropoff: [15.0, -3.0] + near_pedestrian_on_crosswalk: [15.0, -3.0] + near_pedestrian_on_crosswalk_with_ego: [15.0, -3.0] + near_trafficcone_on_driveable: [15.0, -3.0] + on_all_way_stop_intersection: [15.0, -3.0] + on_carpark: [15.0, -3.0] + on_intersection: [15.0, -3.0] + on_pickup_dropoff: [15.0, -3.0] + on_stopline_crosswalk: [15.0, -3.0] + on_stopline_stop_sign: [15.0, -3.0] + on_stopline_traffic_light: [15.0, -3.0] + on_traffic_light_intersection: [15.0, -3.0] + starting_high_speed_turn: [15.0, -3.0] + starting_left_turn: [15.0, -3.0] + starting_low_speed_turn: [15.0, -3.0] + starting_protected_cross_turn: [15.0, -3.0] + starting_protected_noncross_turn: [15.0, -3.0] + starting_right_turn: [15.0, -3.0] + starting_straight_stop_sign_intersection_traversal: [15.0, -3.0] + starting_straight_traffic_light_intersection_traversal: [15.0, -3.0] + starting_u_turn: [15.0, -3.0] + starting_unprotected_cross_turn: [15.0, -3.0] + starting_unprotected_noncross_turn: [15.0, -3.0] + stationary: [15.0, -3.0] + stationary_at_crosswalk: [15.0, -3.0] + stationary_at_traffic_light_with_lead: [15.0, -3.0] + stationary_at_traffic_light_without_lead: [15.0, -3.0] + stationary_in_traffic: [15.0, -3.0] + stopping_at_crosswalk: [15.0, -3.0] + stopping_at_stop_sign_no_crosswalk: [15.0, -3.0] + stopping_at_stop_sign_with_lead: [15.0, -3.0] + stopping_at_stop_sign_without_lead: [15.0, -3.0] + stopping_at_traffic_light_with_lead: [15.0, -3.0] + stopping_at_traffic_light_without_lead: [15.0, -3.0] + stopping_with_lead: [15.0, -3.0] + traversing_crosswalk: [15.0, -3.0] + traversing_intersection: [15.0, -3.0] + traversing_narrow_lane: [15.0, -3.0] + traversing_pickup_dropoff: [15.0, -3.0] + traversing_traffic_light_intersection: [15.0, -3.0] + waiting_for_pedestrian_to_cross: [15.0, -3.0] diff --git a/sledge/script/config/common/scenario_builder/vehicle_parameters/nuplan_vehicle_parameters.yaml b/sledge/script/config/common/scenario_builder/vehicle_parameters/nuplan_vehicle_parameters.yaml new file mode 100644 index 0000000..fa36d06 --- /dev/null +++ b/sledge/script/config/common/scenario_builder/vehicle_parameters/nuplan_vehicle_parameters.yaml @@ -0,0 +1,10 @@ +_target_: nuplan.common.actor_state.vehicle_parameters.VehicleParameters +_convert_: 'all' +width: 2.297 +front_length: 4.049 +rear_length: 1.127 +cog_position_from_rear_axle: 1.67 +height: 1.777 +wheel_base: 3.089 +vehicle_name: "pacifica" +vehicle_type: "gen1" diff --git a/sledge/script/config/common/scenario_filter/filter_bos.yaml b/sledge/script/config/common/scenario_filter/filter_bos.yaml new file mode 100644 index 0000000..26b6df4 --- /dev/null +++ b/sledge/script/config/common/scenario_filter/filter_bos.yaml @@ -0,0 +1,21 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: null # List of scenario types to include + +log_names: null # Filter scenarios by log names +map_names: ["us-ma-boston"] # ["us-nv-las-vegas-strip", "us-pa-pittsburgh-hazelwood", "sg-one-north", "us-ma-boston"] + +num_scenarios_per_type: null # Number of scenarios per type +limit_total_scenarios: null # Limit total scenarios (float = fraction, int = num) - this filter can be applied on top of num_scenarios_per_type +timestamp_threshold_s: 1 # Filter scenarios to ensure scenarios have more than `timestamp_threshold_s` seconds between their initial lidar timestamps +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: true # Whether to expand multi-sample scenarios to multiple single-sample scenarios +remove_invalid_goals: true # Whether to remove scenarios where the mission goal is invalid +shuffle: true + +scenario_tokens: null # List of scenario tokens to include \ No newline at end of file diff --git a/sledge/script/config/common/scenario_filter/filter_lav.yaml b/sledge/script/config/common/scenario_filter/filter_lav.yaml new file mode 100644 index 0000000..ca4da7b --- /dev/null +++ b/sledge/script/config/common/scenario_filter/filter_lav.yaml @@ -0,0 +1,21 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: null # List of scenario types to include + +log_names: null # Filter scenarios by log names +map_names: ["us-nv-las-vegas-strip"] # ["us-nv-las-vegas-strip", "us-pa-pittsburgh-hazelwood", "sg-one-north", "us-ma-boston"] + +num_scenarios_per_type: null # Number of scenarios per type +limit_total_scenarios: null # Limit total scenarios (float = fraction, int = num) - this filter can be applied on top of num_scenarios_per_type +timestamp_threshold_s: 30 # Filter scenarios to ensure scenarios have more than `timestamp_threshold_s` seconds between their initial lidar timestamps +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: true # Whether to expand multi-sample scenarios to multiple single-sample scenarios +remove_invalid_goals: true # Whether to remove scenarios where the mission goal is invalid +shuffle: true + +scenario_tokens: null # List of scenario tokens to include \ No newline at end of file diff --git a/sledge/script/config/common/scenario_filter/filter_pgh.yaml b/sledge/script/config/common/scenario_filter/filter_pgh.yaml new file mode 100644 index 0000000..9cb721d --- /dev/null +++ b/sledge/script/config/common/scenario_filter/filter_pgh.yaml @@ -0,0 +1,21 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: null # List of scenario types to include + +log_names: null # Filter scenarios by log names +map_names: ["us-pa-pittsburgh-hazelwood"] # ["us-nv-las-vegas-strip", "us-pa-pittsburgh-hazelwood", "sg-one-north", "us-ma-boston"] + +num_scenarios_per_type: null # Number of scenarios per type +limit_total_scenarios: null # Limit total scenarios (float = fraction, int = num) - this filter can be applied on top of num_scenarios_per_type +timestamp_threshold_s: 2 # Filter scenarios to ensure scenarios have more than `timestamp_threshold_s` seconds between their initial lidar timestamps +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: true # Whether to expand multi-sample scenarios to multiple single-sample scenarios +remove_invalid_goals: true # Whether to remove scenarios where the mission goal is invalid +shuffle: true + +scenario_tokens: null # List of scenario tokens to include \ No newline at end of file diff --git a/sledge/script/config/common/scenario_filter/filter_sgp.yaml b/sledge/script/config/common/scenario_filter/filter_sgp.yaml new file mode 100644 index 0000000..1911f69 --- /dev/null +++ b/sledge/script/config/common/scenario_filter/filter_sgp.yaml @@ -0,0 +1,21 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: null # List of scenario types to include + +log_names: null # Filter scenarios by log names +map_names: ["sg-one-north"] # ["us-nv-las-vegas-strip", "us-pa-pittsburgh-hazelwood", "sg-one-north", "us-ma-boston"] + +num_scenarios_per_type: null # Number of scenarios per type +limit_total_scenarios: null # Limit total scenarios (float = fraction, int = num) - this filter can be applied on top of num_scenarios_per_type +timestamp_threshold_s: 2 # Filter scenarios to ensure scenarios have more than `timestamp_threshold_s` seconds between their initial lidar timestamps +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: true # Whether to expand multi-sample scenarios to multiple single-sample scenarios +remove_invalid_goals: true # Whether to remove scenarios where the mission goal is invalid +shuffle: true + +scenario_tokens: null # List of scenario tokens to include \ No newline at end of file diff --git a/sledge/script/config/common/scenario_filter/one_continuous_log.yaml b/sledge/script/config/common/scenario_filter/one_continuous_log.yaml new file mode 100644 index 0000000..d28f005 --- /dev/null +++ b/sledge/script/config/common/scenario_filter/one_continuous_log.yaml @@ -0,0 +1,21 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: null # List of scenario types to include +scenario_tokens: null # List of scenario tokens to include + +log_names: # Filter scenarios by log names + - 2021.07.16.20.45.29_veh-35_01095_01486 +map_names: null # Filter scenarios by map names + +num_scenarios_per_type: null # Number of scenarios per type +limit_total_scenarios: null # Limit total scenarios (float = fraction, int = num) - this filter can be applied on top of num_scenarios_per_type +timestamp_threshold_s: null # Filter scenarios to ensure scenarios have more than `timestamp_threshold_s` seconds between their initial lidar timestamps +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: false # Whether to expand multi-sample scenarios to multiple single-sample scenarios +remove_invalid_goals: true # Whether to remove scenarios where the mission goal is invalid +shuffle: false # Whether to shuffle the scenarios diff --git a/sledge/script/config/common/scenario_filter/reduced_val14_split.yaml b/sledge/script/config/common/scenario_filter/reduced_val14_split.yaml new file mode 100644 index 0000000..9be04ca --- /dev/null +++ b/sledge/script/config/common/scenario_filter/reduced_val14_split.yaml @@ -0,0 +1,353 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: # List of scenario types to include + - starting_left_turn + - starting_right_turn + - starting_straight_traffic_light_intersection_traversal + - stopping_with_lead + - high_lateral_acceleration + - high_magnitude_speed + - low_magnitude_speed + - traversing_pickup_dropoff + - waiting_for_pedestrian_to_cross + - behind_long_vehicle + - stationary_in_traffic + - near_multiple_vehicles + - changing_lane + - following_lane_with_lead + +scenario_tokens: + - '026c317cfdcf519a' + - '028bc03bf8a258fa' + - '0317b5f6ca155438' + - '033f14feaa47597e' + - '03a97e72a18c5e48' + - '03e95493fbff5c1d' + - '055c6ffe01db5864' + - '057eb9f2c019548d' + - '05d85cf78a525fb2' + - '063d6d1640f55688' + - '068990e1f0e35c7e' + - '095b84ce36e257ec' + - '096eb4e157b3563c' + - '0a3d3adc5ae45cc2' + - '0b1cdeb3c51b5205' + - '0c37c42e4e4058e3' + - '0d58368e8e125396' + - '12eea643bca95f1d' + - '137c4a602d535d1c' + - '14c25324ce6a5882' + - '15416947b62a540e' + - '162b92f3a7905adc' + - '16747f8ea1e85676' + - '175f26661c2a57c9' + - '17b350a9e3715073' + - '18a984b87ba2506b' + - '197ef7518e4657b2' + - '1a18ee213e0352d0' + - '1acc0a08688c5c99' + - '1d68dfeb693b526b' + - '1e3c0a57fd545a8b' + - '1ef1576ca3b953b3' + - '1f463bd4f9a85923' + - '204d871fbb455d23' + - '20e2686902f55f43' + - '2195dc8b26c851ff' + - '219c281fb14c52e8' + - '230060c908a951b1' + - '23f0855cc9475841' + - '2409a5f91e2f513d' + - '24bbdecdbaa55163' + - '25192935de6e5453' + - '2556145b71c75f61' + - '25b971ffad8d5c53' + - '25d2d661dff95f85' + - '2609befadf81533d' + - '2792f094cbbf5960' + - '28ddcbcd8bb254a5' + - '296d879cff115ae9' + - '2cad2f01cb595862' + - '2cdde2c236085d4d' + - '2d4fa9eb37155845' + - '30e0f8ea46375f52' + - '30e4c1810ee75456' + - '311f4e2a23315d60' + - '3241da6510585d86' + - '329b5ba897f85240' + - '32c5eb6392555f25' + - '33059fea98615a79' + - '34233af8e63e51de' + - '3455996fccd4517c' + - '34c94f4dd8305c06' + - '34cc55c5d4b2539b' + - '365607a1c1615ad4' + - '36c985d6cfeb5ba6' + - '3770584e504559b7' + - '38192f45d5ca558d' + - '38a0b54dbd485c06' + - '3964e5ace1915eb2' + - '396f95a905535bbd' + - '39d93ee37e815c76' + - '3b5ea626aaa8534a' + - '3bce58a1cb04528e' + - '40589f1edc47585d' + - '4064de4004385810' + - '4146a5366b6c5248' + - '41d071d979345e7d' + - '42dc17b3a3e551bc' + - '4476b0b2b59453fd' + - '44bf62e1dab45e1b' + - '4536d0b348705c37' + - '457238fe173052e9' + - '463043c08b5452bd' + - '4694d43b7e2b5ea2' + - '46e6b59c4e215c4f' + - '471bcafec949520f' + - '4827a89281c7541e' + - '48303bdf88815d47' + - '486795baafeb592e' + - '4b17b855d78a5ec1' + - '4b3b50a9f58b5d79' + - '4c1b28da60715d44' + - '4c1c2718ac325e8e' + - '4cc863eea1115fcf' + - '4d81b86694d0573f' + - '4dc6607958295f9c' + - '4ea1ebde0a295bfc' + - '4eb858122cc35a2c' + - '504019ae3310580a' + - '5082a241787d54a0' + - '53ca057d7c7755ca' + - '53fa6fba5cf35991' + - '54e0f65eb90b5dad' + - '550be0ceaa375286' + - '55733c8d885555c6' + - '571283eac7935097' + - '591e2ebb6b575d84' + - '5967b53cc3875e0a' + - '5a84205b674e5af7' + - '5b33af3315f25ecd' + - '5ba475c6dd885918' + - '5bd0e1215a9c5259' + - '5bd536ede7fe50d9' + - '5c4c4ecf0df65ec8' + - '5c78d5aa54ed57c9' + - '5d91ff45ef9d568f' + - '5de7437b35255b18' + - '5e1e67620ae25228' + - '5e7572e75eb953c5' + - '5ead309c59725b11' + - '5f0d9637d5685b2e' + - '5f7dd09ab72c50f8' + - '6050f41df75a5ab8' + - '60f2ca6047dc5021' + - '61829e34d13d5313' + - '6191ea9d0baa5bf0' + - '62452deaa9ce5a07' + - '62e2e6df6cf55f9a' + - '6388c51ebf9f51c2' + - '646b4cb0819453fc' + - '64759ecbeffe5869' + - '648e09834917530e' + - '64a53965ce705ff5' + - '65731b55db835d59' + - '6710960fc4da5d0d' + - '674980b85c3753ba' + - '677070059ece5469' + - '677c92a2c4825334' + - '680e1ce48b255213' + - '686b68fcd8f15a2d' + - '6a7df70b8eb0574d' + - '6bc4abef0de65d50' + - '6c3c40f8dd8e5f5e' + - '6cdae30d3ba55bfb' + - '6d1b914ec13854ec' + - '6edfdec64dd65e54' + - '6ef9524d46425b61' + - '716f0d9b081f52ca' + - '7251ea908bea569b' + - '73a82c645a7c5e8d' + - '73b25588578a5b6b' + - '73eb53dff8b25f4b' + - '747b3f3478005e8b' + - '758013f0b6695e0f' + - '76067b0955425be5' + - '763582c5ead35c59' + - '76f610e18c8f55ac' + - '77694ade4d405d8b' + - '77e4bc92cfc25d12' + - '788b81e474ea5504' + - '7a25cdc9d84f5ecf' + - '7ad0156c17f851c2' + - '7b9720064e21531f' + - '7bd2275c5d3d53df' + - '7d53acab2b74556e' + - '7e8f1a76d9c85e7d' + - '7ea63dc564a05b7a' + - '7f8f161607df5291' + - '808a15c826cd5dbf' + - '80bc03cef5d85e54' + - '83f9706646b25ac3' + - '842a25b8cf2855e3' + - '864e5105fd5752ef' + - '86594583ef5550fd' + - '875e1631b9095dc9' + - '87b249f4851251c5' + - '8a94f1b3d7f7587b' + - '8c56b329396a57a7' + - '8d1cc9933b715e6c' + - '8d81191bda30555e' + - '8ed610ddc1cc52a0' + - '8fc9474f19a35ab8' + - '91394c68db59574b' + - '915c1cd61d905d27' + - '91ed1256a8515b8a' + - '9218c2695ad6520c' + - '926c018999025db7' + - '92ba5d5b56165dfd' + - '9335a6ee8bcc5464' + - '95e64b3330455af3' + - '9604bb1e94d559a7' + - '968722d51b245c90' + - '96e080fd2c295ac5' + - '96ef38ef04695af1' + - '9731e046e0425cdc' + - '973fe5b937345deb' + - '97ad2c7f11d6554e' + - '98b491d41d715181' + - '9b6da76c0de6572f' + - '9be7b4ef51ad537b' + - '9c0d433fc2f15dd6' + - '9c1e334ea3e051dd' + - '9cda228a422a5fb1' + - '9d462a4dd9cd5b11' + - '9fd0937ff1965310' + - '9ff7afcfadad597e' + - 'a05cea1401535849' + - 'a29c93d75770583a' + - 'a2b3136526fc50ab' + - 'a48d32d5f6d35131' + - 'a4ca0544694c5dda' + - 'a5189e52ecf55b73' + - 'a576d67830d55a28' + - 'a57f23e56fce53c6' + - 'a5b1a535fe965823' + - 'a64188586ebc5d4b' + - 'a6d0faf2eb685e5c' + - 'a81d831747125494' + - 'a94aabe03f5f5da7' + - 'a997150843765f15' + - 'ab91cb3d77635d87' + - 'abb39bed2b05589a' + - 'ac13c5a286785307' + - 'acae294200ec51d5' + - 'accb4deeb3d85b2b' + - 'ae03f69c7026533f' + - 'af5cf490cb985fe0' + - 'afa7cac449815786' + - 'b0ab4efe7be45584' + - 'b145ce43a53b5d7b' + - 'b1c554f6b7a152b2' + - 'b1dd8b1cc81154a4' + - 'b2c793f4ef9758eb' + - 'b3546b30fb7258dc' + - 'b43db0f4e65e53c5' + - 'b50546004809509c' + - 'b74909dcf8195ee7' + - 'b76fef5d12315cb4' + - 'b87b476811165ff4' + - 'bae4605ca7b951bc' + - 'bc9cc5644a0454b0' + - 'bd4e4471f33e5687' + - 'bf9d422352ee5c30' + - 'c02c623b251d562e' + - 'c04ff1e8a1f0540f' + - 'c1eda4db881b5aa0' + - 'c5b74bfe6fd657c0' + - 'c7b73bb4ed7a551c' + - 'c89b3a61af8954e9' + - 'c8d19600d1e2562a' + - 'ca286185e214587d' + - 'caa3c84d224d570b' + - 'cabead651cd15c74' + - 'cb7e4535bcd953dc' + - 'cd63e5191fb854c4' + - 'ce19c72af8785b7f' + - 'ce7fd75299195de2' + - 'ced5010080af532e' + - 'cfa4a098011f5ecc' + - 'd1b64be0dfcf5e76' + - 'd240be3c6b8d5118' + - 'd306a231d7f05393' + - 'd365178818f35b5b' + - 'd48e4306258b5f58' + - 'd4e5abff84aa5a14' + - 'd6a462e2c67f54fe' + - 'd7c3a88767af5bcb' + - 'd9ec238b65cb5bda' + - 'da5cfe60db205927' + - 'da689fed99e85377' + - 'dbd756e6109a53a8' + - 'deb2b6a968145974' + - 'deb79037b3465096' + - 'df394bcf150c5221' + - 'df97236882ee56ec' + - 'dfc32e6130a55c18' + - 'dff23f624ea85db5' + - 'e0b9e734ff885d7c' + - 'e1ece70209ca503b' + - 'e3c45edc998b5e96' + - 'e4174448a1bc5e61' + - 'e46194de6db55331' + - 'e56317105f9c5dfe' + - 'e6eb740426755501' + - 'e81020ef70605898' + - 'e90acdd874565fd2' + - 'e911f6752c5f56ca' + - 'e9652e8fbd2f5555' + - 'e9841464cce9536d' + - 'e9b30fc73b785460' + - 'eb9ca536f81350a4' + - 'ec7721dd6251512d' + - 'eca32130c66755c1' + - 'ecbf53f612a05e2e' + - 'ecdd80b34a8f57a1' + - 'edd1dfb6ee0d5ad4' + - 'ee14219f39f454ad' + - 'ef0dabde0a9156ac' + - 'efe0e8c737105303' + - 'f13e150be53454ad' + - 'f216b1453e5352e4' + - 'f2b2d2085e3c5398' + - 'f40b8ada03b151e3' + - 'f484f84c37775861' + - 'f4e16c89a6c65972' + - 'f51fa581f64b5b3e' + - 'f555d4f10b7552ba' + - 'f6804d2da7c7505a' + - 'f7da2c3fd6f05e84' + - 'f8864e39b017508d' + - 'f98525276991509c' + - 'faa88de810d552cf' + - 'fb5f4e7d9d9a502e' + - 'fccb46f95c0559bb' + - 'fe6e2d63d9a855d4' + - 'fee0a0e2c7f35b57' + - 'ff0628976f4d5444' + - 'ffaf0595e6b15a1d' + +log_names: ${splitter.log_splits.val} +map_names: null + +num_scenarios_per_type: 25 +limit_total_scenarios: null +timestamp_threshold_s: 15 +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: false +remove_invalid_goals: true +shuffle: false diff --git a/sledge/script/config/common/scenario_filter/val14_split.yaml b/sledge/script/config/common/scenario_filter/val14_split.yaml new file mode 100644 index 0000000..20401d1 --- /dev/null +++ b/sledge/script/config/common/scenario_filter/val14_split.yaml @@ -0,0 +1,1153 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: # List of scenario types to include + - starting_left_turn + - starting_right_turn + - starting_straight_traffic_light_intersection_traversal + - stopping_with_lead + - high_lateral_acceleration + - high_magnitude_speed + - low_magnitude_speed + - traversing_pickup_dropoff + - waiting_for_pedestrian_to_cross + - behind_long_vehicle + - stationary_in_traffic + - near_multiple_vehicles + - changing_lane + - following_lane_with_lead + +scenario_tokens: + - 'fa20c34947a8558d' + - 'cb468419f191575a' + - '691bc6f41d6456ea' + - '1492e65004b65d05' + - 'c45571dbd2ce5e92' + - '83a18a11bf9b5baa' + - 'f56e788506ca570f' + - '91ed1256a8515b8a' + - '26c3cdb68bbd50e5' + - '4196301b207f5f09' + - '5343ee1857dc5c82' + - '92cebf9e9bee5055' + - 'fa037e7eb0605f8c' + - 'e34c9ed042405fe4' + - 'eca32130c66755c1' + - 'cedc9219a6ca5024' + - '501383db138758ce' + - '1b9234d32b0750bb' + - '0b23f003881e5a2f' + - 'e504f54a41665d9a' + - 'b20af475e6f35e7b' + - '011a76cbdda851bb' + - '317ff9c87dfb50ae' + - '9dde07ed9e5455d3' + - '59ea7796d6bb5870' + - 'c0a520c971775b7e' + - '8eb8410b1e385101' + - '5c6c1dff8d0b5a45' + - 'b1f369f514d95f4c' + - 'd1cc883552ca57aa' + - '9553e5e5324655c9' + - 'ea3ba48c466e55db' + - '8d0a9c0e890c5ec8' + - 'a657d9c0c5ac5d86' + - 'b48240f016ac5440' + - '5598103d55ca5cc2' + - 'b4e45738e3f85601' + - '4c81b08174485d4a' + - 'c8ca37aa5af65553' + - 'e72fb7eca4b857a6' + - '2f9fa2092bf05a7b' + - '3dc3d20b2f78573c' + - '15455c755f3d5f8e' + - '222e748894675f29' + - '0a05b5cb5f6454b0' + - '137c4a602d535d1c' + - 'f354e32413d75c9c' + - '2609befadf81533d' + - 'aa70fa6e14c551a5' + - '587d629b0b535e68' + - '7e4d2e8aba825cdd' + - 'dbbd1fdecb775d9d' + - '56f6af31e23b504b' + - '42eabc4389605381' + - '4930fdc43c1857ae' + - '8680af9e56b05175' + - 'e2c9d4f7a6ca5df7' + - '9d15a6330fb0542b' + - 'ff9f884349e65234' + - '1d68dfeb693b526b' + - 'fbf76317822452b3' + - 'b70085dba0765845' + - 'b776d43d2e295eb2' + - 'a96c109c569b5904' + - '71a1225cd14a53c0' + - '75900dd01f025b22' + - '28a7494f82395273' + - 'c11cf83340ef57cf' + - '477012ad90bd5ead' + - 'a7efeb87f0965e06' + - '875d9d5903385c85' + - '2f8b6f87b2265b65' + - '80ca832887445466' + - '18438b7efd275e7a' + - 'a63404b6ee7a5ca4' + - 'e8f7ada1d5e35559' + - 'b3b660d07d0453cd' + - 'd9af410284a9558a' + - '011d97b0e5a85f5f' + - '8118ba55e04751a7' + - 'de124fbf80c951d3' + - '09387c1b42aa5288' + - '2acb1cd62bbc5f5d' + - '224a49a1ad4d5ed3' + - '626f33408a435f47' + - '8081e335d96056e0' + - '74f33e65028d5917' + - '53348cde5f3e59e4' + - '1a66a7c3545d5323' + - 'd9e89ee9ac10598d' + - '0e3ebea8bc015a83' + - '33d291fdc9ec5f29' + - 'd598deb604155067' + - 'ed6c378bd7c15cf1' + - '84592d9179f25c58' + - '8b4daccc76555c6c' + - 'ae5eff08c15f5772' + - 'a7b19f865b7c5786' + - 'f1c0b066cfca5c81' + - '60111f56cdd658d8' + - 'fd70200392e851fe' + - '6d4ba74aba6d5722' + - 'f9cdf3dd4bcd54d8' + - '8fcf72fe99a4525a' + - 'd2ad32d40e255c03' + - '242b3d8fc7df5fc7' + - 'bdb0af2d9e105721' + - '1b4b10506d6e589a' + - '0ad8665aace75ef7' + - '2f88c85f16a6567f' + - '4eb6d1ceffdc5ef1' + - '6545d57f36cb59cf' + - 'f47d8b7828245583' + - '498d29a688f25631' + - 'efc954a351a350f2' + - 'aba6e59e9c3f5e65' + - 'ea21832979875423' + - 'a22d7a6a37a85b74' + - '390e7453b78a5e61' + - '2d4fa9eb37155845' + - 'cde6323828d75043' + - '6e03cba240eb57b2' + - '71f20f6407c55709' + - '52c0a201a18a5117' + - 'f6de0b6ee41b56b0' + - 'd8d3da1a03be535c' + - '392b41b3af975424' + - '2a9eaeaef4d2579a' + - '9731e046e0425cdc' + - '081cc4dd40c559cc' + - '8ebcb8a509965dc0' + - '79765415cff753ec' + - '7ab2527d43b55c00' + - '7279496852295ca5' + - '05b9e0a5a3055aa2' + - 'cb7015da2f625aa2' + - 'f45001858b8e5422' + - '45ad3eeb549056e1' + - 'f043838992745ad4' + - '7bf0e3c467765904' + - 'e12e5fc62aaa53bd' + - 'fc61fe4f440255ed' + - '6e6ad7c8e2d95123' + - 'a1c2c3c522b7573b' + - '3573dd679e045c58' + - '014633279fde5c6b' + - '71d9e22cea3f5469' + - '6c1c0eee57f15149' + - 'cf0f121bdeed51f1' + - 'ed70a7ca5350593a' + - '5f126e11a529558a' + - 'f84f9906ecaa564c' + - 'f2cf311bbe715643' + - 'b61c04639f435416' + - '9be7b4ef51ad537b' + - '0145ec6231b65c05' + - 'ae3f307de8615b35' + - '2ede0b68d4f05726' + - '119ac9e47adb539c' + - '3ecaae94fe3f5cd8' + - '4a1cedb052d35575' + - 'c31f7d6bb4735d2b' + - '5b9caa65cca55c3f' + - '077f90163e83515a' + - 'e93d10f1942956a1' + - '4162c249e17358ff' + - '0bd3ee0c8e1357b2' + - '4162062bae725fec' + - 'c0a9f2a2cfd357c5' + - 'dadb50dd16e7550f' + - 'e210b8db466755c6' + - 'a6a0c2605bd15498' + - 'eb2a371993b15a06' + - '2cdde2c236085d4d' + - '4d8aa5aba1b157fc' + - '03ef717aff06535f' + - '16b29545f7355276' + - '900d088d9c815aac' + - '618aad445b8d56ed' + - 'b10cf48b6a62535f' + - '11deb182a82952e3' + - '41a9ae95891b5a3d' + - 'a7d5055f7958550f' + - '04dbabe93cdf5835' + - '518293c438b45717' + - '1a9aa434305b551b' + - '90c1f6c5e89352be' + - 'a173bab5c7b9548c' + - 'c74354e114e25a5e' + - 'b4199a4f019e5984' + - 'f2b91abb2682592a' + - 'd2d0152ca8b45448' + - 'cb8c00bd27ef5407' + - '28fc8dad42a25713' + - '8664b363dbe6579c' + - 'ae93f01c97b95e31' + - 'a7531ec94bfe502f' + - 'ce9a8994426651f9' + - '457238fe173052e9' + - '049dada6c5635d20' + - '5ff50502a8025ec4' + - '1001304290285954' + - 'a750010a71535826' + - 'ac83bb7914fa5c49' + - '830d852b4d125dd8' + - 'cd6bb9145f305d6f' + - 'bb316a29dda4579e' + - '0f218605b46855a3' + - 'd651f6468ef752e1' + - '04659d4c41935483' + - '4153b8d26d995fee' + - 'c86e8e693033576e' + - '15416947b62a540e' + - '4850ef64e2c255d4' + - '88eb75d4ebc856d3' + - 'a59dcad188095204' + - 'c3c3362c0698581b' + - 'cd94f6071141524f' + - 'd145b19c2d0b541a' + - '5cf015df04bf592f' + - '6734f71cf94850bb' + - 'fd7082e9536f564d' + - 'b44ae5b2fd4c572f' + - 'd20cdd25bddd593f' + - '81dfdbd451ce5b05' + - 'f80c33f8b5f2519e' + - 'c0f43985dd8c5a6f' + - '710f232ee6ae5933' + - 'a134475021705f11' + - '72f00754294d52f5' + - '771297a09a50590e' + - 'f56a86ca7c8459ba' + - '2e3bc88b97d9591f' + - 'af819d19bc8a5e93' + - '8b5c8db732be5c8d' + - '392f65dbbb4c5b3f' + - '271aa6aadc005dec' + - '5edc32677b025ef2' + - 'ad5cd8212eb05b13' + - '71550ec8a9695581' + - '19d86eeed8aa5034' + - 'da8f7d7db9065d59' + - '992152097519529e' + - 'a51bd16d5f2b5a9b' + - '08efd2bdb91d5b60' + - '5783cad294585faa' + - 'bc9cc5644a0454b0' + - '377263dd49b6523f' + - '96e1ab46830a5abb' + - 'e214384948315c3c' + - 'c09e079e5f675e68' + - 'b01531a0a9da5ec3' + - 'c32797bd505b5dd6' + - '59fb65ed6b6a516b' + - '3196c324c82c553c' + - 'fc4760dc057c5aca' + - '5623ca9a4b3d5764' + - 'c2a9cec54e935adc' + - 'a755cd5ec46b5153' + - '1aef09b936ca5f72' + - 'edee67dc6b665bfb' + - '6badb2820fd251fd' + - 'eecc1b44bd025ffe' + - '68de727e3e2b53d2' + - '4e5f5d37a7f55d50' + - '5279e16c735e589e' + - '84fc152d339359b8' + - 'f46984fbdd745dca' + - 'bc6f3e1e87d7573f' + - '7c0e256904125fa8' + - '9ba4073c7ea05712' + - 'ab2e7df4a2645804' + - 'f3addd50ef9b52ce' + - 'ec707117dc6655b5' + - '0b0e4ef7390c5e32' + - '717f8156c219581c' + - '722bbd202c835db2' + - 'edc52e8bb5a75de0' + - '77f1933d7eea5dad' + - '1f8b1a546a215f1d' + - '29cfa239ca925107' + - 'be122bb68faf5101' + - '6a4b14190ead51ca' + - '798e9ada32655d74' + - 'b1a7fccf043a542c' + - 'b0ab4efe7be45584' + - '005c7b67ab8c5eb7' + - '067e23d0f3495cc2' + - '0b709d67a7b457e3' + - '3b21090dafc054b9' + - '30bd4a80c1f45c5d' + - 'bd9267c026a85b24' + - '219a640329bb5937' + - 'ba53f4f8b3f95f9e' + - '272b44e4498c5e76' + - '6537b89771ad56c6' + - '45a064795bcd5136' + - 'd45ce3fc33ee5111' + - 'c8a6f99ae43750a9' + - '4629b70d7cca561b' + - 'f4d5adfe95c5518c' + - 'aa0c80c9004e53e0' + - '73f9617c55875d0b' + - 'fcaf1d68979d560a' + - 'b9371ca8b59154c3' + - '94486171a0d0586a' + - '15801a1d5ec550f6' + - '81e4805c11bb57ac' + - '7f52eb0df56b536d' + - 'c8b3b870b8f352cf' + - '13206e5a247c5d64' + - 'c1e1aa49ca6d5f5b' + - 'c2c21ab3a46e5fa3' + - 'e1ee5cc0316a5b9d' + - '90fba04924295a4f' + - '22fbae96f7bb57e7' + - 'c1181b16da455fb9' + - 'fa84aaa93fc85b07' + - '89a6f0ca56e15d44' + - 'e5030ab7fbc65617' + - '1898ebe818eb5b39' + - 'f07146d3d0f85bc5' + - 'f4342f3a92e75b7d' + - 'bac0d1b3a1ff5389' + - '6ba25ebff18154fc' + - '10d85fb5f17452dd' + - '2847f10808615238' + - '133baa14fb5457ac' + - '93455e89334950b2' + - '3e0286a995c15ac3' + - '27ac70975fc05636' + - 'f769cbb43790525c' + - '9f809bb88fb7584f' + - '9b74d8ff8b6a56bf' + - '89f2e8d64ab55b4f' + - 'af1ff1ba8bfa51ee' + - '45da1903cbb15d8e' + - 'c31e875e21ba5ef3' + - '3c1bdfaa9c0f5351' + - '4bb4892d273356d0' + - 'c777d540d9d6546a' + - '4e84e89c03a057f4' + - '750f18343edd5906' + - 'dc3be40d19af525a' + - '5c0d5992039c5ae5' + - '7762454067955400' + - 'f84836b6b3b75e15' + - 'efdc9b3c63b4521d' + - '407052775bd75ef0' + - '9734366e17375822' + - '82d44a9b699e5a87' + - '88b1d0e3a0f85a97' + - '2a6fa8840c4b51f3' + - '022fcc82f6d55f46' + - 'ef5afdcb26a95f07' + - '8cc6163baa165a70' + - 'c4b7f40d72ec5860' + - 'dc3fb93cdf8c5a6d' + - '15320d9700e152e2' + - 'd76bf6cad6775ab9' + - 'c2f0546ea2565af3' + - '14f14589432c5e00' + - '9da92ed8992a561d' + - '1c83f8728505588c' + - 'e52a507151b45124' + - 'ff3e4f2876045591' + - 'c9a89ec97d0a5941' + - '30195f6787f757e5' + - '252dc073498258c7' + - 'fb110f8fa95d578b' + - '43dc01b7f36c560b' + - '8bdf19ef1ae35709' + - 'd7d8bec6f4055a6b' + - '7bd2275c5d3d53df' + - '7fdfe78ce40851c8' + - '6c3c40f8dd8e5f5e' + - '951404943c3a522f' + - 'b8dc23bccf4156a0' + - '6c089515e0a35c87' + - '7980ca7e5bdc5919' + - '4ec6c6d1dd2a5704' + - 'c7dfc26b80d15e37' + - '0f2aa59c01b85796' + - 'e03138d48b2350a1' + - '174934676f935862' + - '0b0264ea387a59eb' + - '5c368994f4f2586c' + - '1bb4a47dd9f155cd' + - '5055371cab9c5a76' + - '4ed00530dda15620' + - '363a2433837659dd' + - '7293e44c51595f9a' + - '73b25588578a5b6b' + - 'a47a238ed8be578b' + - '8cccccfd72115761' + - 'fa29b55eb9bd524f' + - 'a66548684abd5654' + - 'b68dadebc7d550c1' + - '2ba56b194be35170' + - '297460a38cf85c4a' + - '75b753d130a15e0f' + - '7d24fb93bcf850e8' + - '391243623ee2507d' + - 'c2da91d86ee95141' + - 'f6cc37b600355f8d' + - '35af99c29b41575b' + - '7f16af19af0f555c' + - '617eb54159435481' + - '018efa6cfcf05b38' + - '3f21b22dddb650f2' + - 'ae68d243a14a5e73' + - 'd9fd1bec7465502d' + - 'c7a667dbc8ff520f' + - 'b9231de6bd845d76' + - '23cb24f7bfb95e52' + - '317d093b6b4f5485' + - '0e80579390285813' + - '6e357a01f9b0593e' + - '0317b5f6ca155438' + - 'aab588ad361a5274' + - 'b5fab79404cb5005' + - '7b7128c02637583e' + - 'b771a8b79cc45834' + - '550be0ceaa375286' + - '40af07e44e135d38' + - '71723288646957b2' + - 'f247363c6a895c18' + - 'e40809aa767b5dae' + - '3cede85b3ad15871' + - '8d3c4c8456485f02' + - '36bb406c02415a1f' + - 'e8a0328d71c95c9e' + - '2e35d8043dfc57ac' + - '1729f89fec425d59' + - '53e9d4fd576f5955' + - '77b6537477045172' + - '470cb336d24754b4' + - '5fa18e89e8b4562c' + - 'cb18c15a8d615d95' + - 'bf6ac67055945ec6' + - '236f917924b6546a' + - '9990c91a09335a2b' + - '23367af0cb705f70' + - 'c30b795fb1025200' + - '9a0e3116d3f95fb0' + - 'cbff64d4a3275eb2' + - 'a81d831747125494' + - '7426524f502552e0' + - '15ad07a391fc531d' + - '806fefcac7255f03' + - 'b117d99525275c5c' + - '40042cb53ea15317' + - '0ba3bc329f625a87' + - 'c80486444a075315' + - 'b26e0f55c6635cfe' + - '4b3b50a9f58b5d79' + - '6b833c4f6ba85107' + - '20dd31cd90325acc' + - '2b5f330cc84a58d3' + - 'fbfa092b46705d05' + - '1aa2af2ce59c5d4b' + - '1d827435afff5a34' + - '6f5f540b79935ef2' + - 'ef4ea04e79b75daf' + - 'bd2ca3e701045cf1' + - 'e32452537a3d559c' + - '2ccf908e66225542' + - 'e56e50ab80aa5e49' + - '43ec910ac8bb5bdd' + - 'b20f8849949a5781' + - '8c29bbd3294a5a6e' + - '63ef5aab300c5f09' + - '005d90072df15dca' + - '0c87850fe06e516c' + - '8982572605425112' + - '53a7722e3f4e5402' + - 'cc1692bc3a625b0d' + - 'e0b356c707fa5cd8' + - '4769a4aa80eb5bfe' + - '5e8c626551b15567' + - '935e480ef74e5cd8' + - '04c0e32756d351e2' + - 'e410e8eb7ebe5cba' + - 'fcc92c4f7550585c' + - '531dcee0f8755393' + - 'eb38a969ed2d5483' + - '2508702d9964588a' + - '6a8c3640f2b550a3' + - '8a8d68f7b91556fd' + - '3c2dc4fa5cea5da2' + - 'a50c3f22fcf65ebb' + - '00301fbca75b5ce4' + - '534858c9d60955fa' + - 'c57a61eb33c45309' + - 'b8ae3eb400ba5752' + - '9735fbcb23e65f5e' + - 'b2baeaa0159953d8' + - '0ef51935e41853ac' + - '2aff210d87d25cf5' + - '4062618aa1345db6' + - '8b6c460edc7e569b' + - 'e8fd7512fe6b5486' + - '1a61f929adf95542' + - '220dc5aa304859b0' + - 'b7ec3b2902655a05' + - 'c5314146ee805579' + - '6a389d45c87e5bd5' + - '5285285e44415f24' + - 'e7f998c098aa5492' + - 'bca5ce4888c0552d' + - 'ced3856a5bee5575' + - '3cb0bc35d9b3568c' + - '806df3c0ed23596f' + - '0e97991aaf6858ae' + - 'e3456b82bcf8583b' + - '4dd6951ee5975dab' + - '1bc4a5a636f7539a' + - '8d1cc9933b715e6c' + - 'efe98217d366577c' + - '0fafd831849b59eb' + - '181412fd9fc453bd' + - '5b3fa11325e95c68' + - '57534af9046058a2' + - '1f82b16afa96569a' + - '96a626b4962a5b79' + - 'c0f1326b41465187' + - 'ce46277ede7151f7' + - 'f13e150be53454ad' + - '8ade6f0fdead5bbe' + - 'fb7742eb940853b5' + - 'f1595418c3325a9a' + - 'f545b79ff3d553a0' + - 'f5a56a2c18045b2b' + - 'f51fa581f64b5b3e' + - 'ddd2c4e69fa159a8' + - '3ca3041442b25293' + - '574ba611316451c3' + - '05f93c480c1d5984' + - '605d5b1c17a65f61' + - 'e76c2e115079581b' + - 'f1ee9c9a46045dd7' + - 'df3209418f485407' + - '6e6b7deeec9151a4' + - '8cf8fdc6335a5afe' + - 'f7a3e8c94a4d5d22' + - '765479ca832950f0' + - 'f9c453610b045457' + - '95ab9a07a8f55760' + - '9795ff6460a358c5' + - 'b9aa20bc06315153' + - '5e0cf44e7e735acb' + - 'b1fbc08053ee5840' + - '1efdeee099b2567b' + - 'f216b1453e5352e4' + - '88cf2d1d3b705e17' + - 'f303c375c15055f9' + - 'ed9770b157c75a90' + - '30aa28efb1a45959' + - '4146a5366b6c5248' + - '82317508dea55b9d' + - 'cd5f0c03103359b4' + - 'efe0e8c737105303' + - 'a68eddd8d64c5748' + - '50de55c64b3a5cec' + - '4274e203840a5744' + - '96424a88a5e5592b' + - 'b09528b71a5451cf' + - '55f10643a7c55642' + - '2e3a20d7f8015043' + - '9c3885a3f47a5393' + - 'c8281e1e1ad454b3' + - '47e88f1126075d8c' + - 'ae126678b4a65269' + - 'f958ec121f5a5287' + - '5ce3e28be5b455c4' + - 'cf1b27fc3ed658b2' + - '8fc3f9bd06a25423' + - 'bc552fcf837c597e' + - '834cf71739e65997' + - '33a911fcc99150b1' + - 'f2775866549b5580' + - 'b145ce43a53b5d7b' + - '0ebba0e8fbd3528f' + - 'fa19fe7ce29b5b2c' + - '150f990d11255003' + - 'ece5db3b69b25403' + - 'ad416b6d102b5397' + - 'd6c015cfb5785305' + - '88b59b17628a52a5' + - '63a31921e3dd56dd' + - '4064de4004385810' + - 'bc62496a861c5c6c' + - '45f20433b45a554c' + - 'c455ca6714a5529d' + - '157d4bcf09955c0f' + - '4eab926af88656af' + - '2965e4c9734152d9' + - '882b5af1243757f1' + - '0cb3e00568cd5056' + - '0adcd0e658d359a7' + - '8bd63e2b025d5e38' + - 'fee0a0e2c7f35b57' + - '2315ca6fdcf65ce0' + - 'c47d40dd4aa95b21' + - 'edeab17e9e495644' + - '99ac1ec87510556d' + - '517c68cd7fc95829' + - 'b5e2215e09995f3c' + - '9d9676d91f13569d' + - 'd4dab678b1f4542d' + - 'c9464a3eeb595732' + - '5e9f495673d85c0a' + - 'ecdd80b34a8f57a1' + - '2ae1a98a4abb5634' + - 'd11127b28e035c06' + - '010daf87039b5ac6' + - '3b6c8de3cfa45f67' + - '0e1e0ca3678759e0' + - '5ee51ad4d9345048' + - '2758a26de2eb5687' + - 'b567e21dec475d16' + - 'ca910aba9f01522a' + - 'fc4ac353d0205b3f' + - '56b6c828cee7571d' + - 'bec10cb7c4985dbe' + - 'dec1c86d9a2e501e' + - '0d1ff60873725367' + - '4618e83e82d851db' + - 'b94178aa0e9a5f6f' + - '8ad4fbbaa4e35772' + - '524c76c5d0d3578a' + - '2b1bf6fda6a35e0f' + - 'fe98ea92e56f5f91' + - '0cf9756ece935921' + - '9ff7afcfadad597e' + - '9d37c0d42e6e5db0' + - 'b7e5b7dc41d95bf4' + - '37a0dd129f335fb5' + - '341f2720ef215146' + - '0338779ea8ba5128' + - '314c40db095d5cf6' + - '83daabde844e54dc' + - '33217f8386bb51fb' + - 'e006d5e1b2ea5da8' + - 'c766017cb6c6548c' + - 'b031b845dcd751fd' + - '53ebedd520395ec4' + - '8aa5b35af763528f' + - '848e7b87675454a7' + - 'b3f3e4c05c19502a' + - '984ee441b9cf51ea' + - 'b1034855ca2b526f' + - 'd1a170aad6bd527a' + - '993b14add9b3537a' + - '44a9f55424b0549d' + - 'b43bd580ef6d5873' + - 'f9ebab3c21c35d4d' + - 'a6177e5e80515a6f' + - '26f9ee1ca73e5e0a' + - '494fe3ac4ae75c8f' + - 'e04d9ab64a0854f3' + - 'e70f19d46048540a' + - '52a6fc6e93945378' + - '3e3d32dde22256f2' + - 'ceade829ae42587d' + - '1863deb853a051b6' + - 'c3afbe8d5dfc5ec7' + - 'c5b92860ee7a565e' + - '99ed7cf98b365d63' + - '870683ea3c405397' + - 'ee9885ffc5195695' + - 'b3e8728bd5ce5340' + - '9387290766565a6d' + - '2dedd41ee75d52ff' + - '3c52b858ed005198' + - 'f0c58378b81d55c6' + - '7842f544a27154be' + - '74756890e549577f' + - 'f3c5008618555385' + - '36516922212f5065' + - '720c398c8755532f' + - '73c65f8cfa0b5f49' + - '443bc5caadc05ea3' + - 'a3aca140c0bf5735' + - '29be0c5292695bb7' + - 'a684abdd377a5526' + - '76c046586ec65d90' + - '1c06e56cf14a5066' + - 'd7dce1fd382b5ae4' + - 'd600cd9504af5c8f' + - 'f199f564fa885384' + - 'd070c0d78c955b55' + - 'f18b5cfbf3ac5e77' + - 'f64e88b392855abd' + - '9f97c852a33c5df5' + - '903bef71f2ef5465' + - 'cdb5400e78d35889' + - 'd5fb322ec0b758d3' + - 'd506fe99e97c5950' + - '55b7c9f3fc3753e9' + - 'ef4fd9e246ca563f' + - '067488f23ef3554a' + - 'a27cf7fded2e5a3e' + - 'fc95574ebac8588b' + - 'ea34b66a5b9052e3' + - '31c1db5f5a285e31' + - 'ad6d872bf6995001' + - '52e379ab3196503b' + - 'b1d79dfb7cec5cbd' + - '6591919b5db45646' + - '3bdf055c00125477' + - '78b39c7e52c85ad5' + - 'a926cdaab7b05fd3' + - '1f463bd4f9a85923' + - 'fe30abef00955b4a' + - '062a19468a765955' + - '4605086c40815117' + - '790824a2df935497' + - 'a264f3c05f175e4c' + - '9335a6ee8bcc5464' + - 'e42b7a4f6bef538a' + - '3ad61242db035638' + - 'c63dad53040755fd' + - '9c3754dd9d09518a' + - 'aa3bce96f7795abb' + - '3e4742ac900d5ecf' + - '960022bf017958a2' + - 'f7b8320599e7561d' + - '6bf2ff95192d5cfb' + - 'f1f8af6bf7685fa3' + - 'd3e4d463eec25d67' + - 'a64188586ebc5d4b' + - '8a5fc1cdb6645f19' + - '16966427ad615141' + - 'c6f813de2faa59ab' + - '8454b2b637175956' + - '0be4b33676df518b' + - '8fc9474f19a35ab8' + - 'ed8c0ae24032517e' + - '0617d2b7c3c956e0' + - '370e453a4f6e55cf' + - '3260e05e584d5fe8' + - '30e0f8ea46375f52' + - 'dc71315766a458aa' + - '18c2c17b55e956a5' + - 'a5617ebfe1ec5b2e' + - '05f5fd6850305b2a' + - '06f3e1d3f9df598a' + - 'c5b585c0103c5340' + - '795a5057ce3c5fdb' + - '6e572a3a5268572b' + - '317878d8107a57e4' + - 'f02ab3bebb3e5532' + - '5424b93732bb5842' + - 'c898e6b6f9e25220' + - '60c1bc7de8ee5b8a' + - '51e1743acbf05a62' + - 'f9d4b59f3a13590d' + - 'd4832fbb31ef591d' + - '07bf12b29c385f13' + - '02a982b22eed53fc' + - '046372b7fe3253ee' + - 'a9cc30d55e1b56de' + - '0777c61d992a5c04' + - '2afa35fd7baa53e0' + - '428a595895755ca9' + - '2436bebf851e5573' + - '2aee1cbe8420561b' + - 'e268e0ae47f25794' + - '2a1ee001593a5b12' + - 'd57e14582102599d' + - '68e0ff1df2b6587c' + - 'cb1fe18bace45263' + - 'f397d4d6ceb650cf' + - '6786d11321ff5a38' + - '383b3d0dc4b955bf' + - 'df14e7a74f035385' + - '2de25261077153ef' + - 'e3e2933994835eba' + - '3639112fbe5354b8' + - '5873cf0a63c65c9d' + - 'c3e46ff35ccd5d8e' + - 'e8997fd743685ce6' + - '60c31b59ab8a563e' + - 'ffaf0595e6b15a1d' + - '5d91ff45ef9d568f' + - '57b1c8e3ba6a5768' + - '6be690074b835ee7' + - '99822c6ea67c5cb2' + - 'dc6ad473cea05799' + - '0a460b8b55f15728' + - '0217d32c99f35e8d' + - '747d04c2b4c2503b' + - '6bd8a2debd375328' + - '62f1226f09135822' + - 'ae2d9cd51ebb58aa' + - '2676de7a1f395982' + - 'aa729da77b7a5900' + - '7a578b7981a1532c' + - '61b67d5ab3da50e0' + - '3dfcf6c1de425df5' + - '7c59b23242215b68' + - '6c29ab9f10c852e6' + - 'a7be49306f3a511e' + - '858b576bf1f25127' + - '02be5baf00be511e' + - 'c6b3b3d1d9fd5bb3' + - '69038042d9085f7e' + - 'ff251a689c125d4b' + - '4f35bf5851185447' + - '14ceb269d7935079' + - '322b2169ba325227' + - '0d306053f89552cc' + - 'c2bf8ba76a1d5439' + - 'b2ed684a0dd1594e' + - '90e0a3e03ea6553b' + - 'e8929dd8178a595f' + - '921506147e78501a' + - 'efd37715c9165bee' + - '23618ef975b35da4' + - 'c556f4bc6f165a76' + - 'bb3441512c405115' + - '9a190b59608a5715' + - 'b7a0f107522e5c85' + - 'ff87f53ee3b85930' + - '1f92b9e3409f52bd' + - '50e522bacee65947' + - 'e59d7ba3a5c1500b' + - '53f040fc7b815331' + - 'dfe09b52c98a5d55' + - '5d070fa13ed956b0' + - '5e401393da265fab' + - 'f05cce67330c500f' + - '9e345f0a2740505e' + - '7e7b9ba07a3c5862' + - '9457f00d911b5fc2' + - '36454c3b5d685c1f' + - '996fc24078565ed4' + - '157675dfe46d564b' + - 'cac80a08729355f1' + - '9632ec0169755615' + - 'a1f7bd32669f583f' + - '3c6ac27e868a5039' + - '0fef035e25f455fc' + - '72d1ff8ce9d65996' + - 'e078f1cf064f5580' + - 'e2f3f2a226bf5570' + - 'e9b30fc73b785460' + - '4838017cd83a5538' + - '1d6eacd8e7425dcc' + - '5348275892365cdb' + - '847d404274f45a31' + - '546748dd6dc15ce0' + - '49fb5285f71b5465' + - 'b57f7e7d71215af7' + - '3069f8795e1c5116' + - 'f5f241207ac8528c' + - '6bd725d8fb4e569f' + - 'eb1ac330d8bc5a34' + - '0cd0bb95c83751e2' + - 'be26059ef668586e' + - 'd4c8c61f071c5f51' + - 'be7706a2289957c8' + - '288df93a91a25d2a' + - 'c158bae7ba995615' + - 'bf6ed5c0c14c5095' + - '4f6461c570d2541d' + - '179eadc7228f54ed' + - 'f54dded0dc4d52fe' + - '6980261fb6c5566c' + - '6ad66438155851ee' + - '1e77a04e93c75ba5' + - '23c15f974b725070' + - 'c56297c6cebd57b4' + - '29b08ff2b0eb53b6' + - 'e990429a8ff45085' + - 'caaab29c36ef5bee' + - '3ef425a63b515bdb' + - 'e214555df62554bd' + - '828a2941f0a8540a' + - '73bf19dc6384554c' + - 'a997150843765f15' + - '34eb025310055085' + - 'e7f119f370385c70' + - '2d974e401de45640' + - '7e8525c900f15ec4' + - 'afc40829c4c151e6' + - '552c8befa3c45662' + - '8b526b8ae68f5848' + - '90378791899c5f0b' + - '39828cebcafe5966' + - '0ec11905be6357bf' + - '9e63f0fb486955cd' + - '495c616d8df45acb' + - 'c89b3a61af8954e9' + - '2a85528ed1735741' + - '9434766d819c5239' + - '6e4234134bad5f11' + - '6744a91db9485d20' + - '471fd96fa4e85e00' + - '1c7d81d771715ba6' + - 'cd63e5191fb854c4' + - 'bf314c96e26159dd' + - '59bb3fec07495d87' + - '6b5577c4ad065b64' + - '5ae90b0e5dac5c9d' + - '35c34962ceb55ae1' + - '5fd2c4ba12af5433' + - 'bff510ccdeaa5f35' + - 'd1233a44bda8587d' + - 'c1567e02a98e53e5' + - '8766f0a66c795245' + - '6e25d4267f1f56cc' + - 'b7ec5cdee9765f23' + - '8ce9083add14517c' + - '89bb5d1470dc5707' + - '15350a341e695fc7' + - '0942e763807c536b' + - 'd8258821fbf4567e' + - 'd77157608aad5340' + - 'db04a329549850c7' + - '3c7fc272a0eb5a74' + - '8129ef38aa0c5416' + - 'f7f0989080295b75' + - '79766599ff6b5b62' + - '15990b4dfd035da3' + - 'bd4a927480be5f40' + - '5a5f644a927b5d42' + - '756942237cdf5a95' + - 'b2ab7d150c1f5fa4' + - '35b63e40217856e4' + - '8c3e010932aa5cee' + - 'ffbd3b860a825ef9' + - '8e9c515ace025bfc' + - 'b3b0248521b35ad9' + - '601c7bce7ecc5b46' + - 'fd58ebc254395f3c' + - '4c24396165f85d8b' + - 'f97fb70da693571a' + - '4665f922e9f658df' + - '834d5f5b024d5b4f' + - 'e15f7fb227925f87' + - 'f75ab608bc2d53c0' + - '76555123d9e15f53' + - '0e230c7c31ee5638' + - 'd0a4453b79835179' + - '26a70fbb0bc6522e' + - 'a00cdefa24dc566f' + - 'bd164fe385355150' + - '131e121d08bc5b5d' + - '605598ad9db955c1' + - '1e5876a1d8045e46' + - 'cf57e14fb0e3549f' + - 'e6c0fbf6b7d95fa4' + - '9ba46464c5d05ba4' + - '00c202ab992e50aa' + - 'bf58490d6df954c9' + - 'bdbe2ae115145f05' + - '357617c630cc5f6c' + - 'b662f7a2aff650bb' + - 'cea6d0cc7fae5b64' + - '13eeb0a3d27454cd' + - '43684e638e545af7' + - '176dbea202c85b1d' + - '07e58fd094685c7c' + - '6f247bc70fe3555e' + - '48303bdf88815d47' + - '2b2650c2d1835ad4' + - 'eb93ad568896524c' + - '068360105b8c5337' + - '04f7fbf12de65cbb' + - 'c784279daa025ecd' + - '3bbe2bed3f6957ea' + - 'cd73b30d7cfb5ed7' + - '6b08669963f850e2' + - '87674f483b1e5419' + - 'ef50b86138685a7f' + - '318ccc0204ad5169' + - 'eac481b592f35bbc' + - '48091c891390551f' + - '11ac0cd68d2b52be' + - '00c0d5bcf6d55c23' + - '80767ab1136e5ef1' + - '9ea5d395e1655fe5' + - '5367c39160105a92' + - '818da44b164e5abe' + - '69aa212a73e658a9' + - 'f6bcde0e79bf5f7a' + - 'bbb47a98cfa85078' + - '2e372e016da350ad' + - '42c42020703a5556' + - 'f79fba8bf0265552' + - 'bdbec8db87c05b88' + - 'dbe1647a3c6f566a' + - '501e6ffbc107514b' + - '49503ce0e8f25ea7' + - '0c6560fdf22051d5' + - 'e6f40cb278175bd0' + - 'd380e90d6bf75fcb' + - 'f013b7d5cc4b5640' + - '8ac5e0919877529a' + - '1093c17783b1551a' + - 'd19f6cd9a32d5eb9' + - '631db6c25ffc54ad' + - 'b5a8d475d9ae5925' + - 'a486bb1feda5577f' + - 'fd3bfa7e0a3858cd' + - 'c711b58fe110530d' + - 'efddae2657e95e70' + - 'cc1488bdd3415eaa' + - '5e22eb66e795588d' + - 'ced53747c9745b55' + - '2103e3d9b2025301' + - '6aaaedc401ee5107' + - '8ff364cff0135509' + - '5bcaaed9e73f5fc2' + - '40d3dea94d5a5df7' + - '8bce1db07e905f11' + - '6df4338f64cf566c' + - '7f8f161607df5291' + - '6d34396ecc375932' + - '71b3a829aa955429' + - 'd02abcb2b018515d' + - 'dce0f77630e05c67' + - 'abdd9be326d85957' + - '653add19767a5aaf' + - 'fed1da9b14af5392' + - '5c66bdaa42cb590d' + - '67e15557b1205ca1' + - '346f60e6d90b59bd' + - '1d5b9b166f0c5912' + - '2cfa341ef8035385' + - 'f36c397ff1125e10' + - '90489718be785427' + - '680e1ce48b255213' + - 'fdab64bdd0b2522a' + - '3c3e8ef4ff185bb3' + - 'fa82f3a838b15805' + - 'c2216319a8815eb9' + - '2d35e82812fc59d0' + - 'e57de37711215456' + - '5badc98899c35e10' + - '5519123f7ac85d9c' + - '72c4b3e9788b5bf7' + - '0d692e23ea0954fa' + - '558d1c44968c5319' + - 'f12b46915ede5842' + - 'ed66148010795fd5' + - '436936e49747508c' + - '773dd0a156a25f05' + - 'bbc7bb010c275ea4' + - '54638dab1a8d5cd0' + - '3b8ed6209db55bce' + - '504019ae3310580a' + - '401700a7aa2d5afd' + - '6cc82f29edba5e42' + - '82eda340c3ff5f37' + - 'c7468120b03a56e7' + - '509c67d849bb5e2f' + - 'fb12a40e998254c0' + - '8c3efbd29dac55de' + - 'b9ab639fd02f5199' + - '8c571a72ea545228' + - '89f3b2c5f2755984' + - '05abec5c2a67537b' + - '36c985d6cfeb5ba6' + - 'dc09c00279cd54a7' + - '2c6685877c5c5add' + - '04a4a92fa9485364' + - 'f00857cf14b15aa5' + - '390b7a4290705fad' + - '029fa42dc2b050b0' + - '2ee2a0bbeef25e75' + - '34abdafdc5bf5f02' + - '72adcbb381bf5750' + - '9d218f3b7cd053a3' + - '8d50a32de74a54d8' + - '42dc17b3a3e551bc' + - '5fdf486d23b65b07' + - '362648401371516f' + - 'c4874465ea1f5f0d' + - '24574a3fa2ed576e' + - '29b2a32bfd73543e' + - 'a9773766c9ac51b8' + - 'f274e21b1d0854f8' + - '760f8c7570d455a1' + - '0dc096721b925852' + - 'eb23e8075d63518c' + - '2ddf1d019e4b560e' + - '26cd2c1bfff85b53' + - '4ce8319955305a2a' + - 'f40b8ada03b151e3' + - '49e45364d9d55b37' + - '77d818bb3b8e5ddf' + - '56dcbd23b5c9522b' + - '7736cf1972215f9a' + - 'eaadad7dbcf05511' + - '9536ab5999ce5999' + - 'f3aa2dce8d605800' + - 'c39fc085da325b58' + - '08388d303eda596b' + - '9af3c5d9eadb562a' + - '738efdf625b35ff9' + - '3904a28507e05231' + - 'f779e983fea75d2c' + - '4d05cdacbc7e5078' + - 'a723915521ec5f4f' + - '1fc135287cce5f74' + - '12384a61bce55dfd' + - '3b882e9cddd75b23' + - 'b4fea136b474504e' + - '81f2464a004e54d0' + - '6c9d5706ae415e2a' + - '57fb4bb3faec56f2' + - 'b6dfb45102bf5e2f' + - 'd7f65016a6795d4e' + - '8a5fb3de4e785f87' + - '37d66f4b3d735297' + +log_names: ${splitter.log_splits.val} +map_names: null + +num_scenarios_per_type: 100 +limit_total_scenarios: null +timestamp_threshold_s: 15 +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: false +remove_invalid_goals: true +shuffle: false diff --git a/sledge/script/config/common/scenario_filter/vegas.yaml b/sledge/script/config/common/scenario_filter/vegas.yaml new file mode 100644 index 0000000..c1528df --- /dev/null +++ b/sledge/script/config/common/scenario_filter/vegas.yaml @@ -0,0 +1,23 @@ +_target_: nuplan.planning.scenario_builder.scenario_filter.ScenarioFilter +_convert_: 'all' + +scenario_types: # List of scenario types to include + - starting_left_turn + - starting_right_turn + +scenario_tokens: null + +log_names: null # Filter scenarios by log names +map_names: ["us-nv-las-vegas-strip"] # ["us-nv-las-vegas-strip", "us-pa-pittsburgh-hazelwood", "sg-one-north", "us-ma-boston"] + +num_scenarios_per_type: 100 # Number of scenarios per type +limit_total_scenarios: null # Limit total scenarios (float = fraction, int = num) - this filter can be applied on top of num_scenarios_per_type +timestamp_threshold_s: 15 # Filter scenarios to ensure scenarios have more than `timestamp_threshold_s` seconds between their initial lidar timestamps +ego_displacement_minimum_m: null # Whether to remove scenarios where the ego moves less than a certain amount +ego_start_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from below +ego_stop_speed_threshold: null # Limit to scenarios where the ego reaches a certain speed from above +speed_noise_tolerance: null # Value at or below which a speed change between two timepoints should be ignored as noise. + +expand_scenarios: false # Whether to expand multi-sample scenarios to multiple single-sample scenarios +remove_invalid_goals: true # Whether to remove scenarios where the mission goal is invalid +shuffle: false diff --git a/sledge/script/config/common/simulation_metric/common_metrics.yaml b/sledge/script/config/common/simulation_metric/common_metrics.yaml new file mode 100644 index 0000000..3233bfb --- /dev/null +++ b/sledge/script/config/common/simulation_metric/common_metrics.yaml @@ -0,0 +1,5 @@ +defaults: + - low_level: + - ego_mean_speed_statistics + - ego_expert_l2_error_statistics + - ego_expert_l2_error_with_yaw_statistics diff --git a/sledge/script/config/common/simulation_metric/default_metrics.yaml b/sledge/script/config/common/simulation_metric/default_metrics.yaml new file mode 100644 index 0000000..7da052f --- /dev/null +++ b/sledge/script/config/common/simulation_metric/default_metrics.yaml @@ -0,0 +1,2 @@ +defaults: + - common_metrics diff --git a/sledge/script/config/common/simulation_metric/ego_in_stop_line/ego_stop_at_stop_line_statistics.yaml b/sledge/script/config/common/simulation_metric/ego_in_stop_line/ego_stop_at_stop_line_statistics.yaml new file mode 100644 index 0000000..34f4c91 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/ego_in_stop_line/ego_stop_at_stop_line_statistics.yaml @@ -0,0 +1,8 @@ +ego_stop_at_stop_line_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.scenario_dependent.ego_stop_at_stop_line.EgoStopAtStopLineStatistics + _convert_: 'all' + name: 'ego_stop_at_stop_line' + category: 'Scenario dependent' + distance_threshold: 5.0 # m + velocity_threshold: 0.1 # m/s^2 + max_violation_threshold: 0 diff --git a/sledge/script/config/common/simulation_metric/high_level/drivable_area_compliance_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/drivable_area_compliance_statistics.yaml new file mode 100644 index 0000000..6db1952 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/drivable_area_compliance_statistics.yaml @@ -0,0 +1,12 @@ +drivable_area_compliance_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.drivable_area_compliance.DrivableAreaComplianceStatistics + _convert_: 'all' + name: 'drivable_area_compliance' + category: 'Planning' + metric_score_unit: 'bool' + + max_violation_threshold: 0.3 # The violatation tolerance threshold in meters + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + lane_change_metric: ego_lane_change_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/driving_direction_compliance_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/driving_direction_compliance_statistics.yaml new file mode 100644 index 0000000..deaefd3 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/driving_direction_compliance_statistics.yaml @@ -0,0 +1,14 @@ +driving_direction_compliance_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.driving_direction_compliance.DrivingDirectionComplianceStatistics + _convert_: 'all' + name: 'driving_direction_compliance' + category: 'Planning' + metric_score_unit: 'bool' + + driving_direction_compliance_threshold: 2 # [m] Driving in opposite direction up to this threshold isn't considered violation + driving_direction_violation_threshold: 6 # [m] Driving in opposite direction above this threshold isn't tolerated + time_horizon: 1 # [s] time horizon in which movement of the vehicle along baseline direction is computed. + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + lane_change_metric: ego_lane_change_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/ego_is_comfortable_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/ego_is_comfortable_statistics.yaml new file mode 100644 index 0000000..0b87ad8 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/ego_is_comfortable_statistics.yaml @@ -0,0 +1,15 @@ +ego_is_comfortable_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_is_comfortable.EgoIsComfortableStatistics + _convert_: 'all' + name: 'ego_is_comfortable' + category: 'Violations' + metric_score_unit: 'bool' + + required_metrics: + # Parameter: base metric name + ego_jerk_metric: ego_jerk_statistics + ego_lat_acceleration_metric: ego_lat_acceleration_statistics + ego_lon_acceleration_metric: ego_lon_acceleration_statistics + ego_lon_jerk_metric: ego_lon_jerk_statistics + ego_yaw_acceleration_metric: ego_yaw_acceleration_statistics + ego_yaw_rate_metric: ego_yaw_rate_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/ego_is_making_progress_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/ego_is_making_progress_statistics.yaml new file mode 100644 index 0000000..de830a6 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/ego_is_making_progress_statistics.yaml @@ -0,0 +1,11 @@ +ego_is_making_progress_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_is_making_progress.EgoIsMakingProgressStatistics + _convert_: 'all' + name: 'ego_is_making_progress' + category: 'Planning' + metric_score_unit: 'bool' + min_progress_threshold: 0.2 + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + ego_progress_along_expert_route_metric: ego_progress_along_expert_route_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/no_ego_at_fault_collisions_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/no_ego_at_fault_collisions_statistics.yaml new file mode 100644 index 0000000..b8ff5fb --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/no_ego_at_fault_collisions_statistics.yaml @@ -0,0 +1,14 @@ +no_ego_at_fault_collisions_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.no_ego_at_fault_collisions.EgoAtFaultCollisionStatistics + _convert_: 'all' + name: 'no_ego_at_fault_collisions' + category: 'Dynamics' + metric_score_unit: 'float' + + max_violation_threshold_vru: 0 + max_violation_threshold_vehicle: 0 + max_violation_threshold_object: 1 + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + ego_lane_change_metric: ego_lane_change_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/planner_expert_average_heading_error_within_bound_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/planner_expert_average_heading_error_within_bound_statistics.yaml new file mode 100644 index 0000000..49fbd19 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/planner_expert_average_heading_error_within_bound_statistics.yaml @@ -0,0 +1,11 @@ +planner_expert_average_heading_error_within_bound_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.planner_expert_average_heading_error_within_bound.PlannerExpertAverageHeadingErrorStatistics + _convert_: 'all' + name: 'planner_expert_average_heading_error_within_bound' + category: 'Planning' + metric_score_unit: 'float' + max_average_heading_error_threshold: 0.8 #radian + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + planner_expert_average_l2_error_within_bound_metric: planner_expert_average_l2_error_within_bound_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/planner_expert_final_heading_error_within_bound_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/planner_expert_final_heading_error_within_bound_statistics.yaml new file mode 100644 index 0000000..3b76097 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/planner_expert_final_heading_error_within_bound_statistics.yaml @@ -0,0 +1,11 @@ +planner_expert_final_heading_error_within_bound_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.planner_expert_final_heading_error_within_bound.PlannerExpertFinalHeadingErrorStatistics + _convert_: 'all' + name: 'planner_expert_final_heading_error_within_bound' + category: 'Planning' + metric_score_unit: 'float' + max_final_heading_error_threshold: 0.8 #radian + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + planner_expert_average_l2_error_within_bound_metric: planner_expert_average_l2_error_within_bound_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/planner_expert_final_l2_error_within_bound_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/planner_expert_final_l2_error_within_bound_statistics.yaml new file mode 100644 index 0000000..129386e --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/planner_expert_final_l2_error_within_bound_statistics.yaml @@ -0,0 +1,11 @@ +planner_expert_final_l2_error_within_bound_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.planner_expert_final_l2_error_within_bound.PlannerExpertFinalL2ErrorStatistics + _convert_: 'all' + name: 'planner_expert_final_l2_error_within_bound' + category: 'Planning' + metric_score_unit: 'float' + max_final_l2_error_threshold: 8 #meter + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + planner_expert_average_l2_error_within_bound_metric: planner_expert_average_l2_error_within_bound_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/planner_miss_rate_within_bound_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/planner_miss_rate_within_bound_statistics.yaml new file mode 100644 index 0000000..ab52639 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/planner_miss_rate_within_bound_statistics.yaml @@ -0,0 +1,13 @@ +planner_miss_rate_within_bound_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.planner_miss_rate_within_bound.PlannerMissRateStatistics + _convert_: 'all' + name: 'planner_miss_rate_within_bound' + category: 'Planning' + metric_score_unit: 'bool' + + max_displacement_threshold: [6.0, 8.0, 16.0] #meter + max_miss_rate_threshold: 0.3 + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + planner_expert_average_l2_error_within_bound_metric: planner_expert_average_l2_error_within_bound_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/speed_limit_compliance_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/speed_limit_compliance_statistics.yaml new file mode 100644 index 0000000..35f48e9 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/speed_limit_compliance_statistics.yaml @@ -0,0 +1,12 @@ +speed_limit_compliance_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.speed_limit_compliance.SpeedLimitComplianceStatistics + _convert_: 'all' + name: 'speed_limit_compliance' + category: 'Violations' + metric_score_unit: 'float' + max_violation_threshold: 1.0 + max_overspeed_value_threshold: 2.23 + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + lane_change_metric: ego_lane_change_statistics diff --git a/sledge/script/config/common/simulation_metric/high_level/time_to_collision_within_bound_statistics.yaml b/sledge/script/config/common/simulation_metric/high_level/time_to_collision_within_bound_statistics.yaml new file mode 100644 index 0000000..36e0146 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/high_level/time_to_collision_within_bound_statistics.yaml @@ -0,0 +1,15 @@ +time_to_collision_within_bound_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.time_to_collision_within_bound.TimeToCollisionStatistics + _convert_: 'all' + name: 'time_to_collision_within_bound' + category: 'Planning' + metric_score_unit: 'bool' + + time_step_size: 0.1 + time_horizon: 3.0 + least_min_ttc: 0.95 + + required_metrics: + # Parameter: base metric name and other high level metrics used in this metric + ego_lane_change_metric: ego_lane_change_statistics + no_ego_at_fault_collisions_metric: no_ego_at_fault_collisions_statistics diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_acceleration_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_acceleration_statistics.yaml new file mode 100644 index 0000000..45f7c35 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_acceleration_statistics.yaml @@ -0,0 +1,5 @@ +ego_acceleration_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_acceleration.EgoAccelerationStatistics + _convert_: 'all' + name: 'ego_acceleration' + category: 'Dynamics' diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_statistics.yaml new file mode 100644 index 0000000..49f163c --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_statistics.yaml @@ -0,0 +1,6 @@ +ego_expert_l2_error_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_expert_l2_error.EgoExpertL2ErrorStatistics + _convert_: 'all' + name: 'ego_expert_L2_error' + category: 'Planning' + discount_factor: 1 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_with_yaw_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_with_yaw_statistics.yaml new file mode 100644 index 0000000..f55db9d --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_expert_l2_error_with_yaw_statistics.yaml @@ -0,0 +1,7 @@ +ego_expert_l2_error_with_yaw_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_expert_l2_error_with_yaw.EgoExpertL2ErrorWithYawStatistics + _convert_: 'all' + name: 'ego_expert_l2_error_with_yaw' + category: 'Planning' + discount_factor: 1 + heading_diff_weight: 2.5 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_jerk_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_jerk_statistics.yaml new file mode 100644 index 0000000..44c6694 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_jerk_statistics.yaml @@ -0,0 +1,7 @@ +ego_jerk_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_jerk.EgoJerkStatistics + _convert_: 'all' + name: 'ego_jerk' + category: 'Dynamics' + + max_abs_mag_jerk: 8.37 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_lane_change_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_lane_change_statistics.yaml new file mode 100644 index 0000000..a58036e --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_lane_change_statistics.yaml @@ -0,0 +1,6 @@ +ego_lane_change_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_lane_change.EgoLaneChangeStatistics + _convert_: 'all' + name: 'ego_lane_change' + category: 'Planning' + max_fail_rate: 0.3 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_lat_acceleration_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_lat_acceleration_statistics.yaml new file mode 100644 index 0000000..934efcb --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_lat_acceleration_statistics.yaml @@ -0,0 +1,7 @@ +ego_lat_acceleration_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_lat_acceleration.EgoLatAccelerationStatistics + _convert_: 'all' + name: 'ego_lat_acceleration' + category: 'Dynamics' + + max_abs_lat_accel: 4.89 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_lat_jerk_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_lat_jerk_statistics.yaml new file mode 100644 index 0000000..9998805 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_lat_jerk_statistics.yaml @@ -0,0 +1,5 @@ +ego_lat_jerk_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_lat_jerk.EgoLatJerkStatistics + _convert_: 'all' + name: 'ego_lat_jerk' + category: 'Dynamics' diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_lon_acceleration_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_lon_acceleration_statistics.yaml new file mode 100644 index 0000000..21c541f --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_lon_acceleration_statistics.yaml @@ -0,0 +1,8 @@ +ego_lon_acceleration_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_lon_acceleration.EgoLonAccelerationStatistics + _convert_: 'all' + name: 'ego_lon_acceleration' + category: 'Dynamics' + + min_lon_accel: -4.05 + max_lon_accel: 2.40 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_lon_jerk_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_lon_jerk_statistics.yaml new file mode 100644 index 0000000..3a69606 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_lon_jerk_statistics.yaml @@ -0,0 +1,7 @@ +ego_lon_jerk_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_lon_jerk.EgoLonJerkStatistics + _convert_: 'all' + name: 'ego_lon_jerk' + category: 'Dynamics' + + max_abs_lon_jerk: 4.13 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_mean_speed_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_mean_speed_statistics.yaml new file mode 100644 index 0000000..dfed042 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_mean_speed_statistics.yaml @@ -0,0 +1,5 @@ +ego_mean_speed_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_mean_speed.EgoMeanSpeedStatistics + _convert_: 'all' + name: 'ego_mean_speed' + category: 'Dynamics' diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_progress_along_expert_route_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_progress_along_expert_route_statistics.yaml new file mode 100644 index 0000000..2ca9a4b --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_progress_along_expert_route_statistics.yaml @@ -0,0 +1,6 @@ +ego_progress_along_expert_route_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_progress_along_expert_route.EgoProgressAlongExpertRouteStatistics + _convert_: 'all' + name: 'ego_progress_along_expert_route' + category: 'Planning' + score_progress_threshold: 2 # [m] diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_yaw_acceleration_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_yaw_acceleration_statistics.yaml new file mode 100644 index 0000000..93b26bf --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_yaw_acceleration_statistics.yaml @@ -0,0 +1,7 @@ +ego_yaw_acceleration_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_yaw_acceleration.EgoYawAccelerationStatistics + _convert_: 'all' + name: 'ego_yaw_acceleration' + category: 'Dynamics' + + max_abs_yaw_accel: 1.93 diff --git a/sledge/script/config/common/simulation_metric/low_level/ego_yaw_rate_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/ego_yaw_rate_statistics.yaml new file mode 100644 index 0000000..4079454 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/ego_yaw_rate_statistics.yaml @@ -0,0 +1,7 @@ +ego_yaw_rate_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.ego_yaw_rate.EgoYawRateStatistics + _convert_: 'all' + name: 'ego_yaw_rate' + category: 'Dynamics' + + max_abs_yaw_rate: 0.95 diff --git a/sledge/script/config/common/simulation_metric/low_level/planner_expert_average_l2_error_within_bound_statistics.yaml b/sledge/script/config/common/simulation_metric/low_level/planner_expert_average_l2_error_within_bound_statistics.yaml new file mode 100644 index 0000000..32c32c6 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/low_level/planner_expert_average_l2_error_within_bound_statistics.yaml @@ -0,0 +1,9 @@ +planner_expert_average_l2_error_within_bound_statistics: + _target_: nuplan.planning.metrics.evaluation_metrics.common.planner_expert_average_l2_error_within_bound.PlannerExpertAverageL2ErrorStatistics + _convert_: 'all' + name: 'planner_expert_average_l2_error_within_bound' + category: 'Planning' + metric_score_unit: 'float' + comparison_horizon: [3, 5, 8] #seconds + comparison_frequency: 1 #Hz + max_average_l2_error_threshold: 8 #meter diff --git a/sledge/script/config/common/simulation_metric/simulation_closed_loop_nonreactive_agents.yaml b/sledge/script/config/common/simulation_metric/simulation_closed_loop_nonreactive_agents.yaml new file mode 100644 index 0000000..6efbed9 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/simulation_closed_loop_nonreactive_agents.yaml @@ -0,0 +1,19 @@ +defaults: + # - common_metrics # Uncomment this for common information about the scenario as specified in the config + - low_level: # Low level metrics + - ego_lane_change_statistics + - ego_jerk_statistics + - ego_lat_acceleration_statistics + - ego_lon_acceleration_statistics + - ego_lon_jerk_statistics + - ego_yaw_acceleration_statistics + - ego_yaw_rate_statistics + - ego_progress_along_expert_route_statistics + - high_level: # High level metrics that depend on low level metrics, they can also rely on the previously called high level metrics + - drivable_area_compliance_statistics + - no_ego_at_fault_collisions_statistics + - time_to_collision_within_bound_statistics + - speed_limit_compliance_statistics + - ego_is_comfortable_statistics + - ego_is_making_progress_statistics + - driving_direction_compliance_statistics diff --git a/sledge/script/config/common/simulation_metric/simulation_closed_loop_reactive_agents.yaml b/sledge/script/config/common/simulation_metric/simulation_closed_loop_reactive_agents.yaml new file mode 100644 index 0000000..6efbed9 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/simulation_closed_loop_reactive_agents.yaml @@ -0,0 +1,19 @@ +defaults: + # - common_metrics # Uncomment this for common information about the scenario as specified in the config + - low_level: # Low level metrics + - ego_lane_change_statistics + - ego_jerk_statistics + - ego_lat_acceleration_statistics + - ego_lon_acceleration_statistics + - ego_lon_jerk_statistics + - ego_yaw_acceleration_statistics + - ego_yaw_rate_statistics + - ego_progress_along_expert_route_statistics + - high_level: # High level metrics that depend on low level metrics, they can also rely on the previously called high level metrics + - drivable_area_compliance_statistics + - no_ego_at_fault_collisions_statistics + - time_to_collision_within_bound_statistics + - speed_limit_compliance_statistics + - ego_is_comfortable_statistics + - ego_is_making_progress_statistics + - driving_direction_compliance_statistics diff --git a/sledge/script/config/common/simulation_metric/simulation_open_loop_boxes.yaml b/sledge/script/config/common/simulation_metric/simulation_open_loop_boxes.yaml new file mode 100644 index 0000000..a879f60 --- /dev/null +++ b/sledge/script/config/common/simulation_metric/simulation_open_loop_boxes.yaml @@ -0,0 +1,9 @@ +defaults: + # - common_metrics # Uncomment this for common information about the scenario as specified in the config + - low_level: # Low level metrics + - planner_expert_average_l2_error_within_bound_statistics + - high_level: # High level metrics that depend on low level metrics, they can also rely on the previously called high level metrics + - planner_expert_final_l2_error_within_bound_statistics + - planner_miss_rate_within_bound_statistics + - planner_expert_final_heading_error_within_bound_statistics + - planner_expert_average_heading_error_within_bound_statistics diff --git a/sledge/script/config/common/splitter/nuplan.yaml b/sledge/script/config/common/splitter/nuplan.yaml new file mode 100644 index 0000000..fe99403 --- /dev/null +++ b/sledge/script/config/common/splitter/nuplan.yaml @@ -0,0 +1,15919 @@ +_target_: nuplan.planning.training.data_loader.log_splitter.LogSplitter +_convert_: 'all' + +log_splits: + train: + - 2021.05.12.19.36.12_veh-35_00005_00204 + - 2021.05.12.19.36.12_veh-35_00215_00405 + - 2021.05.12.19.36.12_veh-35_00416_00557 + - 2021.05.12.19.36.12_veh-35_00568_01168 + - 2021.05.12.19.36.12_veh-35_01179_01278 + - 2021.05.12.19.36.12_veh-35_01305_01389 + - 2021.05.12.19.36.12_veh-35_01400_01643 + - 2021.05.12.19.36.12_veh-35_01654_01733 + - 2021.05.12.19.36.12_veh-35_01744_01934 + - 2021.05.12.19.36.12_veh-35_01945_02065 + - 2021.05.12.19.36.12_veh-35_02079_02176 + - 2021.05.12.22.00.38_veh-35_00005_00118 + - 2021.05.12.22.00.38_veh-35_00129_00204 + - 2021.05.12.22.00.38_veh-35_00215_00995 + - 2021.05.12.22.00.38_veh-35_01008_01518 + - 2021.05.12.22.28.35_veh-35_00025_00115 + - 2021.05.12.22.28.35_veh-35_00126_00339 + - 2021.05.12.22.28.35_veh-35_00350_00568 + - 2021.05.12.22.28.35_veh-35_00620_01164 + - 2021.05.12.22.28.35_veh-35_01175_02127 + - 2021.05.12.22.28.35_veh-35_02138_02481 + - 2021.05.12.23.36.44_veh-35_00063_00141 + - 2021.05.12.23.36.44_veh-35_00152_00504 + - 2021.05.12.23.36.44_veh-35_00515_00701 + - 2021.05.12.23.36.44_veh-35_00712_00774 + - 2021.05.12.23.36.44_veh-35_00785_01041 + - 2021.05.12.23.36.44_veh-35_01133_01535 + - 2021.05.12.23.36.44_veh-35_01585_01724 + - 2021.05.12.23.36.44_veh-35_01735_01957 + - 2021.05.12.23.36.44_veh-35_02035_02387 + - 2021.05.13.17.53.42_veh-35_00005_00645 + - 2021.05.13.17.53.42_veh-35_00656_00753 + - 2021.05.13.17.53.42_veh-35_00793_00878 + - 2021.05.13.17.53.42_veh-35_00889_01750 + - 2021.05.13.17.53.42_veh-35_01768_02013 + - 2021.05.13.17.53.42_veh-35_02035_02549 + - 2021.05.13.17.53.42_veh-35_02560_02650 + - 2021.05.13.17.53.42_veh-35_02661_02750 + - 2021.05.13.17.53.42_veh-35_02761_02926 + - 2021.05.13.17.53.42_veh-35_02937_03209 + - 2021.05.13.17.53.42_veh-35_03220_03341 + - 2021.05.13.17.53.42_veh-35_03352_03415 + - 2021.05.13.17.53.42_veh-35_03426_03664 + - 2021.05.13.17.53.42_veh-35_03675_03769 + - 2021.05.13.17.53.42_veh-35_03780_03997 + - 2021.05.13.17.53.42_veh-35_04008_04186 + - 2021.05.13.17.53.42_veh-35_04197_04669 + - 2021.05.13.17.53.42_veh-35_04701_04815 + - 2021.05.13.17.53.42_veh-35_04876_05066 + - 2021.05.13.17.53.42_veh-35_05077_05485 + - 2021.05.13.17.53.42_veh-35_05496_05680 + - 2021.05.13.17.57.34_veh-30_00005_00130 + - 2021.05.13.17.57.34_veh-30_00186_00357 + - 2021.05.13.17.57.34_veh-30_00368_00452 + - 2021.05.13.17.57.34_veh-30_00463_00761 + - 2021.05.13.17.57.34_veh-30_00772_00880 + - 2021.05.13.17.57.34_veh-30_00908_01212 + - 2021.05.13.17.57.34_veh-30_01262_02143 + - 2021.05.13.17.57.34_veh-30_02154_02224 + - 2021.05.13.17.57.34_veh-30_02262_02549 + - 2021.05.13.17.57.34_veh-30_02560_02624 + - 2021.05.13.17.57.34_veh-30_02635_02940 + - 2021.05.13.17.57.34_veh-30_02951_03209 + - 2021.05.13.17.57.34_veh-30_03220_03378 + - 2021.05.13.17.57.34_veh-30_03389_03901 + - 2021.05.13.17.57.34_veh-30_03912_04072 + - 2021.05.13.17.57.34_veh-30_04083_04176 + - 2021.05.13.17.57.34_veh-30_04187_04467 + - 2021.05.13.17.57.34_veh-30_04478_04567 + - 2021.05.13.19.18.32_veh-30_00015_00465 + - 2021.05.13.19.18.32_veh-30_00610_00787 + - 2021.05.13.19.18.32_veh-30_00798_00927 + - 2021.05.13.19.37.43_veh-30_00099_00203 + - 2021.05.13.19.37.43_veh-30_00214_00287 + - 2021.05.13.19.37.43_veh-30_00324_00516 + - 2021.05.13.19.37.43_veh-30_00527_00666 + - 2021.05.13.19.37.43_veh-30_00677_00815 + - 2021.05.13.19.37.43_veh-30_01001_01138 + - 2021.05.13.19.37.43_veh-30_01150_01230 + - 2021.05.13.20.19.39_veh-35_00015_00194 + - 2021.05.13.20.19.39_veh-35_00205_00378 + - 2021.05.13.20.19.39_veh-35_00389_00484 + - 2021.05.13.20.19.39_veh-35_00495_00569 + - 2021.05.13.20.19.39_veh-35_00580_01200 + - 2021.05.13.20.19.39_veh-35_01211_01272 + - 2021.05.13.20.19.39_veh-35_01283_01353 + - 2021.05.13.20.19.39_veh-35_01397_01459 + - 2021.05.13.20.19.39_veh-35_01537_01697 + - 2021.05.13.20.19.39_veh-35_01762_01871 + - 2021.05.13.20.19.39_veh-35_01892_02188 + - 2021.05.13.20.19.39_veh-35_02211_02290 + - 2021.05.13.20.19.39_veh-35_02301_02535 + - 2021.05.13.20.19.39_veh-35_02547_02650 + - 2021.05.13.20.19.39_veh-35_02663_02789 + - 2021.05.13.20.19.39_veh-35_02800_02956 + - 2021.05.13.20.19.39_veh-35_02967_03378 + - 2021.05.13.20.19.39_veh-35_03389_03754 + - 2021.05.13.20.19.39_veh-35_03824_04002 + - 2021.05.13.20.19.39_veh-35_04013_05183 + - 2021.05.13.21.34.01_veh-30_00150_00555 + - 2021.05.13.21.34.01_veh-30_00601_01000 + - 2021.05.13.21.34.01_veh-30_01049_01112 + - 2021.05.13.21.34.01_veh-30_01123_01224 + - 2021.05.13.21.34.01_veh-30_01284_01368 + - 2021.05.13.21.34.01_veh-30_01379_01575 + - 2021.05.13.21.34.01_veh-30_01586_01695 + - 2021.05.13.21.34.01_veh-30_01706_01850 + - 2021.05.13.21.34.01_veh-30_01861_01928 + - 2021.05.13.21.34.01_veh-30_01994_02126 + - 2021.05.13.21.34.01_veh-30_02137_02233 + - 2021.05.13.21.34.01_veh-30_02244_02475 + - 2021.05.13.21.34.01_veh-30_02486_02624 + - 2021.05.13.21.34.01_veh-30_02684_02780 + - 2021.05.13.21.34.01_veh-30_02791_02928 + - 2021.05.13.21.34.01_veh-30_02958_03187 + - 2021.05.13.21.34.01_veh-30_03198_03311 + - 2021.05.13.22.14.41_veh-35_00147_00263 + - 2021.05.13.22.14.41_veh-35_00378_00521 + - 2021.05.13.22.14.41_veh-35_00532_00726 + - 2021.05.13.22.14.41_veh-35_00737_00951 + - 2021.05.13.22.14.41_veh-35_01014_01079 + - 2021.05.13.22.14.41_veh-35_01090_01156 + - 2021.05.13.22.14.41_veh-35_01234_01536 + - 2021.05.13.22.14.41_veh-35_01547_01865 + - 2021.05.13.22.14.41_veh-35_01928_02142 + - 2021.05.13.22.14.41_veh-35_02184_02260 + - 2021.05.13.22.14.41_veh-35_02271_02550 + - 2021.05.13.22.14.41_veh-35_02561_02638 + - 2021.05.13.22.14.41_veh-35_02706_03001 + - 2021.05.13.22.14.41_veh-35_03018_03140 + - 2021.05.13.22.14.41_veh-35_03151_03492 + - 2021.05.13.22.14.41_veh-35_03503_03652 + - 2021.05.13.22.14.41_veh-35_03663_03732 + - 2021.05.13.22.14.41_veh-35_03743_03917 + - 2021.05.13.22.14.41_veh-35_04042_04142 + - 2021.05.13.22.14.41_veh-35_04153_04277 + - 2021.05.13.22.14.41_veh-35_04288_04427 + - 2021.05.13.22.14.41_veh-35_04513_04644 + - 2021.05.13.22.14.41_veh-35_04694_04847 + - 2021.05.13.22.14.41_veh-35_04914_04975 + - 2021.05.13.22.40.44_veh-30_00071_00137 + - 2021.05.13.22.40.44_veh-30_00336_00499 + - 2021.05.13.22.40.44_veh-30_00510_00612 + - 2021.05.13.22.40.44_veh-30_00630_00797 + - 2021.05.13.22.40.44_veh-30_00822_01000 + - 2021.05.13.22.40.44_veh-30_01097_01201 + - 2021.05.13.22.40.44_veh-30_01212_01276 + - 2021.05.13.22.40.44_veh-30_01287_01375 + - 2021.05.13.22.40.44_veh-30_01411_01530 + - 2021.05.13.22.40.44_veh-30_01600_01771 + - 2021.05.13.22.40.44_veh-30_01809_01944 + - 2021.05.13.22.40.44_veh-30_02005_02091 + - 2021.05.13.22.40.44_veh-30_02102_02176 + - 2021.05.13.22.40.44_veh-30_02187_02256 + - 2021.05.13.22.40.44_veh-30_02267_02457 + - 2021.05.13.22.40.44_veh-30_02587_02718 + - 2021.05.13.22.40.44_veh-30_02767_02846 + - 2021.05.13.22.40.44_veh-30_02960_03062 + - 2021.05.13.22.40.44_veh-30_03141_03317 + - 2021.05.13.22.40.44_veh-30_03328_03532 + - 2021.05.13.22.40.44_veh-30_03570_03903 + - 2021.05.13.22.40.44_veh-30_03914_04018 + - 2021.05.13.22.40.44_veh-30_04029_04226 + - 2021.05.13.22.40.44_veh-30_04298_04415 + - 2021.05.13.23.44.53_veh-35_00032_00113 + - 2021.05.13.23.44.53_veh-35_00124_00437 + - 2021.05.13.23.44.53_veh-35_00528_00682 + - 2021.05.13.23.44.53_veh-35_00693_00820 + - 2021.05.13.23.44.53_veh-35_00831_01113 + - 2021.05.13.23.44.53_veh-35_01124_01412 + - 2021.05.13.23.44.53_veh-35_01483_01602 + - 2021.05.13.23.44.53_veh-35_01613_01725 + - 2021.05.14.00.01.18_veh-30_00016_00095 + - 2021.05.14.00.01.18_veh-30_00106_00508 + - 2021.05.14.00.01.18_veh-30_00519_01041 + - 2021.05.14.00.01.18_veh-30_01052_01259 + - 2021.05.14.16.27.17_veh-35_00005_00134 + - 2021.05.14.16.27.17_veh-35_00145_00331 + - 2021.05.14.16.27.17_veh-35_00353_00424 + - 2021.05.14.16.27.17_veh-35_00435_00495 + - 2021.05.14.16.27.17_veh-35_00534_00627 + - 2021.05.14.16.27.17_veh-35_00638_00872 + - 2021.05.14.16.44.42_veh-35_00079_00261 + - 2021.05.14.16.44.42_veh-35_00272_00421 + - 2021.05.14.16.44.42_veh-35_00543_00758 + - 2021.05.14.16.44.42_veh-35_00824_01266 + - 2021.05.14.16.44.42_veh-35_01298_01395 + - 2021.05.14.16.44.42_veh-35_01502_01718 + - 2021.05.14.16.44.42_veh-35_01876_02126 + - 2021.05.14.16.44.42_veh-35_02137_02291 + - 2021.05.14.16.44.42_veh-35_02302_02483 + - 2021.05.14.16.44.42_veh-35_02494_02625 + - 2021.05.14.16.44.42_veh-35_02688_02938 + - 2021.05.14.16.44.42_veh-35_02949_03415 + - 2021.05.14.16.44.42_veh-35_03516_03607 + - 2021.05.14.17.13.58_veh-30_00005_00195 + - 2021.05.14.17.13.58_veh-30_00254_00508 + - 2021.05.14.17.13.58_veh-30_00519_00625 + - 2021.05.14.17.13.58_veh-30_00636_00706 + - 2021.05.14.17.13.58_veh-30_00766_00882 + - 2021.05.14.17.13.58_veh-30_00895_01175 + - 2021.05.14.17.13.58_veh-30_01234_01326 + - 2021.05.14.17.13.58_veh-30_01338_01923 + - 2021.05.14.17.13.58_veh-30_02022_02113 + - 2021.05.14.17.13.58_veh-30_02124_02510 + - 2021.05.14.17.13.58_veh-30_02570_02735 + - 2021.05.14.17.13.58_veh-30_02814_02876 + - 2021.05.14.17.13.58_veh-30_02887_03417 + - 2021.05.14.17.13.58_veh-30_03428_03554 + - 2021.05.14.17.13.58_veh-30_03565_03723 + - 2021.05.14.17.13.58_veh-30_03734_03810 + - 2021.05.14.17.13.58_veh-30_03821_03938 + - 2021.05.14.17.13.58_veh-30_03949_04328 + - 2021.05.14.17.13.58_veh-30_04339_04410 + - 2021.05.14.18.15.19_veh-35_00005_00077 + - 2021.05.14.18.15.19_veh-35_00088_00217 + - 2021.05.14.18.15.19_veh-35_00228_00462 + - 2021.05.14.18.15.19_veh-35_00473_00548 + - 2021.05.14.18.15.19_veh-35_00594_00709 + - 2021.05.14.18.15.19_veh-35_00720_00802 + - 2021.05.14.18.15.19_veh-35_00813_00937 + - 2021.05.14.18.15.19_veh-35_00949_01287 + - 2021.05.14.18.15.19_veh-35_01298_01475 + - 2021.05.14.18.15.19_veh-35_01486_01754 + - 2021.05.14.18.15.19_veh-35_01765_01872 + - 2021.05.14.18.15.19_veh-35_01883_01974 + - 2021.05.14.18.15.19_veh-35_01985_02048 + - 2021.05.14.18.15.19_veh-35_02059_02498 + - 2021.05.14.18.15.19_veh-35_02509_02602 + - 2021.05.14.18.15.19_veh-35_02740_02890 + - 2021.05.14.18.15.19_veh-35_02901_03385 + - 2021.05.14.18.15.19_veh-35_03396_03484 + - 2021.05.14.18.15.19_veh-35_03505_03616 + - 2021.05.14.18.15.19_veh-35_03627_03728 + - 2021.05.14.18.15.19_veh-35_03772_03846 + - 2021.05.14.18.15.19_veh-35_03891_04078 + - 2021.05.14.18.15.19_veh-35_04091_04222 + - 2021.05.14.18.15.19_veh-35_04271_04600 + - 2021.05.14.18.15.19_veh-35_04611_04708 + - 2021.05.14.18.15.19_veh-35_04771_04935 + - 2021.05.14.18.15.19_veh-35_04946_05039 + - 2021.05.14.22.06.56_veh-30_00012_00180 + - 2021.05.14.22.06.56_veh-30_00191_00598 + - 2021.05.14.22.06.56_veh-30_00609_00722 + - 2021.05.14.22.06.56_veh-30_00777_00917 + - 2021.05.14.22.06.56_veh-30_00928_01072 + - 2021.05.14.22.06.56_veh-30_01083_01216 + - 2021.05.14.22.06.56_veh-30_01283_01693 + - 2021.05.14.22.06.56_veh-30_01749_01882 + - 2021.05.14.22.06.56_veh-30_01893_02087 + - 2021.05.14.22.06.56_veh-30_02098_02612 + - 2021.05.14.22.06.56_veh-30_02667_02853 + - 2021.05.14.22.06.56_veh-30_02864_02947 + - 2021.05.14.22.06.56_veh-30_02965_03114 + - 2021.05.14.22.06.56_veh-30_03125_03201 + - 2021.05.14.22.06.56_veh-30_03212_03411 + - 2021.05.14.22.06.56_veh-30_03422_03578 + - 2021.05.14.22.06.56_veh-30_03589_03757 + - 2021.05.14.22.06.56_veh-30_03768_04187 + - 2021.05.14.22.06.56_veh-30_04216_04302 + - 2021.05.14.22.06.56_veh-30_04313_04377 + - 2021.05.14.22.06.56_veh-30_04388_04587 + - 2021.05.14.22.06.56_veh-30_04613_05224 + - 2021.05.14.22.06.56_veh-30_05253_05453 + - 2021.05.17.16.40.09_veh-35_00108_00387 + - 2021.05.17.16.40.09_veh-35_00530_00628 + - 2021.05.17.16.40.09_veh-35_00640_00750 + - 2021.05.17.16.40.09_veh-35_00761_00835 + - 2021.05.17.16.40.09_veh-35_00846_01051 + - 2021.05.17.16.40.09_veh-35_01062_01263 + - 2021.05.17.16.40.09_veh-35_01364_01431 + - 2021.05.17.16.40.09_veh-35_01458_01570 + - 2021.05.17.16.40.09_veh-35_01581_01692 + - 2021.05.17.16.40.09_veh-35_01703_01806 + - 2021.05.17.16.40.09_veh-35_01817_01942 + - 2021.05.17.16.40.09_veh-35_02126_02204 + - 2021.05.17.16.40.09_veh-35_02279_02341 + - 2021.05.17.16.40.09_veh-35_02441_02512 + - 2021.05.17.16.40.09_veh-35_02523_02654 + - 2021.05.17.16.40.09_veh-35_02665_02762 + - 2021.05.17.16.40.09_veh-35_02902_03040 + - 2021.05.17.16.40.09_veh-35_03051_03233 + - 2021.05.17.16.40.09_veh-35_03245_03329 + - 2021.05.17.16.40.09_veh-35_03340_03516 + - 2021.05.17.16.40.09_veh-35_03528_03621 + - 2021.05.17.16.40.09_veh-35_03684_04046 + - 2021.05.17.16.40.09_veh-35_04057_04412 + - 2021.05.17.16.40.09_veh-35_04461_04586 + - 2021.05.17.16.40.09_veh-35_04600_04931 + - 2021.05.17.16.40.09_veh-35_04942_05257 + - 2021.05.17.16.59.41_veh-30_00126_00196 + - 2021.05.17.16.59.41_veh-30_00207_00294 + - 2021.05.17.16.59.41_veh-30_00305_00628 + - 2021.05.17.16.59.41_veh-30_00641_00864 + - 2021.05.17.16.59.41_veh-30_00991_01118 + - 2021.05.17.16.59.41_veh-30_01129_01211 + - 2021.05.17.17.32.24_veh-30_00038_00208 + - 2021.05.17.17.32.24_veh-30_00223_00346 + - 2021.05.17.17.32.24_veh-30_00357_00473 + - 2021.05.17.17.32.24_veh-30_00484_00646 + - 2021.05.17.17.32.24_veh-30_00657_00795 + - 2021.05.17.17.32.24_veh-30_00836_00908 + - 2021.05.17.17.32.24_veh-30_00954_01217 + - 2021.05.17.17.32.24_veh-30_01358_01450 + - 2021.05.17.17.32.24_veh-30_01461_01677 + - 2021.05.17.17.32.24_veh-30_01749_01922 + - 2021.05.17.17.32.24_veh-30_01933_02133 + - 2021.05.17.17.32.24_veh-30_02144_02312 + - 2021.05.17.17.32.24_veh-30_02323_02479 + - 2021.05.17.17.32.24_veh-30_02494_02598 + - 2021.05.17.17.32.24_veh-30_02609_02679 + - 2021.05.17.17.32.24_veh-30_02722_02812 + - 2021.05.17.17.32.24_veh-30_02823_02935 + - 2021.05.17.17.32.24_veh-30_03026_03093 + - 2021.05.17.17.32.24_veh-30_03104_03482 + - 2021.05.17.17.32.24_veh-30_03493_03554 + - 2021.05.17.17.32.24_veh-30_03565_03858 + - 2021.05.17.17.32.24_veh-30_03936_04043 + - 2021.05.17.17.32.24_veh-30_04196_04329 + - 2021.05.17.17.32.24_veh-30_04515_04743 + - 2021.05.17.17.32.24_veh-30_04809_04901 + - 2021.05.17.17.32.24_veh-30_04912_04987 + - 2021.05.17.17.32.24_veh-30_04998_05176 + - 2021.05.17.17.32.24_veh-30_05187_05307 + - 2021.05.17.21.22.41_veh-35_00005_00090 + - 2021.05.17.21.22.41_veh-35_00150_00486 + - 2021.05.17.21.22.41_veh-35_00497_00596 + - 2021.05.17.21.22.41_veh-35_00607_00735 + - 2021.05.17.21.22.41_veh-35_00746_00857 + - 2021.05.17.21.22.41_veh-35_00868_00985 + - 2021.05.17.21.22.41_veh-35_00997_01090 + - 2021.05.17.21.22.41_veh-35_01101_01615 + - 2021.05.17.21.22.41_veh-35_01626_01795 + - 2021.05.17.21.22.41_veh-35_01877_02198 + - 2021.05.17.21.22.41_veh-35_02209_02809 + - 2021.05.17.21.22.41_veh-35_02856_02931 + - 2021.05.17.21.22.41_veh-35_02946_03058 + - 2021.05.17.21.22.41_veh-35_03069_03175 + - 2021.05.17.21.22.41_veh-35_03219_03305 + - 2021.05.17.21.22.41_veh-35_03316_03520 + - 2021.05.17.21.22.41_veh-35_03531_03790 + - 2021.05.17.21.22.41_veh-35_03801_03864 + - 2021.05.17.21.22.41_veh-35_03895_04128 + - 2021.05.17.21.22.41_veh-35_04139_04513 + - 2021.05.17.21.22.41_veh-35_04524_04761 + - 2021.05.17.21.22.41_veh-35_04772_04996 + - 2021.05.17.21.22.41_veh-35_05088_05183 + - 2021.05.17.21.22.41_veh-35_05194_05362 + - 2021.05.17.22.28.24_veh-30_00008_00227 + - 2021.05.17.22.28.24_veh-30_00238_00349 + - 2021.05.17.22.28.24_veh-30_00390_00577 + - 2021.05.17.22.28.24_veh-30_00588_00702 + - 2021.05.17.22.28.24_veh-30_00715_00967 + - 2021.05.17.22.28.24_veh-30_00978_01170 + - 2021.05.17.22.28.24_veh-30_01242_01364 + - 2021.05.17.22.28.24_veh-30_01395_01762 + - 2021.05.17.22.28.24_veh-30_01773_02307 + - 2021.05.17.22.28.24_veh-30_02318_03007 + - 2021.05.17.22.28.24_veh-30_03018_03122 + - 2021.05.17.22.28.24_veh-30_03133_03382 + - 2021.05.17.22.28.24_veh-30_03470_03561 + - 2021.05.17.22.28.24_veh-30_03597_03767 + - 2021.05.17.22.28.24_veh-30_03778_04007 + - 2021.05.17.22.28.24_veh-30_04072_04482 + - 2021.05.17.22.28.24_veh-30_04538_04670 + - 2021.05.17.22.28.24_veh-30_04681_04937 + - 2021.05.17.22.28.24_veh-30_04948_05113 + - 2021.05.17.23.17.13_veh-35_00005_00174 + - 2021.05.17.23.17.13_veh-35_00185_00294 + - 2021.05.17.23.17.13_veh-35_00305_00504 + - 2021.05.17.23.17.13_veh-35_00515_00682 + - 2021.05.17.23.17.13_veh-35_00717_00893 + - 2021.05.17.23.17.13_veh-35_00904_01105 + - 2021.05.17.23.17.13_veh-35_01116_01264 + - 2021.05.17.23.17.13_veh-35_01403_01530 + - 2021.05.17.23.17.13_veh-35_01541_02135 + - 2021.05.17.23.17.13_veh-35_02242_02305 + - 2021.05.17.23.17.13_veh-35_02316_02559 + - 2021.05.17.23.17.13_veh-35_02635_02965 + - 2021.05.17.23.17.13_veh-35_02976_03484 + - 2021.05.17.23.17.13_veh-35_03495_03754 + - 2021.05.17.23.17.13_veh-35_03857_04160 + - 2021.05.17.23.17.13_veh-35_04171_04330 + - 2021.05.18.12.34.13_veh-24_00072_00158 + - 2021.05.18.12.34.13_veh-24_00169_00325 + - 2021.05.18.12.34.13_veh-24_00336_00755 + - 2021.05.18.12.34.13_veh-24_00766_01072 + - 2021.05.18.12.34.13_veh-24_01084_01364 + - 2021.05.18.12.34.13_veh-24_01388_01449 + - 2021.05.18.12.34.13_veh-24_01477_01662 + - 2021.05.18.12.34.13_veh-24_01673_01806 + - 2021.05.18.12.34.13_veh-24_01817_01959 + - 2021.05.18.12.34.13_veh-24_01992_02684 + - 2021.05.18.12.34.13_veh-24_02868_03004 + - 2021.05.18.12.34.13_veh-24_03034_03127 + - 2021.05.18.12.34.13_veh-24_03141_03230 + - 2021.05.18.12.34.13_veh-24_03241_03320 + - 2021.05.18.12.34.13_veh-24_03431_03837 + - 2021.05.18.12.34.13_veh-24_03848_04122 + - 2021.05.18.12.34.13_veh-24_04133_04341 + - 2021.05.18.12.34.13_veh-24_04352_04622 + - 2021.05.18.12.34.13_veh-24_04697_04776 + - 2021.05.18.12.34.13_veh-24_04850_05366 + - 2021.05.18.13.20.19_veh-25_00005_00485 + - 2021.05.18.13.20.19_veh-25_00512_01305 + - 2021.05.18.13.20.19_veh-25_01331_01467 + - 2021.05.18.13.20.19_veh-25_01478_01581 + - 2021.05.18.13.20.19_veh-25_01625_01780 + - 2021.05.18.13.20.19_veh-25_01808_02181 + - 2021.05.18.13.20.19_veh-25_02192_02315 + - 2021.05.18.13.20.19_veh-25_02326_02599 + - 2021.05.18.13.20.19_veh-25_02610_02690 + - 2021.05.18.13.20.19_veh-25_02701_02869 + - 2021.05.18.13.20.19_veh-25_02920_03265 + - 2021.05.18.13.20.19_veh-25_03282_03419 + - 2021.05.18.13.20.19_veh-25_03430_03528 + - 2021.05.18.13.20.19_veh-25_03608_03919 + - 2021.05.18.13.20.19_veh-25_03930_04015 + - 2021.05.18.13.20.19_veh-25_04086_04266 + - 2021.05.18.13.20.19_veh-25_04346_04714 + - 2021.05.18.13.20.19_veh-25_04768_04844 + - 2021.05.18.13.20.19_veh-25_04888_04991 + - 2021.05.18.13.20.19_veh-25_05002_05130 + - 2021.05.18.14.29.38_veh-24_00143_00254 + - 2021.05.18.14.29.38_veh-24_00265_00397 + - 2021.05.18.14.29.38_veh-24_00408_00594 + - 2021.05.18.14.29.38_veh-24_00641_00831 + - 2021.05.18.14.29.38_veh-24_00842_01094 + - 2021.05.18.14.29.38_veh-24_01105_01412 + - 2021.05.18.14.29.38_veh-24_01423_01564 + - 2021.05.18.14.29.38_veh-24_01575_01648 + - 2021.05.18.14.29.38_veh-24_01728_01791 + - 2021.05.18.14.29.38_veh-24_01802_01895 + - 2021.05.18.14.29.38_veh-24_01932_02021 + - 2021.05.18.14.29.38_veh-24_02032_02178 + - 2021.05.18.14.29.38_veh-24_02189_02606 + - 2021.05.18.14.29.38_veh-24_02649_02711 + - 2021.05.18.14.29.38_veh-24_02784_02849 + - 2021.05.18.14.29.38_veh-24_02861_02930 + - 2021.05.18.14.29.38_veh-24_02941_03136 + - 2021.05.18.14.29.38_veh-24_03258_03390 + - 2021.05.18.14.29.38_veh-24_03411_03554 + - 2021.05.18.14.29.38_veh-24_03594_03850 + - 2021.05.18.14.29.38_veh-24_03861_04228 + - 2021.05.18.14.29.38_veh-24_04251_04515 + - 2021.05.18.14.29.38_veh-24_04676_04810 + - 2021.05.18.14.29.38_veh-24_04821_04955 + - 2021.05.18.14.29.38_veh-24_05026_05434 + - 2021.05.18.17.16.52_veh-30_00030_00498 + - 2021.05.18.17.16.52_veh-30_00510_00729 + - 2021.05.18.17.16.52_veh-30_00740_01408 + - 2021.05.18.17.16.52_veh-30_01419_01819 + - 2021.05.18.17.16.52_veh-30_01849_01910 + - 2021.05.18.17.16.52_veh-30_01981_02079 + - 2021.05.18.17.16.52_veh-30_02090_02201 + - 2021.05.18.17.16.52_veh-30_02212_02459 + - 2021.05.18.17.16.52_veh-30_02470_02809 + - 2021.05.18.17.16.52_veh-30_02821_03106 + - 2021.05.18.17.16.52_veh-30_03117_03550 + - 2021.05.18.17.16.52_veh-30_03561_03650 + - 2021.05.18.17.16.52_veh-30_03732_03862 + - 2021.05.18.17.16.52_veh-30_03873_04143 + - 2021.05.18.17.16.52_veh-30_04231_04529 + - 2021.05.18.17.16.52_veh-30_04540_04743 + - 2021.05.18.17.16.52_veh-30_04754_04919 + - 2021.05.18.17.16.52_veh-30_04930_05570 + - 2021.05.18.17.16.52_veh-30_05581_05702 + - 2021.05.18.17.38.02_veh-24_00005_00076 + - 2021.05.18.17.38.02_veh-24_00087_00349 + - 2021.05.18.17.38.02_veh-24_00434_00543 + - 2021.05.18.17.38.02_veh-24_00554_00636 + - 2021.05.18.17.38.02_veh-24_00647_01297 + - 2021.05.18.17.38.02_veh-24_01308_01533 + - 2021.05.18.17.38.02_veh-24_01599_02196 + - 2021.05.18.17.38.02_veh-24_02281_02452 + - 2021.05.18.17.38.02_veh-24_02463_02587 + - 2021.05.18.17.38.02_veh-24_02605_02947 + - 2021.05.18.17.38.02_veh-24_02958_03089 + - 2021.05.18.17.38.02_veh-24_03100_03275 + - 2021.05.18.17.38.02_veh-24_03286_03509 + - 2021.05.18.17.38.02_veh-24_03582_03729 + - 2021.05.18.17.38.02_veh-24_03740_03990 + - 2021.05.18.17.38.02_veh-24_04001_04065 + - 2021.05.18.17.38.02_veh-24_04076_04164 + - 2021.05.18.17.38.02_veh-24_04294_04638 + - 2021.05.18.17.38.02_veh-24_04656_04796 + - 2021.05.18.17.38.02_veh-24_04851_05344 + - 2021.05.18.18.21.37_veh-25_00005_00348 + - 2021.05.18.18.21.37_veh-25_00359_00498 + - 2021.05.18.18.21.37_veh-25_00509_00683 + - 2021.05.18.18.21.37_veh-25_00694_00903 + - 2021.05.18.18.21.37_veh-25_00975_01245 + - 2021.05.18.18.21.37_veh-25_01304_01367 + - 2021.05.18.18.21.37_veh-25_01378_01493 + - 2021.05.18.18.21.37_veh-25_01504_01827 + - 2021.05.18.18.21.37_veh-25_01838_02014 + - 2021.05.18.18.21.37_veh-25_02039_02131 + - 2021.05.18.18.21.37_veh-25_02189_02788 + - 2021.05.18.18.21.37_veh-25_02800_02993 + - 2021.05.18.18.21.37_veh-25_03004_03112 + - 2021.05.18.18.21.37_veh-25_03123_03323 + - 2021.05.18.18.21.37_veh-25_03334_03399 + - 2021.05.18.19.20.18_veh-30_00005_00091 + - 2021.05.18.19.20.18_veh-30_00102_00164 + - 2021.05.18.19.20.18_veh-30_00175_00403 + - 2021.05.18.19.20.18_veh-30_00582_00735 + - 2021.05.18.19.20.18_veh-30_00746_01436 + - 2021.05.18.19.20.18_veh-30_01469_01536 + - 2021.05.18.19.20.18_veh-30_01615_01841 + - 2021.05.18.19.20.18_veh-30_01912_02104 + - 2021.05.18.19.20.18_veh-30_02115_02248 + - 2021.05.18.19.25.26_veh-24_00005_00216 + - 2021.05.18.19.25.26_veh-24_00352_00641 + - 2021.05.18.19.25.26_veh-24_00652_01124 + - 2021.05.18.19.25.26_veh-24_01135_01443 + - 2021.05.18.19.25.26_veh-24_01454_01633 + - 2021.05.18.19.25.26_veh-24_01644_01705 + - 2021.05.18.19.25.26_veh-24_01716_01807 + - 2021.05.18.19.25.26_veh-24_01849_02173 + - 2021.05.18.19.25.26_veh-24_02252_02404 + - 2021.05.18.19.25.26_veh-24_02415_02768 + - 2021.05.18.19.25.26_veh-24_02791_02899 + - 2021.05.18.19.25.26_veh-24_02910_02980 + - 2021.05.18.19.25.26_veh-24_02991_03092 + - 2021.05.18.19.25.26_veh-24_03103_03279 + - 2021.05.18.19.25.26_veh-24_03290_03464 + - 2021.05.18.19.25.26_veh-24_03475_03674 + - 2021.05.18.19.25.26_veh-24_03685_03831 + - 2021.05.18.19.35.24_veh-25_00046_00153 + - 2021.05.18.19.35.24_veh-25_00164_00358 + - 2021.05.18.19.35.24_veh-25_00390_00504 + - 2021.05.18.19.35.24_veh-25_00515_00581 + - 2021.05.18.19.35.24_veh-25_00592_00652 + - 2021.05.18.19.35.24_veh-25_00663_00933 + - 2021.05.18.19.35.24_veh-25_00944_01186 + - 2021.05.18.19.35.24_veh-25_01233_01296 + - 2021.05.18.19.35.24_veh-25_01307_01518 + - 2021.05.18.19.35.24_veh-25_01529_01609 + - 2021.05.18.19.35.24_veh-25_01620_02053 + - 2021.05.18.19.35.24_veh-25_02064_02263 + - 2021.05.18.19.35.24_veh-25_02313_02637 + - 2021.05.18.20.57.37_veh-35_00005_00256 + - 2021.05.18.20.57.37_veh-35_00267_00696 + - 2021.05.18.20.57.37_veh-35_00707_00902 + - 2021.05.18.20.57.37_veh-35_00913_01031 + - 2021.05.18.20.57.37_veh-35_01042_01166 + - 2021.05.18.20.57.37_veh-35_01183_01768 + - 2021.05.18.20.57.37_veh-35_01798_01959 + - 2021.05.18.20.57.37_veh-35_01970_02109 + - 2021.05.18.20.57.37_veh-35_02187_02358 + - 2021.05.18.20.57.37_veh-35_02369_02494 + - 2021.05.18.20.57.37_veh-35_02552_03276 + - 2021.05.18.20.57.37_veh-35_03287_04175 + - 2021.05.18.20.57.37_veh-35_04186_04644 + - 2021.05.18.20.57.37_veh-35_04655_04823 + - 2021.05.18.20.57.37_veh-35_04834_05146 + - 2021.05.18.20.57.37_veh-35_05157_05225 + - 2021.05.18.20.57.37_veh-35_05236_05666 + - 2021.05.18.21.31.22_veh-30_00062_00160 + - 2021.05.18.21.31.22_veh-30_00178_00308 + - 2021.05.18.21.31.22_veh-30_00320_00499 + - 2021.05.18.21.31.22_veh-30_00583_00643 + - 2021.05.18.21.31.22_veh-30_00654_00862 + - 2021.05.18.21.31.22_veh-30_00918_00998 + - 2021.05.18.21.31.22_veh-30_01076_01183 + - 2021.05.18.21.31.22_veh-30_01317_01444 + - 2021.05.18.21.31.22_veh-30_01462_01768 + - 2021.05.18.21.31.22_veh-30_01779_01868 + - 2021.05.18.21.31.22_veh-30_01879_02254 + - 2021.05.18.21.31.22_veh-30_02309_02530 + - 2021.05.18.21.31.22_veh-30_02541_02614 + - 2021.05.18.21.31.22_veh-30_02719_02801 + - 2021.05.18.21.31.22_veh-30_02854_02956 + - 2021.05.18.21.31.22_veh-30_03040_03150 + - 2021.05.18.21.31.22_veh-30_03233_03329 + - 2021.05.18.21.31.22_veh-30_03340_03446 + - 2021.05.18.21.31.22_veh-30_03457_03531 + - 2021.05.18.21.31.22_veh-30_03543_03621 + - 2021.05.18.21.31.22_veh-30_03702_03837 + - 2021.05.18.21.31.22_veh-30_03850_03920 + - 2021.05.18.21.31.22_veh-30_03974_04069 + - 2021.05.18.21.31.22_veh-30_04080_04189 + - 2021.05.18.21.31.22_veh-30_04200_04314 + - 2021.05.18.21.31.22_veh-30_04344_04463 + - 2021.05.18.21.31.22_veh-30_04483_04576 + - 2021.05.18.21.31.22_veh-30_04660_04805 + - 2021.05.18.21.31.22_veh-30_04816_05074 + - 2021.05.18.21.31.22_veh-30_05086_05214 + - 2021.05.19.12.10.11_veh-25_00067_00939 + - 2021.05.19.12.10.11_veh-25_00976_01518 + - 2021.05.19.12.10.11_veh-25_01552_01665 + - 2021.05.19.12.10.11_veh-25_01676_01808 + - 2021.05.19.12.10.11_veh-25_01819_01899 + - 2021.05.19.12.10.11_veh-25_01910_02782 + - 2021.05.19.12.10.11_veh-25_02828_02984 + - 2021.05.19.12.10.11_veh-25_02995_03536 + - 2021.05.19.12.10.11_veh-25_03552_03692 + - 2021.05.19.12.10.11_veh-25_03703_04062 + - 2021.05.19.12.10.11_veh-25_04073_04237 + - 2021.05.19.12.10.11_veh-25_04277_04482 + - 2021.05.19.12.10.11_veh-25_04494_04555 + - 2021.05.19.12.10.11_veh-25_04566_04713 + - 2021.05.19.12.10.11_veh-25_04724_04888 + - 2021.05.19.12.10.11_veh-25_04947_05037 + - 2021.05.19.12.32.59_veh-24_00075_00173 + - 2021.05.19.12.32.59_veh-24_00475_00549 + - 2021.05.19.12.32.59_veh-24_00560_00730 + - 2021.05.19.12.32.59_veh-24_00741_00999 + - 2021.05.19.12.32.59_veh-24_01010_01318 + - 2021.05.19.12.32.59_veh-24_01329_01440 + - 2021.05.19.12.32.59_veh-24_01470_01562 + - 2021.05.19.12.32.59_veh-24_01645_01949 + - 2021.05.19.12.32.59_veh-24_01960_02214 + - 2021.05.19.12.32.59_veh-24_02225_02329 + - 2021.05.19.12.32.59_veh-24_02340_03008 + - 2021.05.19.12.32.59_veh-24_03019_03319 + - 2021.05.19.12.32.59_veh-24_03330_03500 + - 2021.05.19.12.32.59_veh-24_03591_03726 + - 2021.05.19.12.32.59_veh-24_03737_04011 + - 2021.05.19.12.32.59_veh-24_04022_04101 + - 2021.05.19.12.32.59_veh-24_04157_04301 + - 2021.05.19.12.32.59_veh-24_04336_04641 + - 2021.05.19.12.32.59_veh-24_04652_04912 + - 2021.05.19.12.32.59_veh-24_04923_05097 + - 2021.05.19.12.32.59_veh-24_05108_05257 + - 2021.05.19.13.46.13_veh-27_00005_00182 + - 2021.05.19.13.46.13_veh-27_00193_00367 + - 2021.05.19.13.46.13_veh-27_00378_00521 + - 2021.05.19.13.46.13_veh-27_00697_00806 + - 2021.05.19.13.46.13_veh-27_00817_00996 + - 2021.05.19.13.46.13_veh-27_01007_01107 + - 2021.05.19.13.46.13_veh-27_01118_01241 + - 2021.05.19.13.46.13_veh-27_01252_01366 + - 2021.05.19.13.46.13_veh-27_01377_01714 + - 2021.05.19.13.46.13_veh-27_01725_01786 + - 2021.05.19.13.46.13_veh-27_01797_01936 + - 2021.05.19.13.46.13_veh-27_01947_02082 + - 2021.05.19.13.46.13_veh-27_02166_02560 + - 2021.05.19.13.46.13_veh-27_02571_02653 + - 2021.05.19.13.46.13_veh-27_02664_03046 + - 2021.05.19.13.46.13_veh-27_03153_03824 + - 2021.05.19.13.46.13_veh-27_03835_03986 + - 2021.05.19.13.46.13_veh-27_03997_04104 + - 2021.05.19.13.46.13_veh-27_04115_04444 + - 2021.05.19.13.46.13_veh-27_04489_04708 + - 2021.05.19.13.46.13_veh-27_04719_05063 + - 2021.05.19.14.07.59_veh-25_00015_00516 + - 2021.05.19.14.07.59_veh-25_00527_00738 + - 2021.05.19.14.07.59_veh-25_00749_00855 + - 2021.05.19.14.07.59_veh-25_00866_01174 + - 2021.05.19.14.07.59_veh-25_01197_01287 + - 2021.05.19.14.07.59_veh-25_01298_01487 + - 2021.05.19.14.07.59_veh-25_01553_01657 + - 2021.05.19.14.07.59_veh-25_01718_01857 + - 2021.05.19.14.07.59_veh-25_01869_02031 + - 2021.05.19.14.07.59_veh-25_02042_02222 + - 2021.05.19.14.07.59_veh-25_02233_02361 + - 2021.05.19.14.07.59_veh-25_02372_02499 + - 2021.05.19.14.07.59_veh-25_02525_02667 + - 2021.05.19.14.07.59_veh-25_02678_02768 + - 2021.05.19.14.07.59_veh-25_02830_02898 + - 2021.05.19.14.07.59_veh-25_02909_03113 + - 2021.05.19.14.07.59_veh-25_03145_03382 + - 2021.05.19.14.07.59_veh-25_03394_03673 + - 2021.05.19.14.07.59_veh-25_03684_03868 + - 2021.05.19.14.07.59_veh-25_03879_04233 + - 2021.05.19.14.07.59_veh-25_04244_04415 + - 2021.05.19.14.07.59_veh-25_04426_04598 + - 2021.05.19.14.07.59_veh-25_04609_04740 + - 2021.05.19.14.07.59_veh-25_04817_04963 + - 2021.05.19.14.07.59_veh-25_05033_05153 + - 2021.05.19.14.07.59_veh-25_05223_05303 + - 2021.05.19.16.30.14_veh-27_00073_00236 + - 2021.05.19.16.30.14_veh-27_00301_00431 + - 2021.05.19.16.30.14_veh-27_00442_00578 + - 2021.05.19.16.30.14_veh-27_00603_00849 + - 2021.05.19.16.30.14_veh-27_00895_01187 + - 2021.05.19.16.30.14_veh-27_01211_01307 + - 2021.05.19.16.30.14_veh-27_01374_01493 + - 2021.05.19.16.30.14_veh-27_01504_01678 + - 2021.05.19.16.30.14_veh-27_01689_01797 + - 2021.05.19.16.30.14_veh-27_01808_01890 + - 2021.05.19.16.30.14_veh-27_01901_01981 + - 2021.05.19.16.30.14_veh-27_01992_02167 + - 2021.05.19.16.30.14_veh-27_02179_02338 + - 2021.05.19.16.30.14_veh-27_02584_02687 + - 2021.05.19.16.30.14_veh-27_02753_02860 + - 2021.05.19.16.30.14_veh-27_02993_03131 + - 2021.05.19.16.30.14_veh-27_03274_03477 + - 2021.05.19.16.30.14_veh-27_03540_03693 + - 2021.05.19.16.30.14_veh-27_03727_03811 + - 2021.05.19.16.30.14_veh-27_03822_04098 + - 2021.05.19.16.30.14_veh-27_04168_04235 + - 2021.05.19.16.30.14_veh-27_04251_04419 + - 2021.05.19.16.30.14_veh-27_04439_04642 + - 2021.05.19.16.30.14_veh-27_04653_04841 + - 2021.05.19.16.30.14_veh-27_04875_05032 + - 2021.05.19.16.30.14_veh-27_05043_05313 + - 2021.05.19.16.30.14_veh-27_05324_05384 + - 2021.05.19.17.21.43_veh-25_00005_00219 + - 2021.05.19.17.21.43_veh-25_00230_00365 + - 2021.05.19.17.21.43_veh-25_00424_00626 + - 2021.05.19.17.21.43_veh-25_00708_00911 + - 2021.05.19.17.21.43_veh-25_00922_01159 + - 2021.05.19.17.21.43_veh-25_01170_01394 + - 2021.05.19.17.21.43_veh-25_01405_01763 + - 2021.05.19.17.21.43_veh-25_01805_02032 + - 2021.05.19.17.21.43_veh-25_02050_02414 + - 2021.05.19.17.21.43_veh-25_02425_02509 + - 2021.05.20.12.12.04_veh-27_00005_00212 + - 2021.05.20.12.12.04_veh-27_00248_00316 + - 2021.05.20.12.12.04_veh-27_00327_00553 + - 2021.05.20.12.12.04_veh-27_00749_01148 + - 2021.05.20.12.12.04_veh-27_01159_01381 + - 2021.05.20.12.12.04_veh-27_01392_01481 + - 2021.05.20.12.12.04_veh-27_01492_01983 + - 2021.05.20.12.12.04_veh-27_01994_02428 + - 2021.05.20.12.12.04_veh-27_02439_02527 + - 2021.05.20.12.12.04_veh-27_02538_02621 + - 2021.05.20.12.12.04_veh-27_02703_03396 + - 2021.05.20.12.12.04_veh-27_03407_03483 + - 2021.05.20.12.12.04_veh-27_03494_03761 + - 2021.05.20.12.12.04_veh-27_03772_03882 + - 2021.05.20.12.12.04_veh-27_03893_04265 + - 2021.05.20.12.12.04_veh-27_04311_04740 + - 2021.05.20.12.12.04_veh-27_04751_04855 + - 2021.05.20.12.12.04_veh-27_04866_05086 + - 2021.05.20.12.21.42_veh-25_00015_00184 + - 2021.05.20.12.21.42_veh-25_00195_00425 + - 2021.05.20.12.21.42_veh-25_00462_00581 + - 2021.05.20.12.21.42_veh-25_00675_00869 + - 2021.05.20.12.21.42_veh-25_00916_00997 + - 2021.05.20.12.21.42_veh-25_01008_01113 + - 2021.05.20.12.21.42_veh-25_01124_01784 + - 2021.05.20.12.21.42_veh-25_01962_02151 + - 2021.05.20.12.21.42_veh-25_02204_02428 + - 2021.05.20.12.21.42_veh-25_02439_02551 + - 2021.05.20.12.21.42_veh-25_02562_02770 + - 2021.05.20.12.21.42_veh-25_02781_03044 + - 2021.05.20.12.21.42_veh-25_03055_03225 + - 2021.05.20.12.21.42_veh-25_03236_03574 + - 2021.05.20.12.21.42_veh-25_03585_04043 + - 2021.05.20.12.21.42_veh-25_04054_04336 + - 2021.05.20.12.21.42_veh-25_04462_04811 + - 2021.05.20.12.21.42_veh-25_04822_04971 + - 2021.05.20.12.21.42_veh-25_05051_05163 + - 2021.05.20.13.54.07_veh-25_00005_00203 + - 2021.05.20.13.54.07_veh-25_00226_00504 + - 2021.05.20.13.54.07_veh-25_00515_00613 + - 2021.05.20.13.54.07_veh-25_00624_00813 + - 2021.05.20.13.54.07_veh-25_00825_00904 + - 2021.05.20.13.54.07_veh-25_00915_01014 + - 2021.05.20.13.54.07_veh-25_01025_01090 + - 2021.05.20.13.54.07_veh-25_01101_01458 + - 2021.05.20.13.54.07_veh-25_01469_01819 + - 2021.05.20.13.54.07_veh-25_01830_01998 + - 2021.05.20.13.54.07_veh-25_02046_02279 + - 2021.05.20.13.54.07_veh-25_02291_02404 + - 2021.05.20.13.54.07_veh-25_02415_02524 + - 2021.05.20.13.54.07_veh-25_02535_02690 + - 2021.05.20.14.06.02_veh-27_00005_00119 + - 2021.05.20.14.06.02_veh-27_00130_00229 + - 2021.05.20.14.06.02_veh-27_00240_00381 + - 2021.05.20.14.06.02_veh-27_00441_00612 + - 2021.05.20.14.06.02_veh-27_00649_01188 + - 2021.05.20.14.06.02_veh-27_01299_01408 + - 2021.05.20.14.06.02_veh-27_01419_01600 + - 2021.05.20.14.06.02_veh-27_01611_01825 + - 2021.05.20.14.06.02_veh-27_01836_01924 + - 2021.05.20.14.06.02_veh-27_02006_02100 + - 2021.05.20.14.06.02_veh-27_02166_02354 + - 2021.05.20.14.06.02_veh-27_02365_03373 + - 2021.05.20.14.06.02_veh-27_03384_03470 + - 2021.05.20.14.06.02_veh-27_03517_03625 + - 2021.05.20.14.06.02_veh-27_03636_04050 + - 2021.05.20.14.06.02_veh-27_04186_04334 + - 2021.05.20.14.06.02_veh-27_04345_04439 + - 2021.05.20.14.06.02_veh-27_04451_04964 + - 2021.05.20.14.06.02_veh-27_04985_05118 + - 2021.05.20.14.06.02_veh-27_05129_05199 + - 2021.05.20.14.06.02_veh-27_05210_05286 + - 2021.05.20.14.22.28_veh-30_00065_00878 + - 2021.05.20.14.22.28_veh-30_00889_00953 + - 2021.05.20.14.22.28_veh-30_00964_01030 + - 2021.05.20.14.22.28_veh-30_01041_01328 + - 2021.05.20.14.22.28_veh-30_01339_01418 + - 2021.05.20.14.22.28_veh-30_01441_02199 + - 2021.05.20.14.22.28_veh-30_02231_02544 + - 2021.05.20.14.22.28_veh-30_02555_02726 + - 2021.05.20.14.22.28_veh-30_02737_03013 + - 2021.05.20.14.22.28_veh-30_03024_03187 + - 2021.05.20.14.22.28_veh-30_03198_03518 + - 2021.05.20.14.22.28_veh-30_03542_03748 + - 2021.05.20.14.22.28_veh-30_03759_03959 + - 2021.05.20.14.22.28_veh-30_03970_04458 + - 2021.05.20.14.22.28_veh-30_04580_04643 + - 2021.05.20.14.22.28_veh-30_04670_04800 + - 2021.05.20.14.22.28_veh-30_04811_04889 + - 2021.05.20.14.22.28_veh-30_04900_05035 + - 2021.05.20.14.22.28_veh-30_05050_05204 + - 2021.05.20.14.22.28_veh-30_05215_05510 + - 2021.05.20.14.22.28_veh-30_05521_05679 + - 2021.05.20.15.11.34_veh-25_00038_00213 + - 2021.05.20.15.11.34_veh-25_00224_00340 + - 2021.05.20.15.11.34_veh-25_00378_00457 + - 2021.05.20.15.11.34_veh-25_00468_00672 + - 2021.05.20.15.11.34_veh-25_00699_00797 + - 2021.05.20.15.11.34_veh-25_00808_01209 + - 2021.05.20.15.11.34_veh-25_01308_01667 + - 2021.05.20.15.11.34_veh-25_01678_02253 + - 2021.05.20.15.11.34_veh-25_02264_02397 + - 2021.05.20.15.11.34_veh-25_02436_02569 + - 2021.05.20.16.02.19_veh-36_00016_00284 + - 2021.05.20.16.02.19_veh-36_00310_00464 + - 2021.05.20.16.02.19_veh-36_00521_00684 + - 2021.05.20.16.02.19_veh-36_00733_00876 + - 2021.05.20.16.50.17_veh-30_00049_00312 + - 2021.05.20.16.50.17_veh-30_00339_00424 + - 2021.05.20.16.50.17_veh-30_00435_00606 + - 2021.05.20.16.50.17_veh-30_00617_00732 + - 2021.05.20.16.50.17_veh-30_00743_00841 + - 2021.05.20.16.50.17_veh-30_00852_00927 + - 2021.05.20.16.50.17_veh-30_00938_01089 + - 2021.05.20.16.50.17_veh-30_01144_01780 + - 2021.05.20.16.50.17_veh-30_01820_01922 + - 2021.05.20.16.50.17_veh-30_01933_02181 + - 2021.05.20.16.50.17_veh-30_02192_02277 + - 2021.05.20.16.50.17_veh-30_02288_02517 + - 2021.05.20.16.50.17_veh-30_02528_02610 + - 2021.05.20.16.50.17_veh-30_02621_02803 + - 2021.05.20.16.50.17_veh-30_02814_02944 + - 2021.05.20.16.50.17_veh-30_02969_03139 + - 2021.05.20.16.50.17_veh-30_03150_03706 + - 2021.05.20.16.50.17_veh-30_03738_03859 + - 2021.05.20.16.50.17_veh-30_03870_04051 + - 2021.05.20.16.50.17_veh-30_04062_04138 + - 2021.05.20.16.50.17_veh-30_04149_04252 + - 2021.05.20.16.50.17_veh-30_04364_04539 + - 2021.05.20.16.50.17_veh-30_04588_04672 + - 2021.05.20.16.50.17_veh-30_04683_04760 + - 2021.05.20.16.50.17_veh-30_04771_04888 + - 2021.05.20.16.50.17_veh-30_04993_05204 + - 2021.05.20.16.50.17_veh-30_05215_05521 + - 2021.05.20.16.52.07_veh-35_00037_00142 + - 2021.05.20.16.52.07_veh-35_00245_00440 + - 2021.05.20.16.52.07_veh-35_00531_00875 + - 2021.05.20.16.52.07_veh-35_00985_01101 + - 2021.05.20.16.52.07_veh-35_01112_01204 + - 2021.05.20.16.52.07_veh-35_01215_01444 + - 2021.05.20.16.52.07_veh-35_01455_01520 + - 2021.05.20.16.52.07_veh-35_01571_01635 + - 2021.05.20.16.52.07_veh-35_01658_01867 + - 2021.05.20.16.52.07_veh-35_01970_02106 + - 2021.05.20.16.52.07_veh-35_02117_02182 + - 2021.05.20.16.52.07_veh-35_02217_02290 + - 2021.05.20.16.52.07_veh-35_02301_02385 + - 2021.05.20.16.52.07_veh-35_02396_02471 + - 2021.05.20.16.52.07_veh-35_02482_02653 + - 2021.05.20.16.52.07_veh-35_02664_02749 + - 2021.05.20.16.52.07_veh-35_02783_02991 + - 2021.05.20.16.52.07_veh-35_03163_03335 + - 2021.05.20.16.52.07_veh-35_03356_03658 + - 2021.05.20.16.52.07_veh-35_03686_04247 + - 2021.05.20.16.52.07_veh-35_04267_04406 + - 2021.05.20.16.52.07_veh-35_04482_04621 + - 2021.05.20.16.52.07_veh-35_04632_04946 + - 2021.05.20.16.52.07_veh-35_05009_05105 + - 2021.05.20.16.57.20_veh-24_00115_00438 + - 2021.05.20.16.57.20_veh-24_00598_01149 + - 2021.05.20.16.57.20_veh-24_01160_02058 + - 2021.05.20.16.57.20_veh-24_02085_02422 + - 2021.05.20.16.57.20_veh-24_02497_02595 + - 2021.05.20.16.57.20_veh-24_02626_02770 + - 2021.05.20.17.01.50_veh-27_00005_00183 + - 2021.05.20.17.01.50_veh-27_00201_00766 + - 2021.05.20.17.01.50_veh-27_00797_01449 + - 2021.05.20.17.01.50_veh-27_01524_01608 + - 2021.05.20.17.01.50_veh-27_01619_01794 + - 2021.05.20.17.01.50_veh-27_01805_01912 + - 2021.05.20.17.01.50_veh-27_01923_02314 + - 2021.05.20.17.01.50_veh-27_02333_02539 + - 2021.05.20.17.01.50_veh-27_02550_03035 + - 2021.05.20.17.01.50_veh-27_03046_03210 + - 2021.05.20.17.01.50_veh-27_03257_03369 + - 2021.05.20.17.01.50_veh-27_03381_03480 + - 2021.05.20.17.01.50_veh-27_03491_03639 + - 2021.05.20.17.01.50_veh-27_03650_03819 + - 2021.05.20.17.01.50_veh-27_03830_03979 + - 2021.05.20.17.01.50_veh-27_03990_04155 + - 2021.05.20.17.01.50_veh-27_04166_04258 + - 2021.05.20.17.01.50_veh-27_04269_04360 + - 2021.05.20.17.01.50_veh-27_04371_04555 + - 2021.05.20.17.01.50_veh-27_04566_05189 + - 2021.05.20.17.51.23_veh-24_00005_00286 + - 2021.05.20.17.51.23_veh-24_00297_00464 + - 2021.05.20.17.51.23_veh-24_00491_00585 + - 2021.05.20.17.51.23_veh-24_00611_01072 + - 2021.05.20.17.51.23_veh-24_01083_01345 + - 2021.05.20.17.51.23_veh-24_01356_01444 + - 2021.05.20.17.51.23_veh-24_01455_01622 + - 2021.05.20.17.51.23_veh-24_01633_01796 + - 2021.05.20.17.51.23_veh-24_01807_02188 + - 2021.05.20.17.51.23_veh-24_02199_02456 + - 2021.05.20.17.51.23_veh-24_02467_02762 + - 2021.05.20.17.51.23_veh-24_02869_02964 + - 2021.05.20.17.51.23_veh-24_03001_03404 + - 2021.05.20.17.51.23_veh-24_03415_03700 + - 2021.05.20.17.51.23_veh-24_03743_04060 + - 2021.05.20.17.51.23_veh-24_04071_04206 + - 2021.05.20.18.55.21_veh-27_00005_00066 + - 2021.05.20.18.55.21_veh-27_00078_00249 + - 2021.05.20.18.55.21_veh-27_00339_00451 + - 2021.05.20.18.55.21_veh-27_00463_00697 + - 2021.05.20.18.55.21_veh-27_00749_00886 + - 2021.05.20.18.55.21_veh-27_00959_01120 + - 2021.05.20.18.55.21_veh-27_01131_01315 + - 2021.05.20.18.55.21_veh-27_01326_01548 + - 2021.05.20.18.55.21_veh-27_01559_01837 + - 2021.05.20.18.55.21_veh-27_01914_01978 + - 2021.05.20.18.55.21_veh-27_01989_02318 + - 2021.05.20.18.55.21_veh-27_02329_02643 + - 2021.05.20.18.55.21_veh-27_02655_02827 + - 2021.05.20.18.55.21_veh-27_02872_03300 + - 2021.05.20.18.55.21_veh-27_03323_03418 + - 2021.05.20.18.55.21_veh-27_03429_03634 + - 2021.05.20.18.55.21_veh-27_03736_03845 + - 2021.05.20.18.55.21_veh-27_03856_04314 + - 2021.05.20.18.55.21_veh-27_04336_04614 + - 2021.05.20.19.08.30_veh-35_00005_00091 + - 2021.05.20.19.08.30_veh-35_00102_00176 + - 2021.05.20.19.08.30_veh-35_00187_01040 + - 2021.05.20.19.08.30_veh-35_01051_01202 + - 2021.05.20.19.08.30_veh-35_01288_01419 + - 2021.05.20.19.08.30_veh-35_01430_02093 + - 2021.05.20.19.08.30_veh-35_02154_02310 + - 2021.05.20.19.08.30_veh-35_02321_02622 + - 2021.05.20.19.08.30_veh-35_02753_02916 + - 2021.05.20.19.08.30_veh-35_02927_03108 + - 2021.05.20.19.08.30_veh-35_03119_03366 + - 2021.05.20.19.10.19_veh-24_00032_00096 + - 2021.05.20.19.10.19_veh-24_00235_00717 + - 2021.05.20.19.10.19_veh-24_00728_00857 + - 2021.05.20.19.10.19_veh-24_00868_01109 + - 2021.05.20.19.10.19_veh-24_01120_01278 + - 2021.05.20.19.10.19_veh-24_01289_01475 + - 2021.05.20.19.10.19_veh-24_01486_01592 + - 2021.05.20.19.10.19_veh-24_01716_01810 + - 2021.05.20.19.10.19_veh-24_01821_01953 + - 2021.05.20.19.10.19_veh-24_02104_02221 + - 2021.05.20.19.10.19_veh-24_02232_02369 + - 2021.05.20.19.10.19_veh-24_02381_02446 + - 2021.05.20.19.10.19_veh-24_02458_02604 + - 2021.05.20.19.10.19_veh-24_02615_03305 + - 2021.05.20.19.10.19_veh-24_03316_03463 + - 2021.05.20.19.10.19_veh-24_03478_03554 + - 2021.05.20.19.10.19_veh-24_03565_03625 + - 2021.05.20.19.10.19_veh-24_03636_03745 + - 2021.05.20.19.10.19_veh-24_03791_03935 + - 2021.05.20.19.10.19_veh-24_03946_04065 + - 2021.05.20.19.10.19_veh-24_04076_04171 + - 2021.05.20.19.10.19_veh-24_04182_04245 + - 2021.05.20.19.10.19_veh-24_04269_04599 + - 2021.05.20.19.10.19_veh-24_04610_04757 + - 2021.05.20.19.10.19_veh-24_04768_04847 + - 2021.05.21.11.47.54_veh-27_00009_00100 + - 2021.05.21.11.47.54_veh-27_00111_00311 + - 2021.05.21.11.47.54_veh-27_00367_00548 + - 2021.05.21.11.47.54_veh-27_00559_01105 + - 2021.05.21.11.47.54_veh-27_01126_01283 + - 2021.05.21.11.47.54_veh-27_01377_01456 + - 2021.05.21.11.47.54_veh-27_01467_01529 + - 2021.05.21.11.47.54_veh-27_01593_01712 + - 2021.05.21.11.47.54_veh-27_01723_01842 + - 2021.05.21.11.47.54_veh-27_01853_01979 + - 2021.05.21.11.47.54_veh-27_01990_02201 + - 2021.05.21.11.47.54_veh-27_02212_02338 + - 2021.05.21.11.47.54_veh-27_02439_02631 + - 2021.05.21.11.47.54_veh-27_02709_02782 + - 2021.05.21.11.47.54_veh-27_02901_03098 + - 2021.05.21.11.47.54_veh-27_03109_03215 + - 2021.05.21.11.47.54_veh-27_03227_03327 + - 2021.05.21.11.47.54_veh-27_03407_03700 + - 2021.05.21.11.47.54_veh-27_03711_03895 + - 2021.05.21.11.47.54_veh-27_03943_04017 + - 2021.05.21.11.47.54_veh-27_04028_04180 + - 2021.05.21.11.47.54_veh-27_04191_04266 + - 2021.05.21.11.47.54_veh-27_04277_04381 + - 2021.05.21.11.47.54_veh-27_04392_04703 + - 2021.05.21.11.47.54_veh-27_04714_05083 + - 2021.05.21.11.47.54_veh-27_05094_05161 + - 2021.05.21.11.47.54_veh-27_05172_05416 + - 2021.05.21.11.47.54_veh-27_05427_05509 + - 2021.05.21.11.47.54_veh-27_05521_05708 + - 2021.05.21.11.47.54_veh-27_05719_05880 + - 2021.05.21.11.47.54_veh-27_05894_06171 + - 2021.05.21.11.47.54_veh-27_06232_06294 + - 2021.05.21.11.47.54_veh-27_06305_06546 + - 2021.05.21.12.42.04_veh-35_00098_00531 + - 2021.05.21.12.42.04_veh-35_00627_00984 + - 2021.05.21.12.42.04_veh-35_01016_01348 + - 2021.05.21.12.42.04_veh-35_01359_01536 + - 2021.05.21.12.42.04_veh-35_01601_01781 + - 2021.05.21.12.42.04_veh-35_01792_02076 + - 2021.05.21.12.42.04_veh-35_02087_02443 + - 2021.05.21.12.42.04_veh-35_02513_02799 + - 2021.05.21.12.42.04_veh-35_02810_02959 + - 2021.05.21.12.42.04_veh-35_02970_03179 + - 2021.05.21.12.42.04_veh-35_03190_03459 + - 2021.05.21.12.42.04_veh-35_03470_03774 + - 2021.05.21.12.42.04_veh-35_03785_04029 + - 2021.05.21.12.42.04_veh-35_04042_04151 + - 2021.05.21.12.42.04_veh-35_04166_04547 + - 2021.05.21.12.42.04_veh-35_04558_04646 + - 2021.05.21.12.42.04_veh-35_04657_05159 + - 2021.05.21.12.42.04_veh-35_05183_05360 + - 2021.05.21.13.15.49_veh-25_00087_01065 + - 2021.05.21.13.15.49_veh-25_01127_01441 + - 2021.05.21.13.15.49_veh-25_01452_01641 + - 2021.05.21.13.15.49_veh-25_01652_01791 + - 2021.05.21.13.15.49_veh-25_01803_01894 + - 2021.05.21.13.15.49_veh-25_01946_02137 + - 2021.05.21.13.15.49_veh-25_02148_02562 + - 2021.05.21.13.15.49_veh-25_02597_02677 + - 2021.05.21.13.15.49_veh-25_02688_02810 + - 2021.05.21.13.15.49_veh-25_02885_03042 + - 2021.05.21.13.15.49_veh-25_03128_03398 + - 2021.05.21.13.15.49_veh-25_03409_03547 + - 2021.05.21.13.15.49_veh-25_03558_04574 + - 2021.05.21.13.15.49_veh-25_04605_04803 + - 2021.05.21.13.15.49_veh-25_04814_04916 + - 2021.05.21.13.15.49_veh-25_04927_05174 + - 2021.05.21.13.41.26_veh-12_00005_00150 + - 2021.05.21.13.41.26_veh-12_00161_00720 + - 2021.05.21.13.41.26_veh-12_00731_01747 + - 2021.05.21.13.41.26_veh-12_01758_01894 + - 2021.05.21.13.41.26_veh-12_01917_02165 + - 2021.05.21.13.41.26_veh-12_02176_02562 + - 2021.05.21.13.41.26_veh-12_02573_02780 + - 2021.05.21.13.41.26_veh-12_02791_03519 + - 2021.05.21.13.41.26_veh-12_03530_03666 + - 2021.05.21.13.41.26_veh-12_03734_03812 + - 2021.05.21.13.41.26_veh-12_03823_03953 + - 2021.05.21.13.41.26_veh-12_03964_04676 + - 2021.05.21.13.41.26_veh-12_04687_04835 + - 2021.05.21.13.48.27_veh-27_00032_00184 + - 2021.05.21.13.48.27_veh-27_00221_01058 + - 2021.05.21.13.48.27_veh-27_01069_01299 + - 2021.05.21.13.48.27_veh-27_01370_01449 + - 2021.05.21.13.48.27_veh-27_01539_01873 + - 2021.05.21.13.48.27_veh-27_01899_02107 + - 2021.05.21.13.48.27_veh-27_02118_02259 + - 2021.05.21.13.48.27_veh-27_02416_02533 + - 2021.05.21.13.48.27_veh-27_02588_02990 + - 2021.05.21.13.48.27_veh-27_03001_03072 + - 2021.05.21.13.48.27_veh-27_03119_03301 + - 2021.05.21.13.48.27_veh-27_03352_03425 + - 2021.05.21.13.48.27_veh-27_03436_03574 + - 2021.05.21.13.48.27_veh-27_03585_03791 + - 2021.05.21.13.48.27_veh-27_03802_04080 + - 2021.05.21.13.48.27_veh-27_04151_04501 + - 2021.05.21.13.48.27_veh-27_04512_05048 + - 2021.05.21.13.48.27_veh-27_05059_05456 + - 2021.05.21.14.38.10_veh-35_00005_00092 + - 2021.05.21.14.38.10_veh-35_00103_00264 + - 2021.05.21.14.38.10_veh-35_00340_00766 + - 2021.05.21.14.38.10_veh-35_00810_01480 + - 2021.05.21.14.38.10_veh-35_01491_01721 + - 2021.05.21.14.38.10_veh-35_01780_01867 + - 2021.05.21.14.38.10_veh-35_01888_01979 + - 2021.05.21.14.38.10_veh-35_02049_02170 + - 2021.05.21.14.38.10_veh-35_02181_02588 + - 2021.05.21.14.38.10_veh-35_02620_02740 + - 2021.05.21.14.38.10_veh-35_02751_02818 + - 2021.05.21.14.38.10_veh-35_02829_03076 + - 2021.05.21.14.38.10_veh-35_03087_03194 + - 2021.05.21.14.38.10_veh-35_03280_03513 + - 2021.05.21.14.38.10_veh-35_03524_04200 + - 2021.05.21.14.38.10_veh-35_04218_04410 + - 2021.05.21.14.38.10_veh-35_04421_04539 + - 2021.05.21.14.38.10_veh-35_04646_04892 + - 2021.05.21.14.38.10_veh-35_04989_05123 + - 2021.05.21.14.55.23_veh-25_00043_00130 + - 2021.05.21.14.55.23_veh-25_00141_00275 + - 2021.05.21.14.55.23_veh-25_00286_00553 + - 2021.05.21.14.55.23_veh-25_00564_00832 + - 2021.05.21.14.55.23_veh-25_01102_01185 + - 2021.05.21.14.55.23_veh-25_01196_01397 + - 2021.05.21.14.55.23_veh-25_01408_02030 + - 2021.05.21.14.55.23_veh-25_02061_02278 + - 2021.05.21.14.55.23_veh-25_02289_02403 + - 2021.05.21.14.55.23_veh-25_02414_02570 + - 2021.05.21.14.55.23_veh-25_02583_02673 + - 2021.05.21.14.55.23_veh-25_02787_02961 + - 2021.05.21.14.55.23_veh-25_02972_03249 + - 2021.05.21.14.55.23_veh-25_03260_03367 + - 2021.05.21.14.55.23_veh-25_03378_03465 + - 2021.05.21.14.55.23_veh-25_03578_03715 + - 2021.05.21.14.55.23_veh-25_03726_03841 + - 2021.05.21.14.55.23_veh-25_03852_04031 + - 2021.05.21.14.55.23_veh-25_04042_04690 + - 2021.05.21.14.55.23_veh-25_04706_04824 + - 2021.05.21.17.47.35_veh-35_00016_00170 + - 2021.05.21.17.47.35_veh-35_00181_00278 + - 2021.05.21.17.47.35_veh-35_00289_00574 + - 2021.05.21.17.47.35_veh-35_00585_00825 + - 2021.05.21.17.47.35_veh-35_00836_00902 + - 2021.05.21.17.47.35_veh-35_00913_01189 + - 2021.05.21.17.47.35_veh-35_01200_01415 + - 2021.05.21.17.47.35_veh-35_01444_01719 + - 2021.05.21.17.47.35_veh-35_01791_01995 + - 2021.05.21.17.47.35_veh-35_02046_02478 + - 2021.05.21.17.47.35_veh-35_02526_02685 + - 2021.05.21.17.47.35_veh-35_02696_03221 + - 2021.05.21.17.47.35_veh-35_03232_03616 + - 2021.05.21.17.47.35_veh-35_03627_03743 + - 2021.05.21.17.47.35_veh-35_03754_03991 + - 2021.05.21.17.47.35_veh-35_04002_04117 + - 2021.05.21.17.47.35_veh-35_04128_04433 + - 2021.05.21.17.47.35_veh-35_04444_04681 + - 2021.05.21.17.47.35_veh-35_04692_04906 + - 2021.05.21.18.27.53_veh-12_00029_00233 + - 2021.05.21.18.27.53_veh-12_00244_00485 + - 2021.05.21.18.27.53_veh-12_00496_00594 + - 2021.05.21.18.27.53_veh-12_00605_00783 + - 2021.05.21.18.27.53_veh-12_00813_01103 + - 2021.05.21.18.27.53_veh-12_01156_01346 + - 2021.05.21.18.27.53_veh-12_01357_01471 + - 2021.05.21.18.27.53_veh-12_01566_01795 + - 2021.05.21.18.27.53_veh-12_01806_01918 + - 2021.05.21.18.27.53_veh-12_01932_02075 + - 2021.05.21.19.28.34_veh-12_00057_00574 + - 2021.05.21.19.28.34_veh-12_00585_00820 + - 2021.05.21.19.28.34_veh-12_00831_00994 + - 2021.05.21.19.28.34_veh-12_01034_01521 + - 2021.05.21.19.28.34_veh-12_01532_01660 + - 2021.05.21.19.28.34_veh-12_01671_02192 + - 2021.05.21.19.28.34_veh-12_02203_02723 + - 2021.05.21.19.28.34_veh-12_02734_03168 + - 2021.05.21.19.28.34_veh-12_03179_03351 + - 2021.05.21.19.28.34_veh-12_03530_03597 + - 2021.05.21.19.28.34_veh-12_03608_03778 + - 2021.05.21.19.28.34_veh-12_03789_03979 + - 2021.05.21.19.37.23_veh-27_00163_00529 + - 2021.05.21.19.37.23_veh-27_00540_01163 + - 2021.05.21.19.37.23_veh-27_01174_01426 + - 2021.05.21.19.37.23_veh-27_01437_01528 + - 2021.05.21.19.37.23_veh-27_01539_01606 + - 2021.05.21.19.37.23_veh-27_01617_01878 + - 2021.05.21.19.37.23_veh-27_01889_02017 + - 2021.05.21.19.37.23_veh-27_02028_02093 + - 2021.05.21.19.37.23_veh-27_02104_02371 + - 2021.05.21.19.37.23_veh-27_02408_02919 + - 2021.05.21.19.38.21_veh-25_00005_00328 + - 2021.05.21.19.38.21_veh-25_00400_00599 + - 2021.05.21.19.38.21_veh-25_00636_00875 + - 2021.05.21.19.38.21_veh-25_00886_01004 + - 2021.05.21.19.38.21_veh-25_01050_01374 + - 2021.05.21.19.38.21_veh-25_01385_01539 + - 2021.05.21.19.38.21_veh-25_01550_01628 + - 2021.05.21.19.38.21_veh-25_01655_01776 + - 2021.05.21.19.38.21_veh-25_01787_02114 + - 2021.05.21.19.38.21_veh-25_02125_02279 + - 2021.05.21.19.38.21_veh-25_02290_02371 + - 2021.05.21.19.38.21_veh-25_02468_02544 + - 2021.05.21.19.38.21_veh-25_02555_02895 + - 2021.05.24.12.22.13_veh-47_00030_00234 + - 2021.05.24.12.22.13_veh-47_00245_00582 + - 2021.05.24.12.22.13_veh-47_00615_00779 + - 2021.05.24.12.22.13_veh-47_00790_00860 + - 2021.05.24.12.22.13_veh-47_00871_00946 + - 2021.05.24.12.22.13_veh-47_01063_01184 + - 2021.05.24.12.22.13_veh-47_01195_01384 + - 2021.05.24.12.22.13_veh-47_01395_01569 + - 2021.05.24.12.22.13_veh-47_01618_01944 + - 2021.05.24.12.22.13_veh-47_01976_02139 + - 2021.05.24.12.22.13_veh-47_02209_02330 + - 2021.05.24.12.22.13_veh-47_02361_02550 + - 2021.05.24.12.22.13_veh-47_02595_02658 + - 2021.05.24.12.22.13_veh-47_02669_02843 + - 2021.05.24.12.22.13_veh-47_02854_02928 + - 2021.05.24.12.22.13_veh-47_02940_03068 + - 2021.05.24.12.22.13_veh-47_03079_03233 + - 2021.05.24.12.22.13_veh-47_03244_03306 + - 2021.05.24.12.22.13_veh-47_03317_03660 + - 2021.05.24.12.22.13_veh-47_03671_03927 + - 2021.05.24.12.22.13_veh-47_03939_04145 + - 2021.05.24.12.22.13_veh-47_04156_04271 + - 2021.05.24.12.22.13_veh-47_04351_04546 + - 2021.05.24.12.22.13_veh-47_04557_04825 + - 2021.05.24.12.22.13_veh-47_04878_05014 + - 2021.05.24.12.22.13_veh-47_05025_05275 + - 2021.05.24.12.28.29_veh-12_00011_00185 + - 2021.05.24.12.28.29_veh-12_00196_00324 + - 2021.05.24.12.28.29_veh-12_00345_00437 + - 2021.05.24.12.28.29_veh-12_00448_00832 + - 2021.05.24.12.28.29_veh-12_00843_01169 + - 2021.05.24.12.28.29_veh-12_01277_01429 + - 2021.05.24.12.28.29_veh-12_01440_01806 + - 2021.05.24.12.28.29_veh-12_01818_02031 + - 2021.05.24.12.28.29_veh-12_02092_02332 + - 2021.05.24.12.28.29_veh-12_02343_02418 + - 2021.05.24.12.28.29_veh-12_02429_02898 + - 2021.05.24.12.28.29_veh-12_02931_03071 + - 2021.05.24.12.28.29_veh-12_03082_03202 + - 2021.05.24.12.28.29_veh-12_03213_03330 + - 2021.05.24.12.28.29_veh-12_03341_03405 + - 2021.05.24.12.28.29_veh-12_03416_03527 + - 2021.05.24.12.28.29_veh-12_03538_03733 + - 2021.05.24.12.28.29_veh-12_03813_04040 + - 2021.05.24.12.28.29_veh-12_04051_04235 + - 2021.05.24.12.28.29_veh-12_04246_04420 + - 2021.05.24.12.28.29_veh-12_04432_04576 + - 2021.05.24.12.28.29_veh-12_04587_04791 + - 2021.05.24.12.28.29_veh-12_04802_04907 + - 2021.05.24.12.28.29_veh-12_05017_05313 + - 2021.05.24.13.17.29_veh-25_00066_00254 + - 2021.05.24.13.17.29_veh-25_00276_00497 + - 2021.05.24.13.17.29_veh-25_00508_00997 + - 2021.05.24.13.17.29_veh-25_01008_01140 + - 2021.05.24.13.17.29_veh-25_01255_01324 + - 2021.05.24.13.17.29_veh-25_01406_01497 + - 2021.05.24.13.17.29_veh-25_01508_01612 + - 2021.05.24.13.17.29_veh-25_01623_01776 + - 2021.05.24.13.17.29_veh-25_01826_02022 + - 2021.05.24.13.17.29_veh-25_02052_02131 + - 2021.05.24.13.17.29_veh-25_02153_02543 + - 2021.05.24.13.17.29_veh-25_02602_02920 + - 2021.05.24.13.17.29_veh-25_02931_03001 + - 2021.05.24.13.17.29_veh-25_03012_03073 + - 2021.05.24.13.17.29_veh-25_03084_03314 + - 2021.05.24.13.17.29_veh-25_03378_03440 + - 2021.05.24.13.17.29_veh-25_03455_03566 + - 2021.05.24.13.17.29_veh-25_03577_03693 + - 2021.05.24.13.17.29_veh-25_03704_03821 + - 2021.05.24.13.17.29_veh-25_03832_03991 + - 2021.05.24.13.17.29_veh-25_04002_04080 + - 2021.05.24.13.17.29_veh-25_04091_04210 + - 2021.05.24.13.17.29_veh-25_04234_04304 + - 2021.05.24.13.17.29_veh-25_04315_04516 + - 2021.05.24.13.17.29_veh-25_04539_04831 + - 2021.05.24.13.17.29_veh-25_04842_04944 + - 2021.05.24.13.17.29_veh-25_04971_05075 + - 2021.05.24.13.17.29_veh-25_05086_05394 + - 2021.05.24.13.17.29_veh-25_05405_05475 + - 2021.05.24.13.18.46_veh-30_00016_00265 + - 2021.05.24.13.18.46_veh-30_00277_00381 + - 2021.05.24.13.18.46_veh-30_00403_00573 + - 2021.05.24.13.18.46_veh-30_00584_00890 + - 2021.05.24.13.18.46_veh-30_00901_01355 + - 2021.05.24.13.18.46_veh-30_01366_01448 + - 2021.05.24.13.18.46_veh-30_01459_01589 + - 2021.05.24.13.18.46_veh-30_01600_01714 + - 2021.05.24.13.18.46_veh-30_01725_02058 + - 2021.05.24.13.18.46_veh-30_02069_02204 + - 2021.05.24.13.18.46_veh-30_02215_02384 + - 2021.05.24.14.25.02_veh-47_00005_00077 + - 2021.05.24.14.25.02_veh-47_00088_00269 + - 2021.05.24.14.25.02_veh-47_00280_00353 + - 2021.05.24.14.25.02_veh-47_00364_00470 + - 2021.05.24.14.25.02_veh-47_00574_00665 + - 2021.05.24.14.25.02_veh-47_00676_00964 + - 2021.05.24.14.25.02_veh-47_00975_01374 + - 2021.05.24.14.25.02_veh-47_01462_01588 + - 2021.05.24.14.25.02_veh-47_01663_01887 + - 2021.05.24.14.25.02_veh-47_01900_01995 + - 2021.05.24.14.25.02_veh-47_02006_02117 + - 2021.05.24.14.25.02_veh-47_02220_03099 + - 2021.05.24.14.25.02_veh-47_03110_03269 + - 2021.05.24.14.25.02_veh-47_03305_03459 + - 2021.05.24.14.25.02_veh-47_03538_04059 + - 2021.05.24.14.25.02_veh-47_04070_04209 + - 2021.05.24.14.25.02_veh-47_04220_04315 + - 2021.05.24.14.25.02_veh-47_04326_04754 + - 2021.05.24.14.25.02_veh-47_04765_04914 + - 2021.05.24.14.25.02_veh-47_05057_05154 + - 2021.05.24.14.25.02_veh-47_05246_05339 + - 2021.05.24.14.31.31_veh-30_00005_00097 + - 2021.05.24.14.31.31_veh-30_00108_00315 + - 2021.05.24.14.31.31_veh-30_00375_00578 + - 2021.05.24.14.31.31_veh-30_00589_00857 + - 2021.05.24.14.31.31_veh-30_00973_01071 + - 2021.05.24.14.31.31_veh-30_01082_01627 + - 2021.05.24.14.31.31_veh-30_01638_01733 + - 2021.05.24.14.31.31_veh-30_01744_01826 + - 2021.05.24.14.31.31_veh-30_01890_01974 + - 2021.05.24.15.41.29_veh-25_00005_00366 + - 2021.05.24.15.41.29_veh-25_00377_00524 + - 2021.05.24.15.41.29_veh-25_00535_00833 + - 2021.05.24.15.41.29_veh-25_00844_01006 + - 2021.05.24.15.41.29_veh-25_01116_01179 + - 2021.05.24.15.41.29_veh-25_01190_01422 + - 2021.05.24.15.41.29_veh-25_01443_01639 + - 2021.05.24.15.41.29_veh-25_01650_01739 + - 2021.05.24.15.41.29_veh-25_01750_01867 + - 2021.05.24.15.41.29_veh-25_01944_02016 + - 2021.05.24.15.41.29_veh-25_02027_02121 + - 2021.05.24.15.41.29_veh-25_02209_02497 + - 2021.05.24.15.41.29_veh-25_02508_02717 + - 2021.05.24.15.41.29_veh-25_02728_02900 + - 2021.05.24.15.41.29_veh-25_02969_03547 + - 2021.05.24.15.41.29_veh-25_03558_03939 + - 2021.05.24.15.41.29_veh-25_03996_04859 + - 2021.05.24.15.41.29_veh-25_04892_04956 + - 2021.05.24.15.41.29_veh-25_04967_05074 + - 2021.05.24.15.41.29_veh-25_05085_05171 + - 2021.05.24.15.41.29_veh-25_05182_05352 + - 2021.05.24.16.02.47_veh-35_00036_00138 + - 2021.05.24.16.02.47_veh-35_00225_00336 + - 2021.05.24.16.02.47_veh-35_00347_00433 + - 2021.05.24.16.02.47_veh-35_00496_00861 + - 2021.05.24.16.02.47_veh-35_00898_01165 + - 2021.05.24.16.02.47_veh-35_01176_01268 + - 2021.05.24.16.02.47_veh-35_01291_01905 + - 2021.05.24.16.02.47_veh-35_01916_02143 + - 2021.05.24.16.02.47_veh-35_02154_02289 + - 2021.05.24.16.02.47_veh-35_02300_02418 + - 2021.05.24.16.02.47_veh-35_02429_02671 + - 2021.05.24.16.02.47_veh-35_02747_03030 + - 2021.05.24.16.02.47_veh-35_03041_03301 + - 2021.05.24.16.02.47_veh-35_03312_04244 + - 2021.05.24.16.02.47_veh-35_04255_04473 + - 2021.05.24.16.02.47_veh-35_04484_04615 + - 2021.05.24.16.02.47_veh-35_04626_04734 + - 2021.05.24.16.02.47_veh-35_04745_04910 + - 2021.05.24.16.26.01_veh-30_00011_00215 + - 2021.05.24.16.26.01_veh-30_00226_00990 + - 2021.05.24.16.26.01_veh-30_01127_01451 + - 2021.05.24.16.26.01_veh-30_01462_02062 + - 2021.05.24.16.26.01_veh-30_02119_02200 + - 2021.05.24.16.26.01_veh-30_02211_02518 + - 2021.05.24.16.26.01_veh-30_02584_02797 + - 2021.05.24.16.26.01_veh-30_02808_02970 + - 2021.05.24.16.26.01_veh-30_02981_03555 + - 2021.05.24.16.26.01_veh-30_03566_03950 + - 2021.05.24.16.26.01_veh-30_04016_04140 + - 2021.05.24.16.26.01_veh-30_04151_04261 + - 2021.05.24.16.26.01_veh-30_04272_04444 + - 2021.05.24.16.26.01_veh-30_04506_04720 + - 2021.05.24.16.26.01_veh-30_04731_04855 + - 2021.05.24.16.26.01_veh-30_04985_05111 + - 2021.05.24.16.26.01_veh-30_05139_05276 + - 2021.05.24.17.21.29_veh-25_00005_00466 + - 2021.05.24.17.21.29_veh-25_00477_00675 + - 2021.05.24.17.21.29_veh-25_00712_01023 + - 2021.05.24.17.21.29_veh-25_01037_01431 + - 2021.05.24.17.21.29_veh-25_01443_01564 + - 2021.05.24.17.21.29_veh-25_01755_01839 + - 2021.05.24.17.21.29_veh-25_01904_01970 + - 2021.05.24.17.21.29_veh-25_01997_02154 + - 2021.05.24.17.21.29_veh-25_02165_02240 + - 2021.05.24.17.21.29_veh-25_02252_02356 + - 2021.05.24.17.21.29_veh-25_02368_02669 + - 2021.05.24.17.21.29_veh-25_02900_02963 + - 2021.05.24.17.21.29_veh-25_02974_03189 + - 2021.05.24.17.21.29_veh-25_03234_03412 + - 2021.05.24.17.21.29_veh-25_03423_03801 + - 2021.05.24.17.21.29_veh-25_03877_03943 + - 2021.05.24.17.21.29_veh-25_03954_04024 + - 2021.05.24.17.21.29_veh-25_04035_04117 + - 2021.05.24.17.21.29_veh-25_04149_04324 + - 2021.05.24.17.21.29_veh-25_04338_04487 + - 2021.05.24.17.21.29_veh-25_04498_04728 + - 2021.05.24.17.31.37_veh-27_00040_00244 + - 2021.05.24.17.31.37_veh-27_00255_00347 + - 2021.05.24.17.31.37_veh-27_00358_00429 + - 2021.05.24.17.31.37_veh-27_00440_00689 + - 2021.05.24.17.31.37_veh-27_00700_00869 + - 2021.05.24.17.31.37_veh-27_00880_00986 + - 2021.05.24.17.31.37_veh-27_01025_01092 + - 2021.05.24.17.31.37_veh-27_01159_02084 + - 2021.05.24.17.31.37_veh-27_02095_02524 + - 2021.05.24.17.31.37_veh-27_02554_03449 + - 2021.05.24.17.57.11_veh-35_00005_00071 + - 2021.05.24.17.57.11_veh-35_00085_00250 + - 2021.05.24.17.57.11_veh-35_00261_00570 + - 2021.05.24.17.57.11_veh-35_00709_00871 + - 2021.05.24.17.57.11_veh-35_00972_01219 + - 2021.05.24.17.57.11_veh-35_01289_01499 + - 2021.05.24.17.57.11_veh-35_01510_01615 + - 2021.05.24.17.57.11_veh-35_01626_01704 + - 2021.05.24.17.57.11_veh-35_01715_01832 + - 2021.05.24.17.57.11_veh-35_01906_01975 + - 2021.05.24.17.57.11_veh-35_01986_02255 + - 2021.05.24.17.57.11_veh-35_02266_02338 + - 2021.05.24.17.57.11_veh-35_02356_02731 + - 2021.05.24.17.57.11_veh-35_02742_02829 + - 2021.05.24.17.57.11_veh-35_02840_03058 + - 2021.05.24.17.57.11_veh-35_03069_03379 + - 2021.05.24.17.57.11_veh-35_03404_03523 + - 2021.05.24.17.57.11_veh-35_03534_03808 + - 2021.05.24.17.57.11_veh-35_03819_04068 + - 2021.05.24.17.57.11_veh-35_04079_04173 + - 2021.05.24.17.57.11_veh-35_04185_04503 + - 2021.05.24.17.57.11_veh-35_04514_04588 + - 2021.05.24.17.57.11_veh-35_04599_04888 + - 2021.05.24.17.57.11_veh-35_04906_05064 + - 2021.05.24.17.57.11_veh-35_05075_05292 + - 2021.05.24.17.57.11_veh-35_05304_05429 + - 2021.05.24.17.57.11_veh-35_05474_05595 + - 2021.05.24.17.57.11_veh-35_05625_05781 + - 2021.05.24.18.54.30_veh-25_00020_00195 + - 2021.05.24.18.54.30_veh-25_00206_00313 + - 2021.05.24.18.54.30_veh-25_00324_00389 + - 2021.05.24.18.54.30_veh-25_00400_00850 + - 2021.05.24.18.54.30_veh-25_00861_01060 + - 2021.05.24.18.54.30_veh-25_01071_01234 + - 2021.05.24.18.54.30_veh-25_01245_01314 + - 2021.05.24.18.54.30_veh-25_01325_01454 + - 2021.05.24.18.54.30_veh-25_01465_01556 + - 2021.05.24.18.54.30_veh-25_01567_01680 + - 2021.05.24.18.54.30_veh-25_01691_01824 + - 2021.05.24.18.54.30_veh-25_01835_01962 + - 2021.05.24.18.54.30_veh-25_01973_02269 + - 2021.05.24.18.54.30_veh-25_02290_02855 + - 2021.05.24.18.54.30_veh-25_02866_02981 + - 2021.05.24.18.54.30_veh-25_02992_03242 + - 2021.05.24.18.54.30_veh-25_03253_03350 + - 2021.05.24.18.54.30_veh-25_03361_03558 + - 2021.05.24.18.54.30_veh-25_03569_03900 + - 2021.05.24.18.54.30_veh-25_03923_04077 + - 2021.05.24.18.54.30_veh-25_04157_04227 + - 2021.05.24.18.54.30_veh-25_04291_04376 + - 2021.05.24.18.54.30_veh-25_04387_04494 + - 2021.05.24.18.54.30_veh-25_04505_05004 + - 2021.05.24.18.54.30_veh-25_05015_05188 + - 2021.05.24.18.54.30_veh-25_05205_05324 + - 2021.05.24.20.15.16_veh-27_00183_00377 + - 2021.05.24.20.15.16_veh-27_00469_00553 + - 2021.05.24.20.15.16_veh-27_00592_00684 + - 2021.05.24.20.15.16_veh-27_00695_00851 + - 2021.05.24.20.15.16_veh-27_00986_01402 + - 2021.05.24.20.15.16_veh-27_01413_01483 + - 2021.05.24.20.15.16_veh-27_01513_01574 + - 2021.05.24.20.15.16_veh-27_01585_01692 + - 2021.05.24.20.15.16_veh-27_01893_01958 + - 2021.05.26.12.22.14_veh-38_00016_00393 + - 2021.05.26.12.22.14_veh-38_00404_00630 + - 2021.05.26.12.22.14_veh-38_00641_00797 + - 2021.05.26.12.22.14_veh-38_00808_00982 + - 2021.05.26.12.22.14_veh-38_00993_01175 + - 2021.05.26.12.22.14_veh-38_01186_01302 + - 2021.05.26.12.22.14_veh-38_01313_01485 + - 2021.05.26.12.22.14_veh-38_01506_01577 + - 2021.05.26.12.22.14_veh-38_01588_02037 + - 2021.05.26.12.22.14_veh-38_02083_02227 + - 2021.05.26.12.22.14_veh-38_02238_02482 + - 2021.05.26.12.22.14_veh-38_02518_02627 + - 2021.05.26.12.22.14_veh-38_02638_03017 + - 2021.05.26.12.22.14_veh-38_03028_03126 + - 2021.05.26.12.22.14_veh-38_03297_03365 + - 2021.05.26.12.22.14_veh-38_03398_03577 + - 2021.05.26.12.22.14_veh-38_03613_03720 + - 2021.05.26.12.22.14_veh-38_03731_03934 + - 2021.05.26.12.22.14_veh-38_03989_04357 + - 2021.05.26.12.22.14_veh-38_04368_04740 + - 2021.05.26.12.22.14_veh-38_04751_04852 + - 2021.05.26.12.22.14_veh-38_04863_05596 + - 2021.05.26.12.22.44_veh-25_00016_00287 + - 2021.05.26.12.22.44_veh-25_00320_00615 + - 2021.05.26.12.22.44_veh-25_00672_01038 + - 2021.05.26.12.22.44_veh-25_01049_01114 + - 2021.05.26.12.22.44_veh-25_01153_01222 + - 2021.05.26.12.22.44_veh-25_01305_01491 + - 2021.05.26.12.22.44_veh-25_01502_01655 + - 2021.05.26.12.22.44_veh-25_01666_02495 + - 2021.05.26.12.22.44_veh-25_02568_02648 + - 2021.05.26.12.22.44_veh-25_02659_03161 + - 2021.05.26.12.22.44_veh-25_03211_03412 + - 2021.05.26.12.22.44_veh-25_03470_03559 + - 2021.05.26.12.22.44_veh-25_03570_03735 + - 2021.05.26.12.22.44_veh-25_03844_04155 + - 2021.05.26.12.22.44_veh-25_04166_04345 + - 2021.05.26.12.22.44_veh-25_04356_04459 + - 2021.05.26.12.22.44_veh-25_04517_04759 + - 2021.05.26.12.22.44_veh-25_04828_05347 + - 2021.05.26.12.29.50_veh-35_00044_00332 + - 2021.05.26.12.29.50_veh-35_00343_00455 + - 2021.05.26.12.29.50_veh-35_00501_00834 + - 2021.05.26.12.29.50_veh-35_00876_00964 + - 2021.05.26.12.29.50_veh-35_00975_01707 + - 2021.05.26.12.29.50_veh-35_01797_01954 + - 2021.05.26.12.29.50_veh-35_01967_02442 + - 2021.05.26.12.29.50_veh-35_02576_02958 + - 2021.05.26.12.29.50_veh-35_02969_03145 + - 2021.05.26.12.29.50_veh-35_03156_03286 + - 2021.05.26.12.29.50_veh-35_03323_03481 + - 2021.05.26.12.29.50_veh-35_03513_03771 + - 2021.05.26.12.29.50_veh-35_03924_04210 + - 2021.05.26.12.29.50_veh-35_04221_04344 + - 2021.05.26.12.29.50_veh-35_04440_04666 + - 2021.05.26.12.29.50_veh-35_04742_04897 + - 2021.05.26.12.29.50_veh-35_04944_05074 + - 2021.05.26.12.29.50_veh-35_05136_05246 + - 2021.05.26.12.29.50_veh-35_05257_05401 + - 2021.05.26.12.38.15_veh-47_00006_00088 + - 2021.05.26.12.38.15_veh-47_00174_00399 + - 2021.05.26.12.38.15_veh-47_00410_00693 + - 2021.05.26.12.38.15_veh-47_00730_00795 + - 2021.05.26.12.38.15_veh-47_00816_00908 + - 2021.05.26.12.38.15_veh-47_00975_01056 + - 2021.05.26.12.38.15_veh-47_01082_01688 + - 2021.05.26.12.38.15_veh-47_01699_01991 + - 2021.05.26.12.38.15_veh-47_02002_02100 + - 2021.05.26.12.38.15_veh-47_02111_02329 + - 2021.05.26.12.38.15_veh-47_02350_02484 + - 2021.05.26.12.38.15_veh-47_02495_02633 + - 2021.05.26.12.38.15_veh-47_02644_02760 + - 2021.05.26.12.38.15_veh-47_02839_03079 + - 2021.05.26.12.38.15_veh-47_03090_04078 + - 2021.05.26.12.38.15_veh-47_04187_04443 + - 2021.05.26.12.38.15_veh-47_04512_04663 + - 2021.05.26.12.38.15_veh-47_04736_04797 + - 2021.05.26.12.38.15_veh-47_04808_04970 + - 2021.05.26.12.38.15_veh-47_04981_05117 + - 2021.05.26.12.38.15_veh-47_05189_05264 + - 2021.05.26.13.02.21_veh-30_00005_00298 + - 2021.05.26.13.02.21_veh-30_00309_00459 + - 2021.05.26.13.02.21_veh-30_00470_00555 + - 2021.05.26.13.02.21_veh-30_00642_00783 + - 2021.05.26.13.02.21_veh-30_00794_00874 + - 2021.05.26.13.02.21_veh-30_00885_01150 + - 2021.05.26.13.02.21_veh-30_01161_01296 + - 2021.05.26.13.02.21_veh-30_01323_01391 + - 2021.05.26.13.02.21_veh-30_01402_02007 + - 2021.05.26.13.02.21_veh-30_02018_02283 + - 2021.05.26.13.02.21_veh-30_02294_02455 + - 2021.05.26.13.02.21_veh-30_02466_02685 + - 2021.05.26.13.02.21_veh-30_02696_02975 + - 2021.05.26.13.02.21_veh-30_02986_03058 + - 2021.05.26.13.02.21_veh-30_03069_03581 + - 2021.05.26.13.02.21_veh-30_03593_03660 + - 2021.05.26.13.02.21_veh-30_03671_03801 + - 2021.05.26.13.02.21_veh-30_03812_03938 + - 2021.05.26.13.02.21_veh-30_03949_04110 + - 2021.05.26.13.02.21_veh-30_04127_04299 + - 2021.05.26.13.02.21_veh-30_04310_04581 + - 2021.05.26.13.02.21_veh-30_04622_04917 + - 2021.05.26.13.02.21_veh-30_04928_05182 + - 2021.05.26.13.02.21_veh-30_05193_05371 + - 2021.05.26.14.10.09_veh-38_00073_00224 + - 2021.05.26.14.10.09_veh-38_00330_00431 + - 2021.05.26.14.10.09_veh-38_00442_01034 + - 2021.05.26.14.10.09_veh-38_01250_01406 + - 2021.05.26.14.10.09_veh-38_01486_01577 + - 2021.05.26.14.10.09_veh-38_01605_01769 + - 2021.05.26.14.10.09_veh-38_01796_01922 + - 2021.05.26.14.10.09_veh-38_01933_02010 + - 2021.05.26.14.10.09_veh-38_02047_02113 + - 2021.05.26.14.10.09_veh-38_02124_02259 + - 2021.05.26.14.10.09_veh-38_02379_02633 + - 2021.05.26.14.10.09_veh-38_02670_02841 + - 2021.05.26.14.10.09_veh-38_02852_03674 + - 2021.05.26.14.10.09_veh-38_03685_03828 + - 2021.05.26.14.10.09_veh-38_03887_04102 + - 2021.05.26.14.10.09_veh-38_04113_04344 + - 2021.05.26.14.10.09_veh-38_04435_04651 + - 2021.05.26.14.10.09_veh-38_04662_04761 + - 2021.05.26.14.10.09_veh-38_04785_04953 + - 2021.05.26.14.10.09_veh-38_04964_05185 + - 2021.05.26.14.10.09_veh-38_05319_05440 + - 2021.05.26.14.20.58_veh-35_00115_00274 + - 2021.05.26.14.20.58_veh-35_00323_00606 + - 2021.05.26.14.20.58_veh-35_00680_00963 + - 2021.05.26.14.20.58_veh-35_00974_01164 + - 2021.05.26.14.20.58_veh-35_01175_01266 + - 2021.05.26.14.20.58_veh-35_01277_01369 + - 2021.05.26.14.20.58_veh-35_01381_01477 + - 2021.05.26.14.20.58_veh-35_01515_01725 + - 2021.05.26.14.20.58_veh-35_01736_01806 + - 2021.05.26.14.20.58_veh-35_01817_01883 + - 2021.05.26.14.20.58_veh-35_01998_02515 + - 2021.05.26.14.20.58_veh-35_02540_02844 + - 2021.05.26.14.20.58_veh-35_02858_03021 + - 2021.05.26.14.20.58_veh-35_03058_03145 + - 2021.05.26.14.26.29_veh-47_00071_00328 + - 2021.05.26.14.26.29_veh-47_00339_00757 + - 2021.05.26.14.26.29_veh-47_00831_00923 + - 2021.05.26.14.26.29_veh-47_00934_01092 + - 2021.05.26.14.26.29_veh-47_01103_01291 + - 2021.05.26.14.26.29_veh-47_01302_01426 + - 2021.05.26.14.26.29_veh-47_01437_01660 + - 2021.05.26.14.26.29_veh-47_01671_01835 + - 2021.05.26.14.26.29_veh-47_01846_02253 + - 2021.05.26.14.26.29_veh-47_02280_02415 + - 2021.05.26.14.26.29_veh-47_02426_02810 + - 2021.05.26.14.26.29_veh-47_02821_02949 + - 2021.05.26.14.26.29_veh-47_02960_03799 + - 2021.05.26.15.08.40_veh-30_00068_00214 + - 2021.05.26.15.08.40_veh-30_00225_00689 + - 2021.05.26.15.08.40_veh-30_00700_00904 + - 2021.05.26.15.08.40_veh-30_00915_01061 + - 2021.05.26.15.08.40_veh-30_01072_01351 + - 2021.05.26.15.08.40_veh-30_01364_01432 + - 2021.05.26.15.08.40_veh-30_01485_01591 + - 2021.05.26.15.08.40_veh-30_01602_01851 + - 2021.05.26.15.08.40_veh-30_01907_02262 + - 2021.05.26.15.08.40_veh-30_02273_02337 + - 2021.05.26.15.08.40_veh-30_02502_02709 + - 2021.05.26.15.08.40_veh-30_02720_02811 + - 2021.05.26.15.08.40_veh-30_02822_03063 + - 2021.05.26.15.08.40_veh-30_03120_03212 + - 2021.05.26.15.08.40_veh-30_03328_03469 + - 2021.05.26.15.08.40_veh-30_03486_03691 + - 2021.05.26.15.08.40_veh-30_03702_03942 + - 2021.05.26.15.08.40_veh-30_03954_04924 + - 2021.05.26.15.08.40_veh-30_04935_05334 + - 2021.05.26.16.36.35_veh-38_00028_00456 + - 2021.05.26.16.36.35_veh-38_00467_00608 + - 2021.05.26.16.36.35_veh-38_00674_01004 + - 2021.05.26.16.36.35_veh-38_01038_01127 + - 2021.05.26.16.36.35_veh-38_01189_01434 + - 2021.05.26.16.36.35_veh-38_01445_01512 + - 2021.05.26.16.36.35_veh-38_01534_01599 + - 2021.05.26.16.36.35_veh-38_01610_02263 + - 2021.05.26.16.36.35_veh-38_02274_02599 + - 2021.05.26.16.36.35_veh-38_02610_02795 + - 2021.05.26.16.36.35_veh-38_02806_02993 + - 2021.05.26.16.36.35_veh-38_03014_03193 + - 2021.05.26.16.36.35_veh-38_03204_03536 + - 2021.05.26.16.36.35_veh-38_03547_03778 + - 2021.05.26.16.36.35_veh-38_03800_03968 + - 2021.05.26.16.36.35_veh-38_03979_04145 + - 2021.05.26.16.36.35_veh-38_04156_04282 + - 2021.05.26.16.36.35_veh-38_04293_04765 + - 2021.05.26.16.36.35_veh-38_04776_04890 + - 2021.05.26.16.36.35_veh-38_04901_05526 + - 2021.05.26.17.13.21_veh-25_00071_00302 + - 2021.05.26.17.13.21_veh-25_00383_01022 + - 2021.05.26.17.13.21_veh-25_01033_01171 + - 2021.05.26.17.13.21_veh-25_01182_01323 + - 2021.05.26.17.38.48_veh-47_00019_00610 + - 2021.05.26.17.38.48_veh-47_00674_00766 + - 2021.05.26.17.38.48_veh-47_00777_01077 + - 2021.05.26.17.38.48_veh-47_01089_01431 + - 2021.05.26.17.38.48_veh-47_01442_01685 + - 2021.05.26.17.38.48_veh-47_01696_01775 + - 2021.05.26.17.38.48_veh-47_01787_02228 + - 2021.05.26.17.38.48_veh-47_02239_02320 + - 2021.05.26.17.38.48_veh-47_02347_02428 + - 2021.05.26.17.38.48_veh-47_02439_02690 + - 2021.05.26.17.38.48_veh-47_02801_02982 + - 2021.05.26.17.38.48_veh-47_02993_03173 + - 2021.05.26.17.38.48_veh-47_03184_03355 + - 2021.05.26.17.38.48_veh-47_03366_03561 + - 2021.05.26.17.38.48_veh-47_03621_03733 + - 2021.05.26.17.38.48_veh-47_03744_03914 + - 2021.05.26.17.38.48_veh-47_03925_04278 + - 2021.05.26.17.38.48_veh-47_04289_04553 + - 2021.05.26.17.38.48_veh-47_04564_04817 + - 2021.05.26.17.38.48_veh-47_04828_05198 + - 2021.05.26.17.47.39_veh-25_00016_00301 + - 2021.05.26.17.47.39_veh-25_00378_00453 + - 2021.05.26.17.47.39_veh-25_00593_00829 + - 2021.05.26.17.47.39_veh-25_00840_01262 + - 2021.05.26.17.47.39_veh-25_01286_01489 + - 2021.05.26.17.47.39_veh-25_01560_01735 + - 2021.05.26.17.47.39_veh-25_01746_01946 + - 2021.05.26.17.47.39_veh-25_02308_02458 + - 2021.05.26.17.47.39_veh-25_02535_02636 + - 2021.05.26.17.47.39_veh-25_02656_02737 + - 2021.05.26.17.47.39_veh-25_03024_03106 + - 2021.05.26.17.47.39_veh-25_03117_03201 + - 2021.05.26.17.47.39_veh-25_03313_03445 + - 2021.05.26.17.47.39_veh-25_03803_03911 + - 2021.05.26.17.47.39_veh-25_04048_04180 + - 2021.05.26.17.47.39_veh-25_04191_04253 + - 2021.05.26.17.47.39_veh-25_04498_04676 + - 2021.05.26.17.47.39_veh-25_04694_04778 + - 2021.05.26.17.47.39_veh-25_04931_05231 + - 2021.05.26.17.47.39_veh-25_05242_05606 + - 2021.05.26.17.47.39_veh-25_05617_05744 + - 2021.05.26.17.47.39_veh-25_05812_05886 + - 2021.05.26.17.56.15_veh-35_00048_00975 + - 2021.05.26.17.56.15_veh-35_01086_01155 + - 2021.05.26.17.56.15_veh-35_01197_01353 + - 2021.05.26.17.56.15_veh-35_01364_01512 + - 2021.05.26.17.56.15_veh-35_01523_01666 + - 2021.05.26.17.56.15_veh-35_01678_02342 + - 2021.05.26.17.56.15_veh-35_02353_02485 + - 2021.05.26.17.56.15_veh-35_02496_02691 + - 2021.05.26.17.56.15_veh-35_02702_02792 + - 2021.05.26.17.56.15_veh-35_02803_03107 + - 2021.05.26.17.56.15_veh-35_03118_03301 + - 2021.05.26.17.56.15_veh-35_03312_04403 + - 2021.05.26.17.56.15_veh-35_04414_04639 + - 2021.05.26.17.56.15_veh-35_04650_04970 + - 2021.05.26.17.56.15_veh-35_04981_05212 + - 2021.05.26.18.32.28_veh-17_00005_00245 + - 2021.05.26.18.32.28_veh-17_00256_00370 + - 2021.05.26.18.32.28_veh-17_00438_00680 + - 2021.05.26.18.32.28_veh-17_00691_00805 + - 2021.05.26.18.32.28_veh-17_00954_01056 + - 2021.05.26.18.45.36_veh-30_00005_00271 + - 2021.05.26.18.45.36_veh-30_00282_00359 + - 2021.05.26.18.45.36_veh-30_00386_00470 + - 2021.05.26.18.45.36_veh-30_00481_01434 + - 2021.05.26.18.45.36_veh-30_01450_01779 + - 2021.05.26.18.45.36_veh-30_01790_01968 + - 2021.05.26.18.45.36_veh-30_01979_02240 + - 2021.05.26.18.45.36_veh-30_02278_02414 + - 2021.05.26.18.45.36_veh-30_02426_02526 + - 2021.05.26.18.45.36_veh-30_02573_02835 + - 2021.05.26.18.45.36_veh-30_02847_03060 + - 2021.05.26.18.45.36_veh-30_03071_03323 + - 2021.05.26.18.45.36_veh-30_03334_03687 + - 2021.05.26.18.45.36_veh-30_03795_03915 + - 2021.05.26.18.45.36_veh-30_03926_04423 + - 2021.05.26.18.45.36_veh-30_04434_04571 + - 2021.05.26.18.45.36_veh-30_04616_04807 + - 2021.05.26.18.45.36_veh-30_04818_05065 + - 2021.05.26.18.45.36_veh-30_05076_05256 + - 2021.05.26.18.45.36_veh-30_05267_05352 + - 2021.05.26.18.45.36_veh-30_05387_05568 + - 2021.05.26.18.55.53_veh-17_00022_00295 + - 2021.05.26.18.55.53_veh-17_00323_00423 + - 2021.05.26.18.55.53_veh-17_00534_00638 + - 2021.05.26.18.55.53_veh-17_00649_00733 + - 2021.05.26.18.55.53_veh-17_00943_01013 + - 2021.05.26.18.55.53_veh-17_01038_01138 + - 2021.05.26.18.55.53_veh-17_01150_01225 + - 2021.05.26.19.30.19_veh-47_00016_00096 + - 2021.05.26.19.30.19_veh-47_00213_00461 + - 2021.05.26.19.30.19_veh-47_00472_00667 + - 2021.05.26.19.30.19_veh-47_00739_00810 + - 2021.05.26.19.30.19_veh-47_00893_01236 + - 2021.05.26.19.30.19_veh-47_01315_01652 + - 2021.05.26.19.30.19_veh-47_01678_02032 + - 2021.05.26.19.30.19_veh-47_02043_02254 + - 2021.05.26.19.30.19_veh-47_02325_02627 + - 2021.05.26.19.30.19_veh-47_02638_02966 + - 2021.05.26.19.37.19_veh-25_00015_00185 + - 2021.05.26.19.37.19_veh-25_00210_00545 + - 2021.05.26.19.37.19_veh-25_00556_00675 + - 2021.05.26.19.37.19_veh-25_00686_01190 + - 2021.05.26.19.37.19_veh-25_01226_01304 + - 2021.05.26.19.37.19_veh-25_01395_01484 + - 2021.05.26.19.37.19_veh-25_01495_01680 + - 2021.05.26.19.37.19_veh-25_01691_01754 + - 2021.05.26.19.37.19_veh-25_01765_01945 + - 2021.05.26.19.37.19_veh-25_01956_02035 + - 2021.05.26.19.37.19_veh-25_02046_02150 + - 2021.05.26.19.37.19_veh-25_02161_02306 + - 2021.05.26.19.37.19_veh-25_02351_02786 + - 2021.05.26.19.37.19_veh-25_02797_02965 + - 2021.05.26.19.37.19_veh-25_02976_03064 + - 2021.05.26.20.05.14_veh-38_00005_00395 + - 2021.05.26.20.05.14_veh-38_00406_00535 + - 2021.05.26.20.05.14_veh-38_00546_00610 + - 2021.05.26.20.05.14_veh-38_00621_00780 + - 2021.05.26.20.05.14_veh-38_00837_00994 + - 2021.05.27.12.24.29_veh-30_00016_00131 + - 2021.05.27.12.24.29_veh-30_00142_00217 + - 2021.05.27.12.24.29_veh-30_00228_00443 + - 2021.05.27.12.24.29_veh-30_00454_00796 + - 2021.05.27.12.24.29_veh-30_00807_01608 + - 2021.05.27.12.24.29_veh-30_01619_01842 + - 2021.05.27.12.24.29_veh-30_01920_02209 + - 2021.05.27.12.24.29_veh-30_02220_02316 + - 2021.05.27.12.24.29_veh-30_02327_02399 + - 2021.05.27.12.24.29_veh-30_02436_02521 + - 2021.05.27.12.24.29_veh-30_02532_02765 + - 2021.05.27.12.24.29_veh-30_02776_03003 + - 2021.05.27.12.24.29_veh-30_03014_03102 + - 2021.05.27.12.24.29_veh-30_03113_03173 + - 2021.05.27.12.24.29_veh-30_03184_03252 + - 2021.05.27.12.24.29_veh-30_03477_03777 + - 2021.05.27.12.24.29_veh-30_03872_04303 + - 2021.05.27.12.24.29_veh-30_04314_04655 + - 2021.05.27.12.24.29_veh-30_04666_04791 + - 2021.05.27.12.24.29_veh-30_04802_05373 + - 2021.05.27.12.24.29_veh-30_05384_05826 + - 2021.05.27.12.24.29_veh-30_05837_05911 + - 2021.05.27.12.24.29_veh-30_06003_06197 + - 2021.05.27.12.30.22_veh-35_00016_00216 + - 2021.05.27.12.30.22_veh-35_00307_00395 + - 2021.05.27.12.30.22_veh-35_00406_00500 + - 2021.05.27.12.30.22_veh-35_00511_00576 + - 2021.05.27.12.30.22_veh-35_00672_00795 + - 2021.05.27.12.30.22_veh-35_00806_00926 + - 2021.05.27.12.30.22_veh-35_00937_01339 + - 2021.05.27.12.30.22_veh-35_01361_01495 + - 2021.05.27.12.30.22_veh-35_01506_01655 + - 2021.05.27.12.30.22_veh-35_01669_01878 + - 2021.05.27.12.30.22_veh-35_01889_02126 + - 2021.05.27.12.30.22_veh-35_02137_02218 + - 2021.05.27.12.30.22_veh-35_02229_02335 + - 2021.05.27.12.30.22_veh-35_02366_02488 + - 2021.05.27.12.30.22_veh-35_02499_02603 + - 2021.05.27.12.30.22_veh-35_02640_02768 + - 2021.05.27.12.30.22_veh-35_02779_02846 + - 2021.05.27.12.30.22_veh-35_02923_03087 + - 2021.05.27.12.30.22_veh-35_03099_03186 + - 2021.05.27.12.30.22_veh-35_03307_03446 + - 2021.05.27.12.30.22_veh-35_03458_03558 + - 2021.05.27.12.30.22_veh-35_03707_03840 + - 2021.05.27.12.30.22_veh-35_03851_03975 + - 2021.05.27.12.30.22_veh-35_04032_04188 + - 2021.05.27.12.30.22_veh-35_04199_04271 + - 2021.05.27.12.30.22_veh-35_04329_04584 + - 2021.05.27.12.30.22_veh-35_04600_04792 + - 2021.05.27.12.30.22_veh-35_04803_05258 + - 2021.05.27.12.30.22_veh-35_05269_05374 + - 2021.05.27.12.40.28_veh-38_00031_00256 + - 2021.05.27.12.40.28_veh-38_00267_00332 + - 2021.05.27.12.40.28_veh-38_00343_00640 + - 2021.05.27.12.40.28_veh-38_00651_00711 + - 2021.05.27.12.40.28_veh-38_00750_00832 + - 2021.05.27.12.40.28_veh-38_00942_01107 + - 2021.05.27.12.40.28_veh-38_01118_01256 + - 2021.05.27.12.40.28_veh-38_01348_01717 + - 2021.05.27.12.40.28_veh-38_01728_01924 + - 2021.05.27.12.40.28_veh-38_01935_02036 + - 2021.05.27.12.40.28_veh-38_02047_02262 + - 2021.05.27.12.40.28_veh-38_02273_02385 + - 2021.05.27.12.40.28_veh-38_02396_02532 + - 2021.05.27.12.40.28_veh-38_02570_02713 + - 2021.05.27.12.40.28_veh-38_02724_02802 + - 2021.05.27.12.40.28_veh-38_02852_03027 + - 2021.05.27.12.40.28_veh-38_03090_03520 + - 2021.05.27.12.40.28_veh-38_03531_03612 + - 2021.05.27.12.40.28_veh-38_03693_03778 + - 2021.05.27.12.40.28_veh-38_03789_03869 + - 2021.05.27.12.40.28_veh-38_03881_04101 + - 2021.05.27.12.40.28_veh-38_04175_04276 + - 2021.05.27.12.40.28_veh-38_04287_04402 + - 2021.05.27.12.40.28_veh-38_04492_04765 + - 2021.05.27.12.40.28_veh-38_04880_04955 + - 2021.05.27.12.40.28_veh-38_04977_05052 + - 2021.05.27.12.40.28_veh-38_05075_05177 + - 2021.05.27.12.40.28_veh-38_05208_05373 + - 2021.05.27.12.52.03_veh-47_00005_00085 + - 2021.05.27.12.52.03_veh-47_00096_00300 + - 2021.05.27.12.52.03_veh-47_00311_00406 + - 2021.05.27.12.52.03_veh-47_00417_01071 + - 2021.05.27.12.52.03_veh-47_01082_01162 + - 2021.05.27.12.52.03_veh-47_01173_01290 + - 2021.05.27.12.52.03_veh-47_01346_01541 + - 2021.05.27.12.52.03_veh-47_01552_01904 + - 2021.05.27.12.52.03_veh-47_01915_02355 + - 2021.05.27.12.52.03_veh-47_02366_02429 + - 2021.05.27.12.52.03_veh-47_02440_02543 + - 2021.05.27.12.52.03_veh-47_02554_02636 + - 2021.05.27.12.52.03_veh-47_02709_02771 + - 2021.05.27.12.52.03_veh-47_02816_02985 + - 2021.05.27.12.52.03_veh-47_03082_03510 + - 2021.05.27.12.52.03_veh-47_03566_03741 + - 2021.05.27.12.52.03_veh-47_03752_03910 + - 2021.05.27.14.15.01_veh-47_00023_00089 + - 2021.05.27.14.15.01_veh-47_00100_00169 + - 2021.05.27.14.15.01_veh-47_00248_00350 + - 2021.05.27.14.15.01_veh-47_00375_00506 + - 2021.05.27.14.15.01_veh-47_00517_00694 + - 2021.05.27.14.15.01_veh-47_00705_01079 + - 2021.05.27.14.15.01_veh-47_01090_01292 + - 2021.05.27.14.15.01_veh-47_01303_01444 + - 2021.05.27.14.15.01_veh-47_01455_01660 + - 2021.05.27.14.15.01_veh-47_01731_01837 + - 2021.05.27.14.15.01_veh-47_01848_02009 + - 2021.05.27.14.15.01_veh-47_02120_02497 + - 2021.05.27.14.15.01_veh-47_02529_02663 + - 2021.05.27.14.15.01_veh-47_02699_02901 + - 2021.05.27.14.15.01_veh-47_02912_03139 + - 2021.05.27.14.15.01_veh-47_03174_04038 + - 2021.05.27.14.15.01_veh-47_04049_04184 + - 2021.05.27.14.15.01_veh-47_04195_04362 + - 2021.05.27.14.15.01_veh-47_04382_04457 + - 2021.05.27.14.15.01_veh-47_04468_04530 + - 2021.05.27.14.27.08_veh-35_00022_00962 + - 2021.05.27.14.27.08_veh-35_01036_01318 + - 2021.05.27.14.27.08_veh-35_01389_01627 + - 2021.05.27.14.29.03_veh-38_00016_00144 + - 2021.05.27.14.29.03_veh-38_00169_00274 + - 2021.05.27.14.29.03_veh-38_00285_00354 + - 2021.05.27.14.29.03_veh-38_00365_00544 + - 2021.05.27.14.29.03_veh-38_00555_00800 + - 2021.05.27.14.29.03_veh-38_00811_00945 + - 2021.05.27.14.29.03_veh-38_01023_01503 + - 2021.05.27.14.29.03_veh-38_01514_01629 + - 2021.05.27.14.29.03_veh-38_01649_01909 + - 2021.05.27.14.29.03_veh-38_01920_02010 + - 2021.05.27.14.29.03_veh-38_02021_02095 + - 2021.05.27.14.29.03_veh-38_02118_02471 + - 2021.05.27.14.29.03_veh-38_02482_02584 + - 2021.05.27.14.29.03_veh-38_02631_03233 + - 2021.05.27.14.29.03_veh-38_03244_03417 + - 2021.05.27.14.29.03_veh-38_03428_03573 + - 2021.05.27.14.29.03_veh-38_03584_04205 + - 2021.05.27.14.29.03_veh-38_04216_04590 + - 2021.05.27.14.29.03_veh-38_04601_04776 + - 2021.05.27.14.29.03_veh-38_04833_04931 + - 2021.05.27.14.29.03_veh-38_04942_05142 + - 2021.05.27.14.29.03_veh-38_05153_05238 + - 2021.05.27.14.29.03_veh-38_05249_05523 + - 2021.05.27.14.29.03_veh-38_05534_05724 + - 2021.05.27.15.16.33_veh-30_00140_00395 + - 2021.05.27.15.16.33_veh-30_00406_00729 + - 2021.05.27.15.16.33_veh-30_00740_00963 + - 2021.05.27.15.16.33_veh-30_00974_01064 + - 2021.05.27.15.16.33_veh-30_01080_01465 + - 2021.05.27.15.16.33_veh-30_01476_01762 + - 2021.05.27.15.16.33_veh-30_01773_01981 + - 2021.05.27.15.16.33_veh-30_01992_02495 + - 2021.05.27.15.16.33_veh-30_02506_02645 + - 2021.05.27.15.16.33_veh-30_02656_03162 + - 2021.05.27.15.16.33_veh-30_03173_03411 + - 2021.05.27.15.16.33_veh-30_03422_03541 + - 2021.05.27.15.16.33_veh-30_03552_03901 + - 2021.05.27.15.16.33_veh-30_04100_04219 + - 2021.05.27.15.16.33_veh-30_04230_04538 + - 2021.05.27.15.16.33_veh-30_04549_04661 + - 2021.05.27.15.16.33_veh-30_04673_04794 + - 2021.05.27.15.16.33_veh-30_04805_04891 + - 2021.05.27.15.16.33_veh-30_04902_05061 + - 2021.05.27.15.16.33_veh-30_05072_05171 + - 2021.05.27.15.16.33_veh-30_05184_05477 + - 2021.05.27.15.16.33_veh-30_05488_05655 + - 2021.05.27.15.16.33_veh-30_05666_05734 + - 2021.05.27.15.16.33_veh-30_05745_05943 + - 2021.05.27.15.16.33_veh-30_05954_06030 + - 2021.05.27.15.16.33_veh-30_06041_06120 + - 2021.05.27.16.07.39_veh-35_00016_00162 + - 2021.05.27.16.07.39_veh-35_00173_00237 + - 2021.05.27.16.07.39_veh-35_00248_00360 + - 2021.05.27.16.07.39_veh-35_00371_01431 + - 2021.05.27.16.07.39_veh-35_01495_01796 + - 2021.05.27.16.07.39_veh-35_01850_02017 + - 2021.05.27.16.07.39_veh-35_02039_02398 + - 2021.05.27.16.07.39_veh-35_02490_03033 + - 2021.05.27.16.07.39_veh-35_03044_03120 + - 2021.05.27.16.07.39_veh-35_03131_03559 + - 2021.05.27.16.07.39_veh-35_03570_03808 + - 2021.05.27.16.07.39_veh-35_03819_03940 + - 2021.05.27.16.07.39_veh-35_03951_04095 + - 2021.05.27.16.07.39_veh-35_04107_04565 + - 2021.05.27.16.07.39_veh-35_04576_04647 + - 2021.05.27.16.07.39_veh-35_04658_04951 + - 2021.05.27.16.07.39_veh-35_05062_05311 + - 2021.05.27.17.44.06_veh-35_00076_00137 + - 2021.05.27.17.44.06_veh-35_00398_00485 + - 2021.05.27.17.44.06_veh-35_00523_00707 + - 2021.05.27.17.44.06_veh-35_00775_00892 + - 2021.05.27.17.44.06_veh-35_00910_01323 + - 2021.05.27.17.44.06_veh-35_01334_01586 + - 2021.05.27.17.44.06_veh-35_01597_01670 + - 2021.05.27.17.44.06_veh-35_01681_01742 + - 2021.05.27.17.44.06_veh-35_01816_01922 + - 2021.05.27.17.44.06_veh-35_02010_02302 + - 2021.05.27.17.44.06_veh-35_02313_02405 + - 2021.05.27.17.44.06_veh-35_02416_02712 + - 2021.05.27.17.44.06_veh-35_02743_02871 + - 2021.05.27.17.44.06_veh-35_03110_03378 + - 2021.05.27.17.44.06_veh-35_03463_03538 + - 2021.05.27.17.44.06_veh-35_03549_03617 + - 2021.05.27.17.44.06_veh-35_03628_03690 + - 2021.05.27.17.44.06_veh-35_03720_03946 + - 2021.05.27.18.06.41_veh-47_00005_00111 + - 2021.05.27.18.06.41_veh-47_00188_00491 + - 2021.05.27.18.06.41_veh-47_00502_00716 + - 2021.05.27.18.06.41_veh-47_00727_01059 + - 2021.05.27.18.06.41_veh-47_01071_01147 + - 2021.05.27.18.06.41_veh-47_01231_01574 + - 2021.05.27.18.06.41_veh-47_01586_01702 + - 2021.05.27.18.06.41_veh-47_01713_02004 + - 2021.05.27.18.06.41_veh-47_02040_02349 + - 2021.05.27.18.06.41_veh-47_02360_02539 + - 2021.05.27.18.06.41_veh-47_02550_02687 + - 2021.05.27.18.06.41_veh-47_02755_03092 + - 2021.05.27.18.06.41_veh-47_03103_03314 + - 2021.05.27.18.06.41_veh-47_03325_03669 + - 2021.05.27.18.06.41_veh-47_03680_03763 + - 2021.05.27.18.06.41_veh-47_03830_03978 + - 2021.05.27.18.06.41_veh-47_04001_04224 + - 2021.05.27.18.06.41_veh-47_04235_04331 + - 2021.05.27.18.21.51_veh-30_00048_00479 + - 2021.05.27.18.21.51_veh-30_00490_00717 + - 2021.05.27.18.21.51_veh-30_00755_00902 + - 2021.05.27.18.21.51_veh-30_00913_00984 + - 2021.05.27.18.21.51_veh-30_00995_01059 + - 2021.05.27.18.21.51_veh-30_01070_01194 + - 2021.05.27.18.21.51_veh-30_01278_01395 + - 2021.05.27.18.21.51_veh-30_01406_01507 + - 2021.05.27.18.21.51_veh-30_01518_01602 + - 2021.05.27.18.21.51_veh-30_01661_01897 + - 2021.05.27.18.21.51_veh-30_01908_02199 + - 2021.05.27.18.21.51_veh-30_02210_02682 + - 2021.05.27.18.21.51_veh-30_02693_02908 + - 2021.05.27.18.21.51_veh-30_02919_02986 + - 2021.05.27.18.21.51_veh-30_02997_03071 + - 2021.05.27.18.21.51_veh-30_03082_03146 + - 2021.05.27.18.21.51_veh-30_03157_03269 + - 2021.05.27.18.21.51_veh-30_03280_03382 + - 2021.05.27.18.21.51_veh-30_03393_03556 + - 2021.05.27.18.21.51_veh-30_03611_03870 + - 2021.05.27.18.21.51_veh-30_03900_04010 + - 2021.05.27.18.21.51_veh-30_04022_04274 + - 2021.05.27.18.21.51_veh-30_04285_04429 + - 2021.05.27.18.21.51_veh-30_04485_04659 + - 2021.05.27.18.21.51_veh-30_04670_04733 + - 2021.05.27.18.21.51_veh-30_04744_04932 + - 2021.05.27.18.21.51_veh-30_04943_05111 + - 2021.05.27.18.27.52_veh-38_00016_00182 + - 2021.05.27.18.27.52_veh-38_00193_00352 + - 2021.05.27.18.27.52_veh-38_00363_00521 + - 2021.05.27.18.27.52_veh-38_00578_00834 + - 2021.05.27.18.27.52_veh-38_00905_00968 + - 2021.05.27.18.27.52_veh-38_01019_01133 + - 2021.05.27.18.27.52_veh-38_01144_01220 + - 2021.05.27.18.27.52_veh-38_01231_01458 + - 2021.05.27.18.27.52_veh-38_01469_01569 + - 2021.05.27.18.27.52_veh-38_01587_02162 + - 2021.05.27.18.27.52_veh-38_02216_02388 + - 2021.05.27.18.27.52_veh-38_02399_02523 + - 2021.05.27.18.27.52_veh-38_02614_02714 + - 2021.05.27.18.27.52_veh-38_02725_02901 + - 2021.05.27.18.27.52_veh-38_02912_03005 + - 2021.05.27.18.27.52_veh-38_03016_03086 + - 2021.05.27.18.27.52_veh-38_03097_03306 + - 2021.05.27.18.27.52_veh-38_03317_03383 + - 2021.05.27.18.27.52_veh-38_03416_03716 + - 2021.05.27.18.27.52_veh-38_03777_04025 + - 2021.05.27.18.27.52_veh-38_04138_04477 + - 2021.05.27.18.27.52_veh-38_04519_05052 + - 2021.05.27.18.27.52_veh-38_05063_05123 + - 2021.05.27.18.27.52_veh-38_05134_05248 + - 2021.05.27.18.27.52_veh-38_05269_05502 + - 2021.05.27.18.27.52_veh-38_05513_05630 + - 2021.05.27.18.27.52_veh-38_05672_05782 + - 2021.05.27.19.13.17_veh-35_00177_00253 + - 2021.05.27.19.13.17_veh-35_00388_00667 + - 2021.05.27.19.13.17_veh-35_00702_00856 + - 2021.05.27.19.13.17_veh-35_00959_01039 + - 2021.05.27.19.13.17_veh-35_01050_01485 + - 2021.05.27.19.13.17_veh-35_01506_01654 + - 2021.05.27.19.13.17_veh-35_01760_01844 + - 2021.05.27.19.13.17_veh-35_02089_02156 + - 2021.05.27.19.13.17_veh-35_02167_02343 + - 2021.05.27.19.13.17_veh-35_02354_02721 + - 2021.05.27.19.13.17_veh-35_02732_02894 + - 2021.05.27.19.13.17_veh-35_02906_03003 + - 2021.05.27.19.42.22_veh-47_00022_00148 + - 2021.05.27.19.42.22_veh-47_00224_00359 + - 2021.05.27.19.42.22_veh-47_00464_00669 + - 2021.05.27.19.42.22_veh-47_00680_00884 + - 2021.05.27.19.42.22_veh-47_00895_01283 + - 2021.05.27.19.42.22_veh-47_01294_01475 + - 2021.05.27.19.42.22_veh-47_01487_01767 + - 2021.05.27.19.42.22_veh-47_01788_02018 + - 2021.05.27.19.42.22_veh-47_02094_02183 + - 2021.05.28.12.16.40_veh-35_00082_00226 + - 2021.05.28.12.16.40_veh-35_00237_00326 + - 2021.05.28.12.16.40_veh-35_00558_00632 + - 2021.05.28.12.16.40_veh-35_00643_00763 + - 2021.05.28.12.16.40_veh-35_00774_00876 + - 2021.05.28.12.16.40_veh-35_00941_01054 + - 2021.05.28.12.16.40_veh-35_01082_01315 + - 2021.05.28.12.16.40_veh-35_01326_01476 + - 2021.05.28.12.16.40_veh-35_01762_01822 + - 2021.05.28.12.16.40_veh-35_01963_02082 + - 2021.05.28.12.16.40_veh-35_02093_02191 + - 2021.05.28.12.16.40_veh-35_02202_02300 + - 2021.05.28.12.16.40_veh-35_02418_02667 + - 2021.05.28.12.16.40_veh-35_02678_02775 + - 2021.05.28.12.16.40_veh-35_02898_03134 + - 2021.05.28.12.16.40_veh-35_03229_03408 + - 2021.05.28.12.16.40_veh-35_03419_03498 + - 2021.05.28.12.16.40_veh-35_03509_03570 + - 2021.05.28.12.16.40_veh-35_03701_03888 + - 2021.05.28.12.16.40_veh-35_03904_04029 + - 2021.05.28.12.16.40_veh-35_04049_04114 + - 2021.05.28.12.16.40_veh-35_04266_04502 + - 2021.05.28.12.16.40_veh-35_04513_04713 + - 2021.05.28.12.16.40_veh-35_04783_04910 + - 2021.05.28.12.16.40_veh-35_04958_05034 + - 2021.05.28.12.16.40_veh-35_05046_05352 + - 2021.05.28.12.16.40_veh-35_05394_05525 + - 2021.05.28.12.26.01_veh-30_00107_00215 + - 2021.05.28.12.26.01_veh-30_00252_00327 + - 2021.05.28.12.26.01_veh-30_00350_00414 + - 2021.05.28.12.26.01_veh-30_00440_00731 + - 2021.05.28.12.26.01_veh-30_00742_00875 + - 2021.05.28.12.26.01_veh-30_00886_01022 + - 2021.05.28.12.26.01_veh-30_01088_01196 + - 2021.05.28.12.26.01_veh-30_01207_01388 + - 2021.05.28.12.26.01_veh-30_01520_01586 + - 2021.05.28.12.26.01_veh-30_01597_01728 + - 2021.05.28.12.26.01_veh-30_01795_01859 + - 2021.05.28.12.26.01_veh-30_01870_02089 + - 2021.05.28.12.26.01_veh-30_02216_02299 + - 2021.05.28.12.26.01_veh-30_02310_02583 + - 2021.05.28.12.26.01_veh-30_02594_02853 + - 2021.05.28.12.26.01_veh-30_02864_02996 + - 2021.05.28.12.26.01_veh-30_03091_03308 + - 2021.05.28.12.26.01_veh-30_03319_03436 + - 2021.05.28.12.26.01_veh-30_03447_03518 + - 2021.05.28.12.26.01_veh-30_03847_03919 + - 2021.05.28.12.26.01_veh-30_03945_04028 + - 2021.05.28.12.26.01_veh-30_04128_04228 + - 2021.05.28.12.26.01_veh-30_04321_04390 + - 2021.05.28.12.26.01_veh-30_04401_04515 + - 2021.05.28.12.26.01_veh-30_04614_04773 + - 2021.05.28.12.26.01_veh-30_04784_05201 + - 2021.05.28.12.26.01_veh-30_05212_05334 + - 2021.05.28.12.26.01_veh-30_05345_05408 + - 2021.05.28.12.26.01_veh-30_05419_05511 + - 2021.05.28.12.26.01_veh-30_05536_05598 + - 2021.05.28.12.26.01_veh-30_05653_05741 + - 2021.05.28.12.26.01_veh-30_05752_05824 + - 2021.05.28.12.26.01_veh-30_05835_05983 + - 2021.05.28.12.26.01_veh-30_05994_06094 + - 2021.05.28.12.36.49_veh-12_00005_00764 + - 2021.05.28.12.36.49_veh-12_00775_01095 + - 2021.05.28.12.36.49_veh-12_01106_01411 + - 2021.05.28.12.36.49_veh-12_01422_01653 + - 2021.05.28.12.36.49_veh-12_01664_01724 + - 2021.05.28.12.36.49_veh-12_01735_01821 + - 2021.05.28.12.36.49_veh-12_01832_02215 + - 2021.05.28.12.36.49_veh-12_02226_02520 + - 2021.05.28.12.36.49_veh-12_02531_02687 + - 2021.05.28.12.36.49_veh-12_02698_02802 + - 2021.05.28.12.36.49_veh-12_02958_03283 + - 2021.05.28.12.36.49_veh-12_03294_03630 + - 2021.05.28.12.36.49_veh-12_03641_03871 + - 2021.05.28.12.36.49_veh-12_03964_04088 + - 2021.05.28.12.36.49_veh-12_04301_04897 + - 2021.05.28.12.36.49_veh-12_05016_05202 + - 2021.05.28.12.36.49_veh-12_05213_05357 + - 2021.05.28.12.36.49_veh-12_05368_06079 + - 2021.05.28.12.36.49_veh-12_06124_06233 + - 2021.05.28.12.48.08_veh-38_00077_00235 + - 2021.05.28.12.48.08_veh-38_00272_00585 + - 2021.05.28.12.48.08_veh-38_00597_00821 + - 2021.05.28.12.48.08_veh-38_00832_00969 + - 2021.05.28.12.48.08_veh-38_00980_01243 + - 2021.05.28.12.48.08_veh-38_01254_01619 + - 2021.05.28.12.48.08_veh-38_01630_01703 + - 2021.05.28.12.48.08_veh-38_01714_01791 + - 2021.05.28.12.48.08_veh-38_01802_01935 + - 2021.05.28.12.48.08_veh-38_01946_02050 + - 2021.05.28.12.48.08_veh-38_02061_02268 + - 2021.05.28.12.48.08_veh-38_02279_02370 + - 2021.05.28.12.48.08_veh-38_02518_02631 + - 2021.05.28.12.48.08_veh-38_02642_02843 + - 2021.05.28.12.48.08_veh-38_02854_03136 + - 2021.05.28.12.48.08_veh-38_03147_03253 + - 2021.05.28.13.54.02_veh-35_00026_00555 + - 2021.05.28.13.54.02_veh-35_00615_00714 + - 2021.05.28.13.54.02_veh-35_00725_00908 + - 2021.05.28.13.54.02_veh-35_00934_01072 + - 2021.05.28.13.54.02_veh-35_01152_01222 + - 2021.05.28.13.54.02_veh-35_01233_01307 + - 2021.05.28.13.54.02_veh-35_01339_02659 + - 2021.05.28.13.54.02_veh-35_02670_03272 + - 2021.05.28.13.54.02_veh-35_03283_03443 + - 2021.05.28.13.54.02_veh-35_03454_03730 + - 2021.05.28.14.39.51_veh-30_00016_00293 + - 2021.05.28.14.39.51_veh-30_00338_00482 + - 2021.05.28.14.39.51_veh-30_00493_00866 + - 2021.05.28.14.39.51_veh-30_00946_01037 + - 2021.05.28.14.39.51_veh-30_01170_01355 + - 2021.05.28.14.39.51_veh-30_01366_01463 + - 2021.05.28.14.39.51_veh-30_01495_01607 + - 2021.05.28.14.39.51_veh-30_01760_02040 + - 2021.05.28.14.39.51_veh-30_02079_02301 + - 2021.05.28.14.39.51_veh-30_02312_02813 + - 2021.05.28.14.39.51_veh-30_02893_02993 + - 2021.05.28.14.39.51_veh-30_03039_03792 + - 2021.05.28.14.39.51_veh-30_03803_03874 + - 2021.05.28.14.39.51_veh-30_03885_03961 + - 2021.05.28.14.39.51_veh-30_03972_04288 + - 2021.05.28.14.39.51_veh-30_04299_04554 + - 2021.05.28.14.50.57_veh-12_00016_01524 + - 2021.05.28.14.50.57_veh-12_01535_01797 + - 2021.05.28.14.50.57_veh-12_01808_02244 + - 2021.05.28.14.50.57_veh-12_02255_02467 + - 2021.05.28.14.50.57_veh-12_02478_02754 + - 2021.05.28.14.50.57_veh-12_02765_02913 + - 2021.05.28.14.50.57_veh-12_02924_03094 + - 2021.05.28.14.50.57_veh-12_03144_03330 + - 2021.05.28.14.50.57_veh-12_03343_03661 + - 2021.05.28.14.50.57_veh-12_03672_04081 + - 2021.05.28.14.50.57_veh-12_04092_04223 + - 2021.05.28.14.50.57_veh-12_04246_04399 + - 2021.05.28.14.50.57_veh-12_04410_04504 + - 2021.05.28.14.50.57_veh-12_04515_04611 + - 2021.05.28.14.50.57_veh-12_04655_05008 + - 2021.05.28.14.50.57_veh-12_05019_05087 + - 2021.05.28.14.50.57_veh-12_05099_05219 + - 2021.05.28.14.50.57_veh-12_05231_05306 + - 2021.05.28.16.10.40_veh-47_00070_00149 + - 2021.05.28.16.10.40_veh-47_00160_00770 + - 2021.05.28.16.10.40_veh-47_00781_01079 + - 2021.05.28.16.10.40_veh-47_01090_01191 + - 2021.05.28.16.10.40_veh-47_01250_01682 + - 2021.05.28.16.10.40_veh-47_01820_02131 + - 2021.05.28.16.10.40_veh-47_02149_02400 + - 2021.05.28.16.10.40_veh-47_02411_02518 + - 2021.05.28.16.10.40_veh-47_02529_02716 + - 2021.05.28.16.10.40_veh-47_02765_03075 + - 2021.05.28.16.10.40_veh-47_03086_03154 + - 2021.05.28.16.10.40_veh-47_03174_03539 + - 2021.05.28.16.10.40_veh-47_03570_04225 + - 2021.05.28.16.10.40_veh-47_04299_04471 + - 2021.05.28.16.10.40_veh-47_04482_04704 + - 2021.05.28.16.10.40_veh-47_04715_04884 + - 2021.05.28.16.10.40_veh-47_04895_05228 + - 2021.05.28.16.10.40_veh-47_05254_05411 + - 2021.05.28.16.28.19_veh-35_00016_00261 + - 2021.05.28.16.28.19_veh-35_00272_00409 + - 2021.05.28.16.28.19_veh-35_00420_00621 + - 2021.05.28.16.28.19_veh-35_00632_00819 + - 2021.05.28.16.28.19_veh-35_00841_00924 + - 2021.05.28.16.28.19_veh-35_00935_01203 + - 2021.05.28.16.28.19_veh-35_01214_01756 + - 2021.05.28.16.28.19_veh-35_01806_01952 + - 2021.05.28.16.28.19_veh-35_01963_02115 + - 2021.05.28.16.28.19_veh-35_02126_02385 + - 2021.05.28.16.28.19_veh-35_02396_02491 + - 2021.05.28.16.28.19_veh-35_02502_02696 + - 2021.05.28.16.28.19_veh-35_02707_02819 + - 2021.05.28.16.28.19_veh-35_02830_02966 + - 2021.05.28.16.28.19_veh-35_02977_03195 + - 2021.05.28.16.28.19_veh-35_03206_03513 + - 2021.05.28.16.28.19_veh-35_03567_03702 + - 2021.05.28.16.28.19_veh-35_03713_04078 + - 2021.05.28.16.28.19_veh-35_04090_04190 + - 2021.05.28.16.28.19_veh-35_04201_04271 + - 2021.05.28.16.28.19_veh-35_04350_04856 + - 2021.05.28.16.28.19_veh-35_04958_05319 + - 2021.05.28.17.49.23_veh-47_00016_00293 + - 2021.05.28.17.49.23_veh-47_00304_01082 + - 2021.05.28.17.49.23_veh-47_01120_01252 + - 2021.05.28.17.49.23_veh-47_01263_01596 + - 2021.05.28.17.49.23_veh-47_01654_02033 + - 2021.05.28.17.49.23_veh-47_02044_02699 + - 2021.05.28.17.49.23_veh-47_02710_02823 + - 2021.05.28.17.49.23_veh-47_02834_03438 + - 2021.05.28.17.49.23_veh-47_03481_04053 + - 2021.05.28.17.49.23_veh-47_04064_04188 + - 2021.05.28.17.49.23_veh-47_04199_04460 + - 2021.05.28.17.49.23_veh-47_04471_04654 + - 2021.05.28.17.49.23_veh-47_04665_04728 + - 2021.05.28.17.49.23_veh-47_04740_05223 + - 2021.05.28.17.49.23_veh-47_05234_05583 + - 2021.05.28.17.49.23_veh-47_05594_05794 + - 2021.05.28.17.49.23_veh-47_05834_05954 + - 2021.05.28.18.05.52_veh-35_00024_00300 + - 2021.05.28.18.05.52_veh-35_00311_00449 + - 2021.05.28.18.05.52_veh-35_00460_00535 + - 2021.05.28.18.05.52_veh-35_00726_00866 + - 2021.05.28.18.05.52_veh-35_00877_00955 + - 2021.05.28.18.05.52_veh-35_00966_01080 + - 2021.05.28.18.05.52_veh-35_01129_01480 + - 2021.05.28.18.05.52_veh-35_01491_01737 + - 2021.05.28.18.05.52_veh-35_01748_01914 + - 2021.05.28.18.05.52_veh-35_01961_02184 + - 2021.05.28.18.05.52_veh-35_02218_02373 + - 2021.05.28.18.05.52_veh-35_02452_02554 + - 2021.05.28.18.05.52_veh-35_02632_02966 + - 2021.05.28.18.05.52_veh-35_02977_03205 + - 2021.05.28.18.05.52_veh-35_03238_03333 + - 2021.05.28.18.05.52_veh-35_03384_03506 + - 2021.05.28.18.05.52_veh-35_03517_03690 + - 2021.05.28.18.05.52_veh-35_03701_03788 + - 2021.05.28.18.05.52_veh-35_03878_03954 + - 2021.05.28.18.05.52_veh-35_03965_04031 + - 2021.05.28.18.05.52_veh-35_04083_04273 + - 2021.05.28.18.05.52_veh-35_04309_04443 + - 2021.05.28.18.05.52_veh-35_04512_04626 + - 2021.05.28.18.05.52_veh-35_04713_04812 + - 2021.05.28.18.05.52_veh-35_04896_05251 + - 2021.05.28.18.05.52_veh-35_05333_05628 + - 2021.05.28.18.05.52_veh-35_05639_05779 + - 2021.05.28.18.05.52_veh-35_05790_05859 + - 2021.05.28.18.05.53_veh-30_00016_00168 + - 2021.05.28.18.05.53_veh-30_00179_00583 + - 2021.05.28.18.05.53_veh-30_00613_00747 + - 2021.05.28.18.05.53_veh-30_00759_01099 + - 2021.05.28.18.05.53_veh-30_01133_01454 + - 2021.05.28.18.05.53_veh-30_01465_01908 + - 2021.05.28.18.05.53_veh-30_01920_02079 + - 2021.05.28.18.05.53_veh-30_02090_02152 + - 2021.05.28.18.05.53_veh-30_02163_02562 + - 2021.05.28.18.05.53_veh-30_02644_02737 + - 2021.05.28.18.05.53_veh-30_02748_03209 + - 2021.05.28.18.05.53_veh-30_03220_03359 + - 2021.05.28.18.05.53_veh-30_03370_03741 + - 2021.05.28.18.05.53_veh-30_03752_04145 + - 2021.05.28.18.05.53_veh-30_04158_04881 + - 2021.05.28.18.24.37_veh-12_00016_00588 + - 2021.05.28.18.24.37_veh-12_00627_00917 + - 2021.05.28.18.24.37_veh-12_00928_01041 + - 2021.05.28.18.24.37_veh-12_01092_01159 + - 2021.05.28.18.24.37_veh-12_01170_01402 + - 2021.05.28.18.24.37_veh-12_01414_01567 + - 2021.05.28.18.24.37_veh-12_01621_01725 + - 2021.05.28.18.24.37_veh-12_01806_02100 + - 2021.05.28.18.24.37_veh-12_02173_02853 + - 2021.05.28.18.24.37_veh-12_03034_03283 + - 2021.05.28.18.24.37_veh-12_03442_04048 + - 2021.05.28.18.24.37_veh-12_04121_04268 + - 2021.05.28.18.24.37_veh-12_04419_04531 + - 2021.05.28.18.24.37_veh-12_04635_04894 + - 2021.05.28.18.24.37_veh-12_04905_04967 + - 2021.05.28.18.24.37_veh-12_04990_05109 + - 2021.05.28.18.24.37_veh-12_05199_05540 + - 2021.05.28.18.24.37_veh-12_05551_05808 + - 2021.05.28.18.24.37_veh-12_05932_05995 + - 2021.05.28.18.24.37_veh-12_06006_06138 + - 2021.05.28.18.44.37_veh-16_00005_00258 + - 2021.05.28.18.44.37_veh-16_00269_00366 + - 2021.05.28.18.44.37_veh-16_00377_00571 + - 2021.05.28.18.44.37_veh-16_00644_01023 + - 2021.05.28.18.44.37_veh-16_01055_01365 + - 2021.05.28.18.44.37_veh-16_01376_01524 + - 2021.05.28.18.44.37_veh-16_01536_01634 + - 2021.05.28.18.44.37_veh-16_01645_02209 + - 2021.05.28.18.44.37_veh-16_02228_02384 + - 2021.05.28.18.44.37_veh-16_02465_02564 + - 2021.05.28.18.44.37_veh-16_02575_02694 + - 2021.05.28.18.44.37_veh-16_02705_02796 + - 2021.05.28.18.44.37_veh-16_02874_02989 + - 2021.05.28.18.44.37_veh-16_03000_03417 + - 2021.05.28.18.44.37_veh-16_03450_03532 + - 2021.05.28.18.44.37_veh-16_03543_04342 + - 2021.05.28.18.44.37_veh-16_04353_04536 + - 2021.05.28.18.44.37_veh-16_04547_04780 + - 2021.05.28.18.44.37_veh-16_04805_04941 + - 2021.05.28.18.44.37_veh-16_04996_05110 + - 2021.05.28.18.44.37_veh-16_05121_05301 + - 2021.05.28.19.34.43_veh-47_00057_00264 + - 2021.05.28.19.34.43_veh-47_00295_00406 + - 2021.05.28.19.34.43_veh-47_00417_00696 + - 2021.05.28.19.34.43_veh-47_00751_00858 + - 2021.05.28.19.34.43_veh-47_00927_01387 + - 2021.05.28.19.46.09_veh-30_00016_00207 + - 2021.05.28.19.46.09_veh-30_00228_00437 + - 2021.05.28.19.46.09_veh-30_00448_00791 + - 2021.05.28.19.46.09_veh-30_00802_00918 + - 2021.05.28.19.46.09_veh-30_00938_01047 + - 2021.05.28.19.46.09_veh-30_01058_01134 + - 2021.05.28.19.46.09_veh-30_01145_01260 + - 2021.05.28.19.46.09_veh-30_01271_01561 + - 2021.05.28.21.56.29_veh-24_00005_01617 + - 2021.06.01.12.00.24_veh-35_00118_00238 + - 2021.06.01.12.00.24_veh-35_00249_00418 + - 2021.06.01.12.00.24_veh-35_00460_00582 + - 2021.06.01.12.00.24_veh-35_00593_00738 + - 2021.06.01.12.00.24_veh-35_00764_00870 + - 2021.06.01.12.00.24_veh-35_00886_00966 + - 2021.06.01.12.00.24_veh-35_00977_01092 + - 2021.06.01.12.00.24_veh-35_01286_01486 + - 2021.06.01.12.00.24_veh-35_01511_01640 + - 2021.06.01.12.00.24_veh-35_01758_01951 + - 2021.06.01.12.00.24_veh-35_01969_02150 + - 2021.06.01.12.00.24_veh-35_02161_02319 + - 2021.06.01.12.00.24_veh-35_02330_02400 + - 2021.06.01.12.00.24_veh-35_02472_02629 + - 2021.06.01.12.00.24_veh-35_02640_02753 + - 2021.06.01.12.00.24_veh-35_02776_02845 + - 2021.06.01.12.00.24_veh-35_03166_03328 + - 2021.06.01.12.00.24_veh-35_03377_03496 + - 2021.06.01.12.00.24_veh-35_03507_03841 + - 2021.06.01.12.00.24_veh-35_03906_04019 + - 2021.06.01.12.00.24_veh-35_04114_04179 + - 2021.06.01.12.00.24_veh-35_04299_04448 + - 2021.06.01.12.00.24_veh-35_04466_04854 + - 2021.06.01.12.00.24_veh-35_04865_04932 + - 2021.06.01.12.25.35_veh-38_00015_00130 + - 2021.06.01.12.25.35_veh-38_00141_00233 + - 2021.06.01.12.25.35_veh-38_00353_00426 + - 2021.06.01.12.25.35_veh-38_00600_01079 + - 2021.06.01.12.25.35_veh-38_01090_01206 + - 2021.06.01.12.25.35_veh-38_01217_01383 + - 2021.06.01.12.25.35_veh-38_01394_01466 + - 2021.06.01.12.25.35_veh-38_01477_01732 + - 2021.06.01.12.25.35_veh-38_01831_01944 + - 2021.06.01.12.25.35_veh-38_02017_02380 + - 2021.06.01.12.25.35_veh-38_02391_02461 + - 2021.06.01.12.25.35_veh-38_02472_02600 + - 2021.06.01.12.25.35_veh-38_02611_02936 + - 2021.06.01.12.25.35_veh-38_02963_03136 + - 2021.06.01.12.25.35_veh-38_03161_03302 + - 2021.06.01.12.25.35_veh-38_03313_03629 + - 2021.06.01.12.25.35_veh-38_03640_03801 + - 2021.06.01.12.25.35_veh-38_03812_03965 + - 2021.06.01.12.25.35_veh-38_04011_04075 + - 2021.06.01.12.25.35_veh-38_04086_04217 + - 2021.06.01.12.25.35_veh-38_04228_04309 + - 2021.06.01.12.25.35_veh-38_04320_04425 + - 2021.06.01.12.25.35_veh-38_04498_04594 + - 2021.06.01.12.25.35_veh-38_04629_04855 + - 2021.06.01.12.25.35_veh-38_04984_05091 + - 2021.06.01.12.25.35_veh-38_05102_05251 + - 2021.06.01.12.27.59_veh-12_00162_00316 + - 2021.06.01.12.27.59_veh-12_00396_00480 + - 2021.06.01.12.27.59_veh-12_00491_00614 + - 2021.06.01.12.27.59_veh-12_00681_00786 + - 2021.06.01.12.27.59_veh-12_00797_00880 + - 2021.06.01.12.27.59_veh-12_00947_01152 + - 2021.06.01.12.27.59_veh-12_01304_01379 + - 2021.06.01.12.27.59_veh-12_01457_01596 + - 2021.06.01.12.27.59_veh-12_01694_01766 + - 2021.06.01.12.27.59_veh-12_01831_01952 + - 2021.06.01.12.27.59_veh-12_02132_02275 + - 2021.06.01.12.27.59_veh-12_02286_02415 + - 2021.06.01.12.27.59_veh-12_02426_02726 + - 2021.06.01.12.27.59_veh-12_02737_03282 + - 2021.06.01.12.27.59_veh-12_03293_03387 + - 2021.06.01.12.27.59_veh-12_03398_03650 + - 2021.06.01.12.27.59_veh-12_03661_04021 + - 2021.06.01.12.27.59_veh-12_04033_04212 + - 2021.06.01.12.27.59_veh-12_04235_04310 + - 2021.06.01.12.27.59_veh-12_04321_05129 + - 2021.06.01.12.28.28_veh-47_00005_00136 + - 2021.06.01.12.28.28_veh-47_00191_00283 + - 2021.06.01.12.28.28_veh-47_00294_00617 + - 2021.06.01.12.28.28_veh-47_00710_00840 + - 2021.06.01.12.28.28_veh-47_00851_01026 + - 2021.06.01.12.28.28_veh-47_01037_01216 + - 2021.06.01.12.28.28_veh-47_01227_01318 + - 2021.06.01.12.28.28_veh-47_01329_01896 + - 2021.06.01.12.28.28_veh-47_01908_02357 + - 2021.06.01.12.28.28_veh-47_02446_02562 + - 2021.06.01.12.28.28_veh-47_02654_02771 + - 2021.06.01.12.28.28_veh-47_02797_02900 + - 2021.06.01.12.28.28_veh-47_02988_03352 + - 2021.06.01.12.28.28_veh-47_03363_03596 + - 2021.06.01.12.28.28_veh-47_03607_04071 + - 2021.06.01.12.28.28_veh-47_04090_04228 + - 2021.06.01.12.28.28_veh-47_04239_04319 + - 2021.06.01.12.28.28_veh-47_04330_04666 + - 2021.06.01.12.28.28_veh-47_04677_04770 + - 2021.06.01.12.28.28_veh-47_04781_05116 + - 2021.06.01.12.28.28_veh-47_05241_05342 + - 2021.06.01.12.28.28_veh-47_05353_05572 + - 2021.06.01.13.10.06_veh-16_00016_00077 + - 2021.06.01.13.10.06_veh-16_00094_00541 + - 2021.06.01.13.10.06_veh-16_00611_00770 + - 2021.06.01.13.10.06_veh-16_00841_01336 + - 2021.06.01.13.10.06_veh-16_01347_01445 + - 2021.06.01.13.10.06_veh-16_01456_02861 + - 2021.06.01.13.10.06_veh-16_02872_03369 + - 2021.06.01.13.10.06_veh-16_03380_03474 + - 2021.06.01.13.10.06_veh-16_03485_03959 + - 2021.06.01.13.10.06_veh-16_03970_04251 + - 2021.06.01.13.10.06_veh-16_04307_04561 + - 2021.06.01.13.10.06_veh-16_04572_04650 + - 2021.06.01.13.10.06_veh-16_04706_04941 + - 2021.06.01.13.10.06_veh-16_04952_05022 + - 2021.06.01.13.47.32_veh-35_00005_00088 + - 2021.06.01.13.47.32_veh-35_00149_00493 + - 2021.06.01.13.47.32_veh-35_00504_00651 + - 2021.06.01.13.47.32_veh-35_00662_01050 + - 2021.06.01.13.47.32_veh-35_01074_01258 + - 2021.06.01.13.47.32_veh-35_01270_02044 + - 2021.06.01.13.47.32_veh-35_02055_02163 + - 2021.06.01.13.47.32_veh-35_02245_02358 + - 2021.06.01.13.47.32_veh-35_02369_02503 + - 2021.06.01.13.47.32_veh-35_02514_02613 + - 2021.06.01.13.47.32_veh-35_02624_03019 + - 2021.06.01.13.47.32_veh-35_03030_03119 + - 2021.06.01.13.47.32_veh-35_03130_03273 + - 2021.06.01.13.47.32_veh-35_03284_03407 + - 2021.06.01.13.47.32_veh-35_03437_04412 + - 2021.06.01.13.47.32_veh-35_04423_05065 + - 2021.06.01.13.47.32_veh-35_05076_05162 + - 2021.06.01.13.47.32_veh-35_05176_05259 + - 2021.06.01.13.47.32_veh-35_05276_05667 + - 2021.06.01.14.11.47_veh-47_00016_00156 + - 2021.06.01.14.11.47_veh-47_00167_00343 + - 2021.06.01.14.11.47_veh-47_00354_00433 + - 2021.06.01.14.11.47_veh-47_00444_00518 + - 2021.06.01.14.11.47_veh-47_00529_00733 + - 2021.06.01.14.11.47_veh-47_00744_01002 + - 2021.06.01.14.11.47_veh-47_01013_01170 + - 2021.06.01.14.11.47_veh-47_01183_01330 + - 2021.06.01.14.11.47_veh-47_01342_01668 + - 2021.06.01.14.11.47_veh-47_01679_01968 + - 2021.06.01.14.11.47_veh-47_02059_02196 + - 2021.06.01.14.11.47_veh-47_02207_02304 + - 2021.06.01.14.11.47_veh-47_02315_02658 + - 2021.06.01.14.11.47_veh-47_02735_02806 + - 2021.06.01.14.11.47_veh-47_02831_02929 + - 2021.06.01.14.11.47_veh-47_02940_03001 + - 2021.06.01.14.11.47_veh-47_03033_03549 + - 2021.06.01.14.11.47_veh-47_03604_03854 + - 2021.06.01.14.11.47_veh-47_03865_03968 + - 2021.06.01.14.11.47_veh-47_03979_04098 + - 2021.06.01.14.11.47_veh-47_04109_04353 + - 2021.06.01.14.11.47_veh-47_04402_04515 + - 2021.06.01.14.11.47_veh-47_04526_04588 + - 2021.06.01.14.25.10_veh-38_00189_00251 + - 2021.06.01.14.25.10_veh-38_00262_00364 + - 2021.06.01.14.25.10_veh-38_00386_00454 + - 2021.06.01.14.25.10_veh-38_00488_00723 + - 2021.06.01.14.25.10_veh-38_00899_01033 + - 2021.06.01.14.25.10_veh-38_01044_01114 + - 2021.06.01.14.25.10_veh-38_01127_01284 + - 2021.06.01.14.25.10_veh-38_01296_01452 + - 2021.06.01.14.25.10_veh-38_01602_01717 + - 2021.06.01.14.25.10_veh-38_01755_02111 + - 2021.06.01.14.25.10_veh-38_02167_02328 + - 2021.06.01.14.25.10_veh-38_02396_02576 + - 2021.06.01.14.25.10_veh-38_02682_02770 + - 2021.06.01.14.25.10_veh-38_02936_03011 + - 2021.06.01.14.25.10_veh-38_03022_03412 + - 2021.06.01.14.25.10_veh-38_03475_03736 + - 2021.06.01.14.25.10_veh-38_03844_03931 + - 2021.06.01.14.25.10_veh-38_03942_04033 + - 2021.06.01.14.25.10_veh-38_04081_04155 + - 2021.06.01.14.25.10_veh-38_04166_04301 + - 2021.06.01.14.25.10_veh-38_04394_04464 + - 2021.06.01.14.25.10_veh-38_04623_04702 + - 2021.06.01.14.25.10_veh-38_04740_04847 + - 2021.06.01.14.25.10_veh-38_04946_05307 + - 2021.06.01.14.25.10_veh-38_05371_05475 + - 2021.06.01.14.25.10_veh-38_05570_05632 + - 2021.06.01.14.25.10_veh-38_05709_05785 + - 2021.06.01.14.26.18_veh-12_00005_00087 + - 2021.06.01.14.26.18_veh-12_00203_00359 + - 2021.06.01.14.26.18_veh-12_00370_00559 + - 2021.06.01.14.26.18_veh-12_00578_00659 + - 2021.06.01.14.26.18_veh-12_00723_00831 + - 2021.06.01.14.26.18_veh-12_00919_01149 + - 2021.06.01.14.26.18_veh-12_01161_01233 + - 2021.06.01.14.26.18_veh-12_01279_01572 + - 2021.06.01.14.26.18_veh-12_01612_01717 + - 2021.06.01.14.26.18_veh-12_01788_02113 + - 2021.06.01.14.26.18_veh-12_02141_02335 + - 2021.06.01.14.26.18_veh-12_02360_02850 + - 2021.06.01.14.26.18_veh-12_02861_03011 + - 2021.06.01.14.26.18_veh-12_03022_03289 + - 2021.06.01.14.26.18_veh-12_03300_03402 + - 2021.06.01.14.26.18_veh-12_03413_03485 + - 2021.06.01.14.26.18_veh-12_03498_03577 + - 2021.06.01.14.26.18_veh-12_03588_03724 + - 2021.06.01.14.26.18_veh-12_03749_04705 + - 2021.06.01.14.26.18_veh-12_04716_04838 + - 2021.06.01.14.26.18_veh-12_04849_05096 + - 2021.06.01.14.26.18_veh-12_05153_05306 + - 2021.06.01.16.57.36_veh-35_00016_00135 + - 2021.06.01.16.57.36_veh-35_00146_00755 + - 2021.06.01.16.57.36_veh-35_00826_00965 + - 2021.06.01.16.57.36_veh-35_00976_01092 + - 2021.06.01.16.57.36_veh-35_01156_01415 + - 2021.06.01.16.57.36_veh-35_01426_01790 + - 2021.06.01.16.57.36_veh-35_01956_02429 + - 2021.06.01.16.57.36_veh-35_02440_02668 + - 2021.06.01.16.57.36_veh-35_02679_02890 + - 2021.06.01.16.57.36_veh-35_02901_03186 + - 2021.06.01.16.57.36_veh-35_03197_03274 + - 2021.06.01.16.57.36_veh-35_03285_03410 + - 2021.06.01.16.57.36_veh-35_03593_03748 + - 2021.06.01.16.57.36_veh-35_03759_04161 + - 2021.06.01.16.57.36_veh-35_04239_04379 + - 2021.06.01.16.57.36_veh-35_04417_04595 + - 2021.06.01.16.57.36_veh-35_04676_05004 + - 2021.06.01.16.57.36_veh-35_05015_05413 + - 2021.06.01.17.07.08_veh-16_00005_00213 + - 2021.06.01.17.07.08_veh-16_00246_00613 + - 2021.06.01.17.07.08_veh-16_00649_00828 + - 2021.06.01.17.07.08_veh-16_00839_01009 + - 2021.06.01.17.07.08_veh-16_01054_01127 + - 2021.06.01.17.07.08_veh-16_01138_01409 + - 2021.06.01.17.07.08_veh-16_01420_01618 + - 2021.06.01.17.07.08_veh-16_01680_01805 + - 2021.06.01.17.07.08_veh-16_01831_01983 + - 2021.06.01.17.07.08_veh-16_01994_02106 + - 2021.06.01.17.07.08_veh-16_02123_02191 + - 2021.06.01.17.07.08_veh-16_02202_02267 + - 2021.06.01.17.07.08_veh-16_02278_02498 + - 2021.06.01.17.07.08_veh-16_02509_02637 + - 2021.06.01.17.07.08_veh-16_02704_02856 + - 2021.06.01.17.07.08_veh-16_02900_03022 + - 2021.06.01.17.07.08_veh-16_03033_03093 + - 2021.06.01.17.07.08_veh-16_03207_03341 + - 2021.06.01.17.07.08_veh-16_03380_03443 + - 2021.06.01.17.07.08_veh-16_03562_03663 + - 2021.06.01.17.07.08_veh-16_03674_04630 + - 2021.06.01.17.07.08_veh-16_04641_04933 + - 2021.06.01.17.07.08_veh-16_04944_05147 + - 2021.06.01.17.27.29_veh-47_00005_00096 + - 2021.06.01.17.27.29_veh-47_00107_00403 + - 2021.06.01.17.27.29_veh-47_00414_00716 + - 2021.06.01.17.27.29_veh-47_00727_00815 + - 2021.06.01.17.27.29_veh-47_00826_00906 + - 2021.06.01.17.27.29_veh-47_00917_00985 + - 2021.06.01.17.27.29_veh-47_00996_01197 + - 2021.06.01.17.27.29_veh-47_01208_01485 + - 2021.06.01.17.27.29_veh-47_01544_02101 + - 2021.06.01.17.27.29_veh-47_02112_02235 + - 2021.06.01.17.27.29_veh-47_02246_02791 + - 2021.06.01.17.27.29_veh-47_02849_03440 + - 2021.06.01.17.27.29_veh-47_03451_03515 + - 2021.06.01.17.27.29_veh-47_03595_03672 + - 2021.06.01.17.27.29_veh-47_03683_04423 + - 2021.06.01.17.27.29_veh-47_04434_04805 + - 2021.06.01.17.27.29_veh-47_04862_05024 + - 2021.06.01.17.27.29_veh-47_05053_05145 + - 2021.06.01.17.27.29_veh-47_05184_05397 + - 2021.06.01.17.43.02_veh-38_00046_00307 + - 2021.06.01.17.43.02_veh-38_00352_00762 + - 2021.06.01.17.43.02_veh-38_00773_01085 + - 2021.06.01.17.43.02_veh-38_01096_01239 + - 2021.06.01.17.43.02_veh-38_01251_01629 + - 2021.06.01.17.43.02_veh-38_01640_01900 + - 2021.06.01.17.43.02_veh-38_01911_02028 + - 2021.06.01.17.43.02_veh-38_02069_02536 + - 2021.06.01.17.43.02_veh-38_02547_02631 + - 2021.06.01.17.43.02_veh-38_02665_02983 + - 2021.06.01.17.43.02_veh-38_02994_03463 + - 2021.06.01.17.43.02_veh-38_03474_03586 + - 2021.06.01.17.43.02_veh-38_03618_03776 + - 2021.06.01.17.43.02_veh-38_03803_04163 + - 2021.06.01.17.43.02_veh-38_04174_04342 + - 2021.06.01.17.43.02_veh-38_04353_05317 + - 2021.06.01.18.47.18_veh-35_00034_00429 + - 2021.06.01.18.47.18_veh-35_00440_00508 + - 2021.06.01.18.47.18_veh-35_00519_00639 + - 2021.06.01.18.47.18_veh-35_00650_00717 + - 2021.06.01.18.47.18_veh-35_00728_01039 + - 2021.06.01.18.47.18_veh-35_01076_01240 + - 2021.06.01.18.47.18_veh-35_01251_01809 + - 2021.06.01.18.47.18_veh-35_01830_02131 + - 2021.06.01.18.47.18_veh-35_02156_02398 + - 2021.06.01.18.47.18_veh-35_02416_02557 + - 2021.06.01.18.47.18_veh-35_02568_02847 + - 2021.06.01.18.47.18_veh-35_02858_03265 + - 2021.06.01.18.47.18_veh-35_03276_03427 + - 2021.06.01.18.47.18_veh-35_03438_03756 + - 2021.06.01.18.47.18_veh-35_03767_03888 + - 2021.06.01.18.47.18_veh-35_03950_04054 + - 2021.06.01.18.47.18_veh-35_04065_04189 + - 2021.06.01.18.47.18_veh-35_04300_05244 + - 2021.06.01.18.56.11_veh-12_00066_00890 + - 2021.06.01.18.56.11_veh-12_00901_01075 + - 2021.06.01.18.56.11_veh-12_01086_01314 + - 2021.06.01.18.56.11_veh-12_01325_01435 + - 2021.06.01.18.56.11_veh-12_01446_01624 + - 2021.06.01.18.56.11_veh-12_01699_02219 + - 2021.06.01.18.56.11_veh-12_02317_02430 + - 2021.06.01.18.56.11_veh-12_02441_02570 + - 2021.06.01.18.56.11_veh-12_02581_02645 + - 2021.06.01.18.56.11_veh-12_02656_02841 + - 2021.06.01.18.56.11_veh-12_02871_03000 + - 2021.06.01.18.56.11_veh-12_03068_03387 + - 2021.06.01.18.56.11_veh-12_03463_03592 + - 2021.06.01.19.14.07_veh-47_00070_00644 + - 2021.06.01.19.14.07_veh-47_00715_00821 + - 2021.06.01.19.14.07_veh-47_00832_00914 + - 2021.06.01.19.14.07_veh-47_01024_01134 + - 2021.06.01.19.14.07_veh-47_01145_01219 + - 2021.06.01.19.14.07_veh-47_01230_01309 + - 2021.06.01.19.14.07_veh-47_01320_01548 + - 2021.06.01.19.14.07_veh-47_01595_01755 + - 2021.06.01.19.14.07_veh-47_01776_01903 + - 2021.06.01.19.14.07_veh-47_01933_02044 + - 2021.06.01.19.14.07_veh-47_02079_02299 + - 2021.06.01.19.14.07_veh-47_02329_02532 + - 2021.06.01.19.14.07_veh-47_02543_02681 + - 2021.06.01.19.14.07_veh-47_02692_02854 + - 2021.06.01.19.14.07_veh-47_02865_02932 + - 2021.06.01.19.14.07_veh-47_02973_03049 + - 2021.06.01.19.14.07_veh-47_03060_03204 + - 2021.06.01.19.14.07_veh-47_03224_03467 + - 2021.06.01.19.14.07_veh-47_03478_03544 + - 2021.06.01.19.14.07_veh-47_03555_03790 + - 2021.06.01.19.14.07_veh-47_03801_03924 + - 2021.06.01.19.14.07_veh-47_03935_04087 + - 2021.06.01.19.14.07_veh-47_04098_04385 + - 2021.06.01.19.39.30_veh-38_00091_00911 + - 2021.06.01.19.39.30_veh-38_00922_01034 + - 2021.06.01.19.39.30_veh-38_01046_01130 + - 2021.06.01.19.39.30_veh-38_01141_01257 + - 2021.06.01.19.39.30_veh-38_01323_01385 + - 2021.06.01.19.39.30_veh-38_01396_01795 + - 2021.06.01.19.39.30_veh-38_01832_02061 + - 2021.06.01.19.39.30_veh-38_02072_02170 + - 2021.06.01.19.39.30_veh-38_02181_02252 + - 2021.06.01.19.39.30_veh-38_02263_02804 + - 2021.06.02.12.25.02_veh-16_00005_00264 + - 2021.06.02.12.25.02_veh-16_00347_00704 + - 2021.06.02.12.25.02_veh-16_00761_00890 + - 2021.06.02.12.25.02_veh-16_00950_01167 + - 2021.06.02.12.25.02_veh-16_01178_01261 + - 2021.06.02.12.25.02_veh-16_01339_01475 + - 2021.06.02.12.25.02_veh-16_01549_01681 + - 2021.06.02.12.25.02_veh-16_01693_01986 + - 2021.06.02.12.25.02_veh-16_02016_02111 + - 2021.06.02.12.25.02_veh-16_02204_02341 + - 2021.06.02.12.25.02_veh-16_02354_02494 + - 2021.06.02.12.25.02_veh-16_02563_02635 + - 2021.06.02.12.25.02_veh-16_02665_02818 + - 2021.06.02.12.25.02_veh-16_02883_03222 + - 2021.06.02.12.25.02_veh-16_03324_03456 + - 2021.06.02.12.25.02_veh-16_03503_03573 + - 2021.06.02.12.25.02_veh-16_03651_03743 + - 2021.06.02.12.25.02_veh-16_03814_03930 + - 2021.06.02.12.25.02_veh-16_03941_04151 + - 2021.06.02.12.25.02_veh-16_04162_04286 + - 2021.06.02.12.25.02_veh-16_04427_04627 + - 2021.06.02.12.25.02_veh-16_04638_04739 + - 2021.06.02.12.25.02_veh-16_04819_05215 + - 2021.06.02.12.41.05_veh-47_00082_00210 + - 2021.06.02.12.41.05_veh-47_00221_00640 + - 2021.06.02.12.41.05_veh-47_00651_00789 + - 2021.06.02.12.41.05_veh-47_00800_01139 + - 2021.06.02.12.41.05_veh-47_01150_01227 + - 2021.06.02.12.41.05_veh-47_01238_01370 + - 2021.06.02.12.41.05_veh-47_01381_01455 + - 2021.06.02.12.41.05_veh-47_01549_02075 + - 2021.06.02.12.41.05_veh-47_02086_02256 + - 2021.06.02.12.41.05_veh-47_02390_02958 + - 2021.06.02.12.41.05_veh-47_02970_03143 + - 2021.06.02.12.41.05_veh-47_03154_03410 + - 2021.06.02.12.41.05_veh-47_03444_03662 + - 2021.06.02.12.41.05_veh-47_03673_03807 + - 2021.06.02.12.41.05_veh-47_03818_03960 + - 2021.06.02.12.41.05_veh-47_04041_04221 + - 2021.06.02.12.41.05_veh-47_04234_04371 + - 2021.06.02.12.41.05_veh-47_04383_04740 + - 2021.06.02.12.41.05_veh-47_04751_05192 + - 2021.06.02.12.41.05_veh-47_05204_05348 + - 2021.06.02.12.49.42_veh-38_00005_00072 + - 2021.06.02.12.49.42_veh-38_00169_00234 + - 2021.06.02.12.49.42_veh-38_00245_00485 + - 2021.06.02.12.49.42_veh-38_00496_00580 + - 2021.06.02.12.49.42_veh-38_00686_00829 + - 2021.06.02.12.49.42_veh-38_00840_01232 + - 2021.06.02.12.49.42_veh-38_01251_01429 + - 2021.06.02.12.49.42_veh-38_01548_01634 + - 2021.06.02.12.49.42_veh-38_01645_01717 + - 2021.06.02.12.49.42_veh-38_01747_01822 + - 2021.06.02.12.49.42_veh-38_01833_01899 + - 2021.06.02.12.49.42_veh-38_01910_02005 + - 2021.06.02.12.49.42_veh-38_02016_02296 + - 2021.06.02.12.49.42_veh-38_02307_02658 + - 2021.06.02.12.49.42_veh-38_02713_03139 + - 2021.06.02.12.49.42_veh-38_03150_03800 + - 2021.06.02.12.49.42_veh-38_03875_04010 + - 2021.06.02.12.49.42_veh-38_04021_04198 + - 2021.06.02.12.49.42_veh-38_04209_04355 + - 2021.06.02.12.49.42_veh-38_04410_04578 + - 2021.06.02.12.49.42_veh-38_04589_04817 + - 2021.06.02.12.49.42_veh-38_04866_05071 + - 2021.06.02.12.49.42_veh-38_05145_05237 + - 2021.06.02.12.54.34_veh-35_00016_00349 + - 2021.06.02.12.54.34_veh-35_00429_00532 + - 2021.06.02.12.54.34_veh-35_00650_00723 + - 2021.06.02.12.54.34_veh-35_00734_01011 + - 2021.06.02.12.54.34_veh-35_01166_01255 + - 2021.06.02.12.54.34_veh-35_01266_01340 + - 2021.06.02.12.54.34_veh-35_01351_02194 + - 2021.06.02.12.54.34_veh-35_02205_02508 + - 2021.06.02.12.54.34_veh-35_02567_03058 + - 2021.06.02.12.54.34_veh-35_03069_03337 + - 2021.06.02.12.54.34_veh-35_03348_03416 + - 2021.06.02.12.54.34_veh-35_03444_03575 + - 2021.06.02.12.54.34_veh-35_03586_03672 + - 2021.06.02.12.54.34_veh-35_03683_03744 + - 2021.06.02.12.54.34_veh-35_03755_03916 + - 2021.06.02.12.54.34_veh-35_03927_04143 + - 2021.06.02.12.54.34_veh-35_04154_04218 + - 2021.06.02.12.54.34_veh-35_04229_04360 + - 2021.06.02.12.54.34_veh-35_04371_04614 + - 2021.06.02.12.54.34_veh-35_04677_04797 + - 2021.06.02.12.54.34_veh-35_04861_05024 + - 2021.06.02.12.54.34_veh-35_05070_05221 + - 2021.06.02.12.54.34_veh-35_05232_05666 + - 2021.06.02.12.55.57_veh-12_00016_00170 + - 2021.06.02.12.55.57_veh-12_00230_00592 + - 2021.06.02.12.55.57_veh-12_00617_00838 + - 2021.06.02.12.55.57_veh-12_00943_01069 + - 2021.06.02.12.55.57_veh-12_01125_01191 + - 2021.06.02.12.55.57_veh-12_01202_01272 + - 2021.06.02.12.55.57_veh-12_01283_01578 + - 2021.06.02.12.55.57_veh-12_01618_01686 + - 2021.06.02.12.55.57_veh-12_01698_01810 + - 2021.06.02.12.55.57_veh-12_01951_02318 + - 2021.06.02.12.55.57_veh-12_02352_02448 + - 2021.06.02.12.55.57_veh-12_02502_02627 + - 2021.06.02.12.55.57_veh-12_02638_02803 + - 2021.06.02.12.55.57_veh-12_02825_02903 + - 2021.06.02.12.55.57_veh-12_03037_03263 + - 2021.06.02.12.55.57_veh-12_03274_03459 + - 2021.06.02.12.55.57_veh-12_03470_03727 + - 2021.06.02.12.55.57_veh-12_03749_03815 + - 2021.06.02.12.55.57_veh-12_03826_03896 + - 2021.06.02.12.55.57_veh-12_03959_04161 + - 2021.06.02.12.55.57_veh-12_04172_04317 + - 2021.06.02.12.55.57_veh-12_04328_04395 + - 2021.06.02.12.55.57_veh-12_04430_04547 + - 2021.06.02.12.55.57_veh-12_04746_04810 + - 2021.06.02.12.55.57_veh-12_04880_05042 + - 2021.06.02.12.55.57_veh-12_05053_05118 + - 2021.06.02.12.55.57_veh-12_05139_05231 + - 2021.06.02.12.55.57_veh-12_05299_05447 + - 2021.06.02.12.55.57_veh-12_05569_05677 + - 2021.06.02.12.55.57_veh-12_05688_06016 + - 2021.06.02.14.28.00_veh-16_00035_00148 + - 2021.06.02.14.28.00_veh-16_00159_00299 + - 2021.06.02.14.28.00_veh-16_00483_00800 + - 2021.06.02.14.28.00_veh-16_00866_01006 + - 2021.06.02.14.28.00_veh-16_01064_01191 + - 2021.06.02.14.28.00_veh-16_01238_01358 + - 2021.06.02.14.28.00_veh-16_01436_01614 + - 2021.06.02.14.28.00_veh-16_01705_01851 + - 2021.06.02.14.28.00_veh-16_01934_02003 + - 2021.06.02.14.28.00_veh-16_02018_02160 + - 2021.06.02.14.28.00_veh-16_02240_02300 + - 2021.06.02.14.28.00_veh-16_02372_02443 + - 2021.06.02.14.28.00_veh-16_02454_02943 + - 2021.06.02.14.33.41_veh-47_00016_00087 + - 2021.06.02.14.33.41_veh-47_00098_00516 + - 2021.06.02.14.33.41_veh-47_00527_00638 + - 2021.06.02.14.33.41_veh-47_00649_01011 + - 2021.06.02.14.33.41_veh-47_01022_01116 + - 2021.06.02.14.33.41_veh-47_01127_01323 + - 2021.06.02.14.33.41_veh-47_01334_01500 + - 2021.06.02.14.33.41_veh-47_01581_01707 + - 2021.06.02.14.33.41_veh-47_01718_02276 + - 2021.06.02.14.33.41_veh-47_02287_02524 + - 2021.06.02.14.33.41_veh-47_02598_02687 + - 2021.06.02.14.33.41_veh-47_02783_03103 + - 2021.06.02.14.33.41_veh-47_03149_03259 + - 2021.06.02.14.33.41_veh-47_03270_03332 + - 2021.06.02.14.33.41_veh-47_03343_03415 + - 2021.06.02.14.33.41_veh-47_03426_03502 + - 2021.06.02.14.33.41_veh-47_03513_03787 + - 2021.06.02.14.33.41_veh-47_03798_04439 + - 2021.06.02.14.33.41_veh-47_04507_04584 + - 2021.06.02.14.33.41_veh-47_04595_04848 + - 2021.06.02.14.33.41_veh-47_04859_05063 + - 2021.06.02.14.33.41_veh-47_05074_05434 + - 2021.06.02.14.33.41_veh-47_05445_05613 + - 2021.06.02.14.43.48_veh-38_00005_00103 + - 2021.06.02.14.43.48_veh-38_00115_00795 + - 2021.06.02.14.43.48_veh-38_00823_00890 + - 2021.06.02.14.43.48_veh-38_00901_01741 + - 2021.06.02.14.43.48_veh-38_01752_01844 + - 2021.06.02.14.43.48_veh-38_01931_02107 + - 2021.06.02.14.43.48_veh-38_02118_02331 + - 2021.06.02.14.43.48_veh-38_02342_02542 + - 2021.06.02.14.43.48_veh-38_02575_02738 + - 2021.06.02.14.43.48_veh-38_02749_02855 + - 2021.06.02.14.43.48_veh-38_02866_03097 + - 2021.06.02.14.43.48_veh-38_03139_03403 + - 2021.06.02.14.43.48_veh-38_03414_03494 + - 2021.06.02.14.43.48_veh-38_03538_03791 + - 2021.06.02.14.43.48_veh-38_03883_04285 + - 2021.06.02.14.43.48_veh-38_04296_04455 + - 2021.06.02.14.43.48_veh-38_04466_04616 + - 2021.06.02.14.43.48_veh-38_04627_04797 + - 2021.06.02.14.43.48_veh-38_04808_05042 + - 2021.06.02.14.43.48_veh-38_05065_05260 + - 2021.06.02.14.43.48_veh-38_05278_05387 + - 2021.06.02.14.52.21_veh-35_00005_00157 + - 2021.06.02.14.52.21_veh-35_00168_00514 + - 2021.06.02.14.52.21_veh-35_00525_00609 + - 2021.06.02.14.52.21_veh-35_00708_00923 + - 2021.06.02.14.52.21_veh-35_00934_01086 + - 2021.06.02.14.52.21_veh-35_01097_01175 + - 2021.06.02.14.52.21_veh-35_01187_01272 + - 2021.06.02.14.52.21_veh-35_01283_01462 + - 2021.06.02.14.52.21_veh-35_01473_01586 + - 2021.06.02.14.52.21_veh-35_01597_01672 + - 2021.06.02.14.52.21_veh-35_01683_01860 + - 2021.06.02.14.52.21_veh-35_01871_02047 + - 2021.06.02.14.52.21_veh-35_02058_02207 + - 2021.06.02.14.52.21_veh-35_02259_02350 + - 2021.06.02.14.52.21_veh-35_02403_02531 + - 2021.06.02.14.52.21_veh-35_02542_02788 + - 2021.06.02.14.52.21_veh-35_02836_02928 + - 2021.06.02.14.52.21_veh-35_02978_03182 + - 2021.06.02.14.52.21_veh-35_03193_03341 + - 2021.06.02.14.52.21_veh-35_03408_03483 + - 2021.06.02.14.52.21_veh-35_03494_03574 + - 2021.06.02.14.52.21_veh-35_03665_04028 + - 2021.06.02.14.52.21_veh-35_04039_04112 + - 2021.06.02.14.52.21_veh-35_04123_04337 + - 2021.06.02.14.52.21_veh-35_04348_04884 + - 2021.06.02.14.52.21_veh-35_04895_05042 + - 2021.06.02.15.15.09_veh-12_00083_00226 + - 2021.06.02.15.15.09_veh-12_00237_00658 + - 2021.06.02.15.15.09_veh-12_00669_00939 + - 2021.06.02.15.15.09_veh-12_00950_01112 + - 2021.06.02.15.15.09_veh-12_01123_01453 + - 2021.06.02.15.15.09_veh-12_01464_01741 + - 2021.06.02.15.15.09_veh-12_01801_02363 + - 2021.06.02.15.15.09_veh-12_02374_02543 + - 2021.06.02.15.15.09_veh-12_02555_02818 + - 2021.06.02.15.15.09_veh-12_02848_03002 + - 2021.06.02.15.15.09_veh-12_03013_03212 + - 2021.06.02.15.15.09_veh-12_03223_03456 + - 2021.06.02.15.15.09_veh-12_03467_03612 + - 2021.06.02.15.15.09_veh-12_03718_03787 + - 2021.06.02.15.15.09_veh-12_03798_04227 + - 2021.06.02.15.15.09_veh-12_04238_04342 + - 2021.06.02.15.15.09_veh-12_04407_04874 + - 2021.06.02.15.15.09_veh-12_04885_04947 + - 2021.06.02.15.15.09_veh-12_04958_05072 + - 2021.06.02.15.15.09_veh-12_05083_05287 + - 2021.06.02.15.15.09_veh-12_05298_05400 + - 2021.06.02.15.15.09_veh-12_05440_05917 + - 2021.06.02.15.15.09_veh-12_06022_06091 + - 2021.06.02.15.15.09_veh-12_06102_06217 + - 2021.06.02.17.23.03_veh-16_00050_00323 + - 2021.06.02.17.23.03_veh-16_00423_00568 + - 2021.06.02.17.23.03_veh-16_00579_00702 + - 2021.06.02.17.23.03_veh-16_00763_01140 + - 2021.06.02.17.23.03_veh-16_01186_01252 + - 2021.06.02.17.23.03_veh-16_01263_01374 + - 2021.06.02.17.23.03_veh-16_01444_01522 + - 2021.06.02.17.47.13_veh-47_00053_00296 + - 2021.06.02.17.47.13_veh-47_00307_00460 + - 2021.06.02.17.47.13_veh-47_00471_00784 + - 2021.06.02.17.47.13_veh-47_00795_00892 + - 2021.06.02.17.47.13_veh-47_00903_00976 + - 2021.06.02.17.47.13_veh-47_00987_01231 + - 2021.06.02.17.47.13_veh-47_01242_01336 + - 2021.06.02.17.47.13_veh-47_01347_01497 + - 2021.06.02.17.47.13_veh-47_01598_01673 + - 2021.06.02.17.47.13_veh-47_01684_01971 + - 2021.06.02.17.47.13_veh-47_02078_02480 + - 2021.06.02.17.47.13_veh-47_02544_02637 + - 2021.06.02.17.47.13_veh-47_02648_02953 + - 2021.06.02.17.47.13_veh-47_02965_03172 + - 2021.06.02.17.47.13_veh-47_03183_03704 + - 2021.06.02.17.47.13_veh-47_03715_03821 + - 2021.06.02.17.47.13_veh-47_03832_04066 + - 2021.06.02.17.47.13_veh-47_04196_04436 + - 2021.06.02.17.47.13_veh-47_04448_04628 + - 2021.06.02.17.47.13_veh-47_04639_05097 + - 2021.06.02.17.54.55_veh-38_00042_00416 + - 2021.06.02.17.54.55_veh-38_00428_00686 + - 2021.06.02.17.54.55_veh-38_00697_00881 + - 2021.06.02.17.54.55_veh-38_00892_01014 + - 2021.06.02.17.54.55_veh-38_01025_01298 + - 2021.06.02.17.54.55_veh-38_01357_01486 + - 2021.06.02.17.54.55_veh-38_01497_01643 + - 2021.06.02.17.54.55_veh-38_01665_01883 + - 2021.06.02.17.54.55_veh-38_01936_02261 + - 2021.06.02.17.54.55_veh-38_02304_02667 + - 2021.06.02.17.54.55_veh-38_02754_02914 + - 2021.06.02.17.54.55_veh-38_02925_03025 + - 2021.06.02.17.54.55_veh-38_03064_03152 + - 2021.06.02.17.54.55_veh-38_03163_03421 + - 2021.06.02.17.54.55_veh-38_03457_03681 + - 2021.06.02.17.54.55_veh-38_03705_03782 + - 2021.06.02.17.54.55_veh-38_03793_03893 + - 2021.06.02.17.54.55_veh-38_03904_04201 + - 2021.06.02.17.54.55_veh-38_04212_04343 + - 2021.06.02.17.54.55_veh-38_04354_04421 + - 2021.06.02.17.54.55_veh-38_04432_04525 + - 2021.06.02.17.54.55_veh-38_04607_04816 + - 2021.06.02.17.54.55_veh-38_04902_04974 + - 2021.06.02.17.54.55_veh-38_04985_05093 + - 2021.06.02.17.54.55_veh-38_05104_05266 + - 2021.06.02.17.54.55_veh-38_05277_05415 + - 2021.06.02.17.54.55_veh-38_05455_05556 + - 2021.06.02.17.54.55_veh-38_05567_05723 + - 2021.06.02.17.58.34_veh-35_00020_00562 + - 2021.06.02.17.58.34_veh-35_00586_00717 + - 2021.06.02.17.58.34_veh-35_00728_00955 + - 2021.06.02.17.58.34_veh-35_01069_01236 + - 2021.06.02.17.58.34_veh-35_01247_01329 + - 2021.06.02.17.58.34_veh-35_01340_01608 + - 2021.06.02.17.58.34_veh-35_01619_01804 + - 2021.06.02.17.58.34_veh-35_01883_02013 + - 2021.06.02.17.58.34_veh-35_02024_02093 + - 2021.06.02.17.58.34_veh-35_02224_02491 + - 2021.06.02.17.58.34_veh-35_02502_02776 + - 2021.06.02.17.58.34_veh-35_02794_03377 + - 2021.06.02.17.58.34_veh-35_03566_03747 + - 2021.06.02.17.58.34_veh-35_03758_03841 + - 2021.06.02.17.58.34_veh-35_03852_03912 + - 2021.06.02.17.58.34_veh-35_03923_04056 + - 2021.06.02.17.58.34_veh-35_04135_04731 + - 2021.06.02.17.58.34_veh-35_04745_04819 + - 2021.06.02.18.29.18_veh-16_00017_00314 + - 2021.06.02.18.29.18_veh-16_00325_00668 + - 2021.06.02.18.29.18_veh-16_00679_00743 + - 2021.06.02.18.29.18_veh-16_00754_00997 + - 2021.06.02.18.29.18_veh-16_01009_01113 + - 2021.06.02.18.29.18_veh-16_01124_01352 + - 2021.06.02.18.29.18_veh-16_01363_01634 + - 2021.06.02.18.29.18_veh-16_01645_01721 + - 2021.06.02.18.29.18_veh-16_01813_02352 + - 2021.06.02.18.29.18_veh-16_02363_02609 + - 2021.06.02.18.29.18_veh-16_02620_02739 + - 2021.06.02.18.29.18_veh-16_02794_02877 + - 2021.06.02.18.29.18_veh-16_02888_02952 + - 2021.06.02.18.29.18_veh-16_02963_03106 + - 2021.06.02.18.29.18_veh-16_03117_03592 + - 2021.06.02.18.29.18_veh-16_03603_03664 + - 2021.06.02.18.29.18_veh-16_03710_03914 + - 2021.06.02.18.29.18_veh-16_03925_04128 + - 2021.06.02.18.29.18_veh-16_04139_04304 + - 2021.06.02.18.29.18_veh-16_04315_04721 + - 2021.06.02.18.29.18_veh-16_04732_04806 + - 2021.06.02.18.29.18_veh-16_04817_04879 + - 2021.06.02.18.29.18_veh-16_04891_05029 + - 2021.06.02.18.29.18_veh-16_05088_05396 + - 2021.06.02.18.29.18_veh-16_05454_05558 + - 2021.06.02.19.29.01_veh-47_00082_00323 + - 2021.06.02.19.29.01_veh-47_00390_00674 + - 2021.06.02.19.29.01_veh-47_00685_00867 + - 2021.06.02.19.29.01_veh-47_00878_00952 + - 2021.06.02.19.40.44_veh-35_00016_00092 + - 2021.06.02.19.40.44_veh-35_00103_00614 + - 2021.06.02.19.40.44_veh-35_00632_01053 + - 2021.06.02.19.40.44_veh-35_01064_01243 + - 2021.06.02.19.40.44_veh-35_01308_01410 + - 2021.06.02.19.40.44_veh-35_01421_01540 + - 2021.06.02.19.40.44_veh-35_01585_01898 + - 2021.06.02.19.40.44_veh-35_01909_02036 + - 2021.06.02.19.40.44_veh-35_02097_02387 + - 2021.06.02.19.40.44_veh-35_02398_02831 + - 2021.06.02.19.49.00_veh-38_00008_00119 + - 2021.06.02.19.49.00_veh-38_00132_00227 + - 2021.06.02.19.49.00_veh-38_00311_00687 + - 2021.06.02.19.49.00_veh-38_00698_00870 + - 2021.06.02.19.49.00_veh-38_00881_00949 + - 2021.06.02.19.49.00_veh-38_00960_01038 + - 2021.06.02.19.49.00_veh-38_01049_01231 + - 2021.06.02.19.49.00_veh-38_01242_01431 + - 2021.06.02.19.49.00_veh-38_01442_01564 + - 2021.06.02.19.49.00_veh-38_01575_01642 + - 2021.06.02.19.49.00_veh-38_01653_01903 + - 2021.06.02.19.49.00_veh-38_01914_01996 + - 2021.06.02.19.49.00_veh-38_02068_02212 + - 2021.06.02.19.49.00_veh-38_02223_02719 + - 2021.06.04.11.37.56_veh-47_00016_00573 + - 2021.06.04.11.37.56_veh-47_00584_00656 + - 2021.06.04.11.37.56_veh-47_00667_00753 + - 2021.06.04.11.37.56_veh-47_00764_00922 + - 2021.06.04.11.37.56_veh-47_00933_01365 + - 2021.06.04.11.37.56_veh-47_01408_01575 + - 2021.06.04.11.37.56_veh-47_01594_01967 + - 2021.06.04.11.37.56_veh-47_02027_02370 + - 2021.06.04.11.37.56_veh-47_02474_02615 + - 2021.06.04.11.37.56_veh-47_02641_03035 + - 2021.06.04.11.37.56_veh-47_03056_03179 + - 2021.06.04.11.37.56_veh-47_03205_03283 + - 2021.06.04.11.37.56_veh-47_03315_03623 + - 2021.06.04.11.37.56_veh-47_03696_03802 + - 2021.06.04.11.37.56_veh-47_03813_03947 + - 2021.06.04.11.37.56_veh-47_04067_04215 + - 2021.06.04.11.37.56_veh-47_04294_04450 + - 2021.06.04.11.37.56_veh-47_04461_04546 + - 2021.06.04.11.37.56_veh-47_04567_04740 + - 2021.06.04.11.37.56_veh-47_04751_04856 + - 2021.06.04.11.37.56_veh-47_04867_05012 + - 2021.06.04.11.37.56_veh-47_05070_05799 + - 2021.06.04.12.00.53_veh-16_00029_00680 + - 2021.06.04.12.00.53_veh-16_00691_00828 + - 2021.06.04.12.00.53_veh-16_00839_00935 + - 2021.06.04.12.00.53_veh-16_00991_01168 + - 2021.06.04.12.00.53_veh-16_01179_01439 + - 2021.06.04.12.00.53_veh-16_01450_01559 + - 2021.06.04.12.00.53_veh-16_01570_01703 + - 2021.06.04.12.00.53_veh-16_01786_01886 + - 2021.06.04.12.00.53_veh-16_01897_01983 + - 2021.06.04.12.00.53_veh-16_02059_02179 + - 2021.06.04.12.00.53_veh-16_02190_02642 + - 2021.06.04.12.00.53_veh-16_02653_02874 + - 2021.06.04.12.00.53_veh-16_02895_03285 + - 2021.06.04.12.00.53_veh-16_03296_03509 + - 2021.06.04.12.00.53_veh-16_03520_04036 + - 2021.06.04.12.00.53_veh-16_04106_04207 + - 2021.06.04.12.00.53_veh-16_04218_04348 + - 2021.06.04.12.00.53_veh-16_04379_04505 + - 2021.06.04.12.00.53_veh-16_04516_04615 + - 2021.06.04.12.00.53_veh-16_04626_04690 + - 2021.06.04.12.42.02_veh-35_00016_00131 + - 2021.06.04.12.42.02_veh-35_00142_00346 + - 2021.06.04.12.42.02_veh-35_00357_00561 + - 2021.06.04.12.42.02_veh-35_00575_00796 + - 2021.06.04.12.42.02_veh-35_00807_00907 + - 2021.06.04.12.42.02_veh-35_00918_00995 + - 2021.06.04.12.42.02_veh-35_01015_01084 + - 2021.06.04.12.42.02_veh-35_01095_01381 + - 2021.06.04.12.42.02_veh-35_01392_01483 + - 2021.06.04.12.42.02_veh-35_01565_01747 + - 2021.06.04.12.42.02_veh-35_01758_01842 + - 2021.06.04.12.42.02_veh-35_01853_01931 + - 2021.06.04.12.42.02_veh-35_01942_02203 + - 2021.06.04.12.42.02_veh-35_02214_02369 + - 2021.06.04.12.42.02_veh-35_02458_02711 + - 2021.06.04.12.42.02_veh-35_02725_02799 + - 2021.06.04.12.42.02_veh-35_02855_03099 + - 2021.06.04.12.42.02_veh-35_03183_03250 + - 2021.06.04.12.42.02_veh-35_03279_03525 + - 2021.06.04.12.42.02_veh-35_03536_04150 + - 2021.06.04.12.42.02_veh-35_04161_04303 + - 2021.06.04.12.42.02_veh-35_04387_04953 + - 2021.06.04.12.42.02_veh-35_04970_05303 + - 2021.06.04.12.42.02_veh-35_05352_05480 + - 2021.06.04.12.42.02_veh-35_05491_05749 + - 2021.06.04.13.35.03_veh-47_00085_00202 + - 2021.06.04.13.35.03_veh-47_00213_00312 + - 2021.06.04.13.35.03_veh-47_00323_00417 + - 2021.06.04.13.35.03_veh-47_00428_00599 + - 2021.06.04.13.35.03_veh-47_00617_00827 + - 2021.06.04.13.35.03_veh-47_00838_00942 + - 2021.06.04.13.35.03_veh-47_01128_01233 + - 2021.06.04.13.35.03_veh-47_01291_01843 + - 2021.06.04.13.35.03_veh-47_01854_02075 + - 2021.06.04.13.35.03_veh-47_02086_02337 + - 2021.06.04.13.35.03_veh-47_02355_02675 + - 2021.06.04.13.35.03_veh-47_02704_02831 + - 2021.06.04.13.35.03_veh-47_02844_02977 + - 2021.06.04.13.35.03_veh-47_02988_03122 + - 2021.06.04.13.35.03_veh-47_03173_03400 + - 2021.06.04.13.35.03_veh-47_03411_03562 + - 2021.06.04.13.35.03_veh-47_03573_03668 + - 2021.06.04.13.35.03_veh-47_03708_04047 + - 2021.06.04.13.35.03_veh-47_04061_04257 + - 2021.06.04.13.35.03_veh-47_04268_04348 + - 2021.06.04.13.35.03_veh-47_04464_04536 + - 2021.06.04.13.35.03_veh-47_04738_04818 + - 2021.06.04.13.35.03_veh-47_05003_05193 + - 2021.06.04.13.35.03_veh-47_05324_05485 + - 2021.06.04.13.35.03_veh-47_05496_05600 + - 2021.06.04.13.35.03_veh-47_05679_05845 + - 2021.06.04.14.29.33_veh-30_00005_00300 + - 2021.06.04.14.29.33_veh-30_00311_00472 + - 2021.06.04.14.29.33_veh-30_00503_00995 + - 2021.06.04.14.29.33_veh-30_01050_01526 + - 2021.06.04.16.26.58_veh-30_00016_00184 + - 2021.06.04.16.26.58_veh-30_00195_00494 + - 2021.06.04.16.26.58_veh-30_00530_00743 + - 2021.06.04.16.26.58_veh-30_00774_01043 + - 2021.06.04.16.26.58_veh-30_01054_01156 + - 2021.06.04.16.26.58_veh-30_01167_01243 + - 2021.06.04.16.26.58_veh-30_01267_01432 + - 2021.06.04.16.26.58_veh-30_01539_01627 + - 2021.06.04.16.26.58_veh-30_01652_01749 + - 2021.06.04.16.26.58_veh-30_01760_02214 + - 2021.06.04.16.26.58_veh-30_02295_02366 + - 2021.06.04.16.26.58_veh-30_02377_02763 + - 2021.06.04.16.26.58_veh-30_02774_02896 + - 2021.06.04.16.26.58_veh-30_02907_03222 + - 2021.06.04.16.26.58_veh-30_03252_03806 + - 2021.06.04.16.26.58_veh-30_03817_04081 + - 2021.06.04.16.26.58_veh-30_04103_04279 + - 2021.06.04.16.26.58_veh-30_04291_04655 + - 2021.06.04.16.26.58_veh-30_04666_04783 + - 2021.06.04.16.26.58_veh-30_04910_04983 + - 2021.06.04.16.26.58_veh-30_04995_05063 + - 2021.06.04.16.32.45_veh-16_00079_00164 + - 2021.06.04.16.32.45_veh-16_00176_00239 + - 2021.06.04.16.32.45_veh-16_00300_00396 + - 2021.06.04.16.32.45_veh-16_00407_00581 + - 2021.06.04.16.32.45_veh-16_00595_01448 + - 2021.06.04.16.32.45_veh-16_01475_01587 + - 2021.06.04.16.32.45_veh-16_01599_01847 + - 2021.06.04.16.32.45_veh-16_01858_02158 + - 2021.06.04.16.32.45_veh-16_02230_02423 + - 2021.06.04.16.32.45_veh-16_02435_02619 + - 2021.06.04.16.32.45_veh-16_02729_02875 + - 2021.06.04.16.32.45_veh-16_02886_03821 + - 2021.06.04.16.32.45_veh-16_03832_03916 + - 2021.06.04.16.32.45_veh-16_03927_04044 + - 2021.06.04.16.34.36_veh-38_00085_00189 + - 2021.06.04.16.34.36_veh-38_00200_00300 + - 2021.06.04.16.34.36_veh-38_00311_00414 + - 2021.06.04.16.34.36_veh-38_00425_00582 + - 2021.06.04.16.34.36_veh-38_00665_00806 + - 2021.06.04.16.34.36_veh-38_00860_01021 + - 2021.06.04.16.34.36_veh-38_01048_01343 + - 2021.06.04.16.34.36_veh-38_01354_01747 + - 2021.06.04.16.34.36_veh-38_01758_01839 + - 2021.06.04.16.34.36_veh-38_01850_02046 + - 2021.06.04.16.34.36_veh-38_02057_02394 + - 2021.06.04.16.34.36_veh-38_02405_02513 + - 2021.06.04.16.34.36_veh-38_02524_02656 + - 2021.06.04.16.34.36_veh-38_02667_02853 + - 2021.06.04.16.34.36_veh-38_02864_03099 + - 2021.06.04.16.34.36_veh-38_03113_03321 + - 2021.06.04.16.34.36_veh-38_03332_03859 + - 2021.06.04.16.34.36_veh-38_03992_04293 + - 2021.06.04.16.34.36_veh-38_04304_04639 + - 2021.06.04.16.34.36_veh-38_04650_04899 + - 2021.06.04.16.34.36_veh-38_04910_05062 + - 2021.06.04.16.34.36_veh-38_05073_05303 + - 2021.06.04.16.36.09_veh-35_00016_00194 + - 2021.06.04.16.36.09_veh-35_00205_00637 + - 2021.06.04.16.36.09_veh-35_00648_00779 + - 2021.06.04.16.36.09_veh-35_00790_00979 + - 2021.06.04.16.36.09_veh-35_00990_01346 + - 2021.06.04.16.36.09_veh-35_01357_01427 + - 2021.06.04.16.36.09_veh-35_01438_01797 + - 2021.06.04.16.36.09_veh-35_01964_03397 + - 2021.06.04.16.36.09_veh-35_03439_03710 + - 2021.06.04.16.36.09_veh-35_03721_04289 + - 2021.06.04.16.36.09_veh-35_04300_04543 + - 2021.06.04.16.36.09_veh-35_04554_05001 + - 2021.06.04.16.36.09_veh-35_05031_05118 + - 2021.06.04.16.36.09_veh-35_05208_05409 + - 2021.06.04.16.36.09_veh-35_05465_05557 + - 2021.06.04.16.36.09_veh-35_05568_05673 + - 2021.06.04.16.36.09_veh-35_05684_06149 + - 2021.06.04.16.36.09_veh-35_06353_06735 + - 2021.06.04.16.36.09_veh-35_06746_06870 + - 2021.06.04.16.36.09_veh-35_06995_07096 + - 2021.06.04.16.36.09_veh-35_07107_07176 + - 2021.06.04.17.09.53_veh-47_00005_00483 + - 2021.06.04.17.09.53_veh-47_00494_00804 + - 2021.06.04.17.09.53_veh-47_00855_01199 + - 2021.06.04.17.09.53_veh-47_01210_01697 + - 2021.06.04.17.09.53_veh-47_01708_01936 + - 2021.06.04.17.09.53_veh-47_01991_02296 + - 2021.06.04.17.09.53_veh-47_02307_02726 + - 2021.06.04.17.09.53_veh-47_02737_02973 + - 2021.06.04.17.09.53_veh-47_02984_03147 + - 2021.06.04.17.09.53_veh-47_03240_03448 + - 2021.06.04.17.09.53_veh-47_03460_03649 + - 2021.06.04.17.09.53_veh-47_03670_03829 + - 2021.06.04.17.09.53_veh-47_03840_04106 + - 2021.06.04.17.09.53_veh-47_04117_04208 + - 2021.06.04.17.09.53_veh-47_04219_04343 + - 2021.06.04.17.09.53_veh-47_04354_04724 + - 2021.06.04.17.09.53_veh-47_04735_05164 + - 2021.06.04.17.09.53_veh-47_05252_05605 + - 2021.06.04.18.21.59_veh-30_00024_00228 + - 2021.06.04.18.21.59_veh-30_00239_00340 + - 2021.06.04.18.21.59_veh-30_00418_00750 + - 2021.06.04.18.21.59_veh-30_00761_00961 + - 2021.06.04.18.21.59_veh-30_01010_01222 + - 2021.06.04.18.21.59_veh-30_01234_01398 + - 2021.06.04.18.21.59_veh-30_01409_01593 + - 2021.06.04.18.21.59_veh-30_01604_01686 + - 2021.06.04.18.21.59_veh-30_01697_01808 + - 2021.06.04.18.21.59_veh-30_01982_02236 + - 2021.06.04.18.21.59_veh-30_02247_02376 + - 2021.06.04.18.21.59_veh-30_02441_02576 + - 2021.06.04.18.21.59_veh-30_02616_02761 + - 2021.06.04.18.31.53_veh-38_00005_00200 + - 2021.06.04.18.31.53_veh-38_00348_00665 + - 2021.06.04.18.31.53_veh-38_00676_00756 + - 2021.06.04.18.31.53_veh-38_00767_01071 + - 2021.06.04.18.31.53_veh-38_01082_01425 + - 2021.06.04.18.31.53_veh-38_01532_01605 + - 2021.06.04.18.31.53_veh-38_01616_01716 + - 2021.06.04.18.31.53_veh-38_01727_01789 + - 2021.06.04.18.31.53_veh-38_01806_01968 + - 2021.06.04.18.31.53_veh-38_01979_02225 + - 2021.06.04.18.31.53_veh-38_02236_02315 + - 2021.06.04.18.31.53_veh-38_02326_02395 + - 2021.06.04.18.31.53_veh-38_02477_02810 + - 2021.06.04.18.31.53_veh-38_02821_03029 + - 2021.06.04.18.31.53_veh-38_03040_03138 + - 2021.06.04.18.31.53_veh-38_03149_03445 + - 2021.06.04.19.10.47_veh-47_00005_00316 + - 2021.06.04.19.10.47_veh-47_00388_00551 + - 2021.06.04.19.10.47_veh-47_00562_00946 + - 2021.06.09.11.51.40_veh-47_00034_00103 + - 2021.06.09.11.51.40_veh-47_00114_00379 + - 2021.06.09.11.51.40_veh-47_00390_00454 + - 2021.06.09.11.51.40_veh-47_00465_00552 + - 2021.06.09.11.51.40_veh-47_00563_00666 + - 2021.06.09.11.51.40_veh-47_00677_00775 + - 2021.06.09.11.51.40_veh-47_00786_01147 + - 2021.06.09.11.51.40_veh-47_01244_01698 + - 2021.06.09.11.51.40_veh-47_01748_01813 + - 2021.06.09.11.51.40_veh-47_01845_02096 + - 2021.06.09.11.51.40_veh-47_02107_02294 + - 2021.06.09.11.51.40_veh-47_02344_02428 + - 2021.06.09.11.51.40_veh-47_02450_02824 + - 2021.06.09.11.51.40_veh-47_02901_03536 + - 2021.06.09.11.51.40_veh-47_03547_03610 + - 2021.06.09.11.51.40_veh-47_03621_03737 + - 2021.06.09.11.51.40_veh-47_03748_04018 + - 2021.06.09.11.51.40_veh-47_04045_04125 + - 2021.06.09.11.51.40_veh-47_04136_04221 + - 2021.06.09.11.51.40_veh-47_04355_04463 + - 2021.06.09.11.51.40_veh-47_04549_04622 + - 2021.06.09.11.51.40_veh-47_04633_04694 + - 2021.06.09.11.51.40_veh-47_04705_04774 + - 2021.06.09.11.51.40_veh-47_04803_04906 + - 2021.06.09.11.51.40_veh-47_04917_05079 + - 2021.06.09.11.51.40_veh-47_05090_05212 + - 2021.06.09.11.54.15_veh-12_00015_00259 + - 2021.06.09.11.54.15_veh-12_00270_00339 + - 2021.06.09.11.54.15_veh-12_00361_00678 + - 2021.06.09.11.54.15_veh-12_00689_01229 + - 2021.06.09.11.54.15_veh-12_01240_01361 + - 2021.06.09.11.54.15_veh-12_01403_01526 + - 2021.06.09.11.54.15_veh-12_01537_01628 + - 2021.06.09.11.54.15_veh-12_01705_01845 + - 2021.06.09.11.54.15_veh-12_01902_02277 + - 2021.06.09.11.54.15_veh-12_02288_02529 + - 2021.06.09.11.54.15_veh-12_02540_02723 + - 2021.06.09.11.54.15_veh-12_02734_02946 + - 2021.06.09.11.54.15_veh-12_02957_03110 + - 2021.06.09.11.54.15_veh-12_03121_03319 + - 2021.06.09.11.54.15_veh-12_03371_03642 + - 2021.06.09.11.54.15_veh-12_03653_03902 + - 2021.06.09.11.54.15_veh-12_03917_04069 + - 2021.06.09.11.54.15_veh-12_04138_04355 + - 2021.06.09.11.54.15_veh-12_04366_04810 + - 2021.06.09.11.54.15_veh-12_04821_05096 + - 2021.06.09.11.54.15_veh-12_05108_05331 + - 2021.06.09.11.54.15_veh-12_05342_05403 + - 2021.06.09.11.54.15_veh-12_05414_05511 + - 2021.06.09.11.54.15_veh-12_05543_05765 + - 2021.06.09.12.06.35_veh-35_00149_00262 + - 2021.06.09.12.06.35_veh-35_00284_00410 + - 2021.06.09.12.06.35_veh-35_00422_01112 + - 2021.06.09.12.06.35_veh-35_01164_01494 + - 2021.06.09.12.27.13_veh-38_00115_00263 + - 2021.06.09.12.27.13_veh-38_00398_00654 + - 2021.06.09.12.27.13_veh-38_00730_00825 + - 2021.06.09.12.27.13_veh-38_00870_01045 + - 2021.06.09.12.27.13_veh-38_01056_01125 + - 2021.06.09.12.27.13_veh-38_01136_01226 + - 2021.06.09.12.27.13_veh-38_01502_01569 + - 2021.06.09.12.27.13_veh-38_01730_01824 + - 2021.06.09.12.27.13_veh-38_01909_02061 + - 2021.06.09.12.27.13_veh-38_02072_02240 + - 2021.06.09.12.27.13_veh-38_02271_02380 + - 2021.06.09.12.27.13_veh-38_02531_02616 + - 2021.06.09.12.27.13_veh-38_02716_02832 + - 2021.06.09.12.27.13_veh-38_02843_02907 + - 2021.06.09.12.27.13_veh-38_02946_03239 + - 2021.06.09.12.27.13_veh-38_03250_03472 + - 2021.06.09.12.27.13_veh-38_03483_03739 + - 2021.06.09.12.27.13_veh-38_03763_04002 + - 2021.06.09.12.27.13_veh-38_04013_04091 + - 2021.06.09.12.27.13_veh-38_04156_04249 + - 2021.06.09.12.27.13_veh-38_04401_04533 + - 2021.06.09.12.27.13_veh-38_04741_04819 + - 2021.06.09.12.27.13_veh-38_04831_04900 + - 2021.06.09.12.27.13_veh-38_04911_05021 + - 2021.06.09.12.27.13_veh-38_05060_05151 + - 2021.06.09.12.27.13_veh-38_05200_05338 + - 2021.06.09.12.39.51_veh-26_00055_00360 + - 2021.06.09.12.39.51_veh-26_00371_00480 + - 2021.06.09.12.39.51_veh-26_00492_00587 + - 2021.06.09.12.39.51_veh-26_00609_01168 + - 2021.06.09.12.39.51_veh-26_01179_01338 + - 2021.06.09.12.39.51_veh-26_01418_01480 + - 2021.06.09.12.39.51_veh-26_01491_01642 + - 2021.06.09.12.39.51_veh-26_01653_01919 + - 2021.06.09.12.39.51_veh-26_01943_02303 + - 2021.06.09.12.39.51_veh-26_02338_02459 + - 2021.06.09.12.39.51_veh-26_02470_02648 + - 2021.06.09.12.39.51_veh-26_02729_02878 + - 2021.06.09.12.39.51_veh-26_02901_02978 + - 2021.06.09.12.39.51_veh-26_02989_03385 + - 2021.06.09.12.39.51_veh-26_03409_03722 + - 2021.06.09.12.39.51_veh-26_03733_03918 + - 2021.06.09.12.39.51_veh-26_03951_04180 + - 2021.06.09.12.39.51_veh-26_04255_04331 + - 2021.06.09.12.39.51_veh-26_04374_04513 + - 2021.06.09.12.39.51_veh-26_04543_05321 + - 2021.06.09.12.39.51_veh-26_05332_05540 + - 2021.06.09.12.39.51_veh-26_05620_06003 + - 2021.06.09.12.51.31_veh-35_00007_00089 + - 2021.06.09.12.51.31_veh-35_00100_00277 + - 2021.06.09.12.51.31_veh-35_00288_00529 + - 2021.06.09.12.51.31_veh-35_00540_00631 + - 2021.06.09.12.51.31_veh-35_00697_00820 + - 2021.06.09.12.51.31_veh-35_00852_01020 + - 2021.06.09.12.51.31_veh-35_01047_01415 + - 2021.06.09.12.51.31_veh-35_01427_01576 + - 2021.06.09.12.51.31_veh-35_01587_01718 + - 2021.06.09.12.51.31_veh-35_01729_02626 + - 2021.06.09.12.51.31_veh-35_02677_02842 + - 2021.06.09.12.51.31_veh-35_02853_02964 + - 2021.06.09.12.51.31_veh-35_02975_03207 + - 2021.06.09.12.51.31_veh-35_03229_03360 + - 2021.06.09.12.51.31_veh-35_03371_03476 + - 2021.06.09.12.51.31_veh-35_03487_03821 + - 2021.06.09.12.51.31_veh-35_03869_04221 + - 2021.06.09.12.51.31_veh-35_04247_04424 + - 2021.06.09.12.51.31_veh-35_04435_04593 + - 2021.06.09.12.51.31_veh-35_04715_04871 + - 2021.06.09.12.51.31_veh-35_04882_05013 + - 2021.06.09.12.51.31_veh-35_05024_05275 + - 2021.06.09.12.51.31_veh-35_05299_05468 + - 2021.06.09.13.32.34_veh-47_00016_00113 + - 2021.06.09.13.32.34_veh-47_00124_00865 + - 2021.06.09.13.32.34_veh-47_00882_01014 + - 2021.06.09.13.32.34_veh-47_01025_01103 + - 2021.06.09.13.32.34_veh-47_01181_01363 + - 2021.06.09.13.32.34_veh-47_01374_01568 + - 2021.06.09.13.32.34_veh-47_01579_02038 + - 2021.06.09.13.32.34_veh-47_02049_02153 + - 2021.06.09.13.32.34_veh-47_02174_02348 + - 2021.06.09.13.32.34_veh-47_02359_02567 + - 2021.06.09.13.32.34_veh-47_02578_02737 + - 2021.06.09.13.32.34_veh-47_02748_03336 + - 2021.06.09.13.32.34_veh-47_03398_03463 + - 2021.06.09.13.32.34_veh-47_03475_03578 + - 2021.06.09.13.32.34_veh-47_03668_03746 + - 2021.06.09.13.32.34_veh-47_03757_03828 + - 2021.06.09.13.32.34_veh-47_03839_03984 + - 2021.06.09.13.32.34_veh-47_03995_04208 + - 2021.06.09.13.32.34_veh-47_04250_04365 + - 2021.06.09.13.32.34_veh-47_04400_04559 + - 2021.06.09.13.32.34_veh-47_04570_04908 + - 2021.06.09.13.32.34_veh-47_04975_05215 + - 2021.06.09.14.03.17_veh-12_00015_00099 + - 2021.06.09.14.03.17_veh-12_00159_00283 + - 2021.06.09.14.03.17_veh-12_00294_00364 + - 2021.06.09.14.03.17_veh-12_00375_00566 + - 2021.06.09.14.03.17_veh-12_00711_00839 + - 2021.06.09.14.03.17_veh-12_00859_00931 + - 2021.06.09.14.03.17_veh-12_01094_01213 + - 2021.06.09.14.03.17_veh-12_01225_01437 + - 2021.06.09.14.03.17_veh-12_01603_01708 + - 2021.06.09.14.03.17_veh-12_01883_01955 + - 2021.06.09.14.03.17_veh-12_02011_02101 + - 2021.06.09.14.03.17_veh-12_02112_02202 + - 2021.06.09.14.03.17_veh-12_02213_02304 + - 2021.06.09.14.03.17_veh-12_02495_02573 + - 2021.06.09.14.03.17_veh-12_02584_02970 + - 2021.06.09.14.03.17_veh-12_03014_03120 + - 2021.06.09.14.03.17_veh-12_03200_03333 + - 2021.06.09.14.03.17_veh-12_03344_03461 + - 2021.06.09.14.03.17_veh-12_03584_03667 + - 2021.06.09.14.03.17_veh-12_03678_03787 + - 2021.06.09.14.03.17_veh-12_03798_04118 + - 2021.06.09.14.03.17_veh-12_04129_04237 + - 2021.06.09.14.15.32_veh-38_00016_00130 + - 2021.06.09.14.15.32_veh-38_00141_00219 + - 2021.06.09.14.15.32_veh-38_00230_00330 + - 2021.06.09.14.15.32_veh-38_00428_00555 + - 2021.06.09.14.15.32_veh-38_00566_00741 + - 2021.06.09.14.15.32_veh-38_00798_00928 + - 2021.06.09.14.15.32_veh-38_00939_01005 + - 2021.06.09.14.15.32_veh-38_01080_01165 + - 2021.06.09.14.15.32_veh-38_01176_01311 + - 2021.06.09.14.15.32_veh-38_01398_01461 + - 2021.06.09.14.15.32_veh-38_01472_02247 + - 2021.06.09.14.15.32_veh-38_02258_02523 + - 2021.06.09.14.15.32_veh-38_02588_02758 + - 2021.06.09.14.15.32_veh-38_02769_02894 + - 2021.06.09.14.15.32_veh-38_02915_03001 + - 2021.06.09.14.15.32_veh-38_03052_03295 + - 2021.06.09.14.15.32_veh-38_03306_03660 + - 2021.06.09.14.15.32_veh-38_03742_03932 + - 2021.06.09.14.15.32_veh-38_03943_04019 + - 2021.06.09.14.15.32_veh-38_04044_04176 + - 2021.06.09.14.15.32_veh-38_04198_04357 + - 2021.06.09.14.15.32_veh-38_04368_04716 + - 2021.06.09.14.15.32_veh-38_04860_05310 + - 2021.06.09.14.15.32_veh-38_05341_05532 + - 2021.06.09.14.15.32_veh-38_05543_05643 + - 2021.06.09.14.50.36_veh-26_00063_00350 + - 2021.06.09.14.50.36_veh-26_00598_00665 + - 2021.06.09.14.50.36_veh-26_00677_00819 + - 2021.06.09.14.50.36_veh-26_00832_00905 + - 2021.06.09.14.50.36_veh-26_01037_01113 + - 2021.06.09.14.50.36_veh-26_01124_01198 + - 2021.06.09.14.50.36_veh-26_01209_01393 + - 2021.06.09.14.50.36_veh-26_01537_01600 + - 2021.06.09.14.50.36_veh-26_01698_01771 + - 2021.06.09.14.50.36_veh-26_01782_02044 + - 2021.06.09.14.50.36_veh-26_02081_02143 + - 2021.06.09.14.50.36_veh-26_02376_02484 + - 2021.06.09.14.50.36_veh-26_02495_02669 + - 2021.06.09.14.50.36_veh-26_02680_02781 + - 2021.06.09.14.50.36_veh-26_02826_02955 + - 2021.06.09.14.50.36_veh-26_03061_03152 + - 2021.06.09.14.50.36_veh-26_03208_03299 + - 2021.06.09.14.50.36_veh-26_03310_03392 + - 2021.06.09.14.50.36_veh-26_03403_03496 + - 2021.06.09.14.50.36_veh-26_03507_03584 + - 2021.06.09.14.50.36_veh-26_03595_03863 + - 2021.06.09.14.50.36_veh-26_03874_04112 + - 2021.06.09.14.50.36_veh-26_04123_04185 + - 2021.06.09.14.50.36_veh-26_04226_04484 + - 2021.06.09.14.50.36_veh-26_04495_04561 + - 2021.06.09.14.50.36_veh-26_04605_04729 + - 2021.06.09.14.50.36_veh-26_04746_04837 + - 2021.06.09.14.50.36_veh-26_05055_05138 + - 2021.06.09.14.50.36_veh-26_05225_05311 + - 2021.06.09.14.50.36_veh-26_05326_05387 + - 2021.06.09.14.50.36_veh-26_05398_05800 + - 2021.06.09.14.50.36_veh-26_05825_05901 + - 2021.06.09.14.58.55_veh-35_00016_00182 + - 2021.06.09.14.58.55_veh-35_00193_01084 + - 2021.06.09.14.58.55_veh-35_01095_01484 + - 2021.06.09.14.58.55_veh-35_01496_01664 + - 2021.06.09.14.58.55_veh-35_01675_01774 + - 2021.06.09.14.58.55_veh-35_01785_01883 + - 2021.06.09.14.58.55_veh-35_01894_02311 + - 2021.06.09.14.58.55_veh-35_02388_02465 + - 2021.06.09.14.58.55_veh-35_02476_02569 + - 2021.06.09.14.58.55_veh-35_02580_02649 + - 2021.06.09.14.58.55_veh-35_02660_02757 + - 2021.06.09.14.58.55_veh-35_02778_02850 + - 2021.06.09.14.58.55_veh-35_02861_03037 + - 2021.06.09.14.58.55_veh-35_03048_03301 + - 2021.06.09.14.58.55_veh-35_03312_03379 + - 2021.06.09.14.58.55_veh-35_03390_03537 + - 2021.06.09.14.58.55_veh-35_03548_03800 + - 2021.06.09.14.58.55_veh-35_03811_03916 + - 2021.06.09.14.58.55_veh-35_03927_04034 + - 2021.06.09.14.58.55_veh-35_04047_04349 + - 2021.06.09.14.58.55_veh-35_04360_04484 + - 2021.06.09.14.58.55_veh-35_04541_04657 + - 2021.06.09.14.58.55_veh-35_04695_05321 + - 2021.06.09.14.58.55_veh-35_05473_05626 + - 2021.06.09.14.58.55_veh-35_05655_05745 + - 2021.06.09.16.29.25_veh-47_00016_00242 + - 2021.06.09.16.29.25_veh-47_00280_00599 + - 2021.06.09.16.29.25_veh-47_00610_00834 + - 2021.06.09.16.29.25_veh-47_00845_00947 + - 2021.06.09.16.29.25_veh-47_00958_01050 + - 2021.06.09.16.29.25_veh-47_01487_01640 + - 2021.06.09.16.29.25_veh-47_01663_01798 + - 2021.06.09.16.29.25_veh-47_01809_01887 + - 2021.06.09.16.29.25_veh-47_01999_02073 + - 2021.06.09.16.29.25_veh-47_02157_02338 + - 2021.06.09.16.29.25_veh-47_02349_02422 + - 2021.06.09.16.29.25_veh-47_02643_02744 + - 2021.06.09.16.29.25_veh-47_02791_02876 + - 2021.06.09.16.29.25_veh-47_02894_02991 + - 2021.06.09.16.29.25_veh-47_03081_03258 + - 2021.06.09.16.29.25_veh-47_03269_03429 + - 2021.06.09.16.29.25_veh-47_03570_03713 + - 2021.06.09.16.29.25_veh-47_03724_03926 + - 2021.06.09.16.29.25_veh-47_03937_04085 + - 2021.06.09.16.29.25_veh-47_04097_04294 + - 2021.06.09.16.29.25_veh-47_04305_04369 + - 2021.06.09.16.29.25_veh-47_04380_05005 + - 2021.06.09.16.29.25_veh-47_05053_05228 + - 2021.06.09.17.23.18_veh-38_00016_00120 + - 2021.06.09.17.23.18_veh-38_00131_00294 + - 2021.06.09.17.23.18_veh-38_00305_00597 + - 2021.06.09.17.23.18_veh-38_00609_00762 + - 2021.06.09.17.23.18_veh-38_00773_01140 + - 2021.06.09.17.23.18_veh-38_01151_01532 + - 2021.06.09.17.23.18_veh-38_01598_01750 + - 2021.06.09.17.23.18_veh-38_01761_02019 + - 2021.06.09.17.23.18_veh-38_02094_02305 + - 2021.06.09.17.23.18_veh-38_02316_02391 + - 2021.06.09.17.23.18_veh-38_02450_02515 + - 2021.06.09.17.23.18_veh-38_02526_03027 + - 2021.06.09.17.23.18_veh-38_03095_03280 + - 2021.06.09.17.23.18_veh-38_03302_03414 + - 2021.06.09.17.23.18_veh-38_03425_04047 + - 2021.06.09.17.23.18_veh-38_04163_04245 + - 2021.06.09.17.23.18_veh-38_04286_04521 + - 2021.06.09.17.23.18_veh-38_04544_04697 + - 2021.06.09.17.23.18_veh-38_04708_04770 + - 2021.06.09.17.23.18_veh-38_04782_05228 + - 2021.06.09.17.23.18_veh-38_05239_05412 + - 2021.06.09.17.23.18_veh-38_05423_05550 + - 2021.06.09.17.23.18_veh-38_05602_05695 + - 2021.06.09.17.37.09_veh-12_00016_00140 + - 2021.06.09.17.37.09_veh-12_00151_00393 + - 2021.06.09.17.37.09_veh-12_00404_00864 + - 2021.06.09.17.37.09_veh-12_00875_01204 + - 2021.06.09.17.37.09_veh-12_01215_01375 + - 2021.06.09.17.37.09_veh-12_01386_01454 + - 2021.06.09.17.37.09_veh-12_01465_01790 + - 2021.06.09.17.37.09_veh-12_01801_01925 + - 2021.06.09.17.37.09_veh-12_01936_02067 + - 2021.06.09.17.37.09_veh-12_02082_02170 + - 2021.06.09.17.37.09_veh-12_02239_02313 + - 2021.06.09.17.37.09_veh-12_02324_02434 + - 2021.06.09.17.37.09_veh-12_02445_02566 + - 2021.06.09.17.37.09_veh-12_02639_02992 + - 2021.06.09.17.37.09_veh-12_03003_03121 + - 2021.06.09.17.37.09_veh-12_03132_03193 + - 2021.06.09.17.37.09_veh-12_03219_03372 + - 2021.06.09.17.37.09_veh-12_03420_03578 + - 2021.06.09.17.37.09_veh-12_03600_03810 + - 2021.06.09.17.37.09_veh-12_03830_04329 + - 2021.06.09.17.37.09_veh-12_04340_04478 + - 2021.06.09.17.37.09_veh-12_04489_04816 + - 2021.06.09.18.18.55_veh-47_00016_00100 + - 2021.06.09.18.18.55_veh-47_00214_00518 + - 2021.06.09.18.18.55_veh-47_00575_00649 + - 2021.06.09.18.18.55_veh-47_00677_00749 + - 2021.06.09.18.18.55_veh-47_00760_00888 + - 2021.06.09.18.18.55_veh-47_00899_01014 + - 2021.06.09.18.18.55_veh-47_01060_01141 + - 2021.06.09.18.18.55_veh-47_01220_01310 + - 2021.06.09.18.18.55_veh-47_01413_01597 + - 2021.06.09.18.18.55_veh-47_01608_01781 + - 2021.06.09.18.18.55_veh-47_01792_01854 + - 2021.06.09.18.18.55_veh-47_01865_02041 + - 2021.06.09.18.18.55_veh-47_02052_02377 + - 2021.06.09.18.18.55_veh-47_02388_02908 + - 2021.06.09.18.18.55_veh-47_02959_03249 + - 2021.06.09.18.18.55_veh-47_03260_03459 + - 2021.06.09.18.18.55_veh-47_03591_03664 + - 2021.06.09.18.18.55_veh-47_03675_03946 + - 2021.06.09.18.18.55_veh-47_03957_04034 + - 2021.06.09.18.18.55_veh-47_04096_04197 + - 2021.06.09.18.18.55_veh-47_04276_04363 + - 2021.06.09.18.18.55_veh-47_04374_04703 + - 2021.06.09.18.18.55_veh-47_04845_04976 + - 2021.06.09.18.18.55_veh-47_05047_05259 + - 2021.06.09.18.18.55_veh-47_05270_05347 + - 2021.06.09.18.18.55_veh-47_05428_05610 + - 2021.06.09.18.18.55_veh-47_05621_05711 + - 2021.06.09.18.18.55_veh-47_05766_05828 + - 2021.06.09.18.19.00_veh-26_00015_00244 + - 2021.06.09.18.19.00_veh-26_00255_00884 + - 2021.06.09.18.19.00_veh-26_00895_01037 + - 2021.06.09.18.19.00_veh-26_01100_01405 + - 2021.06.09.18.19.00_veh-26_01438_01612 + - 2021.06.09.18.19.00_veh-26_01623_01696 + - 2021.06.09.18.19.00_veh-26_01707_01832 + - 2021.06.09.18.19.00_veh-26_01843_02055 + - 2021.06.09.18.19.00_veh-26_02066_02605 + - 2021.06.09.18.19.00_veh-26_02616_02772 + - 2021.06.09.18.19.00_veh-26_02853_03050 + - 2021.06.09.18.19.00_veh-26_03061_03155 + - 2021.06.09.18.19.00_veh-26_03187_03253 + - 2021.06.09.18.19.00_veh-26_03264_03546 + - 2021.06.09.18.19.00_veh-26_03558_03699 + - 2021.06.09.18.19.00_veh-26_03710_04045 + - 2021.06.09.18.19.00_veh-26_04058_04137 + - 2021.06.09.18.19.00_veh-26_04148_04234 + - 2021.06.09.18.19.00_veh-26_04262_04410 + - 2021.06.09.18.19.00_veh-26_04421_04839 + - 2021.06.09.18.19.00_veh-26_04853_04926 + - 2021.06.09.18.19.00_veh-26_04937_05394 + - 2021.06.09.18.19.00_veh-26_05427_05725 + - 2021.06.09.18.23.43_veh-35_00026_00274 + - 2021.06.09.18.23.43_veh-35_00349_00544 + - 2021.06.09.18.23.43_veh-35_00555_00726 + - 2021.06.09.18.23.43_veh-35_00799_01004 + - 2021.06.09.18.23.43_veh-35_01028_01221 + - 2021.06.09.18.23.43_veh-35_01232_01405 + - 2021.06.09.18.23.43_veh-35_01416_01573 + - 2021.06.09.18.23.43_veh-35_01584_01691 + - 2021.06.09.18.23.43_veh-35_01702_01928 + - 2021.06.09.18.23.43_veh-35_01939_02025 + - 2021.06.09.18.23.43_veh-35_02086_02333 + - 2021.06.09.18.23.43_veh-35_02344_02669 + - 2021.06.09.18.23.43_veh-35_02680_02868 + - 2021.06.09.18.23.43_veh-35_02945_03099 + - 2021.06.09.18.23.43_veh-35_03110_03179 + - 2021.06.09.18.23.43_veh-35_03190_03392 + - 2021.06.09.18.23.43_veh-35_03403_03481 + - 2021.06.09.18.23.43_veh-35_03500_03586 + - 2021.06.09.18.23.43_veh-35_03609_03793 + - 2021.06.09.18.23.43_veh-35_03804_03956 + - 2021.06.09.18.23.43_veh-35_03967_05057 + - 2021.06.09.18.23.43_veh-35_05068_05186 + - 2021.06.09.18.23.43_veh-35_05198_05504 + - 2021.06.09.19.40.26_veh-12_00133_00268 + - 2021.06.09.19.40.26_veh-12_00279_01212 + - 2021.06.09.19.40.26_veh-12_01241_01510 + - 2021.06.09.19.40.26_veh-12_01525_02020 + - 2021.06.09.19.40.26_veh-12_02031_02228 + - 2021.06.09.20.02.38_veh-47_00016_00117 + - 2021.06.09.20.02.38_veh-47_00128_00312 + - 2021.06.09.20.02.38_veh-47_00400_00462 + - 2021.06.09.20.02.38_veh-47_00533_00646 + - 2021.06.09.20.02.38_veh-47_00747_00930 + - 2021.06.09.20.02.38_veh-47_00941_01369 + - 2021.06.09.20.02.38_veh-47_01380_01497 + - 2021.06.09.20.02.38_veh-47_01508_01652 + - 2021.06.09.20.13.31_veh-26_00005_00177 + - 2021.06.09.20.13.31_veh-26_00188_00416 + - 2021.06.09.20.13.31_veh-26_00427_00490 + - 2021.06.09.20.13.31_veh-26_00501_00857 + - 2021.06.09.20.13.31_veh-26_00868_01042 + - 2021.06.09.20.13.31_veh-26_01053_01487 + - 2021.06.09.20.13.31_veh-26_01498_01560 + - 2021.06.09.20.26.11_veh-35_00026_00236 + - 2021.06.09.20.26.11_veh-35_00247_00529 + - 2021.06.09.20.26.11_veh-35_00540_00789 + - 2021.06.09.20.26.11_veh-35_00825_00942 + - 2021.06.09.20.26.11_veh-35_00970_01216 + - 2021.06.09.20.26.11_veh-35_01227_01514 + - 2021.06.10.11.47.26_veh-35_00016_00131 + - 2021.06.10.11.47.26_veh-35_00142_00348 + - 2021.06.10.11.47.26_veh-35_00366_00452 + - 2021.06.10.11.47.26_veh-35_00463_00605 + - 2021.06.10.11.47.26_veh-35_00616_00694 + - 2021.06.10.11.47.26_veh-35_00705_01123 + - 2021.06.10.11.47.26_veh-35_01134_01623 + - 2021.06.10.11.47.26_veh-35_01634_02424 + - 2021.06.10.11.47.26_veh-35_02435_02807 + - 2021.06.10.11.47.26_veh-35_02818_03117 + - 2021.06.10.11.47.26_veh-35_03128_03824 + - 2021.06.10.11.47.26_veh-35_03915_04078 + - 2021.06.10.11.47.26_veh-35_04089_04283 + - 2021.06.10.11.47.26_veh-35_04370_04442 + - 2021.06.10.11.47.26_veh-35_04479_04672 + - 2021.06.10.11.47.26_veh-35_04707_04802 + - 2021.06.10.11.47.26_veh-35_04846_04973 + - 2021.06.10.11.47.26_veh-35_05029_05116 + - 2021.06.10.11.53.36_veh-26_00005_00096 + - 2021.06.10.11.53.36_veh-26_00107_00211 + - 2021.06.10.11.53.36_veh-26_00222_01201 + - 2021.06.10.11.53.36_veh-26_01266_01551 + - 2021.06.10.11.53.36_veh-26_01592_01776 + - 2021.06.10.11.53.36_veh-26_01812_02041 + - 2021.06.10.11.53.36_veh-26_02080_02195 + - 2021.06.10.11.53.36_veh-26_02279_02696 + - 2021.06.10.11.53.36_veh-26_02707_03020 + - 2021.06.10.11.53.36_veh-26_03116_03335 + - 2021.06.10.11.53.36_veh-26_03346_04002 + - 2021.06.10.11.53.36_veh-26_04099_04166 + - 2021.06.10.11.53.36_veh-26_04177_04413 + - 2021.06.10.11.53.36_veh-26_04424_04615 + - 2021.06.10.11.53.36_veh-26_04626_04896 + - 2021.06.10.11.53.36_veh-26_04907_05011 + - 2021.06.10.11.53.36_veh-26_05022_05190 + - 2021.06.10.11.53.36_veh-26_05201_05641 + - 2021.06.10.11.53.36_veh-26_05717_06297 + - 2021.06.10.11.53.36_veh-26_06308_06381 + - 2021.06.10.11.57.14_veh-38_00015_00410 + - 2021.06.10.11.57.14_veh-38_00459_00680 + - 2021.06.10.11.57.14_veh-38_00703_00775 + - 2021.06.10.11.57.14_veh-38_00810_00872 + - 2021.06.10.11.57.14_veh-38_00883_00980 + - 2021.06.10.11.57.14_veh-38_01147_01218 + - 2021.06.10.11.57.14_veh-38_01229_01294 + - 2021.06.10.11.57.14_veh-38_01305_01366 + - 2021.06.10.11.57.14_veh-38_01377_01534 + - 2021.06.10.11.57.14_veh-38_01607_01747 + - 2021.06.10.11.57.14_veh-38_01758_01967 + - 2021.06.10.11.57.14_veh-38_02098_02431 + - 2021.06.10.11.57.14_veh-38_02553_02652 + - 2021.06.10.11.57.14_veh-38_02663_02893 + - 2021.06.10.11.57.14_veh-38_02955_03158 + - 2021.06.10.11.57.14_veh-38_03169_03284 + - 2021.06.10.11.57.14_veh-38_03461_03544 + - 2021.06.10.11.57.14_veh-38_03555_03714 + - 2021.06.10.11.57.14_veh-38_03785_03905 + - 2021.06.10.11.57.14_veh-38_03955_04041 + - 2021.06.10.11.57.14_veh-38_04052_04502 + - 2021.06.10.11.57.14_veh-38_04547_04611 + - 2021.06.10.11.57.14_veh-38_04762_04954 + - 2021.06.10.11.57.14_veh-38_04965_05038 + - 2021.06.10.11.57.14_veh-38_05110_05224 + - 2021.06.10.11.57.14_veh-38_05298_05374 + - 2021.06.10.11.57.14_veh-38_05440_05502 + - 2021.06.10.11.57.14_veh-38_05513_05676 + - 2021.06.10.12.08.50_veh-47_00016_00226 + - 2021.06.10.12.08.50_veh-47_00272_00412 + - 2021.06.10.12.08.50_veh-47_00423_00567 + - 2021.06.10.12.08.50_veh-47_00639_00723 + - 2021.06.10.12.08.50_veh-47_00734_00924 + - 2021.06.10.12.08.50_veh-47_00935_01020 + - 2021.06.10.12.08.50_veh-47_01032_01342 + - 2021.06.10.12.08.50_veh-47_01378_01555 + - 2021.06.10.12.08.50_veh-47_01566_01701 + - 2021.06.10.12.08.50_veh-47_01734_01897 + - 2021.06.10.12.08.50_veh-47_01908_02029 + - 2021.06.10.12.08.50_veh-47_02043_02572 + - 2021.06.10.12.24.07_veh-12_00006_00215 + - 2021.06.10.12.24.07_veh-12_00310_00571 + - 2021.06.10.12.24.07_veh-12_00585_00651 + - 2021.06.10.12.24.07_veh-12_00662_01611 + - 2021.06.10.12.24.07_veh-12_01827_02180 + - 2021.06.10.12.24.07_veh-12_02203_02433 + - 2021.06.10.12.24.07_veh-12_02492_02571 + - 2021.06.10.12.24.07_veh-12_02582_02989 + - 2021.06.10.12.24.07_veh-12_03000_03471 + - 2021.06.10.12.24.07_veh-12_03482_03576 + - 2021.06.10.12.24.07_veh-12_03587_03878 + - 2021.06.10.12.24.07_veh-12_03889_03962 + - 2021.06.10.12.24.07_veh-12_03973_04124 + - 2021.06.10.12.24.07_veh-12_04207_04307 + - 2021.06.10.12.24.07_veh-12_04318_04411 + - 2021.06.10.12.24.07_veh-12_04422_04641 + - 2021.06.10.12.24.07_veh-12_04724_04791 + - 2021.06.10.12.24.07_veh-12_04803_05000 + - 2021.06.10.12.24.07_veh-12_05011_05413 + - 2021.06.10.12.48.14_veh-16_00016_00160 + - 2021.06.10.12.48.14_veh-16_00233_00294 + - 2021.06.10.12.48.14_veh-16_00305_00398 + - 2021.06.10.12.48.14_veh-16_00409_00613 + - 2021.06.10.12.48.14_veh-16_00625_00713 + - 2021.06.10.12.48.14_veh-16_00797_00896 + - 2021.06.10.12.48.14_veh-16_00907_01107 + - 2021.06.10.12.48.14_veh-16_01181_01385 + - 2021.06.10.12.48.14_veh-16_01415_01608 + - 2021.06.10.12.48.14_veh-16_01619_01740 + - 2021.06.10.12.48.14_veh-16_01751_01891 + - 2021.06.10.12.48.14_veh-16_01996_02145 + - 2021.06.10.12.48.14_veh-16_02173_02279 + - 2021.06.10.12.48.14_veh-16_02343_02742 + - 2021.06.10.12.48.14_veh-16_02753_02823 + - 2021.06.10.12.48.14_veh-16_02834_02979 + - 2021.06.10.12.48.14_veh-16_02990_03075 + - 2021.06.10.12.48.14_veh-16_03086_03482 + - 2021.06.10.12.48.14_veh-16_03518_03697 + - 2021.06.10.12.48.14_veh-16_03708_03777 + - 2021.06.10.12.48.14_veh-16_03788_03908 + - 2021.06.10.12.48.14_veh-16_03976_04050 + - 2021.06.10.12.48.14_veh-16_04061_04351 + - 2021.06.10.12.48.14_veh-16_04362_04464 + - 2021.06.10.12.48.14_veh-16_04614_05030 + - 2021.06.10.12.48.14_veh-16_05042_05832 + - 2021.06.10.13.42.35_veh-35_00005_00253 + - 2021.06.10.13.42.35_veh-35_00264_00492 + - 2021.06.10.13.42.35_veh-35_00539_00673 + - 2021.06.10.13.42.35_veh-35_00754_00835 + - 2021.06.10.13.42.35_veh-35_00846_00922 + - 2021.06.10.13.42.35_veh-35_00949_01110 + - 2021.06.10.13.42.35_veh-35_01164_01395 + - 2021.06.10.13.42.35_veh-35_01406_02153 + - 2021.06.10.13.42.35_veh-35_02246_02553 + - 2021.06.10.13.42.35_veh-35_02602_02802 + - 2021.06.10.13.42.35_veh-35_02855_02928 + - 2021.06.10.13.42.35_veh-35_02939_03004 + - 2021.06.10.13.42.35_veh-35_03015_03420 + - 2021.06.10.13.42.35_veh-35_03483_03548 + - 2021.06.10.13.42.35_veh-35_03559_03630 + - 2021.06.10.13.42.35_veh-35_03641_04005 + - 2021.06.10.13.42.35_veh-35_04016_04159 + - 2021.06.10.13.42.35_veh-35_04189_04516 + - 2021.06.10.13.42.35_veh-35_04527_04613 + - 2021.06.10.13.42.35_veh-35_04624_04738 + - 2021.06.10.13.42.35_veh-35_04749_04943 + - 2021.06.10.13.42.35_veh-35_04987_05138 + - 2021.06.10.13.42.35_veh-35_05149_05239 + - 2021.06.10.13.42.35_veh-35_05250_05341 + - 2021.06.10.13.50.05_veh-38_00075_00310 + - 2021.06.10.13.50.05_veh-38_00321_00382 + - 2021.06.10.13.50.05_veh-38_00393_00538 + - 2021.06.10.13.50.05_veh-38_00587_00825 + - 2021.06.10.13.50.05_veh-38_00863_01028 + - 2021.06.10.13.50.05_veh-38_01040_01179 + - 2021.06.10.13.50.05_veh-38_01223_01394 + - 2021.06.10.13.50.05_veh-38_01420_01553 + - 2021.06.10.13.50.05_veh-38_01564_01661 + - 2021.06.10.13.50.05_veh-38_01672_01787 + - 2021.06.10.13.50.05_veh-38_01858_02042 + - 2021.06.10.13.50.05_veh-38_02053_02269 + - 2021.06.10.13.50.05_veh-38_02280_02420 + - 2021.06.10.13.50.05_veh-38_02431_02517 + - 2021.06.10.13.50.05_veh-38_02528_02783 + - 2021.06.10.13.50.05_veh-38_02794_02877 + - 2021.06.10.13.50.05_veh-38_02943_03028 + - 2021.06.10.13.50.05_veh-38_03093_03168 + - 2021.06.10.13.50.05_veh-38_03179_03349 + - 2021.06.10.13.50.05_veh-38_03360_03486 + - 2021.06.10.13.50.05_veh-38_03639_04330 + - 2021.06.10.13.50.05_veh-38_04409_04606 + - 2021.06.10.13.50.05_veh-38_04617_04753 + - 2021.06.10.13.50.05_veh-38_04765_05120 + - 2021.06.10.13.50.05_veh-38_05131_05502 + - 2021.06.10.13.50.05_veh-38_05566_05673 + - 2021.06.10.13.50.05_veh-38_05684_05761 + - 2021.06.10.14.10.28_veh-47_00024_00430 + - 2021.06.10.14.10.28_veh-47_00585_00863 + - 2021.06.10.14.10.28_veh-47_00926_01485 + - 2021.06.10.14.10.28_veh-47_01580_01886 + - 2021.06.10.14.10.28_veh-47_01897_02021 + - 2021.06.10.14.10.28_veh-47_02032_02119 + - 2021.06.10.14.10.28_veh-47_02130_02318 + - 2021.06.10.14.10.28_veh-47_02357_02542 + - 2021.06.10.14.10.28_veh-47_02553_02671 + - 2021.06.10.14.10.28_veh-47_02682_03004 + - 2021.06.10.14.10.28_veh-47_03036_03307 + - 2021.06.10.14.10.28_veh-47_03318_03473 + - 2021.06.10.14.10.28_veh-47_03485_03574 + - 2021.06.10.14.10.28_veh-47_03585_03834 + - 2021.06.10.14.10.28_veh-47_03884_04038 + - 2021.06.10.14.10.28_veh-47_04150_04343 + - 2021.06.10.14.10.28_veh-47_04354_04650 + - 2021.06.10.14.10.28_veh-47_04690_04855 + - 2021.06.10.14.10.28_veh-47_04947_05008 + - 2021.06.10.14.10.28_veh-47_05045_05349 + - 2021.06.10.14.10.28_veh-47_05428_05495 + - 2021.06.10.14.11.49_veh-12_00037_00176 + - 2021.06.10.14.11.49_veh-12_00187_00567 + - 2021.06.10.14.11.49_veh-12_00578_00709 + - 2021.06.10.14.11.49_veh-12_00720_00880 + - 2021.06.10.14.11.49_veh-12_00891_01297 + - 2021.06.10.14.11.49_veh-12_01308_01392 + - 2021.06.10.14.11.49_veh-12_01416_01822 + - 2021.06.10.14.11.49_veh-12_01833_02142 + - 2021.06.10.14.11.49_veh-12_02153_02255 + - 2021.06.10.14.11.49_veh-12_02266_02412 + - 2021.06.10.14.11.49_veh-12_02423_02521 + - 2021.06.10.14.11.49_veh-12_02532_02827 + - 2021.06.10.14.11.49_veh-12_02895_03024 + - 2021.06.10.14.11.49_veh-12_03035_03188 + - 2021.06.10.14.11.49_veh-12_03199_03432 + - 2021.06.10.14.11.49_veh-12_03443_03627 + - 2021.06.10.14.11.49_veh-12_03676_03796 + - 2021.06.10.14.11.49_veh-12_03807_04497 + - 2021.06.10.14.11.49_veh-12_04508_04596 + - 2021.06.10.14.11.49_veh-12_04607_04746 + - 2021.06.10.14.11.49_veh-12_04783_04922 + - 2021.06.10.14.11.49_veh-12_04933_05018 + - 2021.06.10.14.11.49_veh-12_05029_05385 + - 2021.06.10.14.11.49_veh-12_05396_05821 + - 2021.06.10.14.13.54_veh-26_00005_00535 + - 2021.06.10.14.13.54_veh-26_00546_00977 + - 2021.06.10.14.13.54_veh-26_00999_01122 + - 2021.06.10.14.13.54_veh-26_01134_01321 + - 2021.06.10.14.13.54_veh-26_01332_01577 + - 2021.06.10.14.13.54_veh-26_01588_01695 + - 2021.06.10.14.13.54_veh-26_01768_01937 + - 2021.06.10.14.13.54_veh-26_01948_02118 + - 2021.06.10.14.13.54_veh-26_02158_02457 + - 2021.06.10.14.13.54_veh-26_02469_02549 + - 2021.06.10.14.13.54_veh-26_02560_03081 + - 2021.06.10.14.13.54_veh-26_03092_03192 + - 2021.06.10.14.13.54_veh-26_03267_03357 + - 2021.06.10.14.13.54_veh-26_03418_03527 + - 2021.06.10.14.13.54_veh-26_03538_03622 + - 2021.06.10.14.13.54_veh-26_03633_03837 + - 2021.06.10.14.13.54_veh-26_03848_03914 + - 2021.06.10.14.13.54_veh-26_03925_04115 + - 2021.06.10.14.13.54_veh-26_04126_04318 + - 2021.06.10.14.13.54_veh-26_04329_04498 + - 2021.06.10.14.13.54_veh-26_04509_04877 + - 2021.06.10.14.13.54_veh-26_04913_05103 + - 2021.06.10.14.13.54_veh-26_05114_05361 + - 2021.06.10.16.35.05_veh-16_00085_00218 + - 2021.06.10.16.35.05_veh-16_00229_00674 + - 2021.06.10.16.35.05_veh-16_00735_01279 + - 2021.06.10.16.35.05_veh-16_01290_01396 + - 2021.06.10.16.35.05_veh-16_01407_02289 + - 2021.06.10.16.35.05_veh-16_02417_02825 + - 2021.06.10.16.35.05_veh-16_02836_03357 + - 2021.06.10.16.35.05_veh-16_03368_03734 + - 2021.06.10.16.35.05_veh-16_03745_03964 + - 2021.06.10.16.35.05_veh-16_03975_04045 + - 2021.06.10.16.35.05_veh-16_04056_04145 + - 2021.06.10.16.35.05_veh-16_04156_04283 + - 2021.06.10.16.35.05_veh-16_04309_04807 + - 2021.06.10.16.35.05_veh-16_04818_04968 + - 2021.06.10.16.35.05_veh-16_04979_05412 + - 2021.06.10.16.35.05_veh-16_05454_05588 + - 2021.06.10.16.43.52_veh-35_00005_00089 + - 2021.06.10.16.43.52_veh-35_00101_00294 + - 2021.06.10.16.43.52_veh-35_00368_01462 + - 2021.06.10.16.43.52_veh-35_01473_02158 + - 2021.06.10.16.43.52_veh-35_02241_02619 + - 2021.06.10.16.43.52_veh-35_02671_02866 + - 2021.06.10.16.43.52_veh-35_02877_02968 + - 2021.06.10.16.43.52_veh-35_02979_03315 + - 2021.06.10.16.43.52_veh-35_03326_03535 + - 2021.06.10.16.43.52_veh-35_03546_03748 + - 2021.06.10.16.43.52_veh-35_03759_03920 + - 2021.06.10.16.43.52_veh-35_03931_04017 + - 2021.06.10.16.43.52_veh-35_04028_04194 + - 2021.06.10.16.43.52_veh-35_04302_04631 + - 2021.06.10.16.43.52_veh-35_04711_04864 + - 2021.06.10.16.43.52_veh-35_04935_05049 + - 2021.06.10.16.43.52_veh-35_05060_05466 + - 2021.06.10.16.57.46_veh-38_00061_00490 + - 2021.06.10.16.57.46_veh-38_00571_00992 + - 2021.06.10.16.57.46_veh-38_01003_01300 + - 2021.06.10.16.57.46_veh-38_01312_01426 + - 2021.06.10.16.57.46_veh-38_01476_01987 + - 2021.06.10.16.57.46_veh-38_02067_03812 + - 2021.06.10.16.57.46_veh-38_03834_04059 + - 2021.06.10.16.57.46_veh-38_04070_04164 + - 2021.06.10.16.57.46_veh-38_04175_04887 + - 2021.06.10.16.57.46_veh-38_04898_04980 + - 2021.06.10.16.57.46_veh-38_04991_05111 + - 2021.06.10.16.57.46_veh-38_05251_05404 + - 2021.06.10.16.57.46_veh-38_05428_05502 + - 2021.06.10.16.57.46_veh-38_05513_05674 + - 2021.06.10.17.18.58_veh-26_00015_00216 + - 2021.06.10.17.18.58_veh-26_00348_00478 + - 2021.06.10.17.18.58_veh-26_00525_00641 + - 2021.06.10.17.18.58_veh-26_00696_00939 + - 2021.06.10.17.18.58_veh-26_00968_01116 + - 2021.06.10.17.18.58_veh-26_01127_01282 + - 2021.06.10.17.18.58_veh-26_01450_01541 + - 2021.06.10.17.18.58_veh-26_01552_01813 + - 2021.06.10.17.18.58_veh-26_01844_01909 + - 2021.06.10.17.18.58_veh-26_02024_02185 + - 2021.06.10.17.18.58_veh-26_02196_02280 + - 2021.06.10.17.18.58_veh-26_02291_02370 + - 2021.06.10.17.18.58_veh-26_02381_02510 + - 2021.06.10.17.18.58_veh-26_02546_02748 + - 2021.06.10.17.18.58_veh-26_02824_02934 + - 2021.06.10.17.18.58_veh-26_02945_03174 + - 2021.06.10.17.18.58_veh-26_03185_03250 + - 2021.06.10.17.18.58_veh-26_03305_03374 + - 2021.06.10.17.18.58_veh-26_03395_03568 + - 2021.06.10.17.18.58_veh-26_03579_03756 + - 2021.06.10.17.18.58_veh-26_03767_03905 + - 2021.06.10.17.18.58_veh-26_04027_04193 + - 2021.06.10.17.18.58_veh-26_04204_04283 + - 2021.06.10.17.18.58_veh-26_04294_04382 + - 2021.06.10.17.18.58_veh-26_04462_04554 + - 2021.06.10.17.18.58_veh-26_04565_04701 + - 2021.06.10.17.18.58_veh-26_04773_05188 + - 2021.06.10.17.18.58_veh-26_05213_05493 + - 2021.06.10.17.22.51_veh-47_00016_00356 + - 2021.06.10.17.22.51_veh-47_00367_00506 + - 2021.06.10.17.22.51_veh-47_00517_00689 + - 2021.06.10.17.22.51_veh-47_00700_00784 + - 2021.06.10.17.22.51_veh-47_00795_00891 + - 2021.06.10.17.22.51_veh-47_00908_01291 + - 2021.06.10.17.22.51_veh-47_01342_01671 + - 2021.06.10.17.22.51_veh-47_01705_01814 + - 2021.06.10.17.22.51_veh-47_01825_02129 + - 2021.06.10.17.22.51_veh-47_02140_02851 + - 2021.06.10.17.22.51_veh-47_02864_03326 + - 2021.06.10.17.22.51_veh-47_03337_04002 + - 2021.06.10.17.22.51_veh-47_04013_04101 + - 2021.06.10.17.22.51_veh-47_04129_04221 + - 2021.06.10.17.22.51_veh-47_04242_04316 + - 2021.06.10.17.22.51_veh-47_04327_04439 + - 2021.06.10.17.22.51_veh-47_04550_04671 + - 2021.06.10.17.22.51_veh-47_04683_04826 + - 2021.06.10.17.22.51_veh-47_04842_05168 + - 2021.06.10.17.22.51_veh-47_05179_05528 + - 2021.06.10.17.46.55_veh-12_00016_00275 + - 2021.06.10.17.46.55_veh-12_00286_00553 + - 2021.06.10.17.46.55_veh-12_00564_00705 + - 2021.06.10.17.46.55_veh-12_00716_00800 + - 2021.06.10.17.46.55_veh-12_00811_01133 + - 2021.06.10.17.46.55_veh-12_01191_01288 + - 2021.06.10.17.46.55_veh-12_01300_01608 + - 2021.06.10.17.46.55_veh-12_01619_01910 + - 2021.06.10.17.46.55_veh-12_01930_02032 + - 2021.06.10.17.46.55_veh-12_02072_02231 + - 2021.06.10.17.46.55_veh-12_02242_02394 + - 2021.06.10.17.46.55_veh-12_02405_02840 + - 2021.06.10.17.46.55_veh-12_02858_02972 + - 2021.06.10.17.46.55_veh-12_02983_03364 + - 2021.06.10.17.46.55_veh-12_03493_03570 + - 2021.06.10.17.46.55_veh-12_03599_03679 + - 2021.06.10.17.46.55_veh-12_03725_03869 + - 2021.06.10.17.46.55_veh-12_03880_04345 + - 2021.06.10.17.46.55_veh-12_04356_04476 + - 2021.06.10.17.46.55_veh-12_04497_04627 + - 2021.06.10.17.46.55_veh-12_04638_05134 + - 2021.06.10.17.46.55_veh-12_05145_05293 + - 2021.06.10.17.46.55_veh-12_05304_05651 + - 2021.06.10.17.46.55_veh-12_05662_05766 + - 2021.06.10.18.37.49_veh-35_00005_00276 + - 2021.06.10.18.37.49_veh-35_00287_00486 + - 2021.06.10.18.37.49_veh-35_00550_00722 + - 2021.06.10.18.37.49_veh-35_00733_00901 + - 2021.06.10.18.37.49_veh-35_00938_01014 + - 2021.06.10.18.37.49_veh-35_01025_01095 + - 2021.06.10.18.37.49_veh-35_01107_01275 + - 2021.06.10.18.37.49_veh-35_01286_01668 + - 2021.06.10.18.37.49_veh-35_01679_01977 + - 2021.06.10.18.37.49_veh-35_01989_02144 + - 2021.06.10.18.37.49_veh-35_02195_02258 + - 2021.06.10.18.37.49_veh-35_02292_02415 + - 2021.06.10.18.37.49_veh-35_02451_02523 + - 2021.06.10.18.37.49_veh-35_02642_02717 + - 2021.06.10.18.37.49_veh-35_02768_02922 + - 2021.06.10.18.37.49_veh-35_03012_03137 + - 2021.06.10.18.37.49_veh-35_03148_03514 + - 2021.06.10.18.37.49_veh-35_03525_03825 + - 2021.06.10.18.37.49_veh-35_03851_03941 + - 2021.06.10.18.37.49_veh-35_03996_04172 + - 2021.06.10.18.37.49_veh-35_04183_04251 + - 2021.06.10.18.37.49_veh-35_04288_04448 + - 2021.06.10.18.37.49_veh-35_04459_04627 + - 2021.06.10.18.37.49_veh-35_04658_04755 + - 2021.06.10.18.37.49_veh-35_04766_04976 + - 2021.06.10.18.37.49_veh-35_05046_05177 + - 2021.06.10.18.37.49_veh-35_05188_05293 + - 2021.06.10.18.37.49_veh-35_05374_05615 + - 2021.06.10.18.43.22_veh-16_00016_00134 + - 2021.06.10.18.43.22_veh-16_00159_00562 + - 2021.06.10.18.43.22_veh-16_00643_00724 + - 2021.06.10.18.43.22_veh-16_00735_00813 + - 2021.06.10.18.43.22_veh-16_00824_01043 + - 2021.06.10.18.43.22_veh-16_01054_01237 + - 2021.06.10.18.43.22_veh-16_01248_01367 + - 2021.06.10.18.43.22_veh-16_01378_01542 + - 2021.06.10.18.43.22_veh-16_01560_01841 + - 2021.06.10.18.43.22_veh-16_01871_01994 + - 2021.06.10.18.43.22_veh-16_02018_02173 + - 2021.06.10.18.43.22_veh-16_02184_02274 + - 2021.06.10.18.43.22_veh-16_02349_02708 + - 2021.06.10.18.43.22_veh-16_02719_03772 + - 2021.06.10.18.43.22_veh-16_03783_03889 + - 2021.06.10.18.43.22_veh-16_03919_04000 + - 2021.06.10.18.43.22_veh-16_04111_04205 + - 2021.06.10.18.43.22_veh-16_04216_04285 + - 2021.06.10.18.43.22_veh-16_04297_05030 + - 2021.06.10.18.43.22_veh-16_05137_05472 + - 2021.06.10.18.43.22_veh-16_05520_05636 + - 2021.06.10.18.51.11_veh-38_00016_00223 + - 2021.06.10.18.51.11_veh-38_00234_00354 + - 2021.06.10.18.51.11_veh-38_00365_00536 + - 2021.06.10.18.51.11_veh-38_00547_00678 + - 2021.06.10.18.51.11_veh-38_00689_01297 + - 2021.06.10.18.51.11_veh-38_01308_01817 + - 2021.06.10.18.51.11_veh-38_01847_01941 + - 2021.06.10.18.51.11_veh-38_01952_02160 + - 2021.06.10.18.51.11_veh-38_02228_02560 + - 2021.06.10.18.51.11_veh-38_02670_02826 + - 2021.06.10.18.51.11_veh-38_02837_02961 + - 2021.06.10.18.51.11_veh-38_03043_03131 + - 2021.06.10.18.51.11_veh-38_03142_03599 + - 2021.06.10.18.51.11_veh-38_03650_03949 + - 2021.06.10.18.51.11_veh-38_03972_04057 + - 2021.06.10.18.51.11_veh-38_04068_04160 + - 2021.06.10.18.51.11_veh-38_04171_04270 + - 2021.06.10.19.05.09_veh-26_00036_00248 + - 2021.06.10.19.05.09_veh-26_00491_00741 + - 2021.06.10.19.05.09_veh-26_00752_01223 + - 2021.06.10.19.05.09_veh-26_01250_01510 + - 2021.06.10.19.05.09_veh-26_01632_02048 + - 2021.06.10.19.05.09_veh-26_02059_02235 + - 2021.06.10.19.05.09_veh-26_02272_02339 + - 2021.06.10.19.05.09_veh-26_02350_02422 + - 2021.06.10.19.05.09_veh-26_02433_02794 + - 2021.06.10.19.05.09_veh-26_02805_02907 + - 2021.06.10.19.05.09_veh-26_02919_02994 + - 2021.06.10.19.05.09_veh-26_03005_03312 + - 2021.06.10.19.05.09_veh-26_03385_03496 + - 2021.06.10.19.23.31_veh-47_00016_00096 + - 2021.06.10.19.23.31_veh-47_00135_00526 + - 2021.06.10.19.23.31_veh-47_00538_00606 + - 2021.06.10.19.23.31_veh-47_00617_00712 + - 2021.06.10.19.23.31_veh-47_00723_00834 + - 2021.06.10.19.23.31_veh-47_00845_00936 + - 2021.06.10.19.23.31_veh-47_00947_01071 + - 2021.06.10.19.23.31_veh-47_01246_01431 + - 2021.06.10.19.23.31_veh-47_01442_01641 + - 2021.06.10.19.23.31_veh-47_01652_02183 + - 2021.06.10.19.23.31_veh-47_03580_03691 + - 2021.06.10.19.23.31_veh-47_03702_03822 + - 2021.06.10.19.44.32_veh-12_00005_00103 + - 2021.06.10.19.44.32_veh-12_00114_00210 + - 2021.06.10.19.44.32_veh-12_00288_00464 + - 2021.06.10.19.44.32_veh-12_00487_00677 + - 2021.06.10.19.44.32_veh-12_00694_00765 + - 2021.06.10.19.44.32_veh-12_00776_00934 + - 2021.06.10.19.44.32_veh-12_01184_01281 + - 2021.06.10.19.44.32_veh-12_01321_01519 + - 2021.06.10.19.44.32_veh-12_01530_01700 + - 2021.06.10.19.44.32_veh-12_01711_01903 + - 2021.06.10.19.44.32_veh-12_01914_01997 + - 2021.06.11.11.57.05_veh-12_00088_00277 + - 2021.06.11.11.57.05_veh-12_00288_00352 + - 2021.06.11.11.57.05_veh-12_00363_00511 + - 2021.06.11.11.57.05_veh-12_00593_00712 + - 2021.06.11.11.57.05_veh-12_00723_01116 + - 2021.06.11.11.57.05_veh-12_01127_01650 + - 2021.06.11.11.57.05_veh-12_01674_01851 + - 2021.06.11.11.57.05_veh-12_01862_02056 + - 2021.06.11.11.57.05_veh-12_02112_02243 + - 2021.06.11.11.57.05_veh-12_02266_02556 + - 2021.06.11.11.57.05_veh-12_02593_02741 + - 2021.06.11.11.57.05_veh-12_02843_02909 + - 2021.06.11.11.57.05_veh-12_02920_02999 + - 2021.06.11.11.57.05_veh-12_03037_03223 + - 2021.06.11.11.57.05_veh-12_03342_03463 + - 2021.06.11.11.57.05_veh-12_03513_03687 + - 2021.06.11.11.57.05_veh-12_03698_04111 + - 2021.06.11.11.57.05_veh-12_04123_04271 + - 2021.06.11.11.57.05_veh-12_04323_04663 + - 2021.06.11.11.57.05_veh-12_04674_05277 + - 2021.06.11.12.01.10_veh-26_00090_00152 + - 2021.06.11.12.01.10_veh-26_00163_00420 + - 2021.06.11.12.01.10_veh-26_00509_00615 + - 2021.06.11.12.01.10_veh-26_00627_00793 + - 2021.06.11.12.01.10_veh-26_00820_01050 + - 2021.06.11.12.01.10_veh-26_01061_01317 + - 2021.06.11.12.01.10_veh-26_01328_01441 + - 2021.06.11.12.01.10_veh-26_01465_01649 + - 2021.06.11.12.01.10_veh-26_01660_01856 + - 2021.06.11.12.01.10_veh-26_01867_01930 + - 2021.06.11.12.01.10_veh-26_01941_02089 + - 2021.06.11.12.01.10_veh-26_02100_02381 + - 2021.06.11.12.01.10_veh-26_02425_02689 + - 2021.06.11.12.01.10_veh-26_02700_02913 + - 2021.06.11.12.01.10_veh-26_02924_03197 + - 2021.06.11.12.01.10_veh-26_03264_03462 + - 2021.06.11.12.01.10_veh-26_03473_03653 + - 2021.06.11.12.01.10_veh-26_03664_03874 + - 2021.06.11.12.01.10_veh-26_03895_03982 + - 2021.06.11.12.01.10_veh-26_04128_04229 + - 2021.06.11.12.01.10_veh-26_04264_04651 + - 2021.06.11.12.01.10_veh-26_04662_04801 + - 2021.06.11.12.01.10_veh-26_04812_04923 + - 2021.06.11.12.01.10_veh-26_05018_05350 + - 2021.06.11.12.06.26_veh-35_00016_00114 + - 2021.06.11.12.06.26_veh-35_00187_00326 + - 2021.06.11.12.06.26_veh-35_00337_00645 + - 2021.06.11.12.06.26_veh-35_00656_00905 + - 2021.06.11.12.06.26_veh-35_00991_01119 + - 2021.06.11.12.06.26_veh-35_01130_01231 + - 2021.06.11.12.06.26_veh-35_01250_01430 + - 2021.06.11.12.06.26_veh-35_01480_01773 + - 2021.06.11.12.06.26_veh-35_01786_01983 + - 2021.06.11.12.06.26_veh-35_01994_02233 + - 2021.06.11.12.06.26_veh-35_02266_02396 + - 2021.06.11.12.06.26_veh-35_02407_02525 + - 2021.06.11.12.06.26_veh-35_02576_02650 + - 2021.06.11.12.06.26_veh-35_02661_02970 + - 2021.06.11.12.06.26_veh-35_03011_03428 + - 2021.06.11.12.06.26_veh-35_03490_03715 + - 2021.06.11.12.06.26_veh-35_03726_03971 + - 2021.06.11.12.06.26_veh-35_04021_04085 + - 2021.06.11.12.06.26_veh-35_04096_04227 + - 2021.06.11.12.06.26_veh-35_04260_04949 + - 2021.06.11.12.06.26_veh-35_04986_05511 + - 2021.06.11.12.09.55_veh-16_00104_00221 + - 2021.06.11.12.09.55_veh-16_00340_00414 + - 2021.06.11.12.09.55_veh-16_00425_00626 + - 2021.06.11.12.09.55_veh-16_00637_00717 + - 2021.06.11.12.09.55_veh-16_00737_00827 + - 2021.06.11.12.09.55_veh-16_00982_01235 + - 2021.06.11.12.09.55_veh-16_01246_01411 + - 2021.06.11.12.09.55_veh-16_01483_01592 + - 2021.06.11.12.09.55_veh-16_01603_01937 + - 2021.06.11.12.09.55_veh-16_01948_02283 + - 2021.06.11.12.09.55_veh-16_02462_02547 + - 2021.06.11.12.09.55_veh-16_02558_02998 + - 2021.06.11.12.09.55_veh-16_03009_03089 + - 2021.06.11.12.09.55_veh-16_03100_03317 + - 2021.06.11.12.09.55_veh-16_03342_03665 + - 2021.06.11.12.09.55_veh-16_03676_03770 + - 2021.06.11.12.09.55_veh-16_03796_04097 + - 2021.06.11.12.09.55_veh-16_04108_04215 + - 2021.06.11.12.09.55_veh-16_04303_04429 + - 2021.06.11.12.09.55_veh-16_04449_05055 + - 2021.06.11.12.09.55_veh-16_05066_05155 + - 2021.06.11.12.09.55_veh-16_05264_05333 + - 2021.06.11.12.09.55_veh-16_05344_05731 + - 2021.06.11.12.18.41_veh-38_00026_00171 + - 2021.06.11.12.18.41_veh-38_00182_00300 + - 2021.06.11.12.18.41_veh-38_00311_00819 + - 2021.06.11.12.18.41_veh-38_00830_01561 + - 2021.06.11.12.18.41_veh-38_01574_02095 + - 2021.06.11.12.18.41_veh-38_02106_02281 + - 2021.06.11.12.18.41_veh-38_02292_02426 + - 2021.06.11.12.18.41_veh-38_02437_02511 + - 2021.06.11.12.18.41_veh-38_02522_02898 + - 2021.06.11.12.18.41_veh-38_02972_03401 + - 2021.06.11.12.18.41_veh-38_03412_03816 + - 2021.06.11.12.18.41_veh-38_03843_04236 + - 2021.06.11.12.18.41_veh-38_04247_04309 + - 2021.06.11.12.18.41_veh-38_04320_04811 + - 2021.06.11.12.18.41_veh-38_04822_05311 + - 2021.06.11.13.46.02_veh-12_00016_00244 + - 2021.06.11.13.46.02_veh-12_00269_00454 + - 2021.06.11.13.46.02_veh-12_00476_00537 + - 2021.06.11.13.46.02_veh-12_00592_01090 + - 2021.06.11.14.22.48_veh-38_00016_00236 + - 2021.06.11.14.22.48_veh-38_00247_00588 + - 2021.06.11.14.22.48_veh-38_00599_00685 + - 2021.06.11.14.22.48_veh-38_00696_00951 + - 2021.06.11.14.22.48_veh-38_00962_01511 + - 2021.06.11.14.22.48_veh-38_01563_01822 + - 2021.06.11.14.22.48_veh-38_01858_01980 + - 2021.06.11.14.22.48_veh-38_01991_02246 + - 2021.06.11.14.22.48_veh-38_02306_02903 + - 2021.06.11.14.22.48_veh-38_02914_02978 + - 2021.06.11.14.22.48_veh-38_02989_03138 + - 2021.06.11.14.22.48_veh-38_03149_03306 + - 2021.06.11.14.22.48_veh-38_03394_04121 + - 2021.06.11.14.22.48_veh-38_04132_04200 + - 2021.06.11.14.22.48_veh-38_04221_04312 + - 2021.06.11.14.22.48_veh-38_04323_04426 + - 2021.06.11.14.22.48_veh-38_04503_04573 + - 2021.06.11.14.22.48_veh-38_04584_04669 + - 2021.06.11.14.22.48_veh-38_04680_04827 + - 2021.06.11.14.22.48_veh-38_04838_04925 + - 2021.06.11.14.22.48_veh-38_04936_05014 + - 2021.06.11.14.22.48_veh-38_05025_05368 + - 2021.06.11.14.25.09_veh-35_00016_00146 + - 2021.06.11.14.25.09_veh-35_00208_00348 + - 2021.06.11.14.25.09_veh-35_00359_00494 + - 2021.06.11.14.25.09_veh-35_00505_00655 + - 2021.06.11.14.25.09_veh-35_00667_00769 + - 2021.06.11.14.25.09_veh-35_00847_00916 + - 2021.06.11.14.25.09_veh-35_00960_01112 + - 2021.06.11.14.25.09_veh-35_01123_01202 + - 2021.06.11.14.25.09_veh-35_01213_01298 + - 2021.06.11.14.25.09_veh-35_01309_01412 + - 2021.06.11.14.25.09_veh-35_01423_01516 + - 2021.06.11.14.25.09_veh-35_01527_01588 + - 2021.06.11.14.25.09_veh-35_01643_01968 + - 2021.06.11.14.25.09_veh-35_01979_02090 + - 2021.06.11.14.25.09_veh-35_02204_02357 + - 2021.06.11.14.25.09_veh-35_02377_02480 + - 2021.06.11.14.25.09_veh-35_02503_02675 + - 2021.06.11.14.25.09_veh-35_02687_02792 + - 2021.06.11.14.25.09_veh-35_02842_03232 + - 2021.06.11.14.25.09_veh-35_03243_03333 + - 2021.06.11.14.25.09_veh-35_03347_03948 + - 2021.06.11.14.25.09_veh-35_03959_04035 + - 2021.06.11.14.25.09_veh-35_04177_04246 + - 2021.06.11.14.25.09_veh-35_04257_05126 + - 2021.06.11.14.25.09_veh-35_05137_05222 + - 2021.06.11.14.25.09_veh-35_05233_05397 + - 2021.06.11.14.25.09_veh-35_05429_05516 + - 2021.06.11.14.25.09_veh-35_05527_05595 + - 2021.06.11.14.41.12_veh-26_00005_00564 + - 2021.06.11.14.41.12_veh-26_00575_00851 + - 2021.06.11.14.41.12_veh-26_00862_01048 + - 2021.06.11.14.41.12_veh-26_01096_01241 + - 2021.06.11.14.41.12_veh-26_01252_01400 + - 2021.06.11.14.41.12_veh-26_01412_01763 + - 2021.06.11.14.41.12_veh-26_01774_01913 + - 2021.06.11.14.41.12_veh-26_01924_02052 + - 2021.06.11.14.41.12_veh-26_02063_02361 + - 2021.06.11.14.41.12_veh-26_02372_02527 + - 2021.06.11.14.41.12_veh-26_02620_02974 + - 2021.06.11.14.41.12_veh-26_03029_03118 + - 2021.06.11.14.41.12_veh-26_03150_03381 + - 2021.06.11.14.41.12_veh-26_03392_03518 + - 2021.06.11.14.41.12_veh-26_03529_03702 + - 2021.06.11.14.41.12_veh-26_03713_03791 + - 2021.06.11.14.41.12_veh-26_03802_04826 + - 2021.06.11.14.41.12_veh-26_04837_05012 + - 2021.06.11.14.41.12_veh-26_05090_05170 + - 2021.06.11.14.41.12_veh-26_05181_05448 + - 2021.06.11.14.41.12_veh-26_05459_05548 + - 2021.06.11.14.41.12_veh-26_05560_05746 + - 2021.06.11.16.10.55_veh-16_00005_00129 + - 2021.06.11.16.10.55_veh-16_00140_00251 + - 2021.06.11.16.10.55_veh-16_00262_00463 + - 2021.06.11.16.10.55_veh-16_00474_00597 + - 2021.06.11.16.10.55_veh-16_00677_00805 + - 2021.06.11.16.10.55_veh-16_01042_01242 + - 2021.06.11.16.10.55_veh-16_01287_01351 + - 2021.06.11.16.10.55_veh-16_01362_01435 + - 2021.06.11.16.10.55_veh-16_01511_01576 + - 2021.06.11.16.10.55_veh-16_01626_01707 + - 2021.06.11.16.10.55_veh-16_01843_01941 + - 2021.06.11.16.10.55_veh-16_02048_02273 + - 2021.06.11.16.10.55_veh-16_02284_02423 + - 2021.06.11.16.10.55_veh-16_02545_02893 + - 2021.06.11.16.10.55_veh-16_02904_03064 + - 2021.06.11.16.10.55_veh-16_03089_03294 + - 2021.06.11.16.10.55_veh-16_03305_03507 + - 2021.06.11.16.10.55_veh-16_03520_04307 + - 2021.06.11.16.10.55_veh-16_04318_04435 + - 2021.06.11.16.10.55_veh-16_04446_04557 + - 2021.06.11.16.10.55_veh-16_04592_04702 + - 2021.06.11.16.10.55_veh-16_04713_04865 + - 2021.06.11.16.10.55_veh-16_04955_05018 + - 2021.06.11.16.10.55_veh-16_05029_05136 + - 2021.06.11.16.10.55_veh-16_05147_05460 + - 2021.06.11.16.44.04_veh-12_00015_00176 + - 2021.06.11.16.44.04_veh-12_00187_01135 + - 2021.06.11.16.44.04_veh-12_01146_01271 + - 2021.06.11.16.44.04_veh-12_01282_01479 + - 2021.06.11.16.44.04_veh-12_01490_01577 + - 2021.06.11.16.44.04_veh-12_01588_02133 + - 2021.06.11.16.44.04_veh-12_02144_02264 + - 2021.06.11.16.44.04_veh-12_02275_02409 + - 2021.06.11.16.44.04_veh-12_02450_02799 + - 2021.06.11.16.44.04_veh-12_02810_02875 + - 2021.06.11.16.44.04_veh-12_02991_03076 + - 2021.06.11.16.44.04_veh-12_03178_03529 + - 2021.06.11.16.44.04_veh-12_03540_03605 + - 2021.06.11.16.44.04_veh-12_03616_03858 + - 2021.06.11.16.44.04_veh-12_03869_03953 + - 2021.06.11.16.44.04_veh-12_04037_04133 + - 2021.06.11.16.44.04_veh-12_04144_04379 + - 2021.06.11.16.44.04_veh-12_04444_04588 + - 2021.06.11.16.44.04_veh-12_04599_05127 + - 2021.06.11.16.44.04_veh-12_05138_05403 + - 2021.06.11.17.44.29_veh-26_00016_00590 + - 2021.06.11.17.44.29_veh-26_00601_00816 + - 2021.06.11.17.44.29_veh-26_00827_01263 + - 2021.06.11.17.44.29_veh-26_01274_01438 + - 2021.06.11.17.44.29_veh-26_01452_01581 + - 2021.06.11.17.44.29_veh-26_01592_01767 + - 2021.06.11.17.44.29_veh-26_01778_01987 + - 2021.06.11.17.44.29_veh-26_02104_02198 + - 2021.06.11.17.44.29_veh-26_02245_02582 + - 2021.06.11.17.44.29_veh-26_02593_02803 + - 2021.06.11.17.44.29_veh-26_02883_03330 + - 2021.06.11.17.44.29_veh-26_03358_03512 + - 2021.06.11.17.44.29_veh-26_03523_03587 + - 2021.06.11.17.44.29_veh-26_03646_04342 + - 2021.06.11.17.44.29_veh-26_04353_04820 + - 2021.06.11.17.44.29_veh-26_04831_04985 + - 2021.06.11.17.44.29_veh-26_05014_05112 + - 2021.06.11.17.44.29_veh-26_05123_05733 + - 2021.06.11.17.44.29_veh-26_05844_05950 + - 2021.06.11.17.44.29_veh-26_05961_06259 + - 2021.06.11.18.09.59_veh-16_00005_00347 + - 2021.06.11.18.09.59_veh-16_00473_00580 + - 2021.06.11.18.09.59_veh-16_00645_00720 + - 2021.06.11.18.09.59_veh-16_00731_00833 + - 2021.06.11.18.09.59_veh-16_00844_00911 + - 2021.06.11.18.09.59_veh-16_00922_01232 + - 2021.06.11.18.09.59_veh-16_01243_01617 + - 2021.06.11.18.09.59_veh-16_01628_02022 + - 2021.06.11.18.09.59_veh-16_02033_02277 + - 2021.06.11.18.09.59_veh-16_02288_02377 + - 2021.06.11.18.09.59_veh-16_02388_02514 + - 2021.06.11.18.09.59_veh-16_02662_02781 + - 2021.06.11.18.09.59_veh-16_02792_02911 + - 2021.06.11.18.09.59_veh-16_02923_02987 + - 2021.06.11.18.09.59_veh-16_02998_03099 + - 2021.06.11.18.09.59_veh-16_03151_03337 + - 2021.06.11.18.09.59_veh-16_03417_03521 + - 2021.06.11.18.09.59_veh-16_03532_03642 + - 2021.06.11.18.09.59_veh-16_03704_03841 + - 2021.06.11.18.09.59_veh-16_03915_04202 + - 2021.06.11.18.09.59_veh-16_04213_04465 + - 2021.06.11.18.09.59_veh-16_04476_04744 + - 2021.06.11.18.09.59_veh-16_04766_04828 + - 2021.06.11.18.09.59_veh-16_04839_04949 + - 2021.06.11.18.09.59_veh-16_05013_05255 + - 2021.06.11.18.09.59_veh-16_05266_05372 + - 2021.06.11.18.09.59_veh-16_05404_05601 + - 2021.06.11.18.09.59_veh-16_05617_05901 + - 2021.06.11.18.09.59_veh-16_05912_06063 + - 2021.06.11.18.37.58_veh-12_00016_00088 + - 2021.06.11.18.37.58_veh-12_00108_00184 + - 2021.06.11.18.37.58_veh-12_00195_00536 + - 2021.06.11.18.37.58_veh-12_00547_00616 + - 2021.06.11.18.37.58_veh-12_00666_00989 + - 2021.06.11.18.37.58_veh-12_01007_01074 + - 2021.06.11.18.37.58_veh-12_01085_01164 + - 2021.06.11.18.37.58_veh-12_01240_01684 + - 2021.06.11.18.37.58_veh-12_01695_01764 + - 2021.06.11.18.37.58_veh-12_01831_01910 + - 2021.06.11.18.37.58_veh-12_01987_02124 + - 2021.06.11.18.37.58_veh-12_02205_02335 + - 2021.06.11.18.37.58_veh-12_02365_02586 + - 2021.06.11.18.37.58_veh-12_02597_02680 + - 2021.06.11.18.37.58_veh-12_02709_02926 + - 2021.06.11.18.37.58_veh-12_03019_03163 + - 2021.06.11.18.37.58_veh-12_03178_03353 + - 2021.06.11.18.37.58_veh-12_03364_03446 + - 2021.06.11.18.37.58_veh-12_03470_04143 + - 2021.06.11.18.37.58_veh-12_04300_04486 + - 2021.06.11.18.37.58_veh-12_04497_04623 + - 2021.06.11.18.37.58_veh-12_04634_04695 + - 2021.06.11.18.37.58_veh-12_04706_04874 + - 2021.06.11.18.37.58_veh-12_04885_04964 + - 2021.06.11.18.37.58_veh-12_05025_05393 + - 2021.06.11.18.37.58_veh-12_05404_05694 + - 2021.06.11.18.37.58_veh-12_05762_05877 + - 2021.06.11.18.37.58_veh-12_05956_06051 + - 2021.06.11.18.37.58_veh-12_06062_06311 + - 2021.06.11.18.42.43_veh-38_00018_00203 + - 2021.06.11.18.42.43_veh-38_00214_00533 + - 2021.06.11.18.42.43_veh-38_00544_00662 + - 2021.06.11.18.42.43_veh-38_00673_00918 + - 2021.06.11.18.42.43_veh-38_00929_01247 + - 2021.06.11.18.42.43_veh-38_01258_01623 + - 2021.06.11.18.42.43_veh-38_01634_01789 + - 2021.06.11.18.42.43_veh-38_01800_01892 + - 2021.06.11.18.42.43_veh-38_01903_01969 + - 2021.06.11.18.42.43_veh-38_01980_02474 + - 2021.06.11.18.42.43_veh-38_02495_02876 + - 2021.06.11.18.42.43_veh-38_02935_03342 + - 2021.06.11.18.42.43_veh-38_03356_03525 + - 2021.06.11.18.42.43_veh-38_03549_04070 + - 2021.06.11.18.42.43_veh-38_04081_04409 + - 2021.06.11.18.42.43_veh-38_04508_04880 + - 2021.06.11.18.42.43_veh-38_04906_04977 + - 2021.06.11.18.42.43_veh-38_04988_05159 + - 2021.06.11.18.42.43_veh-38_05170_05238 + - 2021.06.11.18.42.43_veh-38_05249_05467 + - 2021.06.11.18.42.43_veh-38_05484_05694 + - 2021.06.11.18.42.43_veh-38_05705_05932 + - 2021.06.11.18.42.43_veh-38_05943_06066 + - 2021.06.11.18.42.43_veh-38_06077_06427 + - 2021.06.11.18.42.43_veh-38_06438_06606 + - 2021.06.11.20.03.24_veh-26_00048_00238 + - 2021.06.11.20.03.24_veh-26_00302_00385 + - 2021.06.11.20.03.24_veh-26_00396_00626 + - 2021.06.11.20.03.24_veh-26_00638_00736 + - 2021.06.11.20.03.24_veh-26_00822_00997 + - 2021.06.11.20.03.24_veh-26_01008_01497 + - 2021.06.12.11.42.45_veh-47_00010_00146 + - 2021.06.12.11.42.45_veh-47_00157_00232 + - 2021.06.12.11.42.45_veh-47_00399_00508 + - 2021.06.12.11.42.45_veh-47_00519_00594 + - 2021.06.12.11.42.45_veh-47_00605_00790 + - 2021.06.12.11.42.45_veh-47_00801_01017 + - 2021.06.12.11.42.45_veh-47_01114_01189 + - 2021.06.12.11.42.45_veh-47_01243_01329 + - 2021.06.12.11.42.45_veh-47_01340_01412 + - 2021.06.12.11.42.45_veh-47_01423_01486 + - 2021.06.12.11.42.45_veh-47_01534_01613 + - 2021.06.12.11.42.45_veh-47_01624_02319 + - 2021.06.12.11.42.45_veh-47_02355_02523 + - 2021.06.12.11.42.45_veh-47_02569_02691 + - 2021.06.12.11.42.45_veh-47_02722_02808 + - 2021.06.12.11.42.45_veh-47_02886_03055 + - 2021.06.12.11.42.45_veh-47_03231_03335 + - 2021.06.12.11.42.45_veh-47_03346_03415 + - 2021.06.12.11.42.45_veh-47_03457_03561 + - 2021.06.12.11.42.45_veh-47_03572_03697 + - 2021.06.12.11.42.45_veh-47_03708_03908 + - 2021.06.12.11.42.45_veh-47_03980_04158 + - 2021.06.12.11.42.45_veh-47_04169_04354 + - 2021.06.12.11.42.45_veh-47_04376_04589 + - 2021.06.12.11.42.45_veh-47_04612_04838 + - 2021.06.12.11.42.45_veh-47_04849_05115 + - 2021.06.12.11.42.45_veh-47_05126_05190 + - 2021.06.12.11.42.45_veh-47_05214_05355 + - 2021.06.12.11.48.53_veh-35_00150_00230 + - 2021.06.12.11.48.53_veh-35_00241_00457 + - 2021.06.12.11.48.53_veh-35_00468_00630 + - 2021.06.12.11.48.53_veh-35_00651_01093 + - 2021.06.12.11.48.53_veh-35_01104_01327 + - 2021.06.12.11.48.53_veh-35_01338_01413 + - 2021.06.12.11.48.53_veh-35_01455_01537 + - 2021.06.12.11.48.53_veh-35_01549_01679 + - 2021.06.12.11.48.53_veh-35_01702_01922 + - 2021.06.12.11.48.53_veh-35_01984_02143 + - 2021.06.12.11.48.53_veh-35_02154_02285 + - 2021.06.12.11.48.53_veh-35_02316_02488 + - 2021.06.12.11.48.53_veh-35_02538_02836 + - 2021.06.12.11.48.53_veh-35_02847_03118 + - 2021.06.12.11.48.53_veh-35_03129_03557 + - 2021.06.12.11.48.53_veh-35_03582_03650 + - 2021.06.12.11.48.53_veh-35_03661_03825 + - 2021.06.12.11.48.53_veh-35_03836_04625 + - 2021.06.12.11.48.53_veh-35_04636_04817 + - 2021.06.12.11.48.53_veh-35_04828_05080 + - 2021.06.12.11.48.53_veh-35_05119_05313 + - 2021.06.12.11.48.53_veh-35_05324_05459 + - 2021.06.12.11.48.53_veh-35_05508_05735 + - 2021.06.12.11.48.53_veh-35_05746_05851 + - 2021.06.12.11.57.54_veh-38_00005_00145 + - 2021.06.12.11.57.54_veh-38_00177_00963 + - 2021.06.12.11.57.54_veh-38_00974_01131 + - 2021.06.12.11.57.54_veh-38_01160_01250 + - 2021.06.12.11.57.54_veh-38_01355_01655 + - 2021.06.12.11.57.54_veh-38_01666_01749 + - 2021.06.12.11.57.54_veh-38_01760_01947 + - 2021.06.12.11.57.54_veh-38_01973_02293 + - 2021.06.12.11.57.54_veh-38_02304_02364 + - 2021.06.12.11.57.54_veh-38_02375_02800 + - 2021.06.12.11.57.54_veh-38_02811_02975 + - 2021.06.12.11.57.54_veh-38_03066_03347 + - 2021.06.12.11.57.54_veh-38_03377_03675 + - 2021.06.12.11.57.54_veh-38_03716_03884 + - 2021.06.12.11.57.54_veh-38_03984_04048 + - 2021.06.12.11.57.54_veh-38_04138_04449 + - 2021.06.12.11.57.54_veh-38_04460_04638 + - 2021.06.12.11.57.54_veh-38_04649_04783 + - 2021.06.12.11.57.54_veh-38_04794_04892 + - 2021.06.12.11.57.54_veh-38_04903_05039 + - 2021.06.12.11.57.54_veh-38_05050_05133 + - 2021.06.12.11.57.54_veh-38_05144_05292 + - 2021.06.12.11.57.54_veh-38_05303_05439 + - 2021.06.12.11.57.54_veh-38_05507_05644 + - 2021.06.12.11.57.54_veh-38_05684_05746 + - 2021.06.12.12.26.36_veh-26_00078_00436 + - 2021.06.12.12.26.36_veh-26_00490_00613 + - 2021.06.12.12.26.36_veh-26_00783_01133 + - 2021.06.12.12.26.36_veh-26_01144_01288 + - 2021.06.12.12.26.36_veh-26_01299_02108 + - 2021.06.12.12.26.36_veh-26_02119_02320 + - 2021.06.12.12.26.36_veh-26_02341_02472 + - 2021.06.12.12.26.36_veh-26_02550_02699 + - 2021.06.12.12.26.36_veh-26_02710_03367 + - 2021.06.12.12.26.36_veh-26_03378_03480 + - 2021.06.12.12.26.36_veh-26_03492_03601 + - 2021.06.12.12.26.36_veh-26_03657_03877 + - 2021.06.12.12.26.36_veh-26_03888_03958 + - 2021.06.12.12.26.36_veh-26_03970_04101 + - 2021.06.12.12.26.36_veh-26_04112_04173 + - 2021.06.12.12.26.36_veh-26_04184_04246 + - 2021.06.12.12.26.36_veh-26_04257_04477 + - 2021.06.12.12.26.36_veh-26_04506_04664 + - 2021.06.12.12.45.00_veh-16_00005_00161 + - 2021.06.12.12.45.00_veh-16_00172_00240 + - 2021.06.12.12.45.00_veh-16_00251_00477 + - 2021.06.12.12.45.00_veh-16_00488_00655 + - 2021.06.12.12.45.00_veh-16_00699_00771 + - 2021.06.12.12.45.00_veh-16_00916_01146 + - 2021.06.12.12.45.00_veh-16_01157_01357 + - 2021.06.12.12.45.00_veh-16_01368_01458 + - 2021.06.12.12.45.00_veh-16_01583_01665 + - 2021.06.12.12.45.00_veh-16_01676_01936 + - 2021.06.12.12.45.00_veh-16_01947_02039 + - 2021.06.12.12.45.00_veh-16_02050_02112 + - 2021.06.12.12.45.00_veh-16_02123_02336 + - 2021.06.12.12.45.00_veh-16_02408_02485 + - 2021.06.12.12.45.00_veh-16_02509_02707 + - 2021.06.12.12.45.00_veh-16_02718_02783 + - 2021.06.12.12.45.00_veh-16_02821_03010 + - 2021.06.12.12.45.00_veh-16_03115_03255 + - 2021.06.12.12.45.00_veh-16_03532_03614 + - 2021.06.12.12.45.00_veh-16_03695_03801 + - 2021.06.12.12.45.00_veh-16_03864_03924 + - 2021.06.12.12.45.00_veh-16_04002_04095 + - 2021.06.12.12.45.00_veh-16_04305_04567 + - 2021.06.12.12.45.00_veh-16_04614_04915 + - 2021.06.12.12.45.00_veh-16_04943_05136 + - 2021.06.12.12.45.00_veh-16_05270_05341 + - 2021.06.12.12.45.00_veh-16_05409_05472 + - 2021.06.12.12.45.00_veh-16_05494_05592 + - 2021.06.12.12.45.00_veh-16_05603_05678 + - 2021.06.12.13.22.09_veh-47_00036_00099 + - 2021.06.12.13.22.09_veh-47_00151_00283 + - 2021.06.12.13.22.09_veh-47_00361_00452 + - 2021.06.12.13.22.09_veh-47_00463_00565 + - 2021.06.12.13.22.09_veh-47_00608_00837 + - 2021.06.12.13.22.09_veh-47_00866_00975 + - 2021.06.12.13.22.09_veh-47_00986_01153 + - 2021.06.12.13.22.09_veh-47_01201_01330 + - 2021.06.12.13.22.09_veh-47_01342_01457 + - 2021.06.12.13.22.09_veh-47_01492_01565 + - 2021.06.12.13.22.09_veh-47_01602_01930 + - 2021.06.12.13.22.09_veh-47_01962_02043 + - 2021.06.12.13.22.09_veh-47_02054_02145 + - 2021.06.12.13.22.09_veh-47_02177_02290 + - 2021.06.12.13.22.09_veh-47_02436_03151 + - 2021.06.12.13.22.09_veh-47_03162_03475 + - 2021.06.12.13.22.09_veh-47_03507_03801 + - 2021.06.12.13.22.09_veh-47_03853_04218 + - 2021.06.12.13.22.09_veh-47_04243_04441 + - 2021.06.12.13.22.09_veh-47_04452_04772 + - 2021.06.12.13.22.09_veh-47_04803_05071 + - 2021.06.12.13.22.09_veh-47_05082_05417 + - 2021.06.12.13.22.09_veh-47_05428_05546 + - 2021.06.12.13.51.28_veh-35_00016_00192 + - 2021.06.12.13.51.28_veh-35_00203_00573 + - 2021.06.12.13.51.28_veh-35_00584_00720 + - 2021.06.12.13.51.28_veh-35_00731_00793 + - 2021.06.12.13.51.28_veh-35_00805_00908 + - 2021.06.12.13.51.28_veh-35_01037_01284 + - 2021.06.12.13.51.28_veh-35_01308_01510 + - 2021.06.12.13.51.28_veh-35_01521_01685 + - 2021.06.12.13.51.28_veh-35_01696_01791 + - 2021.06.12.13.51.28_veh-35_01802_02001 + - 2021.06.12.13.51.28_veh-35_02031_02140 + - 2021.06.12.13.51.28_veh-35_02167_02675 + - 2021.06.12.13.51.28_veh-35_02686_02781 + - 2021.06.12.13.51.28_veh-35_02813_02955 + - 2021.06.12.13.51.28_veh-35_03039_03231 + - 2021.06.12.13.51.28_veh-35_03242_03310 + - 2021.06.12.13.51.28_veh-35_03331_03409 + - 2021.06.12.13.51.28_veh-35_03507_03585 + - 2021.06.12.13.51.28_veh-35_03596_03810 + - 2021.06.12.13.51.28_veh-35_03821_03936 + - 2021.06.12.13.51.28_veh-35_03974_04143 + - 2021.06.12.13.51.28_veh-35_04322_04480 + - 2021.06.12.13.51.28_veh-35_04573_04650 + - 2021.06.12.13.51.28_veh-35_04661_04911 + - 2021.06.12.13.51.28_veh-35_04922_05091 + - 2021.06.12.13.51.28_veh-35_05102_05168 + - 2021.06.12.13.51.28_veh-35_05179_05558 + - 2021.06.12.13.51.28_veh-35_05570_05632 + - 2021.06.12.13.57.31_veh-38_00016_00159 + - 2021.06.12.13.57.31_veh-38_00170_00359 + - 2021.06.12.13.57.31_veh-38_00370_00814 + - 2021.06.12.13.57.31_veh-38_00825_00967 + - 2021.06.12.13.57.31_veh-38_01043_01308 + - 2021.06.12.13.57.31_veh-38_01319_01451 + - 2021.06.12.13.57.31_veh-38_01462_01661 + - 2021.06.12.13.57.31_veh-38_01672_01774 + - 2021.06.12.13.57.31_veh-38_01785_01868 + - 2021.06.12.13.57.31_veh-38_01901_02125 + - 2021.06.12.13.57.31_veh-38_02136_02271 + - 2021.06.12.13.57.31_veh-38_02282_02865 + - 2021.06.12.13.57.31_veh-38_02876_02947 + - 2021.06.12.13.57.31_veh-38_02958_03586 + - 2021.06.12.13.57.31_veh-38_03597_03685 + - 2021.06.12.13.57.31_veh-38_03696_03947 + - 2021.06.12.13.57.31_veh-38_03989_04211 + - 2021.06.12.13.57.31_veh-38_04264_04330 + - 2021.06.12.13.57.31_veh-38_04341_04467 + - 2021.06.12.13.57.31_veh-38_04488_04663 + - 2021.06.12.13.57.31_veh-38_04674_05071 + - 2021.06.12.13.57.31_veh-38_05105_05341 + - 2021.06.12.13.57.31_veh-38_05352_05491 + - 2021.06.12.13.57.31_veh-38_05502_05614 + - 2021.06.12.13.57.31_veh-38_05625_05877 + - 2021.06.12.13.57.31_veh-38_05888_06197 + - 2021.06.12.14.07.16_veh-26_00016_00261 + - 2021.06.12.14.07.16_veh-26_00272_00473 + - 2021.06.12.14.07.16_veh-26_00509_00902 + - 2021.06.12.14.07.16_veh-26_00939_01003 + - 2021.06.12.14.07.16_veh-26_01063_01327 + - 2021.06.12.14.07.16_veh-26_01338_01677 + - 2021.06.12.14.07.16_veh-26_01742_01839 + - 2021.06.12.14.07.16_veh-26_01919_02267 + - 2021.06.12.14.07.16_veh-26_02279_02389 + - 2021.06.12.14.07.16_veh-26_02400_02467 + - 2021.06.12.14.07.16_veh-26_02478_02827 + - 2021.06.12.14.07.16_veh-26_02838_03032 + - 2021.06.12.14.07.16_veh-26_03043_03310 + - 2021.06.12.14.07.16_veh-26_03404_03778 + - 2021.06.12.14.07.16_veh-26_03789_03975 + - 2021.06.12.14.07.16_veh-26_04011_04372 + - 2021.06.12.16.56.47_veh-26_00016_00215 + - 2021.06.12.16.56.47_veh-26_00226_00411 + - 2021.06.12.16.56.47_veh-26_00423_00636 + - 2021.06.12.16.56.47_veh-26_00956_01045 + - 2021.06.12.16.56.47_veh-26_01117_01204 + - 2021.06.12.16.56.47_veh-26_01288_01602 + - 2021.06.12.16.56.47_veh-26_01665_01735 + - 2021.06.12.16.56.47_veh-26_01746_01965 + - 2021.06.12.16.56.47_veh-26_01976_02960 + - 2021.06.12.16.56.47_veh-26_02971_03367 + - 2021.06.12.16.56.47_veh-26_03378_03491 + - 2021.06.12.16.56.47_veh-26_03528_03762 + - 2021.06.12.16.56.47_veh-26_03773_03838 + - 2021.06.12.16.56.47_veh-26_03849_03932 + - 2021.06.12.16.56.47_veh-26_03943_04148 + - 2021.06.12.16.56.47_veh-26_04271_04410 + - 2021.06.12.16.56.47_veh-26_04421_04485 + - 2021.06.12.16.56.47_veh-26_04509_04590 + - 2021.06.12.16.56.47_veh-26_04655_04903 + - 2021.06.12.16.56.47_veh-26_04914_04985 + - 2021.06.12.16.56.47_veh-26_04996_05306 + - 2021.06.12.16.57.06_veh-35_00033_00109 + - 2021.06.12.16.57.06_veh-35_00168_00323 + - 2021.06.12.16.57.06_veh-35_00334_00394 + - 2021.06.12.16.57.06_veh-35_00406_00518 + - 2021.06.12.16.57.06_veh-35_00529_00666 + - 2021.06.12.16.57.06_veh-35_00715_01149 + - 2021.06.12.16.57.06_veh-35_01160_01269 + - 2021.06.12.16.57.06_veh-35_01280_01464 + - 2021.06.12.16.57.06_veh-35_01475_01670 + - 2021.06.12.16.57.06_veh-35_01681_02239 + - 2021.06.12.16.57.06_veh-35_02285_02366 + - 2021.06.12.16.57.06_veh-35_02413_02513 + - 2021.06.12.16.57.06_veh-35_02524_02597 + - 2021.06.12.16.57.06_veh-35_02608_02830 + - 2021.06.12.16.57.06_veh-35_02876_03155 + - 2021.06.12.16.57.06_veh-35_03166_03331 + - 2021.06.12.16.57.06_veh-35_03342_03473 + - 2021.06.12.16.57.06_veh-35_03519_03695 + - 2021.06.12.16.57.06_veh-35_03706_03939 + - 2021.06.12.16.57.06_veh-35_03950_04199 + - 2021.06.12.16.57.06_veh-35_04211_04342 + - 2021.06.12.16.57.06_veh-35_04390_04755 + - 2021.06.12.16.57.06_veh-35_04766_04880 + - 2021.06.12.16.57.06_veh-35_04891_04958 + - 2021.06.12.16.57.06_veh-35_04980_05088 + - 2021.06.12.16.57.06_veh-35_05133_05244 + - 2021.06.12.16.57.06_veh-35_05301_05410 + - 2021.06.12.16.57.06_veh-35_05421_05635 + - 2021.06.12.16.57.06_veh-35_05646_05716 + - 2021.06.12.16.57.06_veh-35_05727_05825 + - 2021.06.12.16.57.06_veh-35_05836_05897 + - 2021.06.12.16.57.06_veh-35_05908_06309 + - 2021.06.12.17.11.31_veh-38_00005_00153 + - 2021.06.12.17.11.31_veh-38_00164_00337 + - 2021.06.12.17.11.31_veh-38_00348_00563 + - 2021.06.12.17.11.31_veh-38_00594_00870 + - 2021.06.12.17.11.31_veh-38_00881_01116 + - 2021.06.12.17.11.31_veh-38_01129_01273 + - 2021.06.12.17.11.31_veh-38_01284_01472 + - 2021.06.12.17.11.31_veh-38_01483_01781 + - 2021.06.12.17.11.31_veh-38_01792_02072 + - 2021.06.12.17.11.31_veh-38_02083_02384 + - 2021.06.12.17.11.31_veh-38_02444_02616 + - 2021.06.12.17.11.31_veh-38_02627_02735 + - 2021.06.12.17.11.31_veh-38_02787_02963 + - 2021.06.12.17.11.31_veh-38_02974_03171 + - 2021.06.12.17.11.31_veh-38_03183_03275 + - 2021.06.12.17.11.31_veh-38_03286_03372 + - 2021.06.12.17.11.31_veh-38_03383_03478 + - 2021.06.12.17.11.31_veh-38_03489_03633 + - 2021.06.12.17.11.31_veh-38_03644_04150 + - 2021.06.12.17.11.31_veh-38_04161_04362 + - 2021.06.12.17.11.31_veh-38_04413_04705 + - 2021.06.12.17.11.31_veh-38_04716_04923 + - 2021.06.12.17.11.31_veh-38_04934_05088 + - 2021.06.12.17.11.31_veh-38_05154_05472 + - 2021.06.12.17.37.57_veh-47_00128_00481 + - 2021.06.12.17.37.57_veh-47_00492_00635 + - 2021.06.12.17.37.57_veh-47_00646_00721 + - 2021.06.12.17.37.57_veh-47_00902_01189 + - 2021.06.12.17.37.57_veh-47_01200_01367 + - 2021.06.12.17.37.57_veh-47_01378_01461 + - 2021.06.12.17.37.57_veh-47_01472_01779 + - 2021.06.12.17.37.57_veh-47_01977_02295 + - 2021.06.12.17.37.57_veh-47_02306_02953 + - 2021.06.12.17.37.57_veh-47_02998_03221 + - 2021.06.12.17.37.57_veh-47_03354_03522 + - 2021.06.12.17.37.57_veh-47_03534_04235 + - 2021.06.12.17.37.57_veh-47_04246_04538 + - 2021.06.12.17.37.57_veh-47_04579_04722 + - 2021.06.12.17.37.57_veh-47_04733_04829 + - 2021.06.12.17.37.57_veh-47_04840_04922 + - 2021.06.12.17.37.57_veh-47_04934_05336 + - 2021.06.12.19.04.44_veh-26_00085_00148 + - 2021.06.12.19.04.44_veh-26_00159_01592 + - 2021.06.12.19.04.44_veh-26_01603_01687 + - 2021.06.12.19.04.44_veh-26_01698_01804 + - 2021.06.12.19.04.44_veh-26_01815_01903 + - 2021.06.12.19.04.44_veh-26_02007_02115 + - 2021.06.12.19.04.44_veh-26_02206_02791 + - 2021.06.12.19.04.44_veh-26_02802_02918 + - 2021.06.12.19.04.44_veh-26_02997_03242 + - 2021.06.12.19.04.44_veh-26_03265_03866 + - 2021.06.12.19.04.44_veh-26_03918_04399 + - 2021.06.12.19.04.44_veh-26_04410_04569 + - 2021.06.12.19.04.44_veh-26_04580_04806 + - 2021.06.12.19.12.40_veh-35_00029_00172 + - 2021.06.12.19.12.40_veh-35_00183_00303 + - 2021.06.12.19.12.40_veh-35_00391_00460 + - 2021.06.12.19.12.40_veh-35_00471_00576 + - 2021.06.12.19.12.40_veh-35_00587_00794 + - 2021.06.12.19.12.40_veh-35_00805_00973 + - 2021.06.12.19.12.40_veh-35_00984_01206 + - 2021.06.12.19.12.40_veh-35_01225_01389 + - 2021.06.12.19.12.40_veh-35_01400_01681 + - 2021.06.12.19.12.40_veh-35_01692_01773 + - 2021.06.12.19.12.40_veh-35_01784_01915 + - 2021.06.12.19.12.40_veh-35_01959_02064 + - 2021.06.12.19.12.40_veh-35_02165_02274 + - 2021.06.12.19.12.40_veh-35_02285_02549 + - 2021.06.12.19.12.40_veh-35_02560_02956 + - 2021.06.12.19.12.40_veh-35_02967_03263 + - 2021.06.12.19.12.40_veh-35_03274_03354 + - 2021.06.12.19.12.40_veh-35_03366_03455 + - 2021.06.12.19.12.40_veh-35_03476_03719 + - 2021.06.12.19.12.40_veh-35_03731_03968 + - 2021.06.12.19.12.40_veh-35_03979_04108 + - 2021.06.12.19.12.40_veh-35_04134_04225 + - 2021.06.12.19.12.40_veh-35_04236_04466 + - 2021.06.12.19.12.40_veh-35_04477_04538 + - 2021.06.12.19.14.12_veh-38_00005_00102 + - 2021.06.12.19.14.12_veh-38_00113_00179 + - 2021.06.12.19.14.12_veh-38_00190_00711 + - 2021.06.12.19.14.12_veh-38_00827_00970 + - 2021.06.12.19.14.12_veh-38_01110_01274 + - 2021.06.12.19.14.12_veh-38_01285_01425 + - 2021.06.12.19.14.12_veh-38_01474_01827 + - 2021.06.12.19.14.12_veh-38_01838_01904 + - 2021.06.12.19.14.12_veh-38_01975_02086 + - 2021.06.12.19.14.12_veh-38_02118_02453 + - 2021.06.12.19.14.12_veh-38_02521_02668 + - 2021.06.12.19.14.12_veh-38_02679_02757 + - 2021.06.12.19.14.12_veh-38_02768_02841 + - 2021.06.12.19.14.12_veh-38_02852_02925 + - 2021.06.12.19.14.12_veh-38_02937_03192 + - 2021.06.12.19.14.12_veh-38_03203_03569 + - 2021.06.12.19.14.12_veh-38_03580_04007 + - 2021.06.12.19.15.35_veh-47_00005_00316 + - 2021.06.12.19.15.35_veh-47_00334_00437 + - 2021.06.12.19.15.35_veh-47_00448_00723 + - 2021.06.12.19.15.35_veh-47_00734_00856 + - 2021.06.12.19.15.35_veh-47_00867_01217 + - 2021.06.12.19.15.35_veh-47_01228_01539 + - 2021.06.12.19.15.35_veh-47_01550_01634 + - 2021.06.12.19.15.35_veh-47_01645_01970 + - 2021.06.12.19.15.35_veh-47_02006_02179 + - 2021.06.12.19.15.35_veh-47_02190_02354 + - 2021.06.12.19.15.35_veh-47_02365_02535 + - 2021.06.12.19.15.35_veh-47_02649_02750 + - 2021.06.12.19.15.35_veh-47_02851_02957 + - 2021.06.12.19.15.35_veh-47_02968_03119 + - 2021.06.12.19.15.35_veh-47_03130_03329 + - 2021.06.12.19.15.35_veh-47_03340_03460 + - 2021.06.12.19.15.35_veh-47_03542_03725 + - 2021.06.12.19.15.35_veh-47_04013_04080 + - 2021.06.14.11.44.56_veh-35_00059_00410 + - 2021.06.14.11.44.56_veh-35_00453_00731 + - 2021.06.14.11.44.56_veh-35_00742_00927 + - 2021.06.14.11.44.56_veh-35_00938_01134 + - 2021.06.14.11.44.56_veh-35_01145_01297 + - 2021.06.14.11.44.56_veh-35_01308_01584 + - 2021.06.14.11.44.56_veh-35_01595_01804 + - 2021.06.14.11.44.56_veh-35_01869_01972 + - 2021.06.14.11.44.56_veh-35_01983_02053 + - 2021.06.14.11.44.56_veh-35_02064_02388 + - 2021.06.14.11.44.56_veh-35_02399_02672 + - 2021.06.14.11.44.56_veh-35_02696_02932 + - 2021.06.14.11.44.56_veh-35_02983_03378 + - 2021.06.14.11.44.56_veh-35_03389_04017 + - 2021.06.14.11.44.56_veh-35_04178_05084 + - 2021.06.14.11.44.56_veh-35_05211_05338 + - 2021.06.14.13.11.51_veh-47_00015_00330 + - 2021.06.14.13.11.51_veh-47_00341_00592 + - 2021.06.14.13.11.51_veh-47_00603_00702 + - 2021.06.14.13.11.51_veh-47_00839_01049 + - 2021.06.14.13.11.51_veh-47_01085_01321 + - 2021.06.14.13.11.51_veh-47_01392_01678 + - 2021.06.14.13.11.51_veh-47_01714_01785 + - 2021.06.14.13.11.51_veh-47_01796_01923 + - 2021.06.14.13.11.51_veh-47_02008_02133 + - 2021.06.14.13.11.51_veh-47_02169_02476 + - 2021.06.14.13.11.51_veh-47_02487_02669 + - 2021.06.14.13.11.51_veh-47_02707_02809 + - 2021.06.14.13.11.51_veh-47_02871_03182 + - 2021.06.14.13.11.51_veh-47_03244_03360 + - 2021.06.14.13.11.51_veh-47_03371_03772 + - 2021.06.14.13.11.51_veh-47_03946_04223 + - 2021.06.14.13.11.51_veh-47_04234_04392 + - 2021.06.14.13.11.51_veh-47_04445_04511 + - 2021.06.14.13.11.51_veh-47_04522_04724 + - 2021.06.14.13.11.51_veh-47_04735_04933 + - 2021.06.14.13.11.51_veh-47_04944_05088 + - 2021.06.14.13.11.51_veh-47_05101_05340 + - 2021.06.14.13.11.51_veh-47_05351_05672 + - 2021.06.14.13.11.51_veh-47_05683_05754 + - 2021.06.14.13.27.42_veh-35_00005_00123 + - 2021.06.14.13.27.42_veh-35_00142_00231 + - 2021.06.14.13.27.42_veh-35_00243_00342 + - 2021.06.14.13.27.42_veh-35_00353_00531 + - 2021.06.14.13.27.42_veh-35_00542_00645 + - 2021.06.14.13.27.42_veh-35_00691_00798 + - 2021.06.14.13.27.42_veh-35_00842_00940 + - 2021.06.14.13.27.42_veh-35_01025_01086 + - 2021.06.14.13.27.42_veh-35_01160_01331 + - 2021.06.14.13.27.42_veh-35_01342_01461 + - 2021.06.14.13.27.42_veh-35_01472_01666 + - 2021.06.14.13.27.42_veh-35_01698_01822 + - 2021.06.14.13.27.42_veh-35_01854_01994 + - 2021.06.14.13.27.42_veh-35_02028_02106 + - 2021.06.14.13.27.42_veh-35_02117_02272 + - 2021.06.14.13.27.42_veh-35_02283_02603 + - 2021.06.14.13.27.42_veh-35_02614_02842 + - 2021.06.14.13.27.42_veh-35_02853_02953 + - 2021.06.14.13.27.42_veh-35_03142_03404 + - 2021.06.14.13.27.42_veh-35_03463_03587 + - 2021.06.14.13.27.42_veh-35_03624_03705 + - 2021.06.14.13.27.42_veh-35_03806_03990 + - 2021.06.14.13.27.42_veh-35_04001_04236 + - 2021.06.14.13.27.42_veh-35_04362_04572 + - 2021.06.14.13.27.42_veh-35_04596_04692 + - 2021.06.14.13.27.42_veh-35_04704_04782 + - 2021.06.14.13.27.42_veh-35_04793_04883 + - 2021.06.14.13.27.42_veh-35_04894_05018 + - 2021.06.14.13.27.42_veh-35_05029_05340 + - 2021.06.14.13.28.41_veh-12_00005_00158 + - 2021.06.14.13.28.41_veh-12_00169_00783 + - 2021.06.14.13.28.41_veh-12_00906_01063 + - 2021.06.14.13.28.41_veh-12_01138_01284 + - 2021.06.14.13.28.41_veh-12_01313_01541 + - 2021.06.14.13.28.41_veh-12_01591_01695 + - 2021.06.14.13.28.41_veh-12_01779_02059 + - 2021.06.14.13.28.41_veh-12_02070_02140 + - 2021.06.14.13.28.41_veh-12_02245_02340 + - 2021.06.14.13.28.41_veh-12_02414_02601 + - 2021.06.14.13.28.41_veh-12_02612_02703 + - 2021.06.14.13.28.41_veh-12_02845_03153 + - 2021.06.14.13.28.41_veh-12_03221_03301 + - 2021.06.14.13.28.41_veh-12_03312_03409 + - 2021.06.14.13.28.41_veh-12_03457_03543 + - 2021.06.14.13.28.41_veh-12_03763_03829 + - 2021.06.14.13.28.41_veh-12_03841_04014 + - 2021.06.14.13.28.41_veh-12_04090_04289 + - 2021.06.14.13.28.41_veh-12_04300_04506 + - 2021.06.14.13.28.41_veh-12_04530_04609 + - 2021.06.14.13.28.41_veh-12_04719_04892 + - 2021.06.14.13.28.41_veh-12_04903_05107 + - 2021.06.14.13.28.41_veh-12_05118_05258 + - 2021.06.14.13.28.41_veh-12_05269_05369 + - 2021.06.14.13.29.49_veh-16_00016_00241 + - 2021.06.14.14.03.45_veh-38_00088_00769 + - 2021.06.14.14.03.45_veh-38_00780_01007 + - 2021.06.14.14.03.45_veh-38_01018_01144 + - 2021.06.14.14.03.45_veh-38_01155_01358 + - 2021.06.14.14.03.45_veh-38_01369_01458 + - 2021.06.14.14.03.45_veh-38_01547_01613 + - 2021.06.14.14.03.45_veh-38_01624_01811 + - 2021.06.14.14.03.45_veh-38_01927_01996 + - 2021.06.14.14.03.45_veh-38_02007_02072 + - 2021.06.14.14.03.45_veh-38_02112_03169 + - 2021.06.14.14.03.45_veh-38_03180_03766 + - 2021.06.14.14.03.45_veh-38_03777_04059 + - 2021.06.14.14.03.45_veh-38_04137_04387 + - 2021.06.14.14.03.45_veh-38_04398_04488 + - 2021.06.14.14.03.45_veh-38_04499_05170 + - 2021.06.14.14.03.45_veh-38_05222_05347 + - 2021.06.14.14.25.15_veh-26_00398_00578 + - 2021.06.14.14.25.15_veh-26_00597_00827 + - 2021.06.14.14.25.15_veh-26_00867_01088 + - 2021.06.14.14.25.15_veh-26_01236_01585 + - 2021.06.14.14.25.15_veh-26_01600_01699 + - 2021.06.14.14.25.15_veh-26_01752_01813 + - 2021.06.14.14.25.15_veh-26_01835_01960 + - 2021.06.14.14.25.15_veh-26_02009_02099 + - 2021.06.14.14.25.15_veh-26_02179_02316 + - 2021.06.14.14.25.15_veh-26_02376_02575 + - 2021.06.14.14.25.15_veh-26_02586_02648 + - 2021.06.14.14.25.15_veh-26_02659_02759 + - 2021.06.14.14.25.15_veh-26_02770_02830 + - 2021.06.14.14.25.15_veh-26_02841_02921 + - 2021.06.14.14.25.15_veh-26_02932_03190 + - 2021.06.14.14.25.15_veh-26_03201_03386 + - 2021.06.14.14.25.15_veh-26_03415_03581 + - 2021.06.14.14.25.15_veh-26_03592_03664 + - 2021.06.14.14.25.15_veh-26_03675_03860 + - 2021.06.14.14.25.15_veh-26_03871_03953 + - 2021.06.14.14.25.15_veh-26_03964_04278 + - 2021.06.14.14.25.15_veh-26_04289_04406 + - 2021.06.14.14.25.15_veh-26_04417_04531 + - 2021.06.14.14.25.15_veh-26_04542_04617 + - 2021.06.14.14.25.15_veh-26_04629_04724 + - 2021.06.14.14.25.15_veh-26_04735_04829 + - 2021.06.14.14.25.15_veh-26_04936_05073 + - 2021.06.14.14.25.15_veh-26_05108_05312 + - 2021.06.14.15.15.37_veh-47_00156_00540 + - 2021.06.14.15.15.37_veh-47_00551_00715 + - 2021.06.14.15.15.37_veh-47_00726_00841 + - 2021.06.14.15.15.37_veh-47_00905_01074 + - 2021.06.14.15.15.37_veh-47_01106_01177 + - 2021.06.14.15.15.37_veh-47_01189_01865 + - 2021.06.14.15.15.37_veh-47_01899_01979 + - 2021.06.14.15.15.37_veh-47_02015_02199 + - 2021.06.14.15.15.37_veh-47_02213_02564 + - 2021.06.14.15.15.37_veh-47_02575_03183 + - 2021.06.14.15.15.37_veh-47_03194_03304 + - 2021.06.14.15.15.37_veh-47_03315_03669 + - 2021.06.14.15.15.37_veh-47_03680_03743 + - 2021.06.14.15.15.37_veh-47_03755_03875 + - 2021.06.14.15.15.37_veh-47_03886_04318 + - 2021.06.14.15.15.37_veh-47_04336_04416 + - 2021.06.14.15.15.37_veh-47_04447_04575 + - 2021.06.14.15.15.37_veh-47_04586_04885 + - 2021.06.14.15.15.37_veh-47_04897_04965 + - 2021.06.14.15.15.37_veh-47_04986_05072 + - 2021.06.14.15.15.37_veh-47_05084_05640 + - 2021.06.14.15.15.37_veh-47_05651_05742 + - 2021.06.14.16.32.09_veh-35_00016_00087 + - 2021.06.14.16.32.09_veh-35_00100_00272 + - 2021.06.14.16.32.09_veh-35_00283_00357 + - 2021.06.14.16.32.09_veh-35_00429_00563 + - 2021.06.14.16.32.09_veh-35_00574_00989 + - 2021.06.14.16.32.09_veh-35_01219_01415 + - 2021.06.14.16.32.09_veh-35_01489_01563 + - 2021.06.14.16.32.09_veh-35_01620_01699 + - 2021.06.14.16.32.09_veh-35_01710_01770 + - 2021.06.14.16.32.09_veh-35_01781_02379 + - 2021.06.14.16.32.09_veh-35_02435_02526 + - 2021.06.14.16.32.09_veh-35_02537_02597 + - 2021.06.14.16.32.09_veh-35_02618_02873 + - 2021.06.14.16.32.09_veh-35_02928_03118 + - 2021.06.14.16.32.09_veh-35_03129_03220 + - 2021.06.14.16.32.09_veh-35_03231_03426 + - 2021.06.14.16.32.09_veh-35_03438_03580 + - 2021.06.14.16.32.09_veh-35_03635_03792 + - 2021.06.14.16.32.09_veh-35_03803_04103 + - 2021.06.14.16.32.09_veh-35_04114_04359 + - 2021.06.14.16.32.09_veh-35_04370_04488 + - 2021.06.14.16.32.09_veh-35_04516_04698 + - 2021.06.14.16.32.09_veh-35_04749_05027 + - 2021.06.14.16.32.09_veh-35_05038_05402 + - 2021.06.14.16.48.02_veh-12_00009_00127 + - 2021.06.14.16.48.02_veh-12_00285_00574 + - 2021.06.14.16.48.02_veh-12_00585_00672 + - 2021.06.14.16.48.02_veh-12_00721_00828 + - 2021.06.14.16.48.02_veh-12_00839_00980 + - 2021.06.14.16.48.02_veh-12_01020_01720 + - 2021.06.14.16.48.02_veh-12_01732_01853 + - 2021.06.14.16.48.02_veh-12_01880_02198 + - 2021.06.14.16.48.02_veh-12_02317_02401 + - 2021.06.14.16.48.02_veh-12_02412_02506 + - 2021.06.14.16.48.02_veh-12_02517_02590 + - 2021.06.14.16.48.02_veh-12_02601_02668 + - 2021.06.14.16.48.02_veh-12_02679_02850 + - 2021.06.14.16.48.02_veh-12_02861_03047 + - 2021.06.14.16.48.02_veh-12_03091_03461 + - 2021.06.14.16.48.02_veh-12_03472_03779 + - 2021.06.14.16.48.02_veh-12_03790_04046 + - 2021.06.14.16.48.02_veh-12_04057_04438 + - 2021.06.14.16.48.02_veh-12_04492_04604 + - 2021.06.14.16.48.02_veh-12_04615_04689 + - 2021.06.14.16.48.02_veh-12_04783_04967 + - 2021.06.14.16.48.02_veh-12_04978_05337 + - 2021.06.14.17.26.26_veh-38_00104_00944 + - 2021.06.14.17.26.26_veh-38_00955_01067 + - 2021.06.14.17.26.26_veh-38_01078_01166 + - 2021.06.14.17.26.26_veh-38_01177_01256 + - 2021.06.14.17.26.26_veh-38_01293_01488 + - 2021.06.14.17.26.26_veh-38_01499_01849 + - 2021.06.14.17.26.26_veh-38_01860_02729 + - 2021.06.14.17.26.26_veh-38_02740_03036 + - 2021.06.14.17.26.26_veh-38_03086_03150 + - 2021.06.14.17.26.26_veh-38_03162_03227 + - 2021.06.14.17.26.26_veh-38_03238_03403 + - 2021.06.14.17.26.26_veh-38_03414_03761 + - 2021.06.14.17.26.26_veh-38_03772_03967 + - 2021.06.14.17.26.26_veh-38_04030_04274 + - 2021.06.14.17.26.26_veh-38_04285_04392 + - 2021.06.14.17.26.26_veh-38_04403_04533 + - 2021.06.14.17.26.26_veh-38_04544_04920 + - 2021.06.14.17.26.26_veh-38_04931_05037 + - 2021.06.14.17.26.26_veh-38_05048_05270 + - 2021.06.14.17.26.26_veh-38_05281_05444 + - 2021.06.14.17.26.26_veh-38_05455_05749 + - 2021.06.14.17.26.26_veh-38_05760_05896 + - 2021.06.14.18.13.35_veh-26_00027_00215 + - 2021.06.14.18.13.35_veh-26_00259_00374 + - 2021.06.14.18.13.35_veh-26_00385_00471 + - 2021.06.14.18.13.35_veh-26_00522_00702 + - 2021.06.14.18.13.35_veh-26_00713_00818 + - 2021.06.14.18.13.35_veh-26_00863_00924 + - 2021.06.14.18.13.35_veh-26_00954_01050 + - 2021.06.14.18.13.35_veh-26_01062_01139 + - 2021.06.14.18.13.35_veh-26_01150_01320 + - 2021.06.14.18.13.35_veh-26_01331_01526 + - 2021.06.14.18.13.35_veh-26_01537_01717 + - 2021.06.14.18.13.35_veh-26_01728_01918 + - 2021.06.14.18.13.35_veh-26_01931_02022 + - 2021.06.14.18.13.35_veh-26_02033_02313 + - 2021.06.14.18.13.35_veh-26_02324_02430 + - 2021.06.14.18.13.35_veh-26_02441_02514 + - 2021.06.14.18.13.35_veh-26_02724_02920 + - 2021.06.14.18.13.35_veh-26_03030_03119 + - 2021.06.14.18.13.35_veh-26_03130_03197 + - 2021.06.14.18.13.35_veh-26_03258_03349 + - 2021.06.14.18.13.35_veh-26_03401_03691 + - 2021.06.14.18.13.35_veh-26_03853_03946 + - 2021.06.14.18.13.35_veh-26_03957_04032 + - 2021.06.14.18.13.35_veh-26_04058_04170 + - 2021.06.14.18.13.35_veh-26_04204_04323 + - 2021.06.14.18.13.35_veh-26_04412_04536 + - 2021.06.14.18.13.35_veh-26_04547_04710 + - 2021.06.14.18.13.35_veh-26_04721_04800 + - 2021.06.14.18.13.35_veh-26_04811_04953 + - 2021.06.14.18.13.35_veh-26_04964_05075 + - 2021.06.14.18.13.35_veh-26_05205_05275 + - 2021.06.14.18.13.35_veh-26_05286_05411 + - 2021.06.14.18.13.35_veh-26_05422_05488 + - 2021.06.14.18.13.35_veh-26_05600_05660 + - 2021.06.14.18.13.35_veh-26_05671_05749 + - 2021.06.14.18.19.31_veh-47_00005_00403 + - 2021.06.14.18.19.31_veh-47_00414_00606 + - 2021.06.14.18.19.31_veh-47_00684_01123 + - 2021.06.14.18.19.31_veh-47_01134_01226 + - 2021.06.14.18.19.31_veh-47_01254_01377 + - 2021.06.14.18.19.31_veh-47_01388_01678 + - 2021.06.14.18.19.31_veh-47_01689_01831 + - 2021.06.14.18.19.31_veh-47_01842_01976 + - 2021.06.14.18.19.31_veh-47_01987_02049 + - 2021.06.14.18.19.31_veh-47_02060_02169 + - 2021.06.14.18.19.31_veh-47_02180_02551 + - 2021.06.14.18.19.31_veh-47_02562_02817 + - 2021.06.14.18.19.31_veh-47_02828_02889 + - 2021.06.14.18.19.31_veh-47_02944_03084 + - 2021.06.14.18.19.31_veh-47_03102_03235 + - 2021.06.14.18.19.31_veh-47_03309_03548 + - 2021.06.14.18.19.31_veh-47_03559_03645 + - 2021.06.14.18.19.31_veh-47_03659_03854 + - 2021.06.14.18.19.31_veh-47_03865_04818 + - 2021.06.14.18.19.31_veh-47_04829_04966 + - 2021.06.14.18.19.31_veh-47_05010_05231 + - 2021.06.14.18.19.31_veh-47_05264_05374 + - 2021.06.14.18.33.41_veh-35_00016_00213 + - 2021.06.14.18.33.41_veh-35_00224_00344 + - 2021.06.14.18.33.41_veh-35_00355_00477 + - 2021.06.14.18.33.41_veh-35_00488_00562 + - 2021.06.14.18.33.41_veh-35_00573_00643 + - 2021.06.14.18.33.41_veh-35_00654_00887 + - 2021.06.14.18.33.41_veh-35_00898_01182 + - 2021.06.14.18.33.41_veh-35_01193_01304 + - 2021.06.14.18.33.41_veh-35_01363_01636 + - 2021.06.14.18.33.41_veh-35_01647_01714 + - 2021.06.14.18.33.41_veh-35_01739_01918 + - 2021.06.14.18.33.41_veh-35_01970_02043 + - 2021.06.14.18.33.41_veh-35_02054_02129 + - 2021.06.14.18.33.41_veh-35_02140_02328 + - 2021.06.14.18.33.41_veh-35_02339_02447 + - 2021.06.14.18.33.41_veh-35_02521_03356 + - 2021.06.14.18.33.41_veh-35_03367_03508 + - 2021.06.14.18.33.41_veh-35_03575_03668 + - 2021.06.14.18.33.41_veh-35_03679_03787 + - 2021.06.14.18.33.41_veh-35_03798_03867 + - 2021.06.14.18.33.41_veh-35_03901_04264 + - 2021.06.14.18.33.41_veh-35_04275_04435 + - 2021.06.14.18.33.41_veh-35_04446_04756 + - 2021.06.14.18.33.41_veh-35_04768_04894 + - 2021.06.14.18.33.41_veh-35_04905_05090 + - 2021.06.14.18.42.45_veh-12_00016_00185 + - 2021.06.14.18.42.45_veh-12_00364_00501 + - 2021.06.14.18.42.45_veh-12_00547_00777 + - 2021.06.14.18.42.45_veh-12_00789_00920 + - 2021.06.14.18.42.45_veh-12_00968_01052 + - 2021.06.14.18.42.45_veh-12_01065_01152 + - 2021.06.14.18.42.45_veh-12_01253_01334 + - 2021.06.14.18.42.45_veh-12_01345_01523 + - 2021.06.14.18.42.45_veh-12_01535_01612 + - 2021.06.14.18.42.45_veh-12_01680_01744 + - 2021.06.14.18.42.45_veh-12_01762_02072 + - 2021.06.14.18.42.45_veh-12_02099_02167 + - 2021.06.14.18.42.45_veh-12_02233_02300 + - 2021.06.14.18.42.45_veh-12_02318_02407 + - 2021.06.14.18.42.45_veh-12_02520_02585 + - 2021.06.14.18.42.45_veh-12_02596_02661 + - 2021.06.14.18.42.45_veh-12_02737_02967 + - 2021.06.14.18.42.45_veh-12_02978_03068 + - 2021.06.14.18.42.45_veh-12_03200_03329 + - 2021.06.14.18.42.45_veh-12_03340_03403 + - 2021.06.14.18.42.45_veh-12_03445_03902 + - 2021.06.14.18.42.45_veh-12_03913_04017 + - 2021.06.14.18.42.45_veh-12_04086_04221 + - 2021.06.14.18.42.45_veh-12_04233_04472 + - 2021.06.14.18.42.45_veh-12_04534_04609 + - 2021.06.14.18.42.45_veh-12_04620_04742 + - 2021.06.14.18.42.45_veh-12_04838_04927 + - 2021.06.14.18.42.45_veh-12_05000_05079 + - 2021.06.14.18.42.45_veh-12_05170_05261 + - 2021.06.14.19.22.11_veh-38_00040_00464 + - 2021.06.14.19.22.11_veh-38_00572_00648 + - 2021.06.14.19.22.11_veh-38_00675_00889 + - 2021.06.14.19.22.11_veh-38_00910_01029 + - 2021.06.14.19.22.11_veh-38_01134_01389 + - 2021.06.14.19.22.11_veh-38_01400_01469 + - 2021.06.14.19.22.11_veh-38_01480_01860 + - 2021.06.14.19.22.11_veh-38_01871_02040 + - 2021.06.14.19.22.11_veh-38_02051_02264 + - 2021.06.14.19.22.11_veh-38_02275_02455 + - 2021.06.14.19.22.11_veh-38_02466_02675 + - 2021.06.14.19.22.11_veh-38_02686_02846 + - 2021.06.14.19.22.11_veh-38_02857_03230 + - 2021.06.14.19.22.11_veh-38_03242_03907 + - 2021.06.14.19.53.56_veh-47_00040_00127 + - 2021.06.14.19.53.56_veh-47_00138_00238 + - 2021.06.14.19.53.56_veh-47_00249_00424 + - 2021.06.14.19.53.56_veh-47_00435_00713 + - 2021.06.14.19.53.56_veh-47_00775_00922 + - 2021.06.14.19.53.56_veh-47_00949_01164 + - 2021.06.14.19.53.56_veh-47_01175_01637 + - 2021.06.14.19.53.56_veh-47_01745_01964 + - 2021.06.14.19.53.56_veh-47_01975_02149 + - 2021.06.14.19.53.56_veh-47_02160_02314 + - 2021.06.14.19.53.56_veh-47_02325_02395 + - 2021.06.14.19.53.56_veh-47_02487_02584 + - 2021.06.14.19.53.56_veh-47_02595_02705 + - 2021.06.14.20.14.09_veh-26_00024_00237 + - 2021.06.14.20.14.09_veh-26_00248_00477 + - 2021.06.14.20.14.09_veh-26_00488_00601 + - 2021.06.14.20.14.09_veh-26_00612_01016 + - 2021.06.14.20.14.09_veh-26_01027_01110 + - 2021.06.14.20.14.09_veh-26_01121_01211 + - 2021.06.15.12.52.19_veh-38_00027_00289 + - 2021.06.15.12.52.19_veh-38_00300_00373 + - 2021.06.15.12.52.19_veh-38_00385_00463 + - 2021.06.15.12.52.19_veh-38_00548_01068 + - 2021.06.15.12.52.19_veh-38_01079_01183 + - 2021.06.15.12.52.19_veh-38_01194_01429 + - 2021.06.15.12.52.19_veh-38_01440_01608 + - 2021.06.15.12.52.19_veh-38_01619_02065 + - 2021.06.15.12.52.19_veh-38_02076_02377 + - 2021.06.15.12.52.19_veh-38_02425_02677 + - 2021.06.15.12.52.19_veh-38_02688_02934 + - 2021.06.15.12.52.19_veh-38_02945_03023 + - 2021.06.15.12.52.19_veh-38_03053_03225 + - 2021.06.15.12.52.19_veh-38_03236_03372 + - 2021.06.15.12.52.19_veh-38_03383_03630 + - 2021.06.15.12.52.19_veh-38_03717_03903 + - 2021.06.15.12.52.19_veh-38_03914_04098 + - 2021.06.15.12.52.19_veh-38_04109_04248 + - 2021.06.15.12.52.19_veh-38_04260_04325 + - 2021.06.15.12.52.19_veh-38_04405_04633 + - 2021.06.15.12.52.19_veh-38_04644_04732 + - 2021.06.15.12.52.19_veh-38_04743_04883 + - 2021.06.15.12.52.19_veh-38_04894_04985 + - 2021.06.15.12.52.19_veh-38_05054_05266 + - 2021.06.15.12.52.19_veh-38_05278_05434 + - 2021.06.15.12.52.19_veh-38_05503_05616 + - 2021.06.15.12.55.18_veh-35_00101_00654 + - 2021.06.15.12.55.18_veh-35_00725_01058 + - 2021.06.15.12.55.18_veh-35_01069_01311 + - 2021.06.15.12.55.18_veh-35_01338_01510 + - 2021.06.15.12.55.18_veh-35_01521_01813 + - 2021.06.15.12.55.18_veh-35_01920_01987 + - 2021.06.15.12.55.18_veh-35_02092_02356 + - 2021.06.15.12.55.18_veh-35_02367_02443 + - 2021.06.15.12.55.18_veh-35_02454_02593 + - 2021.06.15.12.55.18_veh-35_02604_02706 + - 2021.06.15.12.55.18_veh-35_02768_03441 + - 2021.06.15.12.55.18_veh-35_03452_03591 + - 2021.06.15.12.55.18_veh-35_03613_03844 + - 2021.06.15.12.55.18_veh-35_03855_04078 + - 2021.06.15.12.55.18_veh-35_04137_04487 + - 2021.06.15.12.55.18_veh-35_04498_04961 + - 2021.06.15.12.55.18_veh-35_04972_05041 + - 2021.06.15.12.55.18_veh-35_05052_05319 + - 2021.06.15.12.55.18_veh-35_05358_05419 + - 2021.06.15.12.58.55_veh-47_00095_00240 + - 2021.06.15.12.58.55_veh-47_00251_00470 + - 2021.06.15.12.58.55_veh-47_00487_00615 + - 2021.06.15.12.58.55_veh-47_00660_00779 + - 2021.06.15.12.58.55_veh-47_00821_01311 + - 2021.06.15.12.58.55_veh-47_01322_01805 + - 2021.06.15.12.58.55_veh-47_01878_02253 + - 2021.06.15.12.58.55_veh-47_02264_02376 + - 2021.06.15.12.58.55_veh-47_02387_02680 + - 2021.06.15.12.58.55_veh-47_02702_02766 + - 2021.06.15.12.58.55_veh-47_02777_03116 + - 2021.06.15.12.58.55_veh-47_03127_03336 + - 2021.06.15.12.58.55_veh-47_03347_03716 + - 2021.06.15.12.58.55_veh-47_03727_03812 + - 2021.06.15.12.58.55_veh-47_03823_04022 + - 2021.06.15.12.58.55_veh-47_04033_04203 + - 2021.06.15.12.58.55_veh-47_04214_04291 + - 2021.06.15.12.58.55_veh-47_04302_04673 + - 2021.06.15.12.58.55_veh-47_04684_04771 + - 2021.06.15.12.58.55_veh-47_04782_05040 + - 2021.06.15.12.58.55_veh-47_05051_05122 + - 2021.06.15.12.58.55_veh-47_05133_05355 + - 2021.06.15.12.58.55_veh-47_05366_05639 + - 2021.06.15.12.58.55_veh-47_05650_05936 + - 2021.06.15.14.48.10_veh-38_00016_00117 + - 2021.06.15.14.48.10_veh-38_00128_00504 + - 2021.06.15.14.48.10_veh-38_00515_01120 + - 2021.06.15.14.48.10_veh-38_01131_01465 + - 2021.06.15.14.48.10_veh-38_01476_01839 + - 2021.06.15.14.48.10_veh-38_01850_02096 + - 2021.06.15.14.48.10_veh-38_02107_02213 + - 2021.06.15.14.48.10_veh-38_02224_02505 + - 2021.06.15.14.48.10_veh-38_02516_02631 + - 2021.06.15.14.48.10_veh-38_02642_02739 + - 2021.06.15.14.48.10_veh-38_02750_02846 + - 2021.06.15.14.48.10_veh-38_02857_03008 + - 2021.06.15.14.48.10_veh-38_03057_03407 + - 2021.06.15.14.48.10_veh-38_03435_03595 + - 2021.06.15.14.48.10_veh-38_03606_03670 + - 2021.06.15.14.48.10_veh-38_03740_03932 + - 2021.06.15.14.48.10_veh-38_03989_04108 + - 2021.06.15.14.48.10_veh-38_04119_04252 + - 2021.06.15.14.48.10_veh-38_04301_04567 + - 2021.06.15.14.48.10_veh-38_04643_04739 + - 2021.06.15.14.48.10_veh-38_04808_05059 + - 2021.06.15.14.48.10_veh-38_05070_05156 + - 2021.06.15.14.48.10_veh-38_05167_05358 + - 2021.06.15.14.48.10_veh-38_05369_05479 + - 2021.06.15.14.48.10_veh-38_05558_05640 + - 2021.06.15.15.06.36_veh-47_00101_00305 + - 2021.06.15.15.06.36_veh-47_00316_00461 + - 2021.06.15.15.06.36_veh-47_00603_00746 + - 2021.06.15.15.06.36_veh-47_00778_00991 + - 2021.06.15.15.06.36_veh-47_01003_01146 + - 2021.06.15.15.06.36_veh-47_01157_01654 + - 2021.06.15.15.45.10_veh-26_00052_00119 + - 2021.06.15.15.45.10_veh-26_00130_00198 + - 2021.06.15.15.45.10_veh-26_00237_00353 + - 2021.06.15.15.45.10_veh-26_00433_00559 + - 2021.06.15.15.45.10_veh-26_00570_00659 + - 2021.06.15.15.45.10_veh-26_00800_01125 + - 2021.06.15.15.45.10_veh-26_01136_01196 + - 2021.06.15.15.45.10_veh-26_01207_01376 + - 2021.06.15.15.45.10_veh-26_01401_01747 + - 2021.06.15.15.45.10_veh-26_01758_02205 + - 2021.06.15.15.45.10_veh-26_02221_02449 + - 2021.06.15.15.45.10_veh-26_02512_02579 + - 2021.06.15.15.45.10_veh-26_02590_02765 + - 2021.06.15.15.45.10_veh-26_02776_03077 + - 2021.06.15.15.45.10_veh-26_03088_03179 + - 2021.06.15.15.45.10_veh-26_03190_03414 + - 2021.06.15.15.45.10_veh-26_03425_03694 + - 2021.06.15.15.45.10_veh-26_03716_03799 + - 2021.06.15.15.45.10_veh-26_03810_04062 + - 2021.06.15.15.45.10_veh-26_04108_04222 + - 2021.06.15.15.45.10_veh-26_04259_04602 + - 2021.06.15.15.45.10_veh-26_04613_04752 + - 2021.06.15.15.45.10_veh-26_04763_04963 + - 2021.06.15.15.45.10_veh-26_05019_05237 + - 2021.06.15.15.45.10_veh-26_05248_05439 + - 2021.06.15.15.45.10_veh-26_05450_05531 + - 2021.06.15.15.45.10_veh-26_05542_05697 + - 2021.06.15.15.45.10_veh-26_05708_05845 + - 2021.06.15.16.17.16_veh-12_00031_00115 + - 2021.06.15.16.17.16_veh-12_00193_00274 + - 2021.06.15.16.17.16_veh-12_00285_00573 + - 2021.06.15.16.17.16_veh-12_00619_00682 + - 2021.06.15.16.17.16_veh-12_00725_00876 + - 2021.06.15.16.17.16_veh-12_00887_01294 + - 2021.06.15.16.17.16_veh-12_01305_01368 + - 2021.06.15.16.17.16_veh-12_01379_01530 + - 2021.06.15.16.17.16_veh-12_01560_01673 + - 2021.06.15.16.17.16_veh-12_01684_02245 + - 2021.06.15.16.17.16_veh-12_02256_02679 + - 2021.06.15.16.17.16_veh-12_02690_02852 + - 2021.06.15.16.17.16_veh-12_02863_03200 + - 2021.06.15.16.17.16_veh-12_03211_03414 + - 2021.06.15.16.17.16_veh-12_03485_03690 + - 2021.06.15.16.17.16_veh-12_03701_03867 + - 2021.06.15.16.17.16_veh-12_03878_04094 + - 2021.06.15.16.17.16_veh-12_04105_04217 + - 2021.06.15.16.17.16_veh-12_04325_04472 + - 2021.06.15.16.17.16_veh-12_04483_04609 + - 2021.06.15.16.17.16_veh-12_04620_04830 + - 2021.06.15.16.17.16_veh-12_04841_05013 + - 2021.06.15.16.17.16_veh-12_05024_05247 + - 2021.06.15.17.10.27_veh-47_00016_00079 + - 2021.06.15.17.10.27_veh-47_00120_00225 + - 2021.06.15.17.10.27_veh-47_00236_00430 + - 2021.06.15.17.10.27_veh-47_00441_00509 + - 2021.06.15.17.10.27_veh-47_00520_00639 + - 2021.06.15.17.10.27_veh-47_00650_00711 + - 2021.06.15.17.10.27_veh-47_00722_00860 + - 2021.06.15.17.10.27_veh-47_00871_00978 + - 2021.06.15.17.10.27_veh-47_00989_01056 + - 2021.06.15.17.10.27_veh-47_01136_01327 + - 2021.06.15.17.10.27_veh-47_01392_01663 + - 2021.06.15.17.10.27_veh-47_01674_01848 + - 2021.06.15.17.10.27_veh-47_01869_02049 + - 2021.06.15.17.10.27_veh-47_02088_02281 + - 2021.06.15.17.10.27_veh-47_02340_02463 + - 2021.06.15.17.10.27_veh-47_02474_02683 + - 2021.06.15.17.10.27_veh-47_02720_02790 + - 2021.06.15.17.10.27_veh-47_02820_02894 + - 2021.06.15.17.10.27_veh-47_02925_02998 + - 2021.06.15.17.10.27_veh-47_03017_03094 + - 2021.06.15.17.10.27_veh-47_03105_03257 + - 2021.06.15.17.10.27_veh-47_03270_03407 + - 2021.06.15.17.10.27_veh-47_03450_03529 + - 2021.06.15.17.10.27_veh-47_03540_03604 + - 2021.06.15.17.10.27_veh-47_03615_03706 + - 2021.06.15.17.10.27_veh-47_03717_03779 + - 2021.06.15.17.10.27_veh-47_03817_04041 + - 2021.06.15.17.10.27_veh-47_04052_04139 + - 2021.06.15.17.10.27_veh-47_04150_04506 + - 2021.06.15.17.10.27_veh-47_04517_04778 + - 2021.06.15.17.10.27_veh-47_04789_05029 + - 2021.06.15.17.10.27_veh-47_05040_05184 + - 2021.06.15.17.10.27_veh-47_05195_05267 + - 2021.06.15.17.10.27_veh-47_05397_05460 + - 2021.06.15.17.20.01_veh-35_00005_00119 + - 2021.06.15.17.20.01_veh-35_00130_00237 + - 2021.06.15.17.20.01_veh-35_00289_00500 + - 2021.06.15.17.20.01_veh-35_00511_00583 + - 2021.06.15.17.20.01_veh-35_00607_00733 + - 2021.06.15.17.20.01_veh-35_00744_00849 + - 2021.06.15.17.20.01_veh-35_00860_00949 + - 2021.06.15.17.20.01_veh-35_00960_01109 + - 2021.06.15.17.20.01_veh-35_01206_01335 + - 2021.06.15.17.20.01_veh-35_01445_01507 + - 2021.06.15.17.20.01_veh-35_01518_01597 + - 2021.06.15.17.20.01_veh-35_01608_01711 + - 2021.06.15.17.20.01_veh-35_01722_01797 + - 2021.06.15.17.20.01_veh-35_01808_01923 + - 2021.06.15.17.20.01_veh-35_02047_02142 + - 2021.06.15.17.20.01_veh-35_02163_02257 + - 2021.06.15.17.20.01_veh-35_02450_02528 + - 2021.06.15.17.20.01_veh-35_02585_02666 + - 2021.06.15.17.20.01_veh-35_02689_02938 + - 2021.06.15.17.20.01_veh-35_02949_03058 + - 2021.06.15.17.20.01_veh-35_03190_03253 + - 2021.06.15.17.20.01_veh-35_03372_03443 + - 2021.06.15.17.20.01_veh-35_03454_03541 + - 2021.06.15.17.20.01_veh-35_03592_03680 + - 2021.06.15.17.20.01_veh-35_03792_03909 + - 2021.06.15.17.20.01_veh-35_04024_04120 + - 2021.06.15.17.20.01_veh-35_04232_04308 + - 2021.06.15.17.20.01_veh-35_04319_04392 + - 2021.06.15.17.20.01_veh-35_04449_04556 + - 2021.06.15.17.51.29_veh-26_00021_00133 + - 2021.06.15.17.51.29_veh-26_00144_00698 + - 2021.06.15.17.51.29_veh-26_00709_00855 + - 2021.06.15.17.51.29_veh-26_00945_01124 + - 2021.06.15.17.51.29_veh-26_01135_01206 + - 2021.06.15.17.51.29_veh-26_01220_01353 + - 2021.06.15.17.51.29_veh-26_01398_01538 + - 2021.06.15.17.51.29_veh-26_01574_01748 + - 2021.06.15.17.51.29_veh-26_01759_02062 + - 2021.06.15.17.51.29_veh-26_02073_02158 + - 2021.06.15.17.51.29_veh-26_02169_02333 + - 2021.06.15.17.51.29_veh-26_02364_02497 + - 2021.06.15.17.51.29_veh-26_02549_02757 + - 2021.06.15.17.51.29_veh-26_02930_03104 + - 2021.06.15.17.51.29_veh-26_03115_03232 + - 2021.06.15.17.51.29_veh-26_03243_03333 + - 2021.06.15.17.51.29_veh-26_03344_03413 + - 2021.06.15.17.51.29_veh-26_03450_04063 + - 2021.06.15.17.51.29_veh-26_04074_04419 + - 2021.06.15.17.52.08_veh-12_00016_00233 + - 2021.06.15.17.52.08_veh-12_00284_00409 + - 2021.06.15.17.52.08_veh-12_00489_00793 + - 2021.06.15.17.52.08_veh-12_00992_01219 + - 2021.06.15.17.52.08_veh-12_01230_01578 + - 2021.06.15.17.52.08_veh-12_01589_01792 + - 2021.06.15.17.52.08_veh-12_01803_01887 + - 2021.06.15.17.52.08_veh-12_01902_01963 + - 2021.06.15.17.52.08_veh-12_01974_02236 + - 2021.06.15.17.52.08_veh-12_02247_02403 + - 2021.06.15.17.52.08_veh-12_02414_02678 + - 2021.06.15.17.52.08_veh-12_02689_02822 + - 2021.06.15.17.59.36_veh-38_00075_00145 + - 2021.06.15.17.59.36_veh-38_00217_00533 + - 2021.06.15.17.59.36_veh-38_00544_00639 + - 2021.06.15.17.59.36_veh-38_00650_01176 + - 2021.06.15.17.59.36_veh-38_01187_01375 + - 2021.06.15.17.59.36_veh-38_01386_01487 + - 2021.06.15.17.59.36_veh-38_01584_01682 + - 2021.06.15.17.59.36_veh-38_01693_02136 + - 2021.06.15.17.59.36_veh-38_02147_02484 + - 2021.06.15.17.59.36_veh-38_02495_02585 + - 2021.06.15.17.59.36_veh-38_02662_03018 + - 2021.06.15.17.59.36_veh-38_03029_03274 + - 2021.06.15.17.59.36_veh-38_03296_03477 + - 2021.06.15.17.59.36_veh-38_03534_03639 + - 2021.06.15.17.59.36_veh-38_03650_03806 + - 2021.06.15.17.59.36_veh-38_03841_04039 + - 2021.06.16.11.42.48_veh-38_00016_00130 + - 2021.06.16.11.42.48_veh-38_00141_00245 + - 2021.06.16.11.42.48_veh-38_00256_00331 + - 2021.06.16.11.42.48_veh-38_00342_00483 + - 2021.06.16.11.42.48_veh-38_00494_01220 + - 2021.06.16.11.42.48_veh-38_01231_01338 + - 2021.06.16.11.42.48_veh-38_01373_01953 + - 2021.06.16.11.42.48_veh-38_01964_02585 + - 2021.06.16.11.42.48_veh-38_02596_02784 + - 2021.06.16.11.42.48_veh-38_02855_03074 + - 2021.06.16.11.42.48_veh-38_03085_03208 + - 2021.06.16.11.42.48_veh-38_03238_03520 + - 2021.06.16.11.42.48_veh-38_03605_03725 + - 2021.06.16.11.42.48_veh-38_03736_03817 + - 2021.06.16.11.42.48_veh-38_03829_04230 + - 2021.06.16.11.42.48_veh-38_04241_04527 + - 2021.06.16.11.42.48_veh-38_04538_04980 + - 2021.06.16.11.42.48_veh-38_05030_05093 + - 2021.06.16.11.50.54_veh-26_00016_00326 + - 2021.06.16.11.50.54_veh-26_00407_00638 + - 2021.06.16.11.50.54_veh-26_00649_00986 + - 2021.06.16.11.50.54_veh-26_00997_01071 + - 2021.06.16.11.50.54_veh-26_01082_01211 + - 2021.06.16.11.50.54_veh-26_01222_01319 + - 2021.06.16.11.50.54_veh-26_01333_01422 + - 2021.06.16.11.50.54_veh-26_01433_01880 + - 2021.06.16.11.50.54_veh-26_01891_02007 + - 2021.06.16.11.50.54_veh-26_02124_02707 + - 2021.06.16.11.50.54_veh-26_02719_03119 + - 2021.06.16.11.50.54_veh-26_03130_03251 + - 2021.06.16.11.50.54_veh-26_03280_03782 + - 2021.06.16.11.50.54_veh-26_03793_04226 + - 2021.06.16.11.50.54_veh-26_04237_04445 + - 2021.06.16.11.50.54_veh-26_04509_04652 + - 2021.06.16.11.50.54_veh-26_04688_04970 + - 2021.06.16.11.50.54_veh-26_05028_05206 + - 2021.06.16.11.50.54_veh-26_05254_05320 + - 2021.06.16.12.02.45_veh-47_00047_00463 + - 2021.06.16.12.02.45_veh-47_00474_00585 + - 2021.06.16.12.02.45_veh-47_00597_00700 + - 2021.06.16.12.02.45_veh-47_00711_00791 + - 2021.06.16.12.02.45_veh-47_00863_01224 + - 2021.06.16.12.02.45_veh-47_01261_01331 + - 2021.06.16.12.02.45_veh-47_01399_01715 + - 2021.06.16.12.02.45_veh-47_01756_01843 + - 2021.06.16.12.02.45_veh-47_01854_01952 + - 2021.06.16.12.02.45_veh-47_02007_02081 + - 2021.06.16.12.02.45_veh-47_02135_02493 + - 2021.06.16.12.02.45_veh-47_02505_02567 + - 2021.06.16.12.02.45_veh-47_02649_03018 + - 2021.06.16.12.02.45_veh-47_03030_03363 + - 2021.06.16.12.02.45_veh-47_03375_03530 + - 2021.06.16.12.02.45_veh-47_03580_03705 + - 2021.06.16.12.02.45_veh-47_03741_03892 + - 2021.06.16.12.02.45_veh-47_03903_04099 + - 2021.06.16.12.02.45_veh-47_04110_04219 + - 2021.06.16.12.02.45_veh-47_04288_04583 + - 2021.06.16.12.02.45_veh-47_04640_04780 + - 2021.06.16.12.02.45_veh-47_04835_04898 + - 2021.06.16.12.02.45_veh-47_04909_05327 + - 2021.06.16.12.02.45_veh-47_05416_05544 + - 2021.06.16.12.02.45_veh-47_05565_05724 + - 2021.06.16.12.04.20_veh-35_00034_00180 + - 2021.06.16.12.04.20_veh-35_00191_00260 + - 2021.06.16.12.04.20_veh-35_00317_00549 + - 2021.06.16.12.04.20_veh-35_00560_01107 + - 2021.06.16.12.04.20_veh-35_01118_01773 + - 2021.06.16.12.04.20_veh-35_01784_02181 + - 2021.06.16.12.04.20_veh-35_02223_02396 + - 2021.06.16.12.04.20_veh-35_02407_02574 + - 2021.06.16.12.04.20_veh-35_02585_02721 + - 2021.06.16.12.04.20_veh-35_02742_02863 + - 2021.06.16.12.04.20_veh-35_02874_02945 + - 2021.06.16.12.04.20_veh-35_02956_03210 + - 2021.06.16.12.04.20_veh-35_03221_03385 + - 2021.06.16.12.04.20_veh-35_03396_04070 + - 2021.06.16.12.04.20_veh-35_04126_04485 + - 2021.06.16.12.04.20_veh-35_04562_04800 + - 2021.06.16.12.04.20_veh-35_04840_05046 + - 2021.06.16.13.21.10_veh-38_00016_00107 + - 2021.06.16.13.21.10_veh-38_00164_00277 + - 2021.06.16.13.21.10_veh-38_00288_00627 + - 2021.06.16.13.21.10_veh-38_00638_00809 + - 2021.06.16.13.21.10_veh-38_00820_00889 + - 2021.06.16.13.21.10_veh-38_00900_01143 + - 2021.06.16.13.21.10_veh-38_01154_01377 + - 2021.06.16.13.21.10_veh-38_01388_02541 + - 2021.06.16.13.21.10_veh-38_02552_02621 + - 2021.06.16.13.21.10_veh-38_02632_02969 + - 2021.06.16.13.21.10_veh-38_02980_03051 + - 2021.06.16.13.21.10_veh-38_03062_03263 + - 2021.06.16.13.21.10_veh-38_03277_03897 + - 2021.06.16.13.21.10_veh-38_03908_04332 + - 2021.06.16.13.21.10_veh-38_04406_04519 + - 2021.06.16.13.21.10_veh-38_04530_05203 + - 2021.06.16.13.42.21_veh-26_00012_00088 + - 2021.06.16.13.42.21_veh-26_00136_00326 + - 2021.06.16.13.42.21_veh-26_00337_00452 + - 2021.06.16.13.42.21_veh-26_00556_00943 + - 2021.06.16.13.42.21_veh-26_00954_01089 + - 2021.06.16.13.42.21_veh-26_01100_01510 + - 2021.06.16.13.42.21_veh-26_01564_01758 + - 2021.06.16.13.42.21_veh-26_01769_01898 + - 2021.06.16.13.42.21_veh-26_01970_02104 + - 2021.06.16.13.42.21_veh-26_02175_02368 + - 2021.06.16.13.42.21_veh-26_02380_02879 + - 2021.06.16.13.42.21_veh-26_02994_03460 + - 2021.06.16.13.42.21_veh-26_03509_03809 + - 2021.06.16.13.42.21_veh-26_03836_03904 + - 2021.06.16.13.42.21_veh-26_03915_04194 + - 2021.06.16.13.42.21_veh-26_04205_04309 + - 2021.06.16.13.42.21_veh-26_04367_04684 + - 2021.06.16.13.42.21_veh-26_04695_04759 + - 2021.06.16.13.42.21_veh-26_04770_04840 + - 2021.06.16.13.42.21_veh-26_04852_05013 + - 2021.06.16.14.02.32_veh-35_00016_00093 + - 2021.06.16.14.02.32_veh-35_00104_00445 + - 2021.06.16.14.02.32_veh-35_00513_00916 + - 2021.06.16.14.02.32_veh-35_00928_00994 + - 2021.06.16.14.02.32_veh-35_01005_01227 + - 2021.06.16.14.02.32_veh-35_01284_02457 + - 2021.06.16.14.02.32_veh-35_02489_03014 + - 2021.06.16.14.02.32_veh-35_03026_03334 + - 2021.06.16.14.02.32_veh-35_03357_03520 + - 2021.06.16.14.02.32_veh-35_03531_03620 + - 2021.06.16.14.02.32_veh-35_03764_03905 + - 2021.06.16.14.02.32_veh-35_03916_04094 + - 2021.06.16.14.02.32_veh-35_04105_04414 + - 2021.06.16.14.02.32_veh-35_04425_04500 + - 2021.06.16.14.02.32_veh-35_04511_04677 + - 2021.06.16.14.02.32_veh-35_04688_04876 + - 2021.06.16.14.02.32_veh-35_04887_04963 + - 2021.06.16.14.02.32_veh-35_05003_05164 + - 2021.06.16.16.25.56_veh-38_00005_00072 + - 2021.06.16.16.25.56_veh-38_00083_00352 + - 2021.06.16.16.25.56_veh-38_00475_00587 + - 2021.06.16.16.25.56_veh-38_00639_00987 + - 2021.06.16.16.25.56_veh-38_00998_01170 + - 2021.06.16.16.25.56_veh-38_01181_01440 + - 2021.06.16.16.25.56_veh-38_01452_01528 + - 2021.06.16.16.25.56_veh-38_01543_01628 + - 2021.06.16.16.25.56_veh-38_01639_02591 + - 2021.06.16.16.25.56_veh-38_02618_02682 + - 2021.06.16.17.16.57_veh-35_00016_00478 + - 2021.06.16.17.16.57_veh-35_00489_01287 + - 2021.06.16.17.16.57_veh-35_01344_01485 + - 2021.06.16.17.16.57_veh-35_01496_01660 + - 2021.06.16.17.16.57_veh-35_01671_01861 + - 2021.06.16.17.16.57_veh-35_01872_01947 + - 2021.06.16.17.16.57_veh-35_01958_02091 + - 2021.06.16.17.42.34_veh-26_00005_00134 + - 2021.06.16.17.42.34_veh-26_00146_00261 + - 2021.06.16.17.42.34_veh-26_00272_00391 + - 2021.06.16.17.42.34_veh-26_00415_00587 + - 2021.06.16.17.42.34_veh-26_00650_00712 + - 2021.06.16.17.42.34_veh-26_00724_00972 + - 2021.06.16.17.42.34_veh-26_01112_01606 + - 2021.06.16.17.42.34_veh-26_01617_01728 + - 2021.06.16.17.42.34_veh-26_01897_01978 + - 2021.06.16.17.52.52_veh-47_00016_00140 + - 2021.06.16.17.52.52_veh-47_00206_00290 + - 2021.06.16.17.52.52_veh-47_00301_00479 + - 2021.06.16.17.52.52_veh-47_00490_00648 + - 2021.06.16.17.52.52_veh-47_00659_00976 + - 2021.06.16.17.52.52_veh-47_01083_01679 + - 2021.06.16.17.52.52_veh-47_01690_01773 + - 2021.06.16.17.52.52_veh-47_01799_01926 + - 2021.06.17.11.29.43_veh-47_00005_00139 + - 2021.06.17.11.29.43_veh-47_00177_00504 + - 2021.06.17.11.29.43_veh-47_00515_00727 + - 2021.06.17.11.29.43_veh-47_00738_00913 + - 2021.06.17.11.29.43_veh-47_00924_01054 + - 2021.06.17.11.29.43_veh-47_01065_01220 + - 2021.06.17.11.29.43_veh-47_01231_01405 + - 2021.06.17.11.29.43_veh-47_01416_01725 + - 2021.06.17.11.29.43_veh-47_01736_01990 + - 2021.06.17.11.29.43_veh-47_02001_02147 + - 2021.06.17.11.29.43_veh-47_02158_02218 + - 2021.06.17.11.29.43_veh-47_02247_02399 + - 2021.06.17.11.29.43_veh-47_02410_02728 + - 2021.06.17.11.29.43_veh-47_02739_02810 + - 2021.06.17.11.29.43_veh-47_02821_02905 + - 2021.06.17.11.29.43_veh-47_02916_03071 + - 2021.06.17.11.29.43_veh-47_03091_03585 + - 2021.06.17.11.59.07_veh-38_00059_00790 + - 2021.06.17.11.59.07_veh-38_00801_01221 + - 2021.06.17.11.59.07_veh-38_01232_01841 + - 2021.06.17.11.59.07_veh-38_01884_02157 + - 2021.06.17.11.59.07_veh-38_02168_02358 + - 2021.06.17.11.59.07_veh-38_02369_03098 + - 2021.06.17.11.59.07_veh-38_03109_03267 + - 2021.06.17.11.59.07_veh-38_03294_03383 + - 2021.06.17.11.59.07_veh-38_03394_03555 + - 2021.06.17.11.59.07_veh-38_03566_03633 + - 2021.06.17.11.59.07_veh-38_03660_03769 + - 2021.06.17.11.59.07_veh-38_03780_04123 + - 2021.06.17.11.59.07_veh-38_04134_04447 + - 2021.06.17.11.59.07_veh-38_04458_04780 + - 2021.06.17.11.59.07_veh-38_04791_05079 + - 2021.06.17.11.59.07_veh-38_05111_05369 + - 2021.06.17.11.59.07_veh-38_05380_05616 + - 2021.06.17.11.59.07_veh-38_05627_05763 + - 2021.06.17.12.09.32_veh-26_00024_00256 + - 2021.06.17.12.09.32_veh-26_00267_00337 + - 2021.06.17.12.09.32_veh-26_00348_00595 + - 2021.06.17.12.09.32_veh-26_00606_00743 + - 2021.06.17.12.09.32_veh-26_00754_00942 + - 2021.06.17.12.09.32_veh-26_00953_01099 + - 2021.06.17.12.09.32_veh-26_01136_01661 + - 2021.06.17.12.09.32_veh-26_01672_01940 + - 2021.06.17.12.09.32_veh-26_01951_02043 + - 2021.06.17.12.09.32_veh-26_02148_02350 + - 2021.06.17.12.09.32_veh-26_02406_02550 + - 2021.06.17.12.09.32_veh-26_02561_02668 + - 2021.06.17.12.09.32_veh-26_02679_02878 + - 2021.06.17.12.09.32_veh-26_02889_03020 + - 2021.06.17.12.09.32_veh-26_03091_03175 + - 2021.06.17.12.09.32_veh-26_03186_03300 + - 2021.06.17.12.09.32_veh-26_03311_03386 + - 2021.06.17.12.09.32_veh-26_03447_03536 + - 2021.06.17.12.09.32_veh-26_03646_03916 + - 2021.06.17.12.09.32_veh-26_03927_03992 + - 2021.06.17.12.09.32_veh-26_04047_04171 + - 2021.06.17.12.09.32_veh-26_04215_04507 + - 2021.06.17.12.09.32_veh-26_04519_04796 + - 2021.06.17.12.09.32_veh-26_04808_04868 + - 2021.06.17.12.09.32_veh-26_05005_05134 + - 2021.06.17.12.09.32_veh-26_05166_05272 + - 2021.06.17.12.22.07_veh-35_00031_00185 + - 2021.06.17.12.22.07_veh-35_00196_00376 + - 2021.06.17.12.22.07_veh-35_00387_00480 + - 2021.06.17.12.22.07_veh-35_00543_00716 + - 2021.06.17.12.22.07_veh-35_00753_00898 + - 2021.06.17.12.22.07_veh-35_00909_00986 + - 2021.06.17.12.22.07_veh-35_00997_01308 + - 2021.06.17.12.22.07_veh-35_01337_01581 + - 2021.06.17.12.22.07_veh-35_01614_01774 + - 2021.06.17.12.22.07_veh-35_01834_02232 + - 2021.06.17.12.22.07_veh-35_02626_02723 + - 2021.06.17.12.22.07_veh-35_02734_02881 + - 2021.06.17.12.22.07_veh-35_02988_03093 + - 2021.06.17.12.22.07_veh-35_03209_03393 + - 2021.06.17.12.22.07_veh-35_03432_03524 + - 2021.06.17.12.22.07_veh-35_03542_03645 + - 2021.06.17.12.22.07_veh-35_03656_03786 + - 2021.06.17.12.22.07_veh-35_03833_03894 + - 2021.06.17.12.22.07_veh-35_03990_04609 + - 2021.06.17.12.22.07_veh-35_04813_05175 + - 2021.06.17.12.22.07_veh-35_05318_05405 + - 2021.06.17.12.39.54_veh-47_00016_00114 + - 2021.06.17.12.39.54_veh-47_00139_00720 + - 2021.06.17.12.39.54_veh-47_00731_00997 + - 2021.06.17.12.39.54_veh-47_01008_01173 + - 2021.06.17.12.39.54_veh-47_01184_01555 + - 2021.06.17.12.39.54_veh-47_01566_01756 + - 2021.06.17.12.39.54_veh-47_01783_01892 + - 2021.06.17.13.16.25_veh-47_00016_00215 + - 2021.06.17.13.16.25_veh-47_00226_00336 + - 2021.06.17.13.16.25_veh-47_00347_00614 + - 2021.06.17.13.16.25_veh-47_00801_00874 + - 2021.06.17.13.16.25_veh-47_00923_02052 + - 2021.06.17.13.16.25_veh-47_02063_02387 + - 2021.06.17.13.16.25_veh-47_02422_02570 + - 2021.06.17.13.16.25_veh-47_02608_03012 + - 2021.06.17.13.16.25_veh-47_03157_03290 + - 2021.06.17.13.16.25_veh-47_03302_03465 + - 2021.06.17.13.16.25_veh-47_03571_03908 + - 2021.06.17.13.16.25_veh-47_03919_04024 + - 2021.06.17.13.16.25_veh-47_04096_04217 + - 2021.06.17.13.16.25_veh-47_04232_04484 + - 2021.06.17.13.16.25_veh-47_04495_04591 + - 2021.06.17.13.16.25_veh-47_04654_04741 + - 2021.06.17.13.16.25_veh-47_04752_04940 + - 2021.06.17.13.16.25_veh-47_04951_05065 + - 2021.06.17.13.16.25_veh-47_05083_05316 + - 2021.06.17.14.03.14_veh-26_00007_00186 + - 2021.06.17.14.03.14_veh-26_00222_00314 + - 2021.06.17.14.03.14_veh-26_00346_00641 + - 2021.06.17.14.03.14_veh-26_00652_00846 + - 2021.06.17.14.03.14_veh-26_00857_01118 + - 2021.06.17.14.03.14_veh-26_01129_01310 + - 2021.06.17.14.03.14_veh-26_01321_01501 + - 2021.06.17.14.03.14_veh-26_01512_01603 + - 2021.06.17.14.03.14_veh-26_01614_01684 + - 2021.06.17.14.03.14_veh-26_01695_01816 + - 2021.06.17.14.03.14_veh-26_01827_01919 + - 2021.06.17.14.03.14_veh-26_02020_02141 + - 2021.06.17.14.03.14_veh-26_02218_02521 + - 2021.06.17.14.03.14_veh-26_02532_02703 + - 2021.06.17.14.03.14_veh-26_02714_02775 + - 2021.06.17.14.05.18_veh-38_00016_00491 + - 2021.06.17.14.05.18_veh-38_00793_00859 + - 2021.06.17.14.05.18_veh-38_00870_01114 + - 2021.06.17.14.05.18_veh-38_01125_01255 + - 2021.06.17.14.05.18_veh-38_01266_01329 + - 2021.06.17.14.05.18_veh-38_01341_01590 + - 2021.06.17.14.05.18_veh-38_01658_01726 + - 2021.06.17.14.05.18_veh-38_01737_02008 + - 2021.06.17.14.05.18_veh-38_02056_02137 + - 2021.06.17.14.05.18_veh-38_02148_02910 + - 2021.06.17.14.05.18_veh-38_02958_03094 + - 2021.06.17.14.05.18_veh-38_03170_03359 + - 2021.06.17.14.16.11_veh-35_00016_00194 + - 2021.06.17.14.16.11_veh-35_00205_00317 + - 2021.06.17.14.16.11_veh-35_00328_00513 + - 2021.06.17.14.16.11_veh-35_00572_00688 + - 2021.06.17.14.16.11_veh-35_00699_00764 + - 2021.06.17.14.16.11_veh-35_00818_00924 + - 2021.06.17.14.16.11_veh-35_00954_01019 + - 2021.06.17.14.16.11_veh-35_01069_01139 + - 2021.06.17.14.16.11_veh-35_01150_01254 + - 2021.06.17.14.16.11_veh-35_01265_01417 + - 2021.06.17.14.16.11_veh-35_01470_01587 + - 2021.06.17.14.16.11_veh-35_01640_01709 + - 2021.06.17.14.16.11_veh-35_01741_01815 + - 2021.06.17.16.22.42_veh-26_00016_00189 + - 2021.06.17.16.22.42_veh-26_00319_00542 + - 2021.06.17.16.22.42_veh-26_00553_01042 + - 2021.06.17.16.22.42_veh-26_01063_01131 + - 2021.06.17.16.22.42_veh-26_01189_01301 + - 2021.06.17.16.22.42_veh-26_01312_01391 + - 2021.06.17.16.22.42_veh-26_01462_01749 + - 2021.06.17.16.22.42_veh-26_01760_03043 + - 2021.06.17.16.22.42_veh-26_03054_03148 + - 2021.06.17.16.22.42_veh-26_03159_03370 + - 2021.06.17.16.22.42_veh-26_03382_03770 + - 2021.06.17.16.22.42_veh-26_03781_04090 + - 2021.06.17.16.22.42_veh-26_04101_04176 + - 2021.06.17.16.22.42_veh-26_04187_04285 + - 2021.06.17.16.22.42_veh-26_04296_04412 + - 2021.06.17.16.27.40_veh-47_00005_00204 + - 2021.06.17.16.27.40_veh-47_00215_00461 + - 2021.06.17.16.27.40_veh-47_00506_01030 + - 2021.06.17.16.27.40_veh-47_01142_01282 + - 2021.06.17.16.27.40_veh-47_01293_01671 + - 2021.06.17.16.27.40_veh-47_01682_01983 + - 2021.06.17.16.27.40_veh-47_01994_02242 + - 2021.06.17.16.27.40_veh-47_02253_02353 + - 2021.06.17.16.27.40_veh-47_02440_02566 + - 2021.06.17.16.27.40_veh-47_02577_02722 + - 2021.06.17.16.27.40_veh-47_02733_02854 + - 2021.06.17.16.27.40_veh-47_02931_03232 + - 2021.06.17.16.27.40_veh-47_03299_03455 + - 2021.06.17.16.27.40_veh-47_03514_03761 + - 2021.06.17.16.27.40_veh-47_03820_03971 + - 2021.06.17.16.27.40_veh-47_04031_04156 + - 2021.06.17.16.27.40_veh-47_04167_04670 + - 2021.06.17.16.42.39_veh-35_00016_00201 + - 2021.06.17.16.42.39_veh-35_00212_00318 + - 2021.06.17.16.42.39_veh-35_00329_00496 + - 2021.06.17.16.42.39_veh-35_00507_00849 + - 2021.06.17.16.42.39_veh-35_00860_00921 + - 2021.06.17.16.42.39_veh-35_01087_01307 + - 2021.06.17.16.42.39_veh-35_01318_01769 + - 2021.06.17.17.00.28_veh-38_00027_00115 + - 2021.06.17.17.00.28_veh-38_00126_00202 + - 2021.06.17.17.00.28_veh-38_00230_00411 + - 2021.06.17.17.00.28_veh-38_00452_00630 + - 2021.06.17.17.00.28_veh-38_00641_00712 + - 2021.06.17.17.00.28_veh-38_00723_00924 + - 2021.06.17.17.00.28_veh-38_00935_01210 + - 2021.06.17.17.00.28_veh-38_01221_01350 + - 2021.06.17.17.00.28_veh-38_01361_01666 + - 2021.06.17.17.00.28_veh-38_01677_01905 + - 2021.06.17.17.00.28_veh-38_01916_02040 + - 2021.06.17.17.00.28_veh-38_02051_02409 + - 2021.06.17.17.00.28_veh-38_02420_02526 + - 2021.06.17.17.00.28_veh-38_02537_02667 + - 2021.06.17.17.00.28_veh-38_03080_03305 + - 2021.06.17.17.00.28_veh-38_03316_03541 + - 2021.06.17.17.00.28_veh-38_03552_03688 + - 2021.06.17.17.00.28_veh-38_03699_03998 + - 2021.06.17.17.00.28_veh-38_04014_05173 + - 2021.06.17.17.00.28_veh-38_05285_05522 + - 2021.06.17.18.56.24_veh-26_00008_00086 + - 2021.06.17.18.56.24_veh-26_00097_00285 + - 2021.06.17.18.56.24_veh-26_00296_00627 + - 2021.06.17.18.56.24_veh-26_00638_00822 + - 2021.06.17.18.56.24_veh-26_00896_01312 + - 2021.06.18.18.50.06_veh-30_00057_02081 + - 2021.06.18.18.50.06_veh-30_02092_02466 + - 2021.06.21.16.02.19_veh-47_00019_00423 + - 2021.06.21.16.02.19_veh-47_00502_00811 + - 2021.06.21.16.02.19_veh-47_00832_02051 + - 2021.06.21.16.02.19_veh-47_02072_02371 + - 2021.06.21.16.44.54_veh-35_00016_00389 + - 2021.06.21.16.44.54_veh-35_00411_00884 + - 2021.06.21.16.44.54_veh-35_00895_04154 + - 2021.06.21.16.44.54_veh-35_04165_04869 + - 2021.06.21.16.51.55_veh-47_00061_00514 + - 2021.06.21.16.51.55_veh-47_00525_01335 + - 2021.06.21.16.51.55_veh-47_01346_01709 + - 2021.06.21.16.51.55_veh-47_01720_02849 + - 2021.06.21.16.51.55_veh-47_02871_03064 + - 2021.06.21.16.51.55_veh-47_03075_03310 + - 2021.06.21.17.42.00_veh-38_00058_00159 + - 2021.06.21.17.42.00_veh-38_00170_00272 + - 2021.06.21.17.42.00_veh-38_00283_00539 + - 2021.06.21.17.42.00_veh-38_00550_00792 + - 2021.06.21.17.42.00_veh-38_00813_01132 + - 2021.06.21.17.42.00_veh-38_01154_01311 + - 2021.06.21.17.42.00_veh-38_01333_02377 + - 2021.06.21.17.42.00_veh-38_02399_02867 + - 2021.06.21.17.42.00_veh-38_02895_03392 + - 2021.06.21.17.42.00_veh-38_03403_03670 + - 2021.06.21.17.42.00_veh-38_03692_04076 + - 2021.06.21.17.42.00_veh-38_04098_04812 + - 2021.06.21.17.42.00_veh-38_04833_05454 + - 2021.06.21.17.42.00_veh-38_05475_05890 + - 2021.06.21.17.42.00_veh-38_05947_06493 + - 2021.06.21.17.42.00_veh-38_06514_06612 + - 2021.06.21.18.10.43_veh-47_00027_01293 + - 2021.06.21.18.10.43_veh-47_01304_02309 + - 2021.06.21.18.10.43_veh-47_02320_03114 + - 2021.06.21.18.53.17_veh-35_00016_00499 + - 2021.06.21.18.53.17_veh-35_00520_01144 + - 2021.06.21.18.53.17_veh-35_01155_01359 + - 2021.06.21.18.53.17_veh-35_01381_02097 + - 2021.06.21.18.53.17_veh-35_02119_02628 + - 2021.06.21.18.53.17_veh-35_02653_03032 + - 2021.06.21.18.53.17_veh-35_03043_03374 + - 2021.06.21.18.53.17_veh-35_03385_04164 + - 2021.06.21.18.53.17_veh-35_04175_04763 + - 2021.06.21.18.53.17_veh-35_04784_04954 + - 2021.06.21.18.53.17_veh-35_04975_05225 + - 2021.06.21.20.34.04_veh-26_00016_00175 + - 2021.06.21.20.34.04_veh-26_00186_00281 + - 2021.06.21.20.34.04_veh-26_00292_00417 + - 2021.06.21.20.34.04_veh-26_00428_00550 + - 2021.06.21.20.34.04_veh-26_00561_00676 + - 2021.06.21.20.34.04_veh-26_00687_00959 + - 2021.06.21.20.34.04_veh-26_00986_01246 + - 2021.06.21.20.34.04_veh-26_01257_01478 + - 2021.06.21.20.34.04_veh-26_01551_02170 + - 2021.06.21.20.34.04_veh-26_02181_02566 + - 2021.06.21.20.34.04_veh-26_02658_02779 + - 2021.06.21.20.34.04_veh-26_02832_03127 + - 2021.06.21.20.58.30_veh-47_00015_00351 + - 2021.06.21.20.58.30_veh-47_00362_00436 + - 2021.06.21.20.58.30_veh-47_00447_02056 + - 2021.06.21.20.58.30_veh-47_02077_03850 + - 2021.06.21.21.16.18_veh-38_00023_00411 + - 2021.06.21.21.16.18_veh-38_00422_01113 + - 2021.06.21.21.16.18_veh-38_01124_01795 + - 2021.06.21.21.16.18_veh-38_01806_03301 + - 2021.06.21.21.16.18_veh-38_03328_03400 + - 2021.06.21.21.16.18_veh-38_03424_04806 + - 2021.06.21.21.16.18_veh-38_04817_05288 + - 2021.06.21.21.59.54_veh-26_00014_00084 + - 2021.06.21.21.59.54_veh-26_00132_00252 + - 2021.06.21.21.59.54_veh-26_00263_00579 + - 2021.06.21.21.59.54_veh-26_00590_01078 + - 2021.06.21.21.59.54_veh-26_01131_01705 + - 2021.06.21.21.59.54_veh-26_01716_01809 + - 2021.06.21.21.59.54_veh-26_01820_02222 + - 2021.06.21.21.59.54_veh-26_02298_02886 + - 2021.06.21.22.28.01_veh-47_00015_00321 + - 2021.06.21.22.28.01_veh-47_00332_02197 + - 2021.06.21.22.56.30_veh-35_00016_00141 + - 2021.06.21.22.56.30_veh-35_00152_00356 + - 2021.06.21.22.56.30_veh-35_00367_00781 + - 2021.06.21.22.56.30_veh-35_00792_01473 + - 2021.06.21.22.56.30_veh-35_01484_01611 + - 2021.06.21.22.56.30_veh-35_01656_04055 + - 2021.06.21.22.56.42_veh-38_00016_01237 + - 2021.06.21.22.56.42_veh-38_01258_02103 + - 2021.06.21.22.56.42_veh-38_02127_02380 + - 2021.06.21.22.56.42_veh-38_02401_02519 + - 2021.06.21.22.56.42_veh-38_02540_03393 + - 2021.06.21.22.56.42_veh-38_03404_04356 + - 2021.06.21.23.10.22_veh-47_00015_01999 + - 2021.06.21.23.10.22_veh-47_02023_02873 + - 2021.06.21.23.10.22_veh-47_02909_03392 + - 2021.06.22.15.31.55_veh-35_00016_00473 + - 2021.06.22.15.31.55_veh-35_00484_00772 + - 2021.06.22.15.31.55_veh-35_00793_01638 + - 2021.06.22.15.31.55_veh-35_01659_02423 + - 2021.06.22.15.31.55_veh-35_02434_03755 + - 2021.06.22.16.39.31_veh-35_00016_00204 + - 2021.06.22.16.39.31_veh-35_00215_00734 + - 2021.06.22.16.39.31_veh-35_00745_00962 + - 2021.06.22.16.39.31_veh-35_00983_04055 + - 2021.06.23.14.06.20_veh-26_00020_01142 + - 2021.06.23.14.06.20_veh-26_01192_01541 + - 2021.06.23.14.06.20_veh-26_01563_02494 + - 2021.06.23.14.06.20_veh-26_02505_02775 + - 2021.06.23.14.54.32_veh-16_00016_00290 + - 2021.06.23.14.54.32_veh-16_00301_00410 + - 2021.06.23.14.54.32_veh-16_00421_00625 + - 2021.06.23.14.54.32_veh-16_00636_00840 + - 2021.06.23.14.54.32_veh-16_00862_01000 + - 2021.06.23.14.54.32_veh-16_01011_01166 + - 2021.06.23.14.54.32_veh-16_01187_03336 + - 2021.06.23.14.58.13_veh-35_00016_00153 + - 2021.06.23.14.58.13_veh-35_00175_00744 + - 2021.06.23.14.58.13_veh-35_00765_01108 + - 2021.06.23.14.58.13_veh-35_01130_01820 + - 2021.06.23.14.58.13_veh-35_01831_02026 + - 2021.06.23.14.58.13_veh-35_02037_04783 + - 2021.06.23.15.18.10_veh-26_00016_00143 + - 2021.06.23.15.18.10_veh-26_00165_02848 + - 2021.06.23.15.56.12_veh-16_00066_00818 + - 2021.06.23.15.56.12_veh-16_00839_01285 + - 2021.06.23.15.56.12_veh-16_01308_04289 + - 2021.06.23.16.52.00_veh-26_00038_00602 + - 2021.06.23.16.52.00_veh-26_00624_00817 + - 2021.06.23.16.52.00_veh-26_00828_01032 + - 2021.06.23.16.52.00_veh-26_01043_03099 + - 2021.06.23.16.52.00_veh-26_03120_03293 + - 2021.06.23.16.52.00_veh-26_03304_03611 + - 2021.06.23.16.54.19_veh-35_00016_00755 + - 2021.06.23.16.54.19_veh-35_00808_01256 + - 2021.06.23.16.54.19_veh-35_01277_01592 + - 2021.06.23.16.54.19_veh-35_01603_03271 + - 2021.06.23.16.54.19_veh-35_03299_03425 + - 2021.06.23.16.54.19_veh-35_03436_03683 + - 2021.06.23.16.54.19_veh-35_03705_04009 + - 2021.06.23.17.31.36_veh-16_00016_00377 + - 2021.06.23.17.31.36_veh-16_00398_00623 + - 2021.06.23.17.31.36_veh-16_00634_01421 + - 2021.06.23.17.31.36_veh-16_01443_01606 + - 2021.06.23.17.31.36_veh-16_01617_01791 + - 2021.06.23.17.31.36_veh-16_01812_01883 + - 2021.06.23.17.31.36_veh-16_01904_02129 + - 2021.06.23.17.31.36_veh-16_02150_02774 + - 2021.06.23.17.31.36_veh-16_02795_04024 + - 2021.06.23.18.23.38_veh-26_00069_00642 + - 2021.06.23.18.23.38_veh-26_00663_01217 + - 2021.06.23.18.23.38_veh-26_01238_01416 + - 2021.06.23.18.23.38_veh-26_01438_01758 + - 2021.06.23.18.23.38_veh-26_01769_01925 + - 2021.06.23.20.00.35_veh-35_00016_00119 + - 2021.06.23.20.00.35_veh-35_00130_00949 + - 2021.06.23.20.00.35_veh-35_00960_03649 + - 2021.06.23.20.00.35_veh-35_03660_04140 + - 2021.06.23.20.00.35_veh-35_04162_04257 + - 2021.06.23.20.41.49_veh-26_00364_00426 + - 2021.06.23.20.41.49_veh-26_00438_00498 + - 2021.06.23.20.41.49_veh-26_00598_00675 + - 2021.06.23.20.41.49_veh-26_00924_00984 + - 2021.06.23.20.41.49_veh-26_00996_01065 + - 2021.06.23.20.41.49_veh-26_01076_01145 + - 2021.06.23.20.41.49_veh-26_01157_01240 + - 2021.06.23.20.41.49_veh-26_01380_01446 + - 2021.06.23.20.41.49_veh-26_01458_01613 + - 2021.06.23.20.41.49_veh-26_01717_01824 + - 2021.06.23.20.41.49_veh-26_01836_01922 + - 2021.06.23.20.43.31_veh-16_00016_00216 + - 2021.06.23.20.43.31_veh-16_00238_00577 + - 2021.06.23.20.43.31_veh-16_00588_00792 + - 2021.06.23.20.43.31_veh-16_00803_02194 + - 2021.06.23.20.43.31_veh-16_02216_02667 + - 2021.06.23.20.43.31_veh-16_02678_03586 + - 2021.06.23.20.43.31_veh-16_03607_04007 + - 2021.06.23.21.51.57_veh-26_00163_00230 + - 2021.06.23.21.51.57_veh-26_00518_00606 + - 2021.06.23.21.51.57_veh-26_00753_00842 + - 2021.06.23.21.51.57_veh-26_00900_00961 + - 2021.06.23.21.51.57_veh-26_00973_01035 + - 2021.06.23.21.51.57_veh-26_01537_01610 + - 2021.06.23.21.56.29_veh-35_00097_00209 + - 2021.06.23.21.56.29_veh-35_00220_00936 + - 2021.06.23.21.56.29_veh-35_00947_01581 + - 2021.06.23.21.56.29_veh-35_01603_02401 + - 2021.06.23.21.56.29_veh-35_02412_03161 + - 2021.06.23.22.05.48_veh-16_00015_00276 + - 2021.06.23.22.05.48_veh-16_00287_00591 + - 2021.06.23.22.05.48_veh-16_00602_00800 + - 2021.06.24.13.31.08_veh-47_00015_00148 + - 2021.06.24.13.31.08_veh-47_00169_01137 + - 2021.06.24.13.55.30_veh-47_00020_00165 + - 2021.06.24.13.55.30_veh-47_00186_00295 + - 2021.06.24.13.55.30_veh-47_00319_00933 + - 2021.06.24.14.20.12_veh-47_00015_01331 + - 2021.06.24.14.20.12_veh-47_01342_03087 + - 2021.06.24.14.20.12_veh-47_03110_04677 + - 2021.06.24.14.20.12_veh-47_04688_07299 + - 2021.06.24.14.26.26_veh-35_00101_00848 + - 2021.06.24.14.26.26_veh-35_00859_01100 + - 2021.06.24.14.26.26_veh-35_01122_02840 + - 2021.06.24.14.29.38_veh-16_00016_00651 + - 2021.06.24.14.29.38_veh-16_00662_01189 + - 2021.06.24.14.54.04_veh-16_00005_02926 + - 2021.06.24.15.33.58_veh-35_00023_01304 + - 2021.06.24.15.33.58_veh-35_01326_01439 + - 2021.06.24.15.33.58_veh-35_01460_01897 + - 2021.06.24.15.33.58_veh-35_01919_02912 + - 2021.06.24.15.54.32_veh-16_00008_00122 + - 2021.06.24.15.54.32_veh-16_00133_00787 + - 2021.06.24.15.54.32_veh-16_00798_00880 + - 2021.06.24.15.54.32_veh-16_00891_01705 + - 2021.06.24.15.54.32_veh-16_01716_03224 + - 2021.06.24.17.07.56_veh-26_02395_02460 + - 2021.06.24.17.07.56_veh-26_02549_02672 + - 2021.06.24.17.07.56_veh-26_02701_02772 + - 2021.06.24.17.07.56_veh-26_02894_02979 + - 2021.06.24.17.07.56_veh-26_02991_03105 + - 2021.06.24.17.07.56_veh-26_03132_03226 + - 2021.06.24.17.07.56_veh-26_03265_03463 + - 2021.06.24.17.08.56_veh-35_00016_00217 + - 2021.06.24.17.08.56_veh-35_00239_00371 + - 2021.06.24.17.08.56_veh-35_00393_00903 + - 2021.06.24.17.08.56_veh-35_00914_01333 + - 2021.06.24.17.08.56_veh-35_01344_02635 + - 2021.06.24.17.08.56_veh-35_02656_03104 + - 2021.06.24.17.25.34_veh-16_00099_01053 + - 2021.06.24.17.25.34_veh-16_01064_02093 + - 2021.06.24.17.25.34_veh-16_02104_03070 + - 2021.06.24.17.25.34_veh-16_03081_03343 + - 2021.06.24.18.12.52_veh-35_00005_00344 + - 2021.06.24.18.12.52_veh-35_00366_01200 + - 2021.06.24.18.12.52_veh-35_01222_01508 + - 2021.06.24.18.12.52_veh-35_01531_01812 + - 2021.06.24.20.25.57_veh-47_00016_00212 + - 2021.06.24.20.25.57_veh-47_00233_01577 + - 2021.06.24.20.25.57_veh-47_01588_02245 + - 2021.06.24.20.25.57_veh-47_02256_02752 + - 2021.06.24.20.25.57_veh-47_02773_02860 + - 2021.06.24.20.25.57_veh-47_02871_03128 + - 2021.06.24.20.25.57_veh-47_03149_03435 + - 2021.06.24.20.25.57_veh-47_03460_04227 + - 2021.06.24.21.00.48_veh-35_00005_01154 + - 2021.06.24.21.00.48_veh-35_01165_02891 + - 2021.06.24.21.00.48_veh-35_02913_03255 + - 2021.06.24.21.00.48_veh-35_03266_03457 + - 2021.06.24.21.47.52_veh-16_00005_00274 + - 2021.06.24.21.47.52_veh-16_00285_00761 + - 2021.06.24.21.47.52_veh-16_00782_00929 + - 2021.06.24.21.47.52_veh-16_00940_01669 + - 2021.06.24.21.47.52_veh-16_01680_02551 + - 2021.06.24.21.55.23_veh-26_00528_00616 + - 2021.06.24.21.55.23_veh-26_01247_01321 + - 2021.06.24.21.57.34_veh-47_00065_00278 + - 2021.06.24.21.57.34_veh-47_00289_00493 + - 2021.06.24.21.57.34_veh-47_00515_00791 + - 2021.06.24.21.57.34_veh-47_00802_02463 + - 2021.06.24.21.57.34_veh-47_02474_02818 + - 2021.06.24.21.57.34_veh-47_02829_03589 + - 2021.06.25.14.34.45_veh-26_00714_00775 + - 2021.06.25.14.34.45_veh-26_01589_01678 + - 2021.06.25.14.34.45_veh-26_01728_01822 + - 2021.06.25.14.34.45_veh-26_01834_01957 + - 2021.06.25.14.34.45_veh-26_02322_02429 + - 2021.06.25.14.34.45_veh-26_03271_03362 + - 2021.06.25.14.42.38_veh-38_00005_00881 + - 2021.06.25.14.42.38_veh-38_00892_01413 + - 2021.06.25.14.42.38_veh-38_01424_02409 + - 2021.06.25.14.42.38_veh-38_02420_02936 + - 2021.06.25.14.42.38_veh-38_02958_03051 + - 2021.06.25.14.47.57_veh-35_00016_00487 + - 2021.06.25.14.47.57_veh-35_00508_00677 + - 2021.06.25.14.47.57_veh-35_00738_01476 + - 2021.06.25.14.47.57_veh-35_01497_01679 + - 2021.06.25.15.15.42_veh-16_00022_03589 + - 2021.06.25.16.02.11_veh-35_00016_00509 + - 2021.06.25.16.02.11_veh-35_00533_02948 + - 2021.06.25.16.02.11_veh-35_03032_04731 + - 2021.06.25.16.19.40_veh-26_00223_00306 + - 2021.06.25.16.19.40_veh-26_00360_00438 + - 2021.06.25.16.19.40_veh-26_00637_00705 + - 2021.06.25.16.19.40_veh-26_00991_01052 + - 2021.06.25.16.19.40_veh-26_01179_01243 + - 2021.06.25.16.19.40_veh-26_01439_01503 + - 2021.06.25.16.19.40_veh-26_01514_01577 + - 2021.06.25.16.19.40_veh-26_02098_02166 + - 2021.06.25.16.19.40_veh-26_02222_02297 + - 2021.06.25.16.19.40_veh-26_02573_02676 + - 2021.06.25.16.19.40_veh-26_03497_03565 + - 2021.06.25.16.19.40_veh-26_03883_03949 + - 2021.06.25.16.19.40_veh-26_04002_04075 + - 2021.06.25.16.19.40_veh-26_04119_04180 + - 2021.06.25.16.19.40_veh-26_04191_04282 + - 2021.06.25.16.22.33_veh-16_00189_01733 + - 2021.06.25.16.22.33_veh-16_01744_03670 + - 2021.06.25.16.22.33_veh-16_03694_04261 + - 2021.06.25.16.22.33_veh-16_04272_06227 + - 2021.06.25.17.44.01_veh-35_00016_00107 + - 2021.06.25.17.44.01_veh-35_00128_00226 + - 2021.06.25.17.44.01_veh-35_00247_01572 + - 2021.06.25.17.44.01_veh-35_01583_01727 + - 2021.06.25.17.44.01_veh-35_01738_02915 + - 2021.06.25.17.44.01_veh-35_02926_04787 + - 2021.06.25.19.17.59_veh-26_01819_01903 + - 2021.06.25.19.17.59_veh-26_01946_02014 + - 2021.06.25.19.17.59_veh-26_02512_02597 + - 2021.06.25.19.17.59_veh-26_02858_02989 + - 2021.06.25.19.17.59_veh-26_03237_03306 + - 2021.06.25.19.17.59_veh-26_03432_03505 + - 2021.06.25.19.17.59_veh-26_03567_03628 + - 2021.06.25.19.17.59_veh-26_04034_04101 + - 2021.06.25.19.17.59_veh-26_04355_04417 + - 2021.06.25.19.17.59_veh-26_05147_05222 + - 2021.06.25.21.24.42_veh-47_00005_00274 + - 2021.06.25.21.24.42_veh-47_00285_00674 + - 2021.06.25.21.24.42_veh-47_00685_00900 + - 2021.06.25.21.24.42_veh-47_00921_02284 + - 2021.06.25.21.24.42_veh-47_02295_03384 + - 2021.06.25.21.24.42_veh-47_03395_03699 + - 2021.06.25.21.24.42_veh-47_03710_04436 + - 2021.06.25.21.32.05_veh-26_00058_00141 + - 2021.06.25.21.32.05_veh-26_00703_00773 + - 2021.06.25.21.32.05_veh-26_00903_00979 + - 2021.06.25.21.32.05_veh-26_01027_01096 + - 2021.06.25.21.32.05_veh-26_01223_01293 + - 2021.06.25.21.32.05_veh-26_01617_01695 + - 2021.06.25.21.32.05_veh-26_01825_01902 + - 2021.06.25.21.32.05_veh-26_01955_02021 + - 2021.06.25.21.32.05_veh-26_02908_02985 + - 2021.06.25.21.32.05_veh-26_03278_03338 + - 2021.06.25.21.32.05_veh-26_03638_03707 + - 2021.06.25.21.32.05_veh-26_03878_03955 + - 2021.06.25.21.32.05_veh-26_03966_04044 + - 2021.06.25.21.32.05_veh-26_04055_04122 + - 2021.06.25.21.44.31_veh-16_00016_00630 + - 2021.06.25.21.44.31_veh-16_00671_00760 + - 2021.06.25.21.44.31_veh-16_00771_00948 + - 2021.06.25.21.44.31_veh-16_00969_01207 + - 2021.06.25.21.44.31_veh-16_01228_03165 + - 2021.06.25.21.44.31_veh-16_03247_03700 + - 2021.06.25.21.44.31_veh-16_03721_03855 + - 2021.06.25.21.44.31_veh-16_03866_03964 + - 2021.06.25.22.06.12_veh-35_00016_00792 + - 2021.06.25.22.06.12_veh-35_00816_01764 + - 2021.06.25.23.29.57_veh-38_00006_01027 + - 2021.06.25.23.29.57_veh-38_01065_02178 + - 2021.06.25.23.29.57_veh-38_02189_03155 + - 2021.06.25.23.29.57_veh-38_03166_03795 + - 2021.06.29.13.53.51_veh-26_00040_00193 + - 2021.06.29.13.53.51_veh-26_00204_00276 + - 2021.06.29.13.53.51_veh-26_00736_00799 + - 2021.06.29.13.53.51_veh-26_00854_00965 + - 2021.06.29.13.53.51_veh-26_01197_01267 + - 2021.06.29.13.53.51_veh-26_01278_01341 + - 2021.06.29.13.53.51_veh-26_01600_01683 + - 2021.06.29.13.53.51_veh-26_01696_01776 + - 2021.06.29.13.53.51_veh-26_01821_01907 + - 2021.06.29.13.53.51_veh-26_01981_02047 + - 2021.06.29.13.53.51_veh-26_02213_02283 + - 2021.06.29.13.53.51_veh-26_02860_02925 + - 2021.06.29.13.53.51_veh-26_03002_03078 + - 2021.06.29.13.53.51_veh-26_03393_03465 + - 2021.06.29.13.53.51_veh-26_03510_03577 + - 2021.06.29.13.53.51_veh-26_03588_03649 + - 2021.06.29.13.53.51_veh-26_03660_03729 + - 2021.06.29.13.53.51_veh-26_04283_04350 + - 2021.06.29.13.53.51_veh-26_04708_04919 + - 2021.06.29.13.53.51_veh-26_05286_05347 + - 2021.06.29.13.53.51_veh-26_05358_05463 + - 2021.06.29.14.27.11_veh-14_00016_00244 + - 2021.06.29.14.27.11_veh-14_00255_00561 + - 2021.06.29.14.27.11_veh-14_00572_01688 + - 2021.06.29.14.27.11_veh-14_01699_03897 + - 2021.06.29.14.27.11_veh-14_03918_05041 + - 2021.06.29.14.49.56_veh-38_00016_00556 + - 2021.06.29.14.49.56_veh-38_00567_00753 + - 2021.06.29.14.49.56_veh-38_00774_01467 + - 2021.06.29.14.49.56_veh-38_01488_02149 + - 2021.06.29.14.49.56_veh-38_02190_02324 + - 2021.06.29.14.49.56_veh-38_02335_03640 + - 2021.06.29.14.49.56_veh-38_03662_03887 + - 2021.06.29.14.49.56_veh-38_03908_04357 + - 2021.06.29.16.05.06_veh-26_00229_00319 + - 2021.06.29.16.05.06_veh-26_00346_00452 + - 2021.06.29.16.05.06_veh-26_00509_00578 + - 2021.06.29.16.05.06_veh-26_00694_00774 + - 2021.06.29.16.05.06_veh-26_00858_00929 + - 2021.06.29.16.05.06_veh-26_01243_01304 + - 2021.06.29.16.05.06_veh-26_01351_01441 + - 2021.06.29.16.05.06_veh-26_01723_01817 + - 2021.06.29.16.05.06_veh-26_01828_01895 + - 2021.06.29.16.05.06_veh-26_01906_01982 + - 2021.06.29.16.05.06_veh-26_02031_02094 + - 2021.06.29.16.05.06_veh-26_02299_02366 + - 2021.06.29.16.05.06_veh-26_02455_02524 + - 2021.06.29.16.05.06_veh-26_02808_02872 + - 2021.06.29.16.05.06_veh-26_03075_03143 + - 2021.06.29.16.05.06_veh-26_03197_03299 + - 2021.06.29.16.05.06_veh-26_03467_03542 + - 2021.06.29.16.05.06_veh-26_03625_03687 + - 2021.06.29.16.05.06_veh-26_03859_03925 + - 2021.06.29.16.05.06_veh-26_03936_03999 + - 2021.06.29.16.05.06_veh-26_04010_04081 + - 2021.06.29.16.05.06_veh-26_04145_04209 + - 2021.06.29.16.05.06_veh-26_04416_04480 + - 2021.06.29.16.05.06_veh-26_04692_04768 + - 2021.06.29.16.05.06_veh-26_05139_05203 + - 2021.06.29.16.05.06_veh-26_05451_05545 + - 2021.06.29.16.14.19_veh-16_00016_01338 + - 2021.06.29.16.14.19_veh-16_01349_01526 + - 2021.06.29.16.14.19_veh-16_01550_02749 + - 2021.06.29.16.14.19_veh-16_02760_03649 + - 2021.06.29.16.14.19_veh-16_03660_05650 + - 2021.06.29.16.22.56_veh-14_00015_01628 + - 2021.06.29.16.22.56_veh-14_01639_01780 + - 2021.06.29.16.22.56_veh-14_01801_04869 + - 2021.06.29.16.22.56_veh-14_04880_05318 + - 2021.06.29.16.25.03_veh-38_00077_00179 + - 2021.06.29.16.25.03_veh-38_00190_00623 + - 2021.06.29.16.25.03_veh-38_00644_00804 + - 2021.06.29.16.25.03_veh-38_00865_01279 + - 2021.06.29.16.25.03_veh-38_01290_01935 + - 2021.06.29.16.25.03_veh-38_02034_02189 + - 2021.06.29.16.25.03_veh-38_02210_02675 + - 2021.06.29.16.25.03_veh-38_02696_03004 + - 2021.06.29.16.25.03_veh-38_03015_03242 + - 2021.06.29.16.25.03_veh-38_03382_05211 + - 2021.06.29.18.27.59_veh-16_00005_00127 + - 2021.06.29.18.27.59_veh-16_00138_00202 + - 2021.06.29.18.27.59_veh-16_00217_01053 + - 2021.06.29.19.37.20_veh-26_00016_01863 + - 2021.06.29.19.37.20_veh-26_01874_02766 + - 2021.06.29.19.37.20_veh-26_02790_03313 + - 2021.06.29.19.37.20_veh-26_03324_04198 + - 2021.06.29.19.37.20_veh-26_04209_04424 + - 2021.06.29.19.37.20_veh-26_04447_05193 + - 2021.06.29.19.37.20_veh-26_05215_05843 + - 2021.06.29.20.11.27_veh-38_00016_00616 + - 2021.06.29.20.11.27_veh-38_00824_00972 + - 2021.06.29.20.11.27_veh-38_00983_01189 + - 2021.06.29.20.11.27_veh-38_01252_01556 + - 2021.06.29.20.11.27_veh-38_01633_01817 + - 2021.06.29.20.11.27_veh-38_01839_02800 + - 2021.06.29.20.11.27_veh-38_02822_05566 + - 2021.06.29.21.10.40_veh-14_00016_00129 + - 2021.06.29.21.10.40_veh-14_00140_00419 + - 2021.06.29.21.10.40_veh-14_00441_01040 + - 2021.06.29.21.10.40_veh-14_01061_02208 + - 2021.06.29.21.10.40_veh-14_02239_02429 + - 2021.06.29.21.10.40_veh-14_02451_02838 + - 2021.06.29.21.10.40_veh-14_02859_03486 + - 2021.06.29.21.10.40_veh-14_03508_03868 + - 2021.06.29.21.10.40_veh-14_03879_04466 + - 2021.06.29.21.58.01_veh-26_00016_00658 + - 2021.06.29.21.58.01_veh-26_00669_01583 + - 2021.06.29.21.59.21_veh-38_00023_00259 + - 2021.06.29.21.59.21_veh-38_00270_00973 + - 2021.06.29.21.59.21_veh-38_00995_01479 + - 2021.06.30.13.49.41_veh-26_00603_00670 + - 2021.06.30.13.49.41_veh-26_02751_02811 + - 2021.06.30.13.49.41_veh-26_02855_02924 + - 2021.06.30.13.52.24_veh-35_00005_00306 + - 2021.06.30.13.52.24_veh-35_00328_01059 + - 2021.06.30.13.52.24_veh-35_01092_02065 + - 2021.06.30.13.52.24_veh-35_02087_02322 + - 2021.06.30.13.52.24_veh-35_02333_04797 + - 2021.06.30.13.57.34_veh-37_00015_00346 + - 2021.06.30.13.57.34_veh-37_00368_01036 + - 2021.06.30.13.57.34_veh-37_01079_01625 + - 2021.06.30.13.57.34_veh-37_01636_01716 + - 2021.06.30.13.57.34_veh-37_01727_03023 + - 2021.06.30.14.22.10_veh-38_00015_01621 + - 2021.06.30.14.22.10_veh-38_01632_01976 + - 2021.06.30.15.31.03_veh-35_00016_00534 + - 2021.06.30.15.31.03_veh-35_00556_01495 + - 2021.06.30.15.31.03_veh-35_01536_03198 + - 2021.06.30.15.31.03_veh-35_03209_03348 + - 2021.06.30.15.31.03_veh-35_03372_03449 + - 2021.06.30.15.31.03_veh-35_03460_05094 + - 2021.06.30.15.59.35_veh-38_00021_00545 + - 2021.06.30.15.59.35_veh-38_00567_01263 + - 2021.06.30.15.59.35_veh-38_01284_01629 + - 2021.06.30.15.59.35_veh-38_01650_02127 + - 2021.06.30.15.59.35_veh-38_02149_02252 + - 2021.06.30.15.59.35_veh-38_02274_02376 + - 2021.06.30.15.59.35_veh-38_02387_02454 + - 2021.06.30.15.59.35_veh-38_02475_02815 + - 2021.06.30.15.59.35_veh-38_02836_04491 + - 2021.06.30.15.59.35_veh-38_04514_05250 + - 2021.06.30.16.53.06_veh-37_00043_00553 + - 2021.06.30.16.53.06_veh-37_00576_05927 + - 2021.06.30.16.54.52_veh-26_01783_01843 + - 2021.06.30.16.57.14_veh-12_00109_01120 + - 2021.06.30.16.57.14_veh-12_01141_01554 + - 2021.06.30.16.57.14_veh-12_01576_01730 + - 2021.06.30.16.57.14_veh-12_01751_01828 + - 2021.06.30.16.57.14_veh-12_01839_02010 + - 2021.06.30.16.57.14_veh-12_02031_02143 + - 2021.06.30.16.57.14_veh-12_02154_02293 + - 2021.06.30.16.57.14_veh-12_02304_02619 + - 2021.06.30.16.57.14_veh-12_02641_03125 + - 2021.06.30.16.57.14_veh-12_03146_04059 + - 2021.06.30.16.57.14_veh-12_04081_04378 + - 2021.06.30.16.57.14_veh-12_04389_05339 + - 2021.06.30.16.57.14_veh-12_05350_05949 + - 2021.06.30.16.57.14_veh-12_05970_06723 + - 2021.06.30.17.20.09_veh-35_00020_01040 + - 2021.06.30.17.20.09_veh-35_01063_01147 + - 2021.06.30.17.20.09_veh-35_01187_01951 + - 2021.06.30.17.20.09_veh-35_01962_03926 + - 2021.06.30.17.20.09_veh-35_03947_04028 + - 2021.06.30.17.20.09_veh-35_04050_04129 + - 2021.06.30.17.20.09_veh-35_04150_05364 + - 2021.06.30.17.59.22_veh-38_00033_01094 + - 2021.06.30.17.59.22_veh-38_01105_01561 + - 2021.06.30.17.59.22_veh-38_01572_02991 + - 2021.06.30.17.59.22_veh-38_03002_03759 + - 2021.06.30.17.59.22_veh-38_03770_03902 + - 2021.06.30.20.16.04_veh-37_00016_00476 + - 2021.06.30.20.16.04_veh-37_00487_00860 + - 2021.06.30.20.16.04_veh-37_00882_01051 + - 2021.06.30.20.16.04_veh-37_01062_01530 + - 2021.06.30.20.16.04_veh-37_01557_02851 + - 2021.06.30.20.16.04_veh-37_02877_03776 + - 2021.06.30.20.16.04_veh-37_03787_04577 + - 2021.06.30.20.38.23_veh-12_00016_00982 + - 2021.06.30.20.38.23_veh-12_01004_01207 + - 2021.06.30.20.38.23_veh-12_01236_01525 + - 2021.06.30.20.38.23_veh-12_01546_01691 + - 2021.06.30.20.38.23_veh-12_01712_01892 + - 2021.06.30.20.38.23_veh-12_01913_02048 + - 2021.06.30.20.38.23_veh-12_02078_02192 + - 2021.06.30.20.38.23_veh-12_02291_02894 + - 2021.06.30.20.38.23_veh-12_02915_03193 + - 2021.06.30.20.38.23_veh-12_03204_04124 + - 2021.06.30.20.38.23_veh-12_04135_04633 + - 2021.06.30.20.38.23_veh-12_04644_06306 + - 2021.06.30.20.38.23_veh-12_06327_06451 + - 2021.06.30.20.54.27_veh-38_00016_00102 + - 2021.06.30.20.54.27_veh-38_00123_00285 + - 2021.06.30.20.54.27_veh-38_00307_00918 + - 2021.06.30.20.54.27_veh-38_00940_01095 + - 2021.06.30.20.54.27_veh-38_01116_01610 + - 2021.06.30.20.54.27_veh-38_01632_02301 + - 2021.06.30.20.54.27_veh-38_02312_02646 + - 2021.06.30.20.54.27_veh-38_02657_05556 + - 2021.06.30.20.54.27_veh-38_05567_07046 + - 2021.06.30.21.09.59_veh-35_00005_00092 + - 2021.06.30.21.09.59_veh-35_00154_00678 + - 2021.06.30.21.09.59_veh-35_00700_00987 + - 2021.06.30.21.09.59_veh-35_01009_01456 + - 2021.06.30.21.09.59_veh-35_01467_01692 + - 2021.06.30.21.09.59_veh-35_01714_02232 + - 2021.06.30.21.09.59_veh-35_02243_02787 + - 2021.06.30.21.09.59_veh-35_02810_03888 + - 2021.06.30.21.09.59_veh-35_03899_04567 + - 2021.06.30.21.09.59_veh-35_04578_04968 + - 2021.06.30.21.39.00_veh-26_00180_00250 + - 2021.06.30.21.39.00_veh-26_00966_01041 + - 2021.06.30.21.39.00_veh-26_01166_01246 + - 2021.06.30.21.39.00_veh-26_01502_01572 + - 2021.06.30.21.39.00_veh-26_01990_02053 + - 2021.06.30.21.39.00_veh-26_02802_02867 + - 2021.06.30.21.39.00_veh-26_03168_03229 + - 2021.06.30.21.53.33_veh-37_00015_00837 + - 2021.06.30.21.53.33_veh-37_00859_03311 + - 2021.06.30.21.53.33_veh-37_03334_03788 + - 2021.07.02.13.52.52_veh-35_00017_00580 + - 2021.07.02.13.52.52_veh-35_00602_01198 + - 2021.07.02.13.52.52_veh-35_01220_01884 + - 2021.07.02.13.52.52_veh-35_01926_02647 + - 2021.07.02.13.52.52_veh-35_02731_04992 + - 2021.07.02.13.52.52_veh-35_05003_05822 + - 2021.07.02.13.52.52_veh-35_05833_05991 + - 2021.07.02.14.05.33_veh-12_00016_00214 + - 2021.07.02.14.05.33_veh-12_00225_00353 + - 2021.07.02.14.05.33_veh-12_00364_00457 + - 2021.07.02.14.05.33_veh-12_00478_00803 + - 2021.07.02.14.05.33_veh-12_00824_02234 + - 2021.07.02.14.05.33_veh-12_02256_03054 + - 2021.07.02.14.05.33_veh-12_03085_03901 + - 2021.07.02.14.05.33_veh-12_03922_04442 + - 2021.07.02.14.05.33_veh-12_04509_05776 + - 2021.07.02.15.42.41_veh-38_00046_00112 + - 2021.07.02.15.42.41_veh-38_00133_00467 + - 2021.07.02.15.42.41_veh-38_00488_00917 + - 2021.07.02.15.42.41_veh-38_00928_01486 + - 2021.07.02.15.42.41_veh-38_01497_01729 + - 2021.07.02.15.42.41_veh-38_01750_01879 + - 2021.07.02.15.42.41_veh-38_01900_02096 + - 2021.07.02.15.42.41_veh-38_02117_02877 + - 2021.07.02.15.42.41_veh-38_02963_03530 + - 2021.07.02.15.42.41_veh-38_03551_04075 + - 2021.07.02.15.42.41_veh-38_04155_04487 + - 2021.07.02.15.42.41_veh-38_04498_04594 + - 2021.07.02.15.42.41_veh-38_04605_05717 + - 2021.07.02.15.42.41_veh-38_05739_05965 + - 2021.07.02.15.42.41_veh-38_06056_06280 + - 2021.07.02.15.42.41_veh-38_06301_06821 + - 2021.07.02.15.42.41_veh-38_06868_07675 + - 2021.07.02.15.47.11_veh-37_00023_00748 + - 2021.07.02.15.47.11_veh-37_00769_02059 + - 2021.07.02.16.06.13_veh-35_00016_00763 + - 2021.07.02.16.06.13_veh-35_00774_01035 + - 2021.07.02.16.06.13_veh-35_01057_02690 + - 2021.07.02.16.06.13_veh-35_02713_03322 + - 2021.07.02.16.06.13_veh-35_03343_04780 + - 2021.07.02.16.06.13_veh-35_04802_05616 + - 2021.07.02.16.29.08_veh-14_00016_01036 + - 2021.07.02.16.29.08_veh-14_01059_04439 + - 2021.07.02.16.29.08_veh-14_04450_05695 + - 2021.07.02.16.47.20_veh-12_00016_00251 + - 2021.07.02.16.47.20_veh-12_00333_00995 + - 2021.07.02.16.47.20_veh-12_01018_02130 + - 2021.07.02.16.47.20_veh-12_02141_02305 + - 2021.07.02.16.47.20_veh-12_02327_02752 + - 2021.07.02.16.47.20_veh-12_02773_03661 + - 2021.07.02.16.47.20_veh-12_03683_03828 + - 2021.07.02.17.50.52_veh-37_00015_00760 + - 2021.07.02.17.50.52_veh-37_00781_01790 + - 2021.07.02.17.50.52_veh-37_01812_02199 + - 2021.07.06.15.57.52_veh-38_00016_00635 + - 2021.07.06.15.57.52_veh-38_00691_00964 + - 2021.07.06.15.57.52_veh-38_00986_02374 + - 2021.07.06.15.57.52_veh-38_02397_02939 + - 2021.07.06.15.57.52_veh-38_02960_04115 + - 2021.07.06.15.57.52_veh-38_04137_04309 + - 2021.07.06.16.21.11_veh-35_00019_00223 + - 2021.07.06.16.21.11_veh-35_00245_00438 + - 2021.07.06.16.21.11_veh-35_00521_00833 + - 2021.07.06.16.21.11_veh-35_00878_01362 + - 2021.07.06.16.21.11_veh-35_01384_01590 + - 2021.07.06.16.21.11_veh-35_01611_03654 + - 2021.07.06.16.21.11_veh-35_03676_03991 + - 2021.07.06.16.21.11_veh-35_04014_05270 + - 2021.07.06.16.27.42_veh-26_00096_00186 + - 2021.07.06.16.27.42_veh-26_00361_00643 + - 2021.07.06.16.27.42_veh-26_00659_00886 + - 2021.07.06.16.27.42_veh-26_00902_00967 + - 2021.07.06.16.27.42_veh-26_00986_01050 + - 2021.07.06.16.27.42_veh-26_01068_01132 + - 2021.07.06.16.27.42_veh-26_01146_01286 + - 2021.07.06.16.27.42_veh-26_01318_01387 + - 2021.07.06.16.27.42_veh-26_01398_01693 + - 2021.07.06.16.27.42_veh-26_01714_01950 + - 2021.07.06.16.27.42_veh-26_01991_02192 + - 2021.07.06.16.27.42_veh-26_02203_02670 + - 2021.07.06.16.27.42_veh-26_02692_03417 + - 2021.07.06.16.27.42_veh-26_03429_04098 + - 2021.07.06.16.27.42_veh-26_04109_04228 + - 2021.07.06.16.27.42_veh-26_04239_05400 + - 2021.07.06.16.27.42_veh-26_05411_05585 + - 2021.07.06.16.27.42_veh-26_05597_06002 + - 2021.07.06.16.27.42_veh-26_06013_06091 + - 2021.07.06.16.53.36_veh-14_00005_00158 + - 2021.07.06.16.53.36_veh-14_00272_01785 + - 2021.07.06.17.26.30_veh-14_00274_02913 + - 2021.07.06.17.26.30_veh-14_02935_03665 + - 2021.07.06.17.26.30_veh-14_03676_03891 + - 2021.07.06.17.30.06_veh-38_00026_01268 + - 2021.07.06.17.30.06_veh-38_01290_01944 + - 2021.07.06.17.30.06_veh-38_01965_02585 + - 2021.07.06.17.30.06_veh-38_02596_03046 + - 2021.07.06.17.30.06_veh-38_03057_03145 + - 2021.07.06.17.30.06_veh-38_03166_03797 + - 2021.07.06.17.30.06_veh-38_03818_04736 + - 2021.07.06.17.30.06_veh-38_04783_04932 + - 2021.07.06.17.30.06_veh-38_04943_05684 + - 2021.07.06.18.22.12_veh-35_00016_01227 + - 2021.07.06.20.37.44_veh-26_00022_00153 + - 2021.07.06.20.37.44_veh-26_00225_00944 + - 2021.07.06.20.37.44_veh-26_00955_01199 + - 2021.07.06.20.37.44_veh-26_01226_01706 + - 2021.07.06.20.37.44_veh-26_01728_04617 + - 2021.07.06.20.37.44_veh-26_04698_05477 + - 2021.07.06.20.58.06_veh-14_00022_00260 + - 2021.07.06.20.58.06_veh-14_00281_00474 + - 2021.07.06.20.58.06_veh-14_00485_01043 + - 2021.07.06.20.58.06_veh-14_01054_01245 + - 2021.07.06.20.58.06_veh-14_01256_02850 + - 2021.07.06.20.58.06_veh-14_02861_03646 + - 2021.07.06.20.58.06_veh-14_03657_05981 + - 2021.07.06.20.58.06_veh-14_06003_06271 + - 2021.07.06.20.58.06_veh-14_06282_06749 + - 2021.07.06.21.23.39_veh-35_00017_02448 + - 2021.07.06.21.23.39_veh-35_02470_02533 + - 2021.07.06.21.23.39_veh-35_02544_03644 + - 2021.07.06.21.23.39_veh-35_03666_03982 + - 2021.07.06.21.23.39_veh-35_04004_04895 + - 2021.07.06.23.01.25_veh-38_00093_00390 + - 2021.07.06.23.01.25_veh-38_00412_00588 + - 2021.07.06.23.01.25_veh-38_00627_00824 + - 2021.07.06.23.01.25_veh-38_00917_01319 + - 2021.07.06.23.01.25_veh-38_01330_02378 + - 2021.07.06.23.01.25_veh-38_02400_02574 + - 2021.07.06.23.01.25_veh-38_02615_02804 + - 2021.07.06.23.12.06_veh-26_00015_00492 + - 2021.07.06.23.12.06_veh-26_00503_01254 + - 2021.07.06.23.12.06_veh-26_01265_01416 + - 2021.07.06.23.12.06_veh-26_01427_01923 + - 2021.07.06.23.12.06_veh-26_01944_03912 + - 2021.07.06.23.15.32_veh-35_00016_00298 + - 2021.07.06.23.15.32_veh-35_00322_00492 + - 2021.07.06.23.15.32_veh-35_00520_02202 + - 2021.07.07.01.46.29_veh-12_00036_01177 + - 2021.07.07.01.46.29_veh-12_01198_01516 + - 2021.07.07.01.46.29_veh-12_01537_02307 + - 2021.07.07.01.46.29_veh-12_02318_02969 + - 2021.07.07.01.46.29_veh-12_02980_04591 + - 2021.07.07.01.46.29_veh-12_04616_05582 + - 2021.07.07.01.46.29_veh-12_05603_06576 + - 2021.07.07.01.47.59_veh-26_01210_01271 + - 2021.07.07.01.47.59_veh-26_01540_01607 + - 2021.07.07.01.47.59_veh-26_01869_01984 + - 2021.07.07.01.52.28_veh-35_00016_01122 + - 2021.07.07.01.52.28_veh-35_01144_03289 + - 2021.07.07.01.52.28_veh-35_03314_03843 + - 2021.07.07.01.52.28_veh-35_03867_04933 + - 2021.07.07.01.53.56_veh-38_00019_00141 + - 2021.07.07.01.53.56_veh-38_00163_00312 + - 2021.07.07.01.53.56_veh-38_00334_01318 + - 2021.07.07.01.53.56_veh-38_01329_04128 + - 2021.07.07.16.35.42_veh-35_00016_01839 + - 2021.07.07.16.35.42_veh-35_01850_02091 + - 2021.07.07.16.35.42_veh-35_02102_02655 + - 2021.07.07.16.35.42_veh-35_02666_04755 + - 2021.07.07.16.35.42_veh-35_04766_05248 + - 2021.07.07.16.57.29_veh-12_00016_00631 + - 2021.07.07.16.57.29_veh-12_00642_01681 + - 2021.07.07.16.57.29_veh-12_01702_02027 + - 2021.07.07.16.57.29_veh-12_02048_02393 + - 2021.07.07.16.57.29_veh-12_02415_04324 + - 2021.07.07.16.57.29_veh-12_04346_04623 + - 2021.07.07.16.57.29_veh-12_04696_04893 + - 2021.07.07.16.57.29_veh-12_04904_05114 + - 2021.07.07.16.57.29_veh-12_05125_05673 + - 2021.07.07.16.57.29_veh-12_05694_05817 + - 2021.07.07.17.00.27_veh-37_00015_00456 + - 2021.07.07.17.00.27_veh-37_00467_00671 + - 2021.07.07.17.00.27_veh-37_00682_00793 + - 2021.07.07.17.00.27_veh-37_00815_01343 + - 2021.07.07.17.00.27_veh-37_01400_01648 + - 2021.07.07.17.00.27_veh-37_01669_01822 + - 2021.07.07.17.00.27_veh-37_01833_03852 + - 2021.07.07.17.00.27_veh-37_03873_04022 + - 2021.07.07.17.00.27_veh-37_04033_04881 + - 2021.07.07.17.00.27_veh-37_04892_04976 + - 2021.07.07.17.00.27_veh-37_04987_06329 + - 2021.07.07.17.09.33_veh-26_00015_00177 + - 2021.07.07.17.09.33_veh-26_00198_00826 + - 2021.07.07.17.09.33_veh-26_00850_02406 + - 2021.07.07.17.09.33_veh-26_02417_04116 + - 2021.07.07.17.09.33_veh-26_04127_05689 + - 2021.07.07.18.27.54_veh-35_00016_01411 + - 2021.07.07.18.27.54_veh-35_01422_01972 + - 2021.07.07.18.27.54_veh-35_01983_02204 + - 2021.07.07.18.27.54_veh-35_02272_02338 + - 2021.07.07.18.27.54_veh-35_02349_04158 + - 2021.07.07.18.27.54_veh-35_04169_04446 + - 2021.07.07.18.27.54_veh-35_04468_04916 + - 2021.07.07.18.27.54_veh-35_04937_05184 + - 2021.07.07.18.27.54_veh-35_05205_05417 + - 2021.07.07.20.25.22_veh-38_00022_00748 + - 2021.07.07.20.25.22_veh-38_00770_01043 + - 2021.07.07.20.25.22_veh-38_01054_01890 + - 2021.07.07.20.25.22_veh-38_01901_02274 + - 2021.07.07.20.25.22_veh-38_02298_02495 + - 2021.07.07.20.25.22_veh-38_02506_02696 + - 2021.07.07.20.25.22_veh-38_02718_04318 + - 2021.07.07.20.25.22_veh-38_04329_04394 + - 2021.07.07.20.25.22_veh-38_04415_05240 + - 2021.07.07.20.45.06_veh-37_00016_00783 + - 2021.07.07.20.45.06_veh-37_00804_03458 + - 2021.07.07.20.45.06_veh-37_03479_03978 + - 2021.07.07.20.45.06_veh-37_03999_04154 + - 2021.07.07.20.45.06_veh-37_04178_04660 + - 2021.07.07.21.34.34_veh-35_00033_00818 + - 2021.07.07.21.34.34_veh-35_00839_01023 + - 2021.07.07.21.34.34_veh-35_01034_01190 + - 2021.07.07.21.34.34_veh-35_01224_01773 + - 2021.07.07.21.34.34_veh-35_01784_02655 + - 2021.07.07.21.34.34_veh-35_02676_03048 + - 2021.07.07.21.34.34_veh-35_03069_03265 + - 2021.07.07.21.34.34_veh-35_03290_04078 + - 2021.07.09.01.20.00_veh-37_00016_00213 + - 2021.07.09.01.20.00_veh-37_00234_00397 + - 2021.07.09.01.20.00_veh-37_00408_00612 + - 2021.07.09.01.20.00_veh-37_00623_01472 + - 2021.07.09.01.20.00_veh-37_01483_02577 + - 2021.07.09.01.20.00_veh-37_02600_02779 + - 2021.07.09.01.20.00_veh-37_02800_04009 + - 2021.07.09.01.20.00_veh-37_04031_04498 + - 2021.07.09.01.20.00_veh-37_04519_05143 + - 2021.07.09.01.37.16_veh-26_00692_00762 + - 2021.07.09.01.37.16_veh-26_00936_00996 + - 2021.07.09.01.37.16_veh-26_01336_01396 + - 2021.07.09.01.37.16_veh-26_01726_01793 + - 2021.07.09.01.37.16_veh-26_02856_02932 + - 2021.07.09.01.37.16_veh-26_03306_03373 + - 2021.07.09.01.37.16_veh-26_03432_03503 + - 2021.07.09.01.37.16_veh-26_04224_04293 + - 2021.07.09.01.37.16_veh-26_04675_04767 + - 2021.07.09.01.37.16_veh-26_04815_04878 + - 2021.07.09.01.37.16_veh-26_05530_05595 + - 2021.07.09.01.37.16_veh-26_05710_05791 + - 2021.07.09.02.42.50_veh-35_00038_02629 + - 2021.07.09.02.42.50_veh-35_02651_02770 + - 2021.07.09.02.50.33_veh-37_00016_02566 + - 2021.07.09.02.50.33_veh-37_02587_02662 + - 2021.07.09.15.53.28_veh-38_00053_00163 + - 2021.07.09.15.53.28_veh-38_00184_02293 + - 2021.07.09.15.53.28_veh-38_02316_03434 + - 2021.07.09.15.53.28_veh-38_03528_04262 + - 2021.07.09.15.53.28_veh-38_04273_04767 + - 2021.07.09.15.53.28_veh-38_04778_04886 + - 2021.07.09.15.54.09_veh-37_00016_00140 + - 2021.07.09.15.54.09_veh-37_00228_00439 + - 2021.07.09.15.54.09_veh-37_00461_01340 + - 2021.07.09.15.54.09_veh-37_01352_03942 + - 2021.07.09.15.54.09_veh-37_04036_05572 + - 2021.07.09.15.54.09_veh-37_05595_08092 + - 2021.07.09.15.54.09_veh-37_08103_08440 + - 2021.07.09.16.12.19_veh-26_02509_02592 + - 2021.07.09.16.12.19_veh-26_02985_03053 + - 2021.07.09.16.12.19_veh-26_04434_04498 + - 2021.07.09.16.12.19_veh-26_05071_05149 + - 2021.07.09.16.12.19_veh-26_06527_06591 + - 2021.07.09.16.12.19_veh-26_06964_07035 + - 2021.07.09.16.12.19_veh-26_07208_07271 + - 2021.07.09.17.06.37_veh-35_00049_00237 + - 2021.07.09.17.06.37_veh-35_00258_00748 + - 2021.07.09.17.06.37_veh-35_00769_00907 + - 2021.07.09.17.06.37_veh-35_00928_02567 + - 2021.07.09.17.06.37_veh-35_02609_05015 + - 2021.07.09.17.06.37_veh-35_05026_05593 + - 2021.07.09.17.48.26_veh-38_00037_00254 + - 2021.07.09.17.48.26_veh-38_00275_00605 + - 2021.07.09.17.48.26_veh-38_00627_01024 + - 2021.07.09.17.48.26_veh-38_01164_02247 + - 2021.07.09.17.48.26_veh-38_02268_02387 + - 2021.07.09.17.48.26_veh-38_02408_03970 + - 2021.07.09.17.48.26_veh-38_03992_04124 + - 2021.07.09.17.48.26_veh-38_04146_04339 + - 2021.07.09.17.48.26_veh-38_04350_05087 + - 2021.07.09.18.57.22_veh-37_00012_00230 + - 2021.07.09.18.57.22_veh-37_00241_00318 + - 2021.07.09.18.57.22_veh-37_00341_02691 + - 2021.07.09.18.57.22_veh-37_02713_03560 + - 2021.07.09.18.57.22_veh-37_03571_03959 + - 2021.07.09.20.26.06_veh-35_00016_01757 + - 2021.07.09.20.26.06_veh-35_01768_02782 + - 2021.07.09.20.26.06_veh-35_02793_03289 + - 2021.07.09.20.26.06_veh-35_03314_03877 + - 2021.07.09.20.26.06_veh-35_03898_05974 + - 2021.07.09.20.59.12_veh-38_00113_00669 + - 2021.07.09.20.59.12_veh-38_00690_00762 + - 2021.07.09.20.59.12_veh-38_00773_01187 + - 2021.07.09.20.59.12_veh-38_01208_01692 + - 2021.07.09.20.59.12_veh-38_01713_01842 + - 2021.07.09.20.59.12_veh-38_01853_02043 + - 2021.07.09.20.59.12_veh-38_02064_03281 + - 2021.07.09.20.59.12_veh-38_03292_04331 + - 2021.07.09.20.59.12_veh-38_04342_05676 + - 2021.07.09.20.59.12_veh-38_05697_06861 + - 2021.07.09.20.59.12_veh-38_06872_07220 + - 2021.07.09.20.59.12_veh-38_07245_07341 + - 2021.07.09.22.16.19_veh-12_00061_00402 + - 2021.07.09.22.16.19_veh-12_00413_00511 + - 2021.07.09.22.16.19_veh-12_00522_00738 + - 2021.07.09.22.16.19_veh-12_00760_00991 + - 2021.07.09.22.16.19_veh-12_01038_01164 + - 2021.07.09.23.23.48_veh-26_00054_01295 + - 2021.07.09.23.23.48_veh-26_01319_01432 + - 2021.07.09.23.23.48_veh-26_01454_02217 + - 2021.07.09.23.23.48_veh-26_02228_04624 + - 2021.07.09.23.23.48_veh-26_04648_06327 + - 2021.07.09.23.35.52_veh-37_00015_00628 + - 2021.07.09.23.35.52_veh-37_00649_00932 + - 2021.07.09.23.35.52_veh-37_00953_01953 + - 2021.07.09.23.35.52_veh-37_01974_02942 + - 2021.07.09.23.35.52_veh-37_02963_04877 + - 2021.07.09.23.35.52_veh-37_04888_05168 + - 2021.07.09.23.35.52_veh-37_05190_06183 + - 2021.07.09.23.35.52_veh-37_06201_09958 + - 2021.07.10.01.40.10_veh-35_00016_00983 + - 2021.07.10.01.40.10_veh-35_01004_02846 + - 2021.07.10.01.40.10_veh-35_02857_03676 + - 2021.07.10.01.40.10_veh-35_03687_03778 + - 2021.07.10.01.40.10_veh-35_03802_03891 + - 2021.07.10.01.40.10_veh-35_03902_04721 + - 2021.07.10.01.40.10_veh-35_04804_04893 + - 2021.07.10.01.40.10_veh-35_04947_05069 + - 2021.07.13.01.55.44_veh-38_00015_00270 + - 2021.07.13.01.55.44_veh-38_00281_00537 + - 2021.07.13.01.55.44_veh-38_00631_00744 + - 2021.07.13.01.55.44_veh-38_00766_01710 + - 2021.07.13.01.55.44_veh-38_01741_02203 + - 2021.07.13.16.15.11_veh-38_00025_00412 + - 2021.07.13.16.15.11_veh-38_00433_00603 + - 2021.07.13.16.15.11_veh-38_00624_01978 + - 2021.07.13.16.15.11_veh-38_01999_03449 + - 2021.07.13.16.15.11_veh-38_03470_05420 + - 2021.07.13.16.22.57_veh-35_00056_00688 + - 2021.07.13.16.22.57_veh-35_00709_03450 + - 2021.07.13.16.22.57_veh-35_03461_04157 + - 2021.07.13.16.22.57_veh-35_04178_05080 + - 2021.07.13.16.22.57_veh-35_05103_05171 + - 2021.07.13.16.22.57_veh-35_05192_05329 + - 2021.07.13.16.22.57_veh-35_05354_06602 + - 2021.07.13.16.53.58_veh-37_00016_00486 + - 2021.07.13.16.53.58_veh-37_00511_01959 + - 2021.07.13.17.36.02_veh-12_00015_00383 + - 2021.07.13.17.36.02_veh-12_00405_00806 + - 2021.07.13.17.36.02_veh-12_00828_01121 + - 2021.07.13.17.36.02_veh-12_01164_02414 + - 2021.07.13.17.36.02_veh-12_02488_03487 + - 2021.07.13.17.36.02_veh-12_03512_05167 + - 2021.07.13.17.36.02_veh-12_05189_05594 + - 2021.07.13.17.36.02_veh-12_05616_05694 + - 2021.07.13.17.36.53_veh-26_00023_00092 + - 2021.07.13.17.36.53_veh-26_00109_00307 + - 2021.07.13.17.36.53_veh-26_00371_00479 + - 2021.07.13.17.36.53_veh-26_00490_00556 + - 2021.07.13.17.36.53_veh-26_00567_00648 + - 2021.07.13.17.36.53_veh-26_00659_00731 + - 2021.07.13.17.36.53_veh-26_00744_00852 + - 2021.07.13.17.36.53_veh-26_00891_00969 + - 2021.07.13.17.36.53_veh-26_00991_01247 + - 2021.07.13.17.36.53_veh-26_01300_01686 + - 2021.07.13.17.36.53_veh-26_01697_01802 + - 2021.07.13.17.36.53_veh-26_01892_02001 + - 2021.07.13.17.36.53_veh-26_02012_02117 + - 2021.07.13.17.36.53_veh-26_02138_02207 + - 2021.07.13.17.36.53_veh-26_02218_02495 + - 2021.07.13.17.36.53_veh-26_02506_02964 + - 2021.07.13.17.36.53_veh-26_02975_03062 + - 2021.07.13.17.36.53_veh-26_03073_03253 + - 2021.07.13.17.36.53_veh-26_03264_03404 + - 2021.07.13.17.36.53_veh-26_03429_03538 + - 2021.07.13.17.36.53_veh-26_03549_03812 + - 2021.07.13.17.36.53_veh-26_03823_04159 + - 2021.07.13.18.05.59_veh-37_00005_00241 + - 2021.07.13.18.05.59_veh-37_00263_01914 + - 2021.07.13.18.26.37_veh-38_00016_00661 + - 2021.07.13.18.26.37_veh-38_00683_00976 + - 2021.07.13.18.35.46_veh-35_00016_00296 + - 2021.07.13.18.35.46_veh-35_00317_00903 + - 2021.07.13.18.35.46_veh-35_01000_04898 + - 2021.07.13.18.48.33_veh-37_00016_00197 + - 2021.07.13.18.48.33_veh-37_00208_00429 + - 2021.07.13.18.48.33_veh-37_00440_01932 + - 2021.07.13.18.48.33_veh-37_02016_02995 + - 2021.07.13.20.25.13_veh-26_00008_00153 + - 2021.07.13.20.25.13_veh-26_00175_00630 + - 2021.07.13.20.25.13_veh-26_00698_02662 + - 2021.07.13.20.25.13_veh-26_02673_04797 + - 2021.07.13.20.25.13_veh-26_04808_05241 + - 2021.07.13.20.25.13_veh-26_05281_05387 + - 2021.07.13.21.32.12_veh-12_00022_01115 + - 2021.07.13.21.32.12_veh-12_01172_01544 + - 2021.07.13.21.32.12_veh-12_01627_04213 + - 2021.07.13.21.32.12_veh-12_04234_04580 + - 2021.07.13.21.32.12_veh-12_04602_05055 + - 2021.07.13.21.32.12_veh-12_05066_05326 + - 2021.07.13.21.32.12_veh-12_05337_06073 + - 2021.07.13.22.05.35_veh-35_00006_01284 + - 2021.07.13.22.05.35_veh-35_01305_01428 + - 2021.07.13.22.05.35_veh-35_01439_01608 + - 2021.07.13.22.05.35_veh-35_01630_02498 + - 2021.07.13.22.05.35_veh-35_02509_03297 + - 2021.07.13.22.05.35_veh-35_03308_04360 + - 2021.07.13.22.15.05_veh-26_00016_01272 + - 2021.07.13.22.15.05_veh-26_01298_01391 + - 2021.07.13.22.15.05_veh-26_01402_01600 + - 2021.07.13.22.15.05_veh-26_01622_02793 + - 2021.07.14.16.58.38_veh-38_00016_00144 + - 2021.07.14.16.58.38_veh-38_00165_00428 + - 2021.07.14.16.58.38_veh-38_00450_00836 + - 2021.07.14.16.58.38_veh-38_00863_01848 + - 2021.07.14.16.58.38_veh-38_01869_02142 + - 2021.07.14.16.58.38_veh-38_02164_03516 + - 2021.07.14.16.58.38_veh-38_03527_04257 + - 2021.07.14.16.58.38_veh-38_04268_05695 + - 2021.07.14.17.11.00_veh-12_00044_01243 + - 2021.07.14.17.11.00_veh-12_01254_01352 + - 2021.07.14.17.11.00_veh-12_01460_01532 + - 2021.07.14.17.11.00_veh-12_01553_02224 + - 2021.07.14.17.11.00_veh-12_02247_03268 + - 2021.07.14.17.11.00_veh-12_03279_04045 + - 2021.07.14.17.11.00_veh-12_04067_05629 + - 2021.07.14.18.44.04_veh-35_00016_01313 + - 2021.07.14.18.44.04_veh-35_01356_02983 + - 2021.07.14.18.44.04_veh-35_03006_05188 + - 2021.07.14.18.44.04_veh-35_05199_05488 + - 2021.07.14.21.32.59_veh-12_00016_00211 + - 2021.07.14.21.32.59_veh-12_00222_00325 + - 2021.07.14.21.32.59_veh-12_00346_00438 + - 2021.07.14.21.32.59_veh-12_00460_00810 + - 2021.07.14.21.32.59_veh-12_00832_02605 + - 2021.07.14.21.32.59_veh-12_02626_03313 + - 2021.07.14.21.32.59_veh-12_03334_03757 + - 2021.07.14.21.32.59_veh-12_03778_07784 + - 2021.07.14.21.49.48_veh-17_00016_00312 + - 2021.07.14.21.49.48_veh-17_00364_00654 + - 2021.07.14.21.49.48_veh-17_00677_00810 + - 2021.07.14.21.49.48_veh-17_00831_00912 + - 2021.07.14.21.49.48_veh-17_00934_01386 + - 2021.07.14.21.49.48_veh-17_01410_01744 + - 2021.07.14.21.49.48_veh-17_01766_02708 + - 2021.07.14.21.49.48_veh-17_02732_03177 + - 2021.07.14.21.49.48_veh-17_03213_03679 + - 2021.07.14.21.49.48_veh-17_03700_04045 + - 2021.07.14.21.49.48_veh-17_04069_04830 + - 2021.07.14.21.49.48_veh-17_04873_05701 + - 2021.07.14.21.49.48_veh-17_05723_06195 + - 2021.07.14.21.49.48_veh-17_06212_06532 + - 2021.07.14.21.49.48_veh-17_06543_06855 + - 2021.07.14.22.08.15_veh-35_00010_02682 + - 2021.07.14.22.08.15_veh-35_02704_04094 + - 2021.07.14.22.08.15_veh-35_04105_05270 + - 2021.07.14.22.16.49_veh-38_00024_00086 + - 2021.07.14.22.16.49_veh-38_00097_00867 + - 2021.07.14.22.16.49_veh-38_00889_01932 + - 2021.07.14.22.16.49_veh-38_01943_03036 + - 2021.07.14.22.16.49_veh-38_03058_03316 + - 2021.07.14.22.16.49_veh-38_03327_04163 + - 2021.07.14.22.16.49_veh-38_04184_04877 + - 2021.07.14.22.16.49_veh-38_04994_05194 + - 2021.07.14.22.16.49_veh-38_05215_05654 + - 2021.07.14.22.16.49_veh-38_05676_05923 + - 2021.07.14.23.51.56_veh-37_00016_01051 + - 2021.07.14.23.51.56_veh-37_01078_01376 + - 2021.07.14.23.51.56_veh-37_01400_01578 + - 2021.07.14.23.51.56_veh-37_01589_03509 + - 2021.07.15.00.02.16_veh-17_00016_00611 + - 2021.07.15.00.02.16_veh-17_00622_00767 + - 2021.07.15.00.02.16_veh-17_00788_01601 + - 2021.07.15.00.02.16_veh-17_01612_02227 + - 2021.07.15.00.06.06_veh-38_00016_00139 + - 2021.07.15.00.06.06_veh-38_00160_00412 + - 2021.07.15.00.06.06_veh-38_00423_01201 + - 2021.07.15.00.06.06_veh-38_01222_01428 + - 2021.07.15.00.06.06_veh-38_01439_01882 + - 2021.07.15.00.06.06_veh-38_01903_01986 + - 2021.07.15.00.13.17_veh-35_00018_00211 + - 2021.07.15.00.13.17_veh-35_00233_00488 + - 2021.07.15.00.13.17_veh-35_00499_00703 + - 2021.07.15.00.13.17_veh-35_00714_00911 + - 2021.07.15.00.13.17_veh-35_01012_01125 + - 2021.07.15.00.13.17_veh-35_01146_01373 + - 2021.07.15.00.19.42_veh-47_00015_00235 + - 2021.07.15.00.19.42_veh-47_00257_00698 + - 2021.07.15.00.19.42_veh-47_00759_01283 + - 2021.07.15.00.19.42_veh-47_01294_01795 + - 2021.07.15.00.19.42_veh-47_01879_02074 + - 2021.07.15.00.19.42_veh-47_02095_02195 + - 2021.07.15.02.40.35_veh-12_00064_00268 + - 2021.07.15.02.40.35_veh-12_00290_00648 + - 2021.07.15.02.40.35_veh-12_00659_00772 + - 2021.07.15.02.40.35_veh-12_00855_01334 + - 2021.07.15.02.40.35_veh-12_01345_01964 + - 2021.07.15.02.40.35_veh-12_01986_02533 + - 2021.07.15.02.40.35_veh-12_02607_02957 + - 2021.07.15.16.56.34_veh-12_00025_00161 + - 2021.07.15.16.56.34_veh-12_00182_00371 + - 2021.07.15.16.56.34_veh-12_00382_00916 + - 2021.07.15.16.56.34_veh-12_00937_01741 + - 2021.07.15.16.56.34_veh-12_01752_01892 + - 2021.07.15.16.56.34_veh-12_01913_02673 + - 2021.07.15.16.56.34_veh-12_02695_03282 + - 2021.07.15.16.56.34_veh-12_03293_03535 + - 2021.07.15.16.56.34_veh-12_03556_03751 + - 2021.07.15.16.56.34_veh-12_03762_04241 + - 2021.07.15.16.56.34_veh-12_04262_04798 + - 2021.07.15.16.56.34_veh-12_04820_05325 + - 2021.07.15.16.56.34_veh-12_05346_05866 + - 2021.07.15.16.56.34_veh-12_05887_06757 + - 2021.07.15.16.56.34_veh-12_06778_07210 + - 2021.07.15.16.56.34_veh-12_07232_07566 + - 2021.07.15.16.56.34_veh-12_07587_07968 + - 2021.07.15.16.56.34_veh-12_07990_08320 + - 2021.07.15.18.04.19_veh-35_00016_00111 + - 2021.07.15.18.04.19_veh-35_00133_00328 + - 2021.07.15.18.04.19_veh-35_00339_00422 + - 2021.07.15.18.04.19_veh-35_00433_00968 + - 2021.07.15.18.04.19_veh-35_00990_02496 + - 2021.07.15.19.15.37_veh-35_00020_00364 + - 2021.07.15.19.15.37_veh-35_00386_02633 + - 2021.07.15.19.15.37_veh-35_02657_03358 + - 2021.07.15.19.15.37_veh-35_03369_04528 + - 2021.07.15.19.15.37_veh-35_04569_05240 + - 2021.07.15.21.07.10_veh-12_00005_00092 + - 2021.07.15.21.07.10_veh-12_00103_00307 + - 2021.07.15.21.07.10_veh-12_00318_00583 + - 2021.07.15.21.07.10_veh-12_00605_00847 + - 2021.07.15.21.07.10_veh-12_00858_02217 + - 2021.07.15.21.07.10_veh-12_02228_02863 + - 2021.07.15.21.07.10_veh-12_02884_03354 + - 2021.07.15.21.07.10_veh-12_03488_05812 + - 2021.07.15.21.07.10_veh-12_05823_06549 + - 2021.07.15.21.07.10_veh-12_06571_07072 + - 2021.07.15.21.07.10_veh-12_07083_07287 + - 2021.07.15.21.07.10_veh-12_07298_07471 + - 2021.07.15.21.07.10_veh-12_07482_08424 + - 2021.07.15.21.07.10_veh-12_08445_08614 + - 2021.07.15.21.19.31_veh-38_00017_00932 + - 2021.07.15.21.19.31_veh-38_00953_02718 + - 2021.07.15.22.36.53_veh-38_00032_00258 + - 2021.07.15.22.36.53_veh-38_00307_00405 + - 2021.07.15.22.36.53_veh-38_00426_01441 + - 2021.07.15.22.36.53_veh-38_01452_02087 + - 2021.07.15.22.36.53_veh-38_02098_02210 + - 2021.07.15.22.36.53_veh-38_02232_02737 + - 2021.07.15.22.36.53_veh-38_02758_03652 + - 2021.07.15.22.36.53_veh-38_03674_03989 + - 2021.07.15.22.36.53_veh-38_04036_04161 + - 2021.07.15.22.36.53_veh-38_04172_05323 + - 2021.07.15.23.06.09_veh-35_00036_00103 + - 2021.07.15.23.06.09_veh-35_00186_00773 + - 2021.07.15.23.06.09_veh-35_00795_00913 + - 2021.07.15.23.06.09_veh-35_00934_01788 + - 2021.07.15.23.18.35_veh-14_00016_00168 + - 2021.07.15.23.18.35_veh-14_00179_00972 + - 2021.07.15.23.18.35_veh-14_00994_01323 + - 2021.07.15.23.18.35_veh-14_01334_02310 + - 2021.07.15.23.18.35_veh-14_02331_02683 + - 2021.07.15.23.18.35_veh-14_02708_05708 + - 2021.07.15.23.18.35_veh-14_05719_05795 + - 2021.07.15.23.36.06_veh-17_00043_01091 + - 2021.07.16.00.03.12_veh-37_00041_00885 + - 2021.07.16.00.03.12_veh-37_00907_02168 + - 2021.07.16.00.03.12_veh-37_02189_03199 + - 2021.07.16.00.03.12_veh-37_03220_05763 + - 2021.07.16.00.03.12_veh-37_05774_06273 + - 2021.07.16.00.03.12_veh-37_06295_06602 + - 2021.07.16.00.03.12_veh-37_06623_06829 + - 2021.07.16.00.24.14_veh-38_00094_00346 + - 2021.07.16.00.24.14_veh-38_00367_01154 + - 2021.07.16.00.24.14_veh-38_01165_01425 + - 2021.07.16.00.24.14_veh-38_01447_01621 + - 2021.07.16.00.33.19_veh-12_00007_00332 + - 2021.07.16.00.33.19_veh-12_00353_00687 + - 2021.07.16.00.33.19_veh-12_00708_01004 + - 2021.07.16.00.51.05_veh-17_00023_01331 + - 2021.07.16.00.51.05_veh-17_01352_01901 + - 2021.07.16.00.51.05_veh-17_01938_03243 + - 2021.07.16.00.51.05_veh-17_03264_05261 + - 2021.07.16.01.22.41_veh-14_00015_00547 + - 2021.07.16.01.22.41_veh-14_00572_01716 + - 2021.07.16.01.22.41_veh-14_01737_01980 + - 2021.07.16.01.22.41_veh-14_02003_02615 + - 2021.07.16.01.22.41_veh-14_02626_04289 + - 2021.07.16.01.22.41_veh-14_04315_07102 + - 2021.07.16.02.35.53_veh-37_00024_00237 + - 2021.07.16.02.35.53_veh-37_00259_00555 + - 2021.07.16.02.35.53_veh-37_00577_01479 + - 2021.07.16.02.35.53_veh-37_01490_02396 + - 2021.07.16.02.53.40_veh-17_00016_01588 + - 2021.07.16.16.01.30_veh-38_00016_00333 + - 2021.07.16.16.01.30_veh-38_00356_02486 + - 2021.07.16.16.01.30_veh-38_02497_03871 + - 2021.07.16.16.01.30_veh-38_03893_05253 + - 2021.07.16.16.01.30_veh-38_05274_05744 + - 2021.07.16.16.01.30_veh-38_05766_06843 + - 2021.07.16.16.08.35_veh-35_00132_00784 + - 2021.07.16.16.08.35_veh-35_00805_01292 + - 2021.07.16.16.08.35_veh-35_01303_01641 + - 2021.07.16.16.08.35_veh-35_01664_02376 + - 2021.07.16.16.08.35_veh-35_02397_02540 + - 2021.07.16.16.08.35_veh-35_02551_02640 + - 2021.07.16.16.08.35_veh-35_02651_03700 + - 2021.07.16.16.08.35_veh-35_03711_04709 + - 2021.07.16.16.08.35_veh-35_04744_06051 + - 2021.07.16.16.27.22_veh-26_00016_01515 + - 2021.07.16.16.27.22_veh-26_01536_02260 + - 2021.07.16.16.27.22_veh-26_02282_03814 + - 2021.07.16.16.27.22_veh-26_03836_05047 + - 2021.07.16.16.27.22_veh-26_05058_05383 + - 2021.07.16.16.27.22_veh-26_05416_05596 + - 2021.07.16.18.06.21_veh-38_00016_00747 + - 2021.07.16.18.06.21_veh-38_00770_01505 + - 2021.07.16.18.06.21_veh-38_01526_02150 + - 2021.07.16.18.06.21_veh-38_02197_03220 + - 2021.07.16.18.06.21_veh-38_03231_03712 + - 2021.07.16.18.06.21_veh-38_03733_04300 + - 2021.07.16.18.06.21_veh-38_04311_04460 + - 2021.07.16.18.06.21_veh-38_04471_04922 + - 2021.07.16.18.06.21_veh-38_04933_05307 + - 2021.07.16.18.06.21_veh-38_05338_05486 + - 2021.07.16.18.19.22_veh-35_00023_00234 + - 2021.07.16.18.19.22_veh-35_00255_00418 + - 2021.07.16.18.19.22_veh-35_00440_00858 + - 2021.07.16.18.19.22_veh-35_00869_03454 + - 2021.07.16.18.49.56_veh-26_00015_00235 + - 2021.07.16.18.49.56_veh-26_00256_00822 + - 2021.07.16.18.49.56_veh-26_00833_03384 + - 2021.07.16.18.49.56_veh-26_03407_03538 + - 2021.07.16.20.45.29_veh-35_00016_00589 + - 2021.07.16.20.45.29_veh-35_00600_01084 + - 2021.07.16.20.45.29_veh-35_01095_01486 + - 2021.07.16.20.45.29_veh-35_01513_02486 + - 2021.07.16.20.45.29_veh-35_02509_02649 + - 2021.07.16.21.17.55_veh-26_00715_00781 + - 2021.07.16.21.17.55_veh-26_00872_00937 + - 2021.07.16.21.17.55_veh-26_01014_01075 + - 2021.07.16.21.17.55_veh-26_01392_01488 + - 2021.07.16.21.17.55_veh-26_02927_02992 + - 2021.07.16.21.17.55_veh-26_03254_03336 + - 2021.07.16.21.17.55_veh-26_03772_03842 + - 2021.07.16.21.17.55_veh-26_03860_03930 + - 2021.07.16.21.17.55_veh-26_04426_04488 + - 2021.07.16.21.17.55_veh-26_05156_05225 + - 2021.07.16.21.17.55_veh-26_05558_05627 + - 2021.07.16.21.42.48_veh-12_00016_00589 + - 2021.07.16.21.42.48_veh-12_00610_00879 + - 2021.07.16.21.42.48_veh-12_00900_01912 + - 2021.07.16.21.42.48_veh-12_01933_02129 + - 2021.07.16.21.42.48_veh-12_02140_02536 + - 2021.07.16.21.42.48_veh-12_02547_02996 + - 2021.07.16.21.42.48_veh-12_03018_03223 + - 2021.07.16.21.42.48_veh-12_03245_04702 + - 2021.07.16.21.42.48_veh-12_04713_05075 + - 2021.07.16.22.40.23_veh-38_00016_00182 + - 2021.07.16.22.40.23_veh-38_00204_00360 + - 2021.07.16.22.40.23_veh-38_00371_00797 + - 2021.07.16.22.40.23_veh-38_00818_03032 + - 2021.07.16.23.22.27_veh-14_00015_01368 + - 2021.07.16.23.22.27_veh-14_01383_01479 + - 2021.07.16.23.22.27_veh-14_01502_01610 + - 2021.07.16.23.22.27_veh-14_01631_03833 + - 2021.07.16.23.22.27_veh-14_03844_04474 + - 2021.07.16.23.22.27_veh-14_04496_06203 + - 2021.07.16.23.22.27_veh-14_06214_06318 + - 2021.07.16.23.22.27_veh-14_06339_07673 + - 2021.07.16.23.26.30_veh-37_00016_00829 + - 2021.07.16.23.26.30_veh-37_00840_01124 + - 2021.07.16.23.26.30_veh-37_01135_01364 + - 2021.07.16.23.26.30_veh-37_01388_01521 + - 2021.07.16.23.26.30_veh-37_01532_02449 + - 2021.07.16.23.26.30_veh-37_02460_03844 + - 2021.07.16.23.26.30_veh-37_04126_06474 + - 2021.07.16.23.43.16_veh-12_00016_00584 + - 2021.07.16.23.43.16_veh-12_00595_00810 + - 2021.07.16.23.43.16_veh-12_00833_01147 + - 2021.07.16.23.56.02_veh-47_00015_02042 + - 2021.07.16.23.56.02_veh-47_02064_02307 + - 2021.07.16.23.56.02_veh-47_02318_03077 + - 2021.07.16.23.56.02_veh-47_03088_04735 + - 2021.07.16.23.56.02_veh-47_04767_06093 + - 2021.07.17.00.50.34_veh-35_00016_01761 + - 2021.07.17.00.50.34_veh-35_01805_03532 + - 2021.07.17.00.50.34_veh-35_03553_04991 + - 2021.07.17.00.50.34_veh-35_05016_05895 + - 2021.07.17.00.50.34_veh-35_05922_06215 + - 2021.07.17.00.50.34_veh-35_06257_06421 + - 2021.07.17.02.11.48_veh-47_00077_00585 + - 2021.07.17.02.11.48_veh-47_00596_00989 + - 2021.07.17.02.11.48_veh-47_01011_02469 + - 2021.07.17.02.11.48_veh-47_02491_03260 + - 2021.07.17.02.11.48_veh-47_03289_04478 + - 2021.07.17.03.04.44_veh-35_00016_01141 + - 2021.07.17.19.14.24_veh-12_00005_00089 + - 2021.07.17.19.14.24_veh-12_00100_00273 + - 2021.07.17.19.14.24_veh-12_00387_00809 + - 2021.07.17.19.14.24_veh-12_00820_01114 + - 2021.07.17.19.14.24_veh-12_01125_01388 + - 2021.07.17.19.14.24_veh-12_01434_01542 + - 2021.07.17.19.14.24_veh-12_01563_01692 + - 2021.07.17.19.14.24_veh-12_01703_01836 + - 2021.07.17.19.14.24_veh-12_01858_02235 + - 2021.07.17.19.14.24_veh-12_02246_02659 + - 2021.07.17.19.14.24_veh-12_02670_04309 + - 2021.07.17.22.20.17_veh-12_00049_00392 + - 2021.07.17.22.20.17_veh-12_00414_00831 + - 2021.07.17.22.20.17_veh-12_00852_01104 + - 2021.07.17.22.20.17_veh-12_01115_01404 + - 2021.07.17.22.20.17_veh-12_01415_02091 + - 2021.07.19.16.17.27_veh-35_00016_00983 + - 2021.07.19.16.17.27_veh-35_01006_01201 + - 2021.07.19.16.17.27_veh-35_01224_05808 + - 2021.07.19.16.17.27_veh-35_05854_06022 + - 2021.07.19.16.17.27_veh-35_06046_06310 + - 2021.07.19.17.15.36_veh-47_00016_00094 + - 2021.07.19.17.15.36_veh-47_00116_01292 + - 2021.07.19.17.15.36_veh-47_01314_01762 + - 2021.07.19.17.15.36_veh-47_01773_01850 + - 2021.07.19.17.15.36_veh-47_01872_02077 + - 2021.07.19.17.15.36_veh-47_02088_04153 + - 2021.07.19.17.15.36_veh-47_04164_06727 + - 2021.07.19.18.30.51_veh-35_00120_00182 + - 2021.07.19.18.30.51_veh-35_00308_03247 + - 2021.07.19.18.30.51_veh-35_03270_04994 + - 2021.07.19.21.34.07_veh-35_00005_00428 + - 2021.07.19.21.34.07_veh-35_00439_00551 + - 2021.07.19.21.34.07_veh-35_00573_02543 + - 2021.07.19.21.34.07_veh-35_02554_03358 + - 2021.07.19.21.34.07_veh-35_03380_04245 + - 2021.07.19.21.34.07_veh-35_04256_04494 + - 2021.07.19.21.39.06_veh-17_00021_00434 + - 2021.07.19.21.39.06_veh-17_00457_00953 + - 2021.07.19.21.39.06_veh-17_00964_01118 + - 2021.07.19.21.39.06_veh-17_01142_01669 + - 2021.07.19.21.39.06_veh-17_01693_01793 + - 2021.07.19.21.39.06_veh-17_01838_01980 + - 2021.07.19.23.10.40_veh-17_00016_00218 + - 2021.07.19.23.10.40_veh-17_00239_00513 + - 2021.07.19.23.10.40_veh-17_00534_00729 + - 2021.07.19.23.10.40_veh-17_00751_01689 + - 2021.07.19.23.10.40_veh-17_01700_02000 + - 2021.07.19.23.10.40_veh-17_02068_02924 + - 2021.07.19.23.10.40_veh-17_02948_03303 + - 2021.07.19.23.12.29_veh-35_00005_00999 + - 2021.07.19.23.12.29_veh-35_01047_01849 + - 2021.07.19.23.12.29_veh-35_01860_02096 + - 2021.07.19.23.12.29_veh-35_02119_03408 + - 2021.07.19.23.12.29_veh-35_03429_04359 + - 2021.07.19.23.12.29_veh-35_04381_04940 + - 2021.07.19.23.12.29_veh-35_04964_05295 + - 2021.07.21.00.48.35_veh-38_00005_00424 + - 2021.07.21.00.48.35_veh-38_00445_00843 + - 2021.07.21.00.48.35_veh-38_00932_01671 + - 2021.07.21.00.48.35_veh-38_01727_02453 + - 2021.07.21.00.48.35_veh-38_02475_02681 + - 2021.07.21.00.48.35_veh-38_02702_03522 + - 2021.07.21.00.48.35_veh-38_03544_03707 + - 2021.07.21.00.48.35_veh-38_03728_05121 + - 2021.07.21.00.48.35_veh-38_05142_05254 + - 2021.07.21.00.48.35_veh-38_05275_05666 + - 2021.07.21.00.49.45_veh-37_00016_00440 + - 2021.07.21.00.49.45_veh-37_00462_00932 + - 2021.07.21.00.49.45_veh-37_00954_02291 + - 2021.07.21.00.49.45_veh-37_02302_02692 + - 2021.07.21.00.49.45_veh-37_02715_03901 + - 2021.07.21.00.49.45_veh-37_03923_05752 + - 2021.07.21.00.49.45_veh-37_05763_06789 + - 2021.07.21.00.49.45_veh-37_06813_07204 + - 2021.07.21.00.57.59_veh-47_00124_00429 + - 2021.07.21.00.57.59_veh-47_00440_00939 + - 2021.07.21.00.57.59_veh-47_00950_01834 + - 2021.07.21.00.57.59_veh-47_01856_02500 + - 2021.07.21.00.57.59_veh-47_02521_02664 + - 2021.07.21.00.57.59_veh-47_02685_03635 + - 2021.07.21.00.57.59_veh-47_03657_04618 + - 2021.07.21.00.57.59_veh-47_04629_04722 + - 2021.07.21.00.57.59_veh-47_04747_06334 + - 2021.07.21.00.57.59_veh-47_06345_06740 + - 2021.07.21.00.57.59_veh-47_06761_07031 + - 2021.07.21.01.14.08_veh-35_00050_00459 + - 2021.07.21.01.14.08_veh-35_00470_00737 + - 2021.07.21.01.14.08_veh-35_00748_01179 + - 2021.07.21.01.14.08_veh-35_01201_01265 + - 2021.07.21.01.14.08_veh-35_01293_01466 + - 2021.07.21.01.14.08_veh-35_01489_02536 + - 2021.07.21.01.14.08_veh-35_02572_03383 + - 2021.07.21.01.14.08_veh-35_03405_04116 + - 2021.07.21.01.14.08_veh-35_04140_04651 + - 2021.07.21.01.44.59_veh-12_00005_00559 + - 2021.07.21.01.44.59_veh-12_00570_00778 + - 2021.07.21.01.44.59_veh-12_00799_02101 + - 2021.07.21.01.44.59_veh-12_02122_02408 + - 2021.07.21.01.44.59_veh-12_02419_03053 + - 2021.07.21.01.44.59_veh-12_03064_03621 + - 2021.07.21.02.32.00_veh-26_00045_00305 + - 2021.07.21.02.32.00_veh-26_00316_00660 + - 2021.07.21.02.32.00_veh-26_00671_00894 + - 2021.07.21.02.32.00_veh-26_00905_01033 + - 2021.07.21.16.11.10_veh-12_00016_00754 + - 2021.07.21.16.11.10_veh-12_00765_01045 + - 2021.07.21.16.11.10_veh-12_01066_01509 + - 2021.07.21.16.11.10_veh-12_01531_01926 + - 2021.07.21.16.11.10_veh-12_01948_02094 + - 2021.07.21.16.11.10_veh-12_02118_02861 + - 2021.07.21.16.11.10_veh-12_02882_03206 + - 2021.07.21.16.11.10_veh-12_03217_03279 + - 2021.07.21.16.11.10_veh-12_03300_03645 + - 2021.07.21.16.11.10_veh-12_03667_04166 + - 2021.07.21.16.11.10_veh-12_04239_04714 + - 2021.07.21.16.11.10_veh-12_04725_05100 + - 2021.07.21.16.11.10_veh-12_05178_05323 + - 2021.07.21.16.11.10_veh-12_05334_05452 + - 2021.07.21.16.11.10_veh-12_05473_05694 + - 2021.07.21.16.11.10_veh-12_05705_06293 + - 2021.07.21.16.11.10_veh-12_06315_06469 + - 2021.07.21.16.11.10_veh-12_06491_06865 + - 2021.07.21.16.13.30_veh-47_00016_01155 + - 2021.07.21.16.13.30_veh-47_01176_01690 + - 2021.07.21.16.13.30_veh-47_01712_03045 + - 2021.07.21.16.13.30_veh-47_03078_03143 + - 2021.07.21.16.13.30_veh-47_03155_04859 + - 2021.07.21.16.13.30_veh-47_04870_05184 + - 2021.07.21.16.13.30_veh-47_05195_06137 + - 2021.07.21.16.18.22_veh-38_00016_00589 + - 2021.07.21.16.18.22_veh-38_00697_01586 + - 2021.07.21.16.18.22_veh-38_01607_02015 + - 2021.07.21.16.18.22_veh-38_02052_02997 + - 2021.07.21.16.18.22_veh-38_03018_03826 + - 2021.07.21.16.18.22_veh-38_03890_04322 + - 2021.07.21.16.18.22_veh-38_04333_04441 + - 2021.07.21.16.18.22_veh-38_04452_05015 + - 2021.07.21.16.26.10_veh-26_00015_00202 + - 2021.07.21.16.26.10_veh-26_00213_00628 + - 2021.07.21.16.26.10_veh-26_00649_02602 + - 2021.07.21.16.26.10_veh-26_02670_04272 + - 2021.07.21.17.06.47_veh-17_00016_00403 + - 2021.07.21.17.06.47_veh-17_00424_01393 + - 2021.07.21.17.06.47_veh-17_01415_02944 + - 2021.07.21.17.06.47_veh-17_02968_03884 + - 2021.07.21.18.05.12_veh-26_00015_00187 + - 2021.07.21.18.05.12_veh-26_00198_03503 + - 2021.07.21.18.05.12_veh-26_03532_04334 + - 2021.07.21.18.05.12_veh-26_04345_04420 + - 2021.07.21.18.06.16_veh-38_00015_00361 + - 2021.07.21.18.06.16_veh-38_00382_00721 + - 2021.07.21.18.06.16_veh-38_00743_00984 + - 2021.07.21.18.06.16_veh-38_00995_01221 + - 2021.07.21.18.06.16_veh-38_01243_01427 + - 2021.07.21.18.06.16_veh-38_01438_03998 + - 2021.07.21.18.06.16_veh-38_04009_04748 + - 2021.07.21.18.30.29_veh-47_00014_00456 + - 2021.07.21.18.30.29_veh-47_00523_00683 + - 2021.07.21.18.30.29_veh-47_00694_01315 + - 2021.07.21.18.30.29_veh-47_01372_02018 + - 2021.07.21.18.30.29_veh-47_02029_02110 + - 2021.07.21.18.30.29_veh-47_02121_02323 + - 2021.07.21.18.30.29_veh-47_02334_02909 + - 2021.07.21.18.52.17_veh-17_00015_00377 + - 2021.07.21.18.52.17_veh-17_00388_00659 + - 2021.07.21.18.52.17_veh-17_00671_02761 + - 2021.07.21.18.52.17_veh-17_02786_03536 + - 2021.07.21.21.06.04_veh-37_00016_00798 + - 2021.07.21.21.06.04_veh-37_00819_02440 + - 2021.07.21.21.06.04_veh-37_02451_03425 + - 2021.07.21.21.06.04_veh-37_03436_05688 + - 2021.07.21.21.27.19_veh-47_00026_02248 + - 2021.07.21.21.27.19_veh-47_02259_02545 + - 2021.07.21.21.27.19_veh-47_02581_04848 + - 2021.07.21.22.25.57_veh-35_00016_00398 + - 2021.07.21.22.25.57_veh-35_00409_03657 + - 2021.07.21.22.59.47_veh-38_00031_00349 + - 2021.07.21.22.59.47_veh-38_00372_00800 + - 2021.07.21.22.59.47_veh-38_00811_01640 + - 2021.07.21.22.59.47_veh-38_01651_02395 + - 2021.07.21.22.59.47_veh-38_02406_03106 + - 2021.07.21.22.59.47_veh-38_03166_03761 + - 2021.07.21.22.59.47_veh-38_03772_04757 + - 2021.07.21.23.58.34_veh-26_01004_01085 + - 2021.07.21.23.58.34_veh-26_04982_05062 + - 2021.07.21.23.58.34_veh-26_05583_05667 + - 2021.07.22.00.15.38_veh-37_00015_00245 + - 2021.07.22.00.15.38_veh-37_00267_00877 + - 2021.07.22.00.15.38_veh-37_00903_05858 + - 2021.07.22.00.15.38_veh-37_05881_07016 + - 2021.07.22.00.22.57_veh-47_00016_00242 + - 2021.07.22.00.22.57_veh-47_00263_01280 + - 2021.07.22.00.22.57_veh-47_01291_01680 + - 2021.07.22.00.22.57_veh-47_01691_03445 + - 2021.07.22.00.22.57_veh-47_03467_05195 + - 2021.07.22.00.22.57_veh-47_05206_05498 + - 2021.07.22.00.26.04_veh-38_00021_00233 + - 2021.07.22.00.26.04_veh-38_00244_00313 + - 2021.07.22.00.26.04_veh-38_00324_00630 + - 2021.07.22.00.26.04_veh-38_00641_01007 + - 2021.07.22.00.26.04_veh-38_01029_01273 + - 2021.07.22.00.26.04_veh-38_01295_01371 + - 2021.07.22.00.26.04_veh-38_01393_02311 + - 2021.07.22.00.26.04_veh-38_02383_02661 + - 2021.07.22.00.26.04_veh-38_02683_04368 + - 2021.07.22.00.26.04_veh-38_04379_05417 + - 2021.07.22.01.42.44_veh-12_00016_00274 + - 2021.07.22.01.42.44_veh-12_00295_00511 + - 2021.07.22.01.42.44_veh-12_00537_03284 + - 2021.07.22.01.42.44_veh-12_03306_03483 + - 2021.07.22.01.42.44_veh-12_03494_03635 + - 2021.07.22.01.42.44_veh-12_03657_04835 + - 2021.07.22.01.42.44_veh-12_04846_05296 + - 2021.07.22.01.42.44_veh-12_05318_06079 + - 2021.07.22.02.19.53_veh-26_00952_01034 + - 2021.07.22.02.19.53_veh-26_01084_01387 + - 2021.07.22.02.19.53_veh-26_01409_01686 + - 2021.07.22.02.25.58_veh-47_00382_03685 + - 2021.07.22.16.04.21_veh-35_00016_00535 + - 2021.07.22.16.04.21_veh-35_00546_00639 + - 2021.07.22.16.04.21_veh-35_00686_02515 + - 2021.07.22.16.04.21_veh-35_02539_05454 + - 2021.07.22.16.18.55_veh-12_00148_00438 + - 2021.07.22.16.18.55_veh-12_00461_00527 + - 2021.07.22.16.18.55_veh-12_00538_00913 + - 2021.07.22.16.18.55_veh-12_00924_01042 + - 2021.07.22.16.18.55_veh-12_01053_01734 + - 2021.07.22.16.18.55_veh-12_01755_01894 + - 2021.07.22.16.18.55_veh-12_01951_02457 + - 2021.07.22.16.18.55_veh-12_02468_02792 + - 2021.07.22.16.18.55_veh-12_02803_02932 + - 2021.07.22.16.18.55_veh-12_02943_03969 + - 2021.07.22.16.18.55_veh-12_03990_04057 + - 2021.07.22.16.18.55_veh-12_04078_04212 + - 2021.07.22.16.18.55_veh-12_04233_05238 + - 2021.07.22.16.18.55_veh-12_05260_05353 + - 2021.07.22.16.18.55_veh-12_05374_05823 + - 2021.07.22.16.37.00_veh-47_00016_00761 + - 2021.07.22.16.37.00_veh-47_00782_02865 + - 2021.07.22.16.37.00_veh-47_02887_03133 + - 2021.07.22.16.37.00_veh-47_03144_03372 + - 2021.07.22.16.46.00_veh-17_00024_00584 + - 2021.07.22.16.46.00_veh-17_00606_02666 + - 2021.07.22.16.46.00_veh-17_02677_02906 + - 2021.07.22.16.48.26_veh-26_00016_01128 + - 2021.07.22.16.48.26_veh-26_01139_04501 + - 2021.07.22.17.40.23_veh-47_00015_00544 + - 2021.07.22.17.40.23_veh-47_00568_00852 + - 2021.07.22.17.40.23_veh-47_00863_01682 + - 2021.07.22.17.40.23_veh-47_01693_01897 + - 2021.07.22.17.40.23_veh-47_01908_05229 + - 2021.07.22.17.54.22_veh-17_00016_02153 + - 2021.07.22.17.54.22_veh-17_02164_02368 + - 2021.07.22.17.54.22_veh-17_02379_04909 + - 2021.07.22.18.31.29_veh-12_00013_00138 + - 2021.07.22.18.31.29_veh-12_00160_00365 + - 2021.07.22.18.31.29_veh-12_00376_00496 + - 2021.07.22.18.31.29_veh-12_00517_00846 + - 2021.07.22.18.31.29_veh-12_00857_01139 + - 2021.07.22.18.31.29_veh-12_01150_01341 + - 2021.07.22.18.31.29_veh-12_01352_01418 + - 2021.07.22.18.31.29_veh-12_01429_02006 + - 2021.07.22.18.31.29_veh-12_02017_02484 + - 2021.07.22.18.31.29_veh-12_02505_02664 + - 2021.07.22.18.31.29_veh-12_02675_02774 + - 2021.07.22.18.31.29_veh-12_02796_04434 + - 2021.07.22.18.57.03_veh-26_00015_00129 + - 2021.07.22.18.57.03_veh-26_00150_00685 + - 2021.07.22.18.57.03_veh-26_00706_01903 + - 2021.07.22.18.57.03_veh-26_01938_02163 + - 2021.07.22.18.57.03_veh-26_02185_02678 + - 2021.07.22.18.57.03_veh-26_02709_03192 + - 2021.07.22.19.31.55_veh-37_00039_01612 + - 2021.07.22.19.31.55_veh-37_01623_01922 + - 2021.07.22.19.31.55_veh-37_01943_02092 + - 2021.07.22.19.31.55_veh-37_02103_02935 + - 2021.07.22.19.31.55_veh-37_02958_04057 + - 2021.07.22.21.07.31_veh-47_00006_00828 + - 2021.07.22.21.07.31_veh-47_00878_01382 + - 2021.07.22.21.07.31_veh-47_01403_01676 + - 2021.07.22.21.07.31_veh-47_01734_01971 + - 2021.07.22.21.07.31_veh-47_01992_02248 + - 2021.07.22.21.07.31_veh-47_02259_02968 + - 2021.07.22.21.07.31_veh-47_02992_03420 + - 2021.07.22.21.07.31_veh-47_03431_03956 + - 2021.07.22.21.07.31_veh-47_03977_04545 + - 2021.07.22.21.07.31_veh-47_04556_04823 + - 2021.07.22.21.43.45_veh-35_00019_00122 + - 2021.07.22.21.43.45_veh-35_00149_00338 + - 2021.07.22.21.43.45_veh-35_00360_01140 + - 2021.07.22.21.43.45_veh-35_01163_02859 + - 2021.07.22.21.43.45_veh-35_02881_03540 + - 2021.07.23.00.10.00_veh-47_00011_02394 + - 2021.07.23.00.10.00_veh-47_02405_05754 + - 2021.07.23.00.37.06_veh-37_00015_00429 + - 2021.07.23.00.37.06_veh-37_00440_00645 + - 2021.07.23.00.37.06_veh-37_00670_00900 + - 2021.07.23.00.37.06_veh-37_01053_01705 + - 2021.07.23.00.37.06_veh-37_01716_02548 + - 2021.07.23.00.37.06_veh-37_02572_05844 + - 2021.07.23.00.37.06_veh-37_05855_06176 + - 2021.07.23.00.42.15_veh-12_00016_00147 + - 2021.07.23.00.42.15_veh-12_00168_00694 + - 2021.07.23.00.42.15_veh-12_00727_01153 + - 2021.07.23.00.42.15_veh-12_01174_01768 + - 2021.07.23.00.42.15_veh-12_01789_04077 + - 2021.07.23.00.42.43_veh-35_00016_00360 + - 2021.07.23.00.42.43_veh-35_00371_01008 + - 2021.07.23.00.42.43_veh-35_01029_01865 + - 2021.07.23.00.42.43_veh-35_02542_02725 + - 2021.07.23.00.42.43_veh-35_02751_02928 + - 2021.07.23.00.42.43_veh-35_02950_03774 + - 2021.07.23.00.42.43_veh-35_03795_05835 + - 2021.07.23.00.42.43_veh-35_05846_07323 + - 2021.07.23.01.57.53_veh-47_00016_02733 + - 2021.07.23.01.57.53_veh-47_02744_03696 + - 2021.07.23.01.57.53_veh-47_03707_05399 + - 2021.07.23.02.31.44_veh-12_00016_00680 + - 2021.07.23.02.31.44_veh-12_00702_00856 + - 2021.07.23.02.31.44_veh-12_00878_01145 + - 2021.07.23.02.31.44_veh-12_01167_02559 + - 2021.07.23.02.50.50_veh-26_00016_00835 + - 2021.07.23.02.50.50_veh-26_00857_02082 + - 2021.07.23.15.54.28_veh-35_00005_00335 + - 2021.07.23.15.54.28_veh-35_00356_00519 + - 2021.07.23.15.54.28_veh-35_00566_00776 + - 2021.07.23.15.54.28_veh-35_00787_01742 + - 2021.07.23.15.54.28_veh-35_01764_02705 + - 2021.07.23.15.54.28_veh-35_02716_04310 + - 2021.07.23.15.54.28_veh-35_04331_06076 + - 2021.07.23.15.59.40_veh-47_00015_00116 + - 2021.07.23.15.59.40_veh-47_00184_00896 + - 2021.07.23.15.59.40_veh-47_00907_02711 + - 2021.07.23.15.59.40_veh-47_02722_03152 + - 2021.07.23.15.59.40_veh-47_03189_04337 + - 2021.07.23.16.08.51_veh-26_00616_00680 + - 2021.07.23.16.08.51_veh-26_00749_00819 + - 2021.07.23.16.08.51_veh-26_02208_02271 + - 2021.07.23.16.08.51_veh-26_02434_02506 + - 2021.07.23.16.08.51_veh-26_02836_02899 + - 2021.07.23.16.08.51_veh-26_02971_03035 + - 2021.07.23.16.08.51_veh-26_03052_03136 + - 2021.07.23.16.08.51_veh-26_03267_03360 + - 2021.07.23.16.08.51_veh-26_03384_03447 + - 2021.07.23.16.08.51_veh-26_03573_03681 + - 2021.07.23.16.08.51_veh-26_03746_03945 + - 2021.07.23.16.08.51_veh-26_04012_04183 + - 2021.07.23.16.09.49_veh-37_00016_00412 + - 2021.07.23.16.09.49_veh-37_00434_02332 + - 2021.07.23.16.32.39_veh-17_00016_00934 + - 2021.07.23.16.32.39_veh-17_00960_01437 + - 2021.07.23.16.32.39_veh-17_01485_02337 + - 2021.07.23.16.32.39_veh-17_02362_06733 + - 2021.07.23.16.32.39_veh-17_06754_07524 + - 2021.07.23.16.54.45_veh-37_00019_00397 + - 2021.07.23.16.54.45_veh-37_00408_01005 + - 2021.07.23.16.54.45_veh-37_01026_01707 + - 2021.07.23.16.58.15_veh-12_00074_00454 + - 2021.07.23.16.58.15_veh-12_00465_00714 + - 2021.07.23.16.58.15_veh-12_00805_01080 + - 2021.07.23.16.58.15_veh-12_01101_01256 + - 2021.07.23.16.58.15_veh-12_01277_02181 + - 2021.07.23.16.58.15_veh-12_02202_04053 + - 2021.07.23.16.58.15_veh-12_04100_04563 + - 2021.07.23.16.58.15_veh-12_04584_04738 + - 2021.07.23.16.58.15_veh-12_04759_05274 + - 2021.07.23.17.30.53_veh-47_00016_00489 + - 2021.07.23.17.30.53_veh-47_00500_00628 + - 2021.07.23.17.30.53_veh-47_00639_00903 + - 2021.07.23.17.30.53_veh-47_00914_02978 + - 2021.07.23.17.30.53_veh-47_02999_04804 + - 2021.07.23.17.51.38_veh-26_00016_00832 + - 2021.07.23.17.51.38_veh-26_00854_01027 + - 2021.07.23.17.51.38_veh-26_01052_01195 + - 2021.07.23.17.51.38_veh-26_01206_03107 + - 2021.07.23.17.54.34_veh-35_00016_00311 + - 2021.07.23.17.54.34_veh-35_00399_00925 + - 2021.07.23.17.54.34_veh-35_00947_01561 + - 2021.07.23.17.54.34_veh-35_01589_02046 + - 2021.07.23.17.54.34_veh-35_02068_02758 + - 2021.07.23.17.54.34_veh-35_02785_03788 + - 2021.07.23.17.54.34_veh-35_03811_04215 + - 2021.07.23.17.54.34_veh-35_04236_04410 + - 2021.07.23.17.54.34_veh-35_04421_04833 + - 2021.07.23.17.54.34_veh-35_04855_05204 + - 2021.07.23.17.54.34_veh-35_05215_05397 + - 2021.07.23.18.11.29_veh-37_00005_00499 + - 2021.07.23.18.11.29_veh-37_00522_00614 + - 2021.07.23.18.11.29_veh-37_00625_01669 + - 2021.07.23.18.11.29_veh-37_01691_03419 + - 2021.07.23.18.11.29_veh-37_03467_03968 + - 2021.07.23.18.59.02_veh-12_00016_01879 + - 2021.07.23.18.59.02_veh-12_01890_03984 + - 2021.07.23.20.32.07_veh-26_00016_00627 + - 2021.07.23.20.32.07_veh-26_00658_00864 + - 2021.07.23.20.32.07_veh-26_00875_02077 + - 2021.07.23.20.32.07_veh-26_02098_03853 + - 2021.07.23.20.55.34_veh-37_00040_01188 + - 2021.07.23.20.55.34_veh-37_01210_03362 + - 2021.07.23.20.55.34_veh-37_03437_05891 + - 2021.07.23.20.55.34_veh-37_05921_07585 + - 2021.07.23.21.07.18_veh-47_00016_00597 + - 2021.07.23.21.07.18_veh-47_00608_00700 + - 2021.07.23.21.07.18_veh-47_00721_00947 + - 2021.07.23.21.07.18_veh-47_00968_01447 + - 2021.07.23.21.07.18_veh-47_01458_02100 + - 2021.07.23.21.07.18_veh-47_02121_03205 + - 2021.07.23.21.07.18_veh-47_03216_04638 + - 2021.07.23.21.07.18_veh-47_04649_05361 + - 2021.07.23.22.08.17_veh-26_00087_00149 + - 2021.07.23.22.08.17_veh-26_00175_01522 + - 2021.07.23.22.08.40_veh-12_00016_00361 + - 2021.07.23.22.08.40_veh-12_00405_01212 + - 2021.07.23.22.08.40_veh-12_01223_02192 + - 2021.07.23.23.47.09_veh-35_00016_00752 + - 2021.07.23.23.47.09_veh-35_00763_01527 + - 2021.07.23.23.47.09_veh-35_01604_03034 + - 2021.07.23.23.47.09_veh-35_03056_04094 + - 2021.07.23.23.47.09_veh-35_04117_05594 + - 2021.07.26.00.50.21_veh-47_00021_00999 + - 2021.07.26.00.50.21_veh-47_01020_01993 + - 2021.07.26.00.50.21_veh-47_02030_03739 + - 2021.07.26.00.50.21_veh-47_03761_04157 + - 2021.07.26.00.50.21_veh-47_04168_05238 + - 2021.07.26.00.50.21_veh-47_05263_07077 + - 2021.07.26.01.19.38_veh-26_00015_00088 + - 2021.07.26.01.19.38_veh-26_00110_02156 + - 2021.07.26.01.19.38_veh-26_02167_04333 + - 2021.07.26.01.19.38_veh-26_04361_04895 + - 2021.07.26.01.22.11_veh-35_00431_01411 + - 2021.07.26.01.22.11_veh-35_01432_01839 + - 2021.07.26.01.22.11_veh-35_01863_02425 + - 2021.07.26.01.22.11_veh-35_02436_02834 + - 2021.07.26.01.22.11_veh-35_02857_03234 + - 2021.07.26.01.22.11_veh-35_03256_03536 + - 2021.07.26.01.43.29_veh-12_00016_00728 + - 2021.07.26.01.43.29_veh-12_00749_01440 + - 2021.07.26.01.43.29_veh-12_01464_02163 + - 2021.07.26.01.43.29_veh-12_02174_02603 + - 2021.07.26.01.43.29_veh-12_02624_02859 + - 2021.07.26.01.43.29_veh-12_02870_03748 + - 2021.07.26.01.54.30_veh-17_00096_00373 + - 2021.07.26.01.54.30_veh-17_00384_00813 + - 2021.07.26.01.54.30_veh-17_00824_01225 + - 2021.07.26.01.54.30_veh-17_01236_01380 + - 2021.07.26.01.54.30_veh-17_01391_03030 + - 2021.07.26.01.54.30_veh-17_03079_03435 + - 2021.07.26.01.54.30_veh-17_03446_03510 + - 2021.08.17.13.10.50_veh-08_00122_00295 + - 2021.08.17.13.10.50_veh-08_00313_00564 + - 2021.08.17.13.10.50_veh-08_00726_01027 + - 2021.08.17.13.10.50_veh-08_01060_01340 + - 2021.08.17.13.15.12_veh-45_00168_00302 + - 2021.08.17.13.15.12_veh-45_00324_00489 + - 2021.08.17.13.15.12_veh-45_00565_00643 + - 2021.08.17.13.15.12_veh-45_00691_00794 + - 2021.08.17.13.15.12_veh-45_00819_00884 + - 2021.08.17.13.15.12_veh-45_00925_00987 + - 2021.08.17.13.15.12_veh-45_01049_01467 + - 2021.08.17.13.15.12_veh-45_01517_01668 + - 2021.08.17.13.15.12_veh-45_01679_01816 + - 2021.08.17.13.15.12_veh-45_02025_02103 + - 2021.08.17.13.15.12_veh-45_02124_02293 + - 2021.08.17.13.15.12_veh-45_02304_02650 + - 2021.08.17.14.32.33_veh-08_00016_00354 + - 2021.08.17.14.32.33_veh-08_00390_00468 + - 2021.08.17.14.32.33_veh-08_00521_01051 + - 2021.08.17.14.32.33_veh-08_01072_01231 + - 2021.08.17.14.32.33_veh-08_01262_01528 + - 2021.08.17.14.32.33_veh-08_01576_01919 + - 2021.08.17.14.45.12_veh-42_00092_00301 + - 2021.08.17.14.45.12_veh-42_00312_00531 + - 2021.08.17.14.45.12_veh-42_00542_00803 + - 2021.08.17.14.45.12_veh-42_00831_01079 + - 2021.08.17.14.45.12_veh-42_01119_01535 + - 2021.08.17.14.45.12_veh-42_01562_01754 + - 2021.08.17.14.45.12_veh-42_01866_01999 + - 2021.08.17.15.02.08_veh-45_00167_00480 + - 2021.08.17.15.02.08_veh-45_00505_00606 + - 2021.08.17.15.02.08_veh-45_00723_00823 + - 2021.08.17.15.02.08_veh-45_00860_01324 + - 2021.08.17.15.02.08_veh-45_01348_01731 + - 2021.08.17.15.02.08_veh-45_01756_01966 + - 2021.08.17.15.02.08_veh-45_02003_02086 + - 2021.08.17.15.02.08_veh-45_02111_02303 + - 2021.08.17.15.02.08_veh-45_02452_02521 + - 2021.08.17.16.48.45_veh-43_00114_00415 + - 2021.08.17.16.48.45_veh-43_00451_00871 + - 2021.08.17.16.48.45_veh-43_00936_01035 + - 2021.08.17.16.48.45_veh-43_01060_01405 + - 2021.08.17.16.48.45_veh-43_01439_01665 + - 2021.08.17.16.48.45_veh-43_01676_01764 + - 2021.08.17.16.48.45_veh-43_01837_02038 + - 2021.08.17.16.48.45_veh-43_02070_02652 + - 2021.08.17.16.48.45_veh-43_02693_03062 + - 2021.08.17.16.48.45_veh-43_03137_03245 + - 2021.08.17.16.48.45_veh-43_03268_03352 + - 2021.08.17.16.48.45_veh-43_03384_03788 + - 2021.08.17.16.57.11_veh-08_00206_00331 + - 2021.08.17.16.57.11_veh-08_00354_01167 + - 2021.08.17.16.57.11_veh-08_01200_01636 + - 2021.08.17.17.17.01_veh-45_00123_00191 + - 2021.08.17.17.17.01_veh-45_00207_00594 + - 2021.08.17.17.17.01_veh-45_00762_01166 + - 2021.08.17.17.17.01_veh-45_01207_01417 + - 2021.08.17.17.17.01_veh-45_01443_01678 + - 2021.08.17.17.17.01_veh-45_01796_02069 + - 2021.08.17.17.17.01_veh-45_02098_02251 + - 2021.08.17.17.17.01_veh-45_02314_02798 + - 2021.08.17.17.55.18_veh-43_00016_00083 + - 2021.08.17.17.55.18_veh-43_00122_00325 + - 2021.08.17.17.55.18_veh-43_00358_00673 + - 2021.08.17.17.55.18_veh-43_00802_01030 + - 2021.08.17.17.55.18_veh-43_01240_01704 + - 2021.08.17.18.11.12_veh-08_00083_00200 + - 2021.08.17.18.11.12_veh-08_00234_00611 + - 2021.08.17.18.11.12_veh-08_00629_01599 + - 2021.08.17.18.11.12_veh-08_01622_01709 + - 2021.08.17.18.13.38_veh-45_00016_00127 + - 2021.08.17.18.13.38_veh-45_00151_00387 + - 2021.08.17.18.13.38_veh-45_00410_00618 + - 2021.08.17.18.13.38_veh-45_00641_00881 + - 2021.08.17.18.13.38_veh-45_00946_01854 + - 2021.08.17.18.43.12_veh-43_00125_00805 + - 2021.08.17.18.43.12_veh-43_01023_01358 + - 2021.08.17.18.43.12_veh-43_01390_01589 + - 2021.08.17.18.43.12_veh-43_01611_01812 + - 2021.08.17.18.43.12_veh-43_01906_02722 + - 2021.08.17.18.43.12_veh-43_02784_02851 + - 2021.08.17.18.43.12_veh-43_02889_03258 + - 2021.08.17.18.43.12_veh-43_03294_03490 + - 2021.08.17.18.44.32_veh-08_00016_00564 + - 2021.08.17.18.44.32_veh-08_00586_00848 + - 2021.08.17.18.44.32_veh-08_00873_01540 + - 2021.08.17.18.54.02_veh-45_00016_00304 + - 2021.08.17.18.54.02_veh-45_00511_00579 + - 2021.08.17.18.54.02_veh-45_00665_01065 + - 2021.08.17.18.54.02_veh-45_01103_01238 + - 2021.08.17.18.54.02_veh-45_01261_02086 + - 2021.08.17.18.54.02_veh-45_02105_02189 + - 2021.08.17.18.54.02_veh-45_02202_02416 + - 2021.08.17.19.18.39_veh-08_00118_00178 + - 2021.08.17.19.18.39_veh-08_00208_00380 + - 2021.08.17.19.18.39_veh-08_00407_00595 + - 2021.08.17.19.18.39_veh-08_00696_00823 + - 2021.08.18.06.04.33_veh-51_00016_00170 + - 2021.08.18.06.04.33_veh-51_00183_00300 + - 2021.08.18.06.04.33_veh-51_00311_00373 + - 2021.08.18.06.04.33_veh-51_00497_00566 + - 2021.08.18.06.04.33_veh-51_00623_00696 + - 2021.08.18.06.04.33_veh-51_00754_00869 + - 2021.08.18.06.04.33_veh-51_00934_01016 + - 2021.08.18.06.04.33_veh-51_01191_01270 + - 2021.08.18.06.04.33_veh-51_01508_01674 + - 2021.08.18.06.04.33_veh-51_01690_01842 + - 2021.08.18.06.42.12_veh-51_00014_00097 + - 2021.08.18.06.42.12_veh-51_00135_00205 + - 2021.08.18.06.42.12_veh-51_00273_00932 + - 2021.08.18.06.42.12_veh-51_01150_01229 + - 2021.08.18.06.42.12_veh-51_01284_01348 + - 2021.08.18.06.42.12_veh-51_01435_01500 + - 2021.08.18.06.42.12_veh-51_01511_01825 + - 2021.08.18.08.10.40_veh-51_00069_00246 + - 2021.08.18.08.10.40_veh-51_00267_00402 + - 2021.08.18.08.10.40_veh-51_00485_00708 + - 2021.08.18.08.10.40_veh-51_00750_01165 + - 2021.08.18.08.10.40_veh-51_01340_01701 + - 2021.08.18.08.10.40_veh-51_01725_01828 + - 2021.08.18.18.32.06_veh-28_00049_00111 + - 2021.08.18.18.32.06_veh-28_00173_00332 + - 2021.08.18.18.32.06_veh-28_00419_00633 + - 2021.08.18.18.32.06_veh-28_00838_00949 + - 2021.08.18.18.32.06_veh-28_00981_01223 + - 2021.08.18.18.32.06_veh-28_01247_01356 + - 2021.08.18.18.32.06_veh-28_01425_01518 + - 2021.08.18.18.32.06_veh-28_01529_01718 + - 2021.08.18.18.32.06_veh-28_01784_01889 + - 2021.08.18.18.32.06_veh-28_01927_02029 + - 2021.08.18.18.46.28_veh-40_00016_00089 + - 2021.08.18.18.46.28_veh-40_00251_00328 + - 2021.08.18.18.46.28_veh-40_00340_00504 + - 2021.08.18.18.46.28_veh-40_00737_00852 + - 2021.08.18.19.08.11_veh-40_00016_00079 + - 2021.08.18.19.08.11_veh-40_00103_00265 + - 2021.08.18.19.08.11_veh-40_00329_00432 + - 2021.08.18.19.08.11_veh-40_00443_00685 + - 2021.08.18.19.08.11_veh-40_00723_00784 + - 2021.08.18.19.08.11_veh-40_00857_00929 + - 2021.08.18.19.15.03_veh-28_00016_00076 + - 2021.08.18.19.15.03_veh-28_00136_00231 + - 2021.08.18.19.15.03_veh-28_00349_00579 + - 2021.08.18.19.15.03_veh-28_00673_00747 + - 2021.08.18.19.15.03_veh-28_00791_00881 + - 2021.08.18.19.15.03_veh-28_00896_00997 + - 2021.08.18.19.15.03_veh-28_01035_01151 + - 2021.08.18.19.15.03_veh-28_01228_01350 + - 2021.08.18.19.15.03_veh-28_01471_01546 + - 2021.08.18.19.15.03_veh-28_01585_01683 + - 2021.08.19.14.06.23_veh-45_00353_00623 + - 2021.08.19.14.06.23_veh-45_00656_00769 + - 2021.08.19.14.06.23_veh-45_00878_01453 + - 2021.08.19.14.06.23_veh-45_01563_01875 + - 2021.08.19.14.06.23_veh-45_01977_02108 + - 2021.08.19.14.06.23_veh-45_02208_02388 + - 2021.08.19.14.06.23_veh-45_02467_02637 + - 2021.08.19.14.06.23_veh-45_02707_03078 + - 2021.08.19.14.17.23_veh-28_00021_00114 + - 2021.08.19.14.17.23_veh-28_00138_00203 + - 2021.08.19.14.17.23_veh-28_00337_00416 + - 2021.08.19.14.17.23_veh-28_00428_00538 + - 2021.08.19.14.17.23_veh-28_00587_00711 + - 2021.08.19.14.17.23_veh-28_00830_01065 + - 2021.08.19.14.17.23_veh-28_01295_01421 + - 2021.08.19.14.17.23_veh-28_01488_01554 + - 2021.08.19.14.17.23_veh-28_01650_01822 + - 2021.08.19.15.03.05_veh-45_00037_00124 + - 2021.08.19.15.03.05_veh-45_00216_00500 + - 2021.08.19.15.03.05_veh-45_00533_00692 + - 2021.08.19.15.03.05_veh-45_00752_00982 + - 2021.08.19.15.03.05_veh-45_01098_01311 + - 2021.08.19.15.03.05_veh-45_01383_01593 + - 2021.08.19.15.03.05_veh-45_01660_01736 + - 2021.08.19.15.03.05_veh-45_01749_02365 + - 2021.08.19.17.06.41_veh-08_00058_00421 + - 2021.08.19.17.06.41_veh-08_00443_00624 + - 2021.08.19.17.06.41_veh-08_00708_00885 + - 2021.08.19.17.06.41_veh-08_01217_01483 + - 2021.08.19.17.06.41_veh-08_01509_01662 + - 2021.08.19.17.14.40_veh-45_00298_00804 + - 2021.08.19.17.14.40_veh-45_00860_01021 + - 2021.08.19.17.14.40_veh-45_01146_01379 + - 2021.08.19.17.14.40_veh-45_01390_01535 + - 2021.08.19.17.14.40_veh-45_01590_01660 + - 2021.08.19.17.14.40_veh-45_01683_02036 + - 2021.08.19.17.14.40_veh-45_02179_02379 + - 2021.08.19.17.14.40_veh-45_02490_02553 + - 2021.08.19.17.14.40_veh-45_02585_02856 + - 2021.08.19.17.14.40_veh-45_02916_03059 + - 2021.08.19.17.42.11_veh-08_00020_00206 + - 2021.08.19.17.42.11_veh-08_00324_00407 + - 2021.08.19.17.42.11_veh-08_00509_00701 + - 2021.08.19.17.42.11_veh-08_00726_01062 + - 2021.08.19.17.42.11_veh-08_01092_01496 + - 2021.08.19.17.42.11_veh-08_01521_01775 + - 2021.08.19.18.08.28_veh-45_00056_00141 + - 2021.08.19.18.08.28_veh-45_00342_00404 + - 2021.08.19.18.08.28_veh-45_00419_00852 + - 2021.08.19.18.08.28_veh-45_01089_01386 + - 2021.08.19.18.08.28_veh-45_01456_02210 + - 2021.08.19.18.08.28_veh-45_02541_02749 + - 2021.08.19.18.08.28_veh-45_02903_03030 + - 2021.08.19.19.03.27_veh-45_00214_00561 + - 2021.08.19.19.03.27_veh-45_00584_00788 + - 2021.08.19.19.03.27_veh-45_00912_01425 + - 2021.08.19.19.03.27_veh-45_01734_02055 + - 2021.08.19.19.03.27_veh-45_02080_02443 + - 2021.08.19.19.03.27_veh-45_02464_02752 + - 2021.08.19.19.22.25_veh-08_00016_00108 + - 2021.08.19.19.22.25_veh-08_00186_00866 + - 2021.08.19.19.22.25_veh-08_00941_01172 + - 2021.08.19.19.22.25_veh-08_01427_01614 + - 2021.08.19.19.22.25_veh-08_01633_01801 + - 2021.08.19.19.22.25_veh-08_01918_01980 + - 2021.08.20.12.28.52_veh-42_00290_00447 + - 2021.08.20.12.28.52_veh-42_00458_00698 + - 2021.08.20.12.28.52_veh-42_00730_00891 + - 2021.08.20.12.28.52_veh-42_00902_01153 + - 2021.08.20.12.28.52_veh-42_01164_01236 + - 2021.08.20.12.28.52_veh-42_01247_01550 + - 2021.08.20.12.28.52_veh-42_01561_01693 + - 2021.08.20.13.00.37_veh-08_00042_00208 + - 2021.08.20.13.00.37_veh-08_00230_00585 + - 2021.08.20.13.00.37_veh-08_00607_01068 + - 2021.08.20.13.00.37_veh-08_01079_01449 + - 2021.08.20.13.00.37_veh-08_01475_01596 + - 2021.08.20.13.00.37_veh-08_01632_01702 + - 2021.08.20.13.00.37_veh-08_01737_02048 + - 2021.08.20.13.00.37_veh-08_02071_02182 + - 2021.08.20.13.00.37_veh-08_02201_02303 + - 2021.08.20.13.00.37_veh-08_02328_02673 + - 2021.08.20.13.00.37_veh-08_02898_03012 + - 2021.08.20.13.02.56_veh-42_00025_00095 + - 2021.08.20.13.02.56_veh-42_00247_00349 + - 2021.08.20.13.02.56_veh-42_00450_00541 + - 2021.08.20.13.02.56_veh-42_00670_00861 + - 2021.08.20.13.02.56_veh-42_00944_01048 + - 2021.08.20.13.02.56_veh-42_01059_01186 + - 2021.08.20.13.02.56_veh-42_01204_01440 + - 2021.08.20.13.02.56_veh-42_01642_01706 + - 2021.08.20.13.02.56_veh-42_01717_01787 + - 2021.08.20.13.34.11_veh-45_00132_00257 + - 2021.08.20.13.34.11_veh-45_00280_00652 + - 2021.08.20.13.34.11_veh-45_00805_01087 + - 2021.08.20.13.34.11_veh-45_01098_01161 + - 2021.08.20.13.34.11_veh-45_01652_01717 + - 2021.08.20.13.40.56_veh-28_00173_00328 + - 2021.08.20.13.40.56_veh-28_00351_00416 + - 2021.08.20.13.40.56_veh-28_00432_00507 + - 2021.08.20.13.40.56_veh-28_00607_00716 + - 2021.08.20.13.55.47_veh-08_00219_00531 + - 2021.08.20.13.55.47_veh-08_00599_01086 + - 2021.08.20.13.55.47_veh-08_01097_01218 + - 2021.08.20.13.55.47_veh-08_01236_01299 + - 2021.08.20.13.55.47_veh-08_01327_02066 + - 2021.08.20.13.55.47_veh-08_02119_02235 + - 2021.08.20.13.55.47_veh-08_02311_02831 + - 2021.08.20.13.59.49_veh-28_00062_00135 + - 2021.08.20.13.59.49_veh-28_00172_00240 + - 2021.08.20.13.59.49_veh-28_00378_00456 + - 2021.08.20.13.59.49_veh-28_00570_00835 + - 2021.08.20.13.59.49_veh-28_00858_00933 + - 2021.08.20.13.59.49_veh-28_00956_01631 + - 2021.08.20.14.28.03_veh-45_00016_00087 + - 2021.08.20.14.28.03_veh-45_00239_00641 + - 2021.08.20.14.28.03_veh-45_00686_00863 + - 2021.08.20.14.28.03_veh-45_01060_01883 + - 2021.08.20.14.28.03_veh-45_01994_02130 + - 2021.08.20.14.28.03_veh-45_02163_02317 + - 2021.08.20.14.28.03_veh-45_02328_02743 + - 2021.08.20.14.28.03_veh-45_02828_03042 + - 2021.08.20.14.28.03_veh-45_03053_03141 + - 2021.08.20.14.28.03_veh-45_03203_03263 + - 2021.08.20.14.28.03_veh-45_03303_03404 + - 2021.08.20.14.45.02_veh-28_00023_00132 + - 2021.08.20.14.45.02_veh-28_00278_00472 + - 2021.08.20.14.45.02_veh-28_00550_00617 + - 2021.08.20.14.45.02_veh-28_00629_00829 + - 2021.08.20.14.45.02_veh-28_00849_00982 + - 2021.08.20.16.40.09_veh-45_00168_00513 + - 2021.08.20.16.40.09_veh-45_00565_00646 + - 2021.08.20.16.40.09_veh-45_00670_00796 + - 2021.08.20.16.40.09_veh-45_00984_01075 + - 2021.08.20.16.40.09_veh-45_01263_01423 + - 2021.08.20.16.40.09_veh-45_01463_01693 + - 2021.08.20.16.40.09_veh-45_01765_02019 + - 2021.08.20.16.40.09_veh-45_02114_02226 + - 2021.08.20.16.40.09_veh-45_02376_02493 + - 2021.08.20.16.40.09_veh-45_02662_02781 + - 2021.08.20.16.40.09_veh-45_02957_03034 + - 2021.08.20.16.54.30_veh-08_00084_00217 + - 2021.08.20.16.54.30_veh-08_00228_00289 + - 2021.08.20.16.54.30_veh-08_00300_00392 + - 2021.08.20.16.54.30_veh-08_00411_00476 + - 2021.08.20.16.54.30_veh-08_00500_00814 + - 2021.08.20.16.54.30_veh-08_00994_01084 + - 2021.08.20.16.54.30_veh-08_01153_01419 + - 2021.08.20.16.54.30_veh-08_01442_01584 + - 2021.08.20.16.54.30_veh-08_01609_02051 + - 2021.08.20.16.54.30_veh-08_02083_02192 + - 2021.08.20.16.54.30_veh-08_02218_02541 + - 2021.08.20.16.54.30_veh-08_02610_02673 + - 2021.08.20.17.52.54_veh-08_00097_00188 + - 2021.08.20.17.52.54_veh-08_00199_00643 + - 2021.08.20.17.52.54_veh-08_00686_00838 + - 2021.08.20.17.52.54_veh-08_00849_00930 + - 2021.08.20.17.52.54_veh-08_00976_01257 + - 2021.08.20.17.52.54_veh-08_01282_01539 + - 2021.08.20.17.52.54_veh-08_01560_01736 + - 2021.08.20.17.52.54_veh-08_01757_02070 + - 2021.08.20.17.52.54_veh-08_02092_02238 + - 2021.08.20.17.52.54_veh-08_02468_02559 + - 2021.08.20.17.52.54_veh-08_02570_02827 + - 2021.08.20.17.54.47_veh-45_00036_00173 + - 2021.08.20.17.54.47_veh-45_00195_00307 + - 2021.08.20.17.54.47_veh-45_00482_00549 + - 2021.08.20.17.54.47_veh-45_00607_00997 + - 2021.08.20.17.54.47_veh-45_01021_01105 + - 2021.08.20.17.54.47_veh-45_01116_01203 + - 2021.08.20.17.54.47_veh-45_01647_01760 + - 2021.08.20.17.54.47_veh-45_01855_02076 + - 2021.08.20.17.54.47_veh-45_02107_02455 + - 2021.08.20.17.54.47_veh-45_02466_02619 + - 2021.08.20.17.54.47_veh-45_02642_02801 + - 2021.08.20.17.54.47_veh-45_02812_02894 + - 2021.08.20.17.54.47_veh-45_03050_03111 + - 2021.08.20.17.54.47_veh-45_03280_03373 + - 2021.08.20.18.15.01_veh-28_00016_00436 + - 2021.08.20.18.15.01_veh-28_00632_00886 + - 2021.08.20.18.15.01_veh-28_00898_01085 + - 2021.08.20.18.15.01_veh-28_01167_01277 + - 2021.08.20.18.15.01_veh-28_01288_01360 + - 2021.08.20.18.15.01_veh-28_01861_01958 + - 2021.08.20.18.16.02_veh-40_00016_00077 + - 2021.08.20.18.16.02_veh-40_00106_00237 + - 2021.08.20.18.16.02_veh-40_00358_00441 + - 2021.08.20.18.16.02_veh-40_00481_00659 + - 2021.08.20.18.16.02_veh-40_00684_00971 + - 2021.08.20.18.16.02_veh-40_00996_01196 + - 2021.08.20.18.16.02_veh-40_01209_01288 + - 2021.08.20.18.44.47_veh-08_00016_00108 + - 2021.08.20.18.44.47_veh-08_00181_00718 + - 2021.08.20.18.44.47_veh-08_00738_01340 + - 2021.08.20.18.44.47_veh-08_01382_01958 + - 2021.08.20.18.44.47_veh-08_01985_02317 + - 2021.08.20.19.10.41_veh-45_00197_00454 + - 2021.08.20.19.10.41_veh-45_00485_00684 + - 2021.08.20.19.10.41_veh-45_00726_00967 + - 2021.08.20.19.10.41_veh-45_01130_01205 + - 2021.08.20.19.10.41_veh-45_01461_01572 + - 2021.08.20.19.10.41_veh-45_01720_02069 + - 2021.08.20.19.10.41_veh-45_02095_02240 + - 2021.08.20.19.10.41_veh-45_02382_02477 + - 2021.08.23.12.33.24_veh-42_00024_00229 + - 2021.08.23.12.33.24_veh-42_00259_00476 + - 2021.08.23.12.33.24_veh-42_00497_00763 + - 2021.08.23.12.33.24_veh-42_00864_01009 + - 2021.08.23.12.33.24_veh-42_01020_01288 + - 2021.08.23.12.33.24_veh-42_01527_01630 + - 2021.08.23.12.33.24_veh-42_01704_01918 + - 2021.08.23.12.33.24_veh-42_01929_02029 + - 2021.08.23.12.33.24_veh-42_02040_02116 + - 2021.08.23.12.33.24_veh-42_02142_02317 + - 2021.08.23.12.37.38_veh-45_00047_00110 + - 2021.08.23.12.37.38_veh-45_00172_00636 + - 2021.08.23.12.37.38_veh-45_00659_00861 + - 2021.08.23.12.37.38_veh-45_00887_01034 + - 2021.08.23.12.37.38_veh-45_01111_01182 + - 2021.08.23.12.37.38_veh-45_01235_01421 + - 2021.08.23.12.37.38_veh-45_01443_01536 + - 2021.08.23.12.37.38_veh-45_01558_01741 + - 2021.08.23.12.37.38_veh-45_01839_01949 + - 2021.08.23.12.37.38_veh-45_01968_02032 + - 2021.08.23.12.37.38_veh-45_02043_02159 + - 2021.08.23.12.37.38_veh-45_02215_02443 + - 2021.08.23.12.37.38_veh-45_02493_02636 + - 2021.08.23.12.37.38_veh-45_02654_02741 + - 2021.08.23.13.17.08_veh-42_00015_00194 + - 2021.08.23.13.17.08_veh-42_00276_00400 + - 2021.08.23.13.17.08_veh-42_00411_00488 + - 2021.08.23.13.17.08_veh-42_00499_00568 + - 2021.08.23.13.17.08_veh-42_00591_00844 + - 2021.08.23.13.17.08_veh-42_00863_00924 + - 2021.08.23.13.17.08_veh-42_00936_01423 + - 2021.08.23.13.17.08_veh-42_01464_01720 + - 2021.08.23.13.17.08_veh-42_01731_01885 + - 2021.08.23.13.17.08_veh-42_01951_02106 + - 2021.08.23.13.17.08_veh-42_02140_02271 + - 2021.08.23.13.17.08_veh-42_02282_02392 + - 2021.08.23.13.17.08_veh-42_02403_02476 + - 2021.08.23.13.26.46_veh-45_00087_00372 + - 2021.08.23.13.26.46_veh-45_00471_00548 + - 2021.08.23.13.26.46_veh-45_00560_01038 + - 2021.08.23.13.26.46_veh-45_01129_01386 + - 2021.08.23.13.26.46_veh-45_01481_02501 + - 2021.08.23.13.26.46_veh-45_02653_02762 + - 2021.08.23.13.28.21_veh-08_00015_00111 + - 2021.08.23.13.28.21_veh-08_00123_00253 + - 2021.08.23.13.28.21_veh-08_00485_00577 + - 2021.08.23.13.28.21_veh-08_00953_01183 + - 2021.08.23.13.28.21_veh-08_01254_01911 + - 2021.08.23.13.28.21_veh-08_01965_02031 + - 2021.08.23.13.28.21_veh-08_02058_02261 + - 2021.08.23.14.02.02_veh-42_00378_00460 + - 2021.08.23.14.02.02_veh-42_00565_00643 + - 2021.08.23.14.02.02_veh-42_00654_00738 + - 2021.08.23.14.02.02_veh-42_00908_00996 + - 2021.08.23.14.02.02_veh-42_01042_01130 + - 2021.08.23.14.02.02_veh-42_01242_01339 + - 2021.08.23.14.02.02_veh-42_01474_01535 + - 2021.08.23.14.02.02_veh-42_01893_01985 + - 2021.08.23.14.02.02_veh-42_02230_02309 + - 2021.08.23.14.27.31_veh-45_00034_00095 + - 2021.08.23.14.27.31_veh-45_00118_00181 + - 2021.08.23.14.27.31_veh-45_00205_00471 + - 2021.08.23.14.27.31_veh-45_00482_00552 + - 2021.08.23.14.27.31_veh-45_00574_00876 + - 2021.08.23.14.27.31_veh-45_00895_01001 + - 2021.08.23.14.27.31_veh-45_01043_01301 + - 2021.08.23.14.27.31_veh-45_01312_01398 + - 2021.08.23.14.27.31_veh-45_01488_02301 + - 2021.08.23.14.27.31_veh-45_02387_02641 + - 2021.08.23.14.27.31_veh-45_02698_02761 + - 2021.08.23.15.14.44_veh-08_00025_00097 + - 2021.08.23.15.14.44_veh-08_00161_00895 + - 2021.08.23.15.14.44_veh-08_00917_01175 + - 2021.08.23.15.14.44_veh-08_01218_01477 + - 2021.08.23.15.14.44_veh-08_01499_01583 + - 2021.08.23.15.14.44_veh-08_01602_01663 + - 2021.08.23.15.14.44_veh-08_01674_01795 + - 2021.08.23.16.32.43_veh-45_00157_00218 + - 2021.08.23.16.32.43_veh-45_00229_00620 + - 2021.08.23.16.32.43_veh-45_00694_00778 + - 2021.08.23.16.32.43_veh-45_00804_00872 + - 2021.08.23.16.32.43_veh-45_00894_00969 + - 2021.08.23.16.32.43_veh-45_01107_01249 + - 2021.08.23.16.32.43_veh-45_01332_01572 + - 2021.08.23.16.32.43_veh-45_01604_01698 + - 2021.08.23.16.32.43_veh-45_01722_01877 + - 2021.08.23.16.32.43_veh-45_01957_02241 + - 2021.08.23.16.32.43_veh-45_02387_02504 + - 2021.08.23.16.51.29_veh-42_00090_00263 + - 2021.08.23.16.51.29_veh-42_00291_01035 + - 2021.08.23.16.51.29_veh-42_01142_01404 + - 2021.08.23.16.51.29_veh-42_01425_01555 + - 2021.08.23.16.51.29_veh-42_01566_01715 + - 2021.08.23.16.51.29_veh-42_01737_02472 + - 2021.08.23.16.53.37_veh-08_00016_00648 + - 2021.08.23.16.53.37_veh-08_00672_00981 + - 2021.08.23.16.53.37_veh-08_01006_01696 + - 2021.08.23.16.53.37_veh-08_01751_01825 + - 2021.08.23.17.05.22_veh-40_00030_00318 + - 2021.08.23.17.05.22_veh-40_00518_00695 + - 2021.08.23.17.05.22_veh-40_00724_00979 + - 2021.08.23.17.05.22_veh-40_00990_01496 + - 2021.08.23.17.05.22_veh-40_01507_01577 + - 2021.08.23.17.20.10_veh-45_00180_00324 + - 2021.08.23.17.20.10_veh-45_00379_00544 + - 2021.08.23.17.20.10_veh-45_00567_00746 + - 2021.08.23.17.20.10_veh-45_00810_01031 + - 2021.08.23.17.20.10_veh-45_01126_01485 + - 2021.08.23.17.20.10_veh-45_01575_01690 + - 2021.08.23.17.20.10_veh-45_01813_01917 + - 2021.08.23.17.20.10_veh-45_02083_02152 + - 2021.08.23.17.20.10_veh-45_02170_02244 + - 2021.08.23.17.20.10_veh-45_02277_02706 + - 2021.08.23.17.20.10_veh-45_02731_02903 + - 2021.08.23.17.33.08_veh-08_00029_00104 + - 2021.08.23.17.33.08_veh-08_00115_00764 + - 2021.08.23.17.33.08_veh-08_00996_01066 + - 2021.08.23.17.33.08_veh-08_01233_01327 + - 2021.08.23.17.33.08_veh-08_01349_01692 + - 2021.08.23.17.33.08_veh-08_01774_01913 + - 2021.08.23.17.33.08_veh-08_01938_02492 + - 2021.08.23.17.33.08_veh-08_02683_02743 + - 2021.08.23.17.33.08_veh-08_03123_03228 + - 2021.08.23.17.36.45_veh-42_00023_01720 + - 2021.08.23.17.36.45_veh-42_01794_02120 + - 2021.08.23.18.02.44_veh-40_00021_00088 + - 2021.08.23.18.02.44_veh-40_00127_00209 + - 2021.08.23.18.02.44_veh-40_00257_00382 + - 2021.08.23.18.02.44_veh-40_00394_00588 + - 2021.08.23.18.02.44_veh-40_00793_00856 + - 2021.08.23.18.02.44_veh-40_00932_01178 + - 2021.08.23.18.02.44_veh-40_01225_01381 + - 2021.08.23.18.02.44_veh-40_01476_01735 + - 2021.08.23.18.02.44_veh-40_01747_01868 + - 2021.08.23.18.07.38_veh-28_00015_00137 + - 2021.08.23.18.07.38_veh-28_00164_00228 + - 2021.08.23.18.07.38_veh-28_00270_00539 + - 2021.08.23.18.07.38_veh-28_00583_00660 + - 2021.08.23.18.07.38_veh-28_00672_00801 + - 2021.08.23.18.07.38_veh-28_00837_00965 + - 2021.08.23.18.07.38_veh-28_00976_01322 + - 2021.08.23.18.07.38_veh-28_01409_01512 + - 2021.08.23.18.16.02_veh-42_00016_00227 + - 2021.08.23.18.16.02_veh-42_00251_01022 + - 2021.08.23.18.16.02_veh-42_01033_01222 + - 2021.08.23.18.16.02_veh-42_01241_01395 + - 2021.08.23.18.16.02_veh-42_01413_01555 + - 2021.08.23.18.16.02_veh-42_01566_01807 + - 2021.08.23.18.22.47_veh-45_00016_00104 + - 2021.08.23.18.22.47_veh-45_00343_00814 + - 2021.08.23.18.22.47_veh-45_00970_01645 + - 2021.08.23.18.22.47_veh-45_01865_01950 + - 2021.08.23.18.22.47_veh-45_02093_02243 + - 2021.08.23.18.22.47_veh-45_02267_02767 + - 2021.08.23.18.38.30_veh-40_00027_00197 + - 2021.08.23.18.38.30_veh-40_00297_00688 + - 2021.08.23.18.38.30_veh-40_00806_00974 + - 2021.08.23.18.38.30_veh-40_00985_01251 + - 2021.08.23.18.38.30_veh-40_01263_01350 + - 2021.08.23.18.38.30_veh-40_01365_01448 + - 2021.08.23.18.38.30_veh-40_01754_01855 + - 2021.08.23.18.41.38_veh-28_00027_00150 + - 2021.08.23.18.41.38_veh-28_00239_00456 + - 2021.08.23.18.41.38_veh-28_00493_00743 + - 2021.08.23.18.41.38_veh-28_00754_00917 + - 2021.08.23.18.41.38_veh-28_00985_01399 + - 2021.08.23.18.41.38_veh-28_01424_01506 + - 2021.08.23.19.08.29_veh-42_00041_00135 + - 2021.08.23.19.08.29_veh-42_00159_00870 + - 2021.08.23.19.08.29_veh-42_00902_01533 + - 2021.08.23.19.08.29_veh-42_01544_01835 + - 2021.08.23.19.08.29_veh-42_01874_02073 + - 2021.08.23.19.12.30_veh-45_00037_01032 + - 2021.08.23.19.12.30_veh-45_01055_01285 + - 2021.08.23.19.12.30_veh-45_01511_01572 + - 2021.08.23.19.12.30_veh-45_01745_01829 + - 2021.08.23.19.12.30_veh-45_01983_02145 + - 2021.08.23.19.12.30_veh-45_02224_02317 + - 2021.08.23.19.12.30_veh-45_02341_02655 + - 2021.08.23.19.12.30_veh-45_02836_03051 + - 2021.08.23.19.22.43_veh-28_00195_00263 + - 2021.08.23.19.22.43_veh-28_00274_00431 + - 2021.08.23.19.22.43_veh-28_00612_00681 + - 2021.08.23.19.22.43_veh-28_00777_01152 + - 2021.08.23.19.22.43_veh-28_01168_01257 + - 2021.08.23.19.22.43_veh-28_01269_01346 + - 2021.08.23.19.22.43_veh-28_01416_01505 + - 2021.08.23.19.22.43_veh-28_01529_01598 + - 2021.08.23.19.22.43_veh-28_01609_01684 + - 2021.08.23.19.22.43_veh-28_01782_01887 + - 2021.08.23.19.33.55_veh-08_00140_00308 + - 2021.08.23.19.33.55_veh-08_00343_00558 + - 2021.08.23.19.33.55_veh-08_00580_01530 + - 2021.08.23.19.33.55_veh-08_01605_01702 + - 2021.08.23.19.33.55_veh-08_01803_01915 + - 2021.08.23.19.33.55_veh-08_01936_02041 + - 2021.08.23.19.33.55_veh-08_02133_02243 + - 2021.08.23.19.47.22_veh-42_00030_00572 + - 2021.08.23.19.47.22_veh-42_00590_01217 + - 2021.08.23.19.47.22_veh-42_01274_01475 + - 2021.08.23.19.47.22_veh-42_01486_01554 + - 2021.08.23.19.47.22_veh-42_01565_01638 + - 2021.08.23.19.47.22_veh-42_01709_01904 + - 2021.08.23.19.47.22_veh-42_02056_02234 + - 2021.08.23.20.15.12_veh-45_00015_00124 + - 2021.08.23.20.15.12_veh-45_00349_00611 + - 2021.08.23.20.15.12_veh-45_00631_00974 + - 2021.08.23.20.15.12_veh-45_01011_01258 + - 2021.08.23.20.15.12_veh-45_01280_01426 + - 2021.08.23.20.15.12_veh-45_01555_01643 + - 2021.08.23.20.15.12_veh-45_01670_01782 + - 2021.08.24.13.25.16_veh-28_00015_00078 + - 2021.08.24.13.25.16_veh-28_00089_00184 + - 2021.08.24.13.25.16_veh-28_00308_00515 + - 2021.08.24.13.25.16_veh-28_00647_00719 + - 2021.08.24.13.25.16_veh-28_00733_00962 + - 2021.08.24.13.25.16_veh-28_01152_01215 + - 2021.08.24.13.25.16_veh-28_01333_01432 + - 2021.08.24.13.25.16_veh-28_01443_01508 + - 2021.08.24.13.25.16_veh-28_01558_01641 + - 2021.08.24.13.25.16_veh-28_01727_01889 + - 2021.08.24.14.40.55_veh-28_00016_00503 + - 2021.08.24.14.40.55_veh-28_00579_00697 + - 2021.08.24.14.40.55_veh-28_00735_00968 + - 2021.08.24.14.40.55_veh-28_01190_01458 + - 2021.08.24.14.40.55_veh-28_01570_01776 + - 2021.08.24.18.06.27_veh-28_00016_00147 + - 2021.08.24.18.06.27_veh-28_00336_00467 + - 2021.08.24.18.06.27_veh-28_00492_00762 + - 2021.08.24.18.06.27_veh-28_00775_01054 + - 2021.08.24.18.06.27_veh-28_01221_01303 + - 2021.08.24.18.06.27_veh-28_01318_01427 + - 2021.08.24.18.06.27_veh-28_01439_01504 + - 2021.08.24.18.06.27_veh-28_01579_01664 + - 2021.08.25.08.01.53_veh-51_00016_00110 + - 2021.08.25.08.01.53_veh-51_00126_00261 + - 2021.08.25.08.01.53_veh-51_00307_01132 + - 2021.08.25.08.01.53_veh-51_01146_01239 + - 2021.08.25.08.01.53_veh-51_01320_01408 + - 2021.08.25.08.01.53_veh-51_01430_01744 + - 2021.08.25.08.40.28_veh-51_00016_00117 + - 2021.08.25.08.40.28_veh-51_00144_00248 + - 2021.08.25.08.40.28_veh-51_00366_00604 + - 2021.08.25.08.40.28_veh-51_00746_00807 + - 2021.08.25.08.40.28_veh-51_00854_00933 + - 2021.08.25.08.40.28_veh-51_00988_01060 + - 2021.08.25.08.40.28_veh-51_01176_01549 + - 2021.08.25.08.40.28_veh-51_01607_01719 + - 2021.08.25.13.09.17_veh-08_00082_00176 + - 2021.08.25.13.09.17_veh-08_00200_00412 + - 2021.08.25.13.09.17_veh-08_00425_00803 + - 2021.08.25.13.09.17_veh-08_00826_00959 + - 2021.08.25.13.09.17_veh-08_00981_01122 + - 2021.08.25.13.09.17_veh-08_01292_01384 + - 2021.08.25.13.09.17_veh-08_01411_01493 + - 2021.08.25.13.09.17_veh-08_01517_01767 + - 2021.08.25.13.09.17_veh-08_01908_02534 + - 2021.08.25.13.09.17_veh-08_02585_03033 + - 2021.08.25.13.09.17_veh-08_03046_03319 + - 2021.08.25.13.09.17_veh-08_03341_03489 + - 2021.08.25.13.48.45_veh-28_00047_00120 + - 2021.08.25.13.48.45_veh-28_00358_00562 + - 2021.08.25.13.48.45_veh-28_00573_01170 + - 2021.08.25.13.48.45_veh-28_01239_01437 + - 2021.08.25.14.12.46_veh-08_00038_00211 + - 2021.08.25.14.12.46_veh-08_00348_00488 + - 2021.08.25.14.12.46_veh-08_00569_00995 + - 2021.08.25.14.12.46_veh-08_01017_01100 + - 2021.08.25.14.12.46_veh-08_01151_01237 + - 2021.08.25.14.12.46_veh-08_01312_01787 + - 2021.08.25.14.12.46_veh-08_01808_01956 + - 2021.08.25.14.12.46_veh-08_01978_02109 + - 2021.08.25.14.12.46_veh-08_02234_02354 + - 2021.08.25.14.12.46_veh-08_02366_02551 + - 2021.08.25.14.12.46_veh-08_02563_02869 + - 2021.08.25.14.12.46_veh-08_02891_02968 + - 2021.08.25.14.12.46_veh-08_03028_03089 + - 2021.08.25.14.12.46_veh-08_03118_03426 + - 2021.08.25.14.46.50_veh-45_00215_00305 + - 2021.08.25.14.46.50_veh-45_00369_00789 + - 2021.08.25.14.46.50_veh-45_00813_00965 + - 2021.08.25.14.46.50_veh-45_01092_01182 + - 2021.08.25.14.46.50_veh-45_01277_01444 + - 2021.08.25.14.46.50_veh-45_01467_01688 + - 2021.08.25.14.46.50_veh-45_01821_02094 + - 2021.08.25.14.46.50_veh-45_02207_02269 + - 2021.08.25.14.46.50_veh-45_02340_02431 + - 2021.08.25.14.46.50_veh-45_02488_02636 + - 2021.08.25.14.46.50_veh-45_02717_02829 + - 2021.08.25.17.10.24_veh-45_00005_00102 + - 2021.08.25.17.10.24_veh-45_00154_00509 + - 2021.08.25.17.10.24_veh-45_00520_01082 + - 2021.08.25.17.10.24_veh-45_01106_01560 + - 2021.08.25.17.10.24_veh-45_01579_01664 + - 2021.08.25.17.10.24_veh-45_01778_02003 + - 2021.08.25.17.10.24_veh-45_02061_02315 + - 2021.08.25.17.10.24_veh-45_02371_02582 + - 2021.08.25.17.10.24_veh-45_02593_02684 + - 2021.08.25.17.10.24_veh-45_02857_03252 + - 2021.08.25.17.17.57_veh-42_00237_00302 + - 2021.08.25.17.17.57_veh-42_00327_01003 + - 2021.08.25.17.17.57_veh-42_01021_01312 + - 2021.08.25.17.17.57_veh-42_01356_01819 + - 2021.08.25.17.22.01_veh-41_00016_00138 + - 2021.08.25.17.22.01_veh-41_00441_00505 + - 2021.08.25.17.22.01_veh-41_00526_00622 + - 2021.08.25.17.22.01_veh-41_00680_00949 + - 2021.08.25.17.22.01_veh-41_00979_01090 + - 2021.08.25.17.22.01_veh-41_01174_01356 + - 2021.08.25.17.22.01_veh-41_01378_01557 + - 2021.08.25.17.22.01_veh-41_01568_01649 + - 2021.08.25.17.54.16_veh-42_00060_00249 + - 2021.08.25.17.54.16_veh-42_00314_00440 + - 2021.08.25.17.54.16_veh-42_00572_00683 + - 2021.08.25.17.54.16_veh-42_00820_01292 + - 2021.08.25.17.54.16_veh-42_01305_01423 + - 2021.08.25.17.54.16_veh-42_01453_01881 + - 2021.08.25.17.55.51_veh-41_00094_00185 + - 2021.08.25.17.55.51_veh-41_00197_00328 + - 2021.08.25.17.55.51_veh-41_00339_00964 + - 2021.08.25.17.55.51_veh-41_01020_01140 + - 2021.08.25.17.55.51_veh-41_01488_01561 + - 2021.08.25.18.07.15_veh-45_00030_00236 + - 2021.08.25.18.07.15_veh-45_00260_00761 + - 2021.08.25.18.07.15_veh-45_00805_01036 + - 2021.08.25.18.07.15_veh-45_01074_01672 + - 2021.08.25.18.07.15_veh-45_01717_01910 + - 2021.08.25.18.07.15_veh-45_01930_02011 + - 2021.08.25.18.07.15_veh-45_02049_02366 + - 2021.08.25.18.07.15_veh-45_02390_02727 + - 2021.08.25.18.07.15_veh-45_02814_02915 + - 2021.08.25.18.07.15_veh-45_02926_02990 + - 2021.08.25.18.10.09_veh-28_00190_00257 + - 2021.08.25.18.10.09_veh-28_00278_00362 + - 2021.08.25.18.29.43_veh-42_00016_00243 + - 2021.08.25.18.29.43_veh-42_00326_00721 + - 2021.08.25.18.29.43_veh-42_00791_00888 + - 2021.08.25.18.29.43_veh-42_00912_01178 + - 2021.08.25.18.29.43_veh-42_01203_01483 + - 2021.08.25.18.29.43_veh-42_01494_01818 + - 2021.08.25.18.29.43_veh-42_01829_01914 + - 2021.08.25.19.06.07_veh-42_00016_00153 + - 2021.08.25.19.06.07_veh-42_00164_00475 + - 2021.08.25.19.06.07_veh-42_00489_00943 + - 2021.08.25.19.06.07_veh-42_00965_01115 + - 2021.08.25.19.06.07_veh-42_01126_01421 + - 2021.08.25.19.06.07_veh-42_01513_01603 + - 2021.08.25.19.06.07_veh-42_01637_01700 + - 2021.08.25.19.15.01_veh-45_00017_00093 + - 2021.08.25.19.15.01_veh-45_00179_00590 + - 2021.08.25.19.15.01_veh-45_00626_00943 + - 2021.08.25.19.15.01_veh-45_01070_01141 + - 2021.08.25.19.15.01_veh-45_01176_01238 + - 2021.08.25.19.15.01_veh-45_01280_01416 + - 2021.08.25.19.15.01_veh-45_01455_01721 + - 2021.08.25.19.15.01_veh-45_01798_02592 + - 2021.08.25.19.22.51_veh-41_00009_00073 + - 2021.08.25.19.22.51_veh-41_00085_00185 + - 2021.08.25.19.22.51_veh-41_00258_00328 + - 2021.08.25.19.22.51_veh-41_00342_00522 + - 2021.08.25.19.22.51_veh-41_00597_00706 + - 2021.08.25.19.22.51_veh-41_00718_00912 + - 2021.08.25.19.22.51_veh-41_01078_01231 + - 2021.08.25.19.22.51_veh-41_01251_01347 + - 2021.08.25.19.22.51_veh-41_01392_01637 + - 2021.08.25.19.22.51_veh-41_01689_01835 + - 2021.08.25.19.30.22_veh-08_00028_00107 + - 2021.08.25.19.30.22_veh-08_00219_00371 + - 2021.08.25.19.30.22_veh-08_00467_00546 + - 2021.08.25.19.30.22_veh-08_00867_01103 + - 2021.08.25.19.30.22_veh-08_01138_01710 + - 2021.08.25.19.45.41_veh-42_00154_00291 + - 2021.08.25.19.45.41_veh-42_00314_00472 + - 2021.08.25.19.45.41_veh-42_00483_00762 + - 2021.08.25.19.45.41_veh-42_00784_01012 + - 2021.08.25.19.45.41_veh-42_01035_01564 + - 2021.08.25.19.45.41_veh-42_01680_01821 + - 2021.08.25.20.03.09_veh-08_00016_00999 + - 2021.08.25.20.03.09_veh-08_01019_01079 + - 2021.08.25.20.03.09_veh-08_01152_01305 + - 2021.08.25.20.03.09_veh-08_01402_01468 + - 2021.08.25.20.03.09_veh-08_01492_01761 + - 2021.08.25.20.03.37_veh-45_00171_00276 + - 2021.08.25.20.03.37_veh-45_00366_00464 + - 2021.08.25.20.03.37_veh-45_00540_00920 + - 2021.08.25.20.03.37_veh-45_00947_01390 + - 2021.08.25.20.03.37_veh-45_01408_01468 + - 2021.08.25.20.03.37_veh-45_01501_01800 + - 2021.08.25.20.03.37_veh-45_01824_02008 + - 2021.08.25.20.20.58_veh-42_00015_00077 + - 2021.08.25.20.20.58_veh-42_00128_00365 + - 2021.08.25.20.20.58_veh-42_00403_00851 + - 2021.08.25.20.20.58_veh-42_00884_01136 + - 2021.08.25.20.20.58_veh-42_01147_01456 + - 2021.08.25.20.20.58_veh-42_01467_02256 + - 2021.08.26.14.34.54_veh-08_00055_00161 + - 2021.08.26.14.34.54_veh-08_00195_00411 + - 2021.08.26.14.34.54_veh-08_00422_00617 + - 2021.08.26.14.34.54_veh-08_00637_00697 + - 2021.08.26.14.34.54_veh-08_00781_01186 + - 2021.08.26.14.34.54_veh-08_01440_01502 + - 2021.08.26.14.34.54_veh-08_01772_02335 + - 2021.08.26.14.34.54_veh-08_02393_02538 + - 2021.08.26.15.12.21_veh-42_00102_00169 + - 2021.08.26.15.12.21_veh-42_00210_00292 + - 2021.08.26.15.12.21_veh-42_00303_00378 + - 2021.08.26.15.12.21_veh-42_00678_00809 + - 2021.08.26.15.12.21_veh-42_01118_01197 + - 2021.08.26.15.12.21_veh-42_01870_01936 + - 2021.08.26.15.22.00_veh-08_00086_00240 + - 2021.08.26.15.22.00_veh-08_00274_00485 + - 2021.08.26.15.22.00_veh-08_00507_00746 + - 2021.08.26.15.22.00_veh-08_00766_00899 + - 2021.08.26.15.22.00_veh-08_00987_01440 + - 2021.08.26.15.22.00_veh-08_01542_01639 + - 2021.08.26.17.14.36_veh-08_00072_00174 + - 2021.08.26.17.14.36_veh-08_00206_00395 + - 2021.08.26.17.14.36_veh-08_00406_00489 + - 2021.08.26.17.14.36_veh-08_00510_00722 + - 2021.08.26.17.14.36_veh-08_00754_00957 + - 2021.08.26.17.14.36_veh-08_01032_01188 + - 2021.08.26.17.14.36_veh-08_01230_01327 + - 2021.08.26.17.14.36_veh-08_01348_01954 + - 2021.08.26.17.14.36_veh-08_02018_02246 + - 2021.08.26.17.14.36_veh-08_02322_02631 + - 2021.08.26.17.14.36_veh-08_02734_02919 + - 2021.08.26.17.14.36_veh-08_03079_03437 + - 2021.08.26.17.48.33_veh-28_00016_00258 + - 2021.08.26.17.48.33_veh-28_00313_00404 + - 2021.08.26.17.48.33_veh-28_00860_01038 + - 2021.08.26.17.48.33_veh-28_01114_01549 + - 2021.08.26.17.48.33_veh-28_01571_01651 + - 2021.08.26.18.17.33_veh-08_00016_00313 + - 2021.08.26.18.17.33_veh-08_00324_00678 + - 2021.08.26.18.17.33_veh-08_00697_01065 + - 2021.08.26.18.24.36_veh-28_00116_00269 + - 2021.08.26.18.24.36_veh-28_00578_00663 + - 2021.08.26.18.24.36_veh-28_00818_00929 + - 2021.08.26.18.24.36_veh-28_01152_01293 + - 2021.08.26.18.24.36_veh-28_01311_01492 + - 2021.08.26.18.24.36_veh-28_01505_01593 + - 2021.08.26.18.24.36_veh-28_01639_01724 + - 2021.08.26.19.35.22_veh-28_00223_00312 + - 2021.08.26.19.35.22_veh-28_00370_00745 + - 2021.08.26.19.35.22_veh-28_00790_00887 + - 2021.08.26.19.35.22_veh-28_00899_01167 + - 2021.08.26.19.35.22_veh-28_01225_01351 + - 2021.08.26.19.35.22_veh-28_01393_01481 + - 2021.08.26.19.35.22_veh-28_01644_01761 + - 2021.08.27.02.49.18_veh-51_00016_00515 + - 2021.08.27.02.49.18_veh-51_00585_00755 + - 2021.08.27.02.49.18_veh-51_00798_00957 + - 2021.08.27.02.49.18_veh-51_01041_01304 + - 2021.08.27.02.49.18_veh-51_01317_01505 + - 2021.08.27.02.49.18_veh-51_01516_01601 + - 2021.08.27.02.49.18_veh-51_01635_01780 + - 2021.08.27.03.25.14_veh-51_00110_00765 + - 2021.08.27.03.25.14_veh-51_00828_00949 + - 2021.08.27.03.25.14_veh-51_00987_01079 + - 2021.08.27.03.25.14_veh-51_01102_01401 + - 2021.08.27.03.25.14_veh-51_01454_01515 + - 2021.08.27.03.25.14_veh-51_01559_01758 + - 2021.08.27.03.25.14_veh-51_01853_01928 + - 2021.08.27.03.47.52_veh-53_00016_00432 + - 2021.08.27.03.47.52_veh-53_00480_00705 + - 2021.08.27.03.47.52_veh-53_00790_01036 + - 2021.08.27.03.47.52_veh-53_01054_01168 + - 2021.08.27.03.47.52_veh-53_01182_01302 + - 2021.08.27.03.47.52_veh-53_01440_01558 + - 2021.08.27.03.47.52_veh-53_01591_01697 + - 2021.08.27.04.11.22_veh-51_00016_00126 + - 2021.08.27.04.11.22_veh-51_00230_00441 + - 2021.08.27.04.11.22_veh-51_00544_00639 + - 2021.08.27.04.11.22_veh-51_00650_00779 + - 2021.08.27.04.11.22_veh-51_00813_00933 + - 2021.08.27.04.11.22_veh-51_01003_01092 + - 2021.08.27.04.11.22_veh-51_01143_01371 + - 2021.08.27.04.11.22_veh-51_01395_01767 + - 2021.08.27.04.26.17_veh-53_00058_00130 + - 2021.08.27.04.26.17_veh-53_00142_00699 + - 2021.08.27.04.26.17_veh-53_00746_00832 + - 2021.08.27.04.26.17_veh-53_00864_00950 + - 2021.08.27.04.26.17_veh-53_01010_01120 + - 2021.08.27.04.26.17_veh-53_01183_01334 + - 2021.08.27.04.26.17_veh-53_01346_01492 + - 2021.08.27.04.26.17_veh-53_01638_01722 + - 2021.08.27.06.16.41_veh-51_00016_00183 + - 2021.08.27.06.16.41_veh-51_00241_00326 + - 2021.08.27.06.16.41_veh-51_00338_00446 + - 2021.08.27.06.16.41_veh-51_00458_01165 + - 2021.08.27.06.16.41_veh-51_01176_01261 + - 2021.08.27.06.16.41_veh-51_01401_01513 + - 2021.08.27.06.55.03_veh-51_00081_00373 + - 2021.08.27.06.55.03_veh-51_00384_00455 + - 2021.08.27.06.55.03_veh-51_00467_00560 + - 2021.08.27.06.55.03_veh-51_00686_00872 + - 2021.08.27.06.55.03_veh-51_00906_01062 + - 2021.08.27.06.55.03_veh-51_01207_01533 + - 2021.08.27.06.55.03_veh-51_01581_01727 + - 2021.08.27.13.08.25_veh-42_00112_00352 + - 2021.08.27.13.08.25_veh-42_00375_01720 + - 2021.08.27.13.08.25_veh-42_01743_02420 + - 2021.08.27.13.08.25_veh-42_02443_02605 + - 2021.08.27.13.08.25_veh-42_02751_02840 + - 2021.08.27.13.48.56_veh-08_00390_00458 + - 2021.08.27.13.48.56_veh-08_00487_00644 + - 2021.08.27.13.48.56_veh-08_00666_00828 + - 2021.08.27.13.48.56_veh-08_00894_01162 + - 2021.08.27.13.48.56_veh-08_01391_01765 + - 2021.08.27.13.48.56_veh-08_01902_01978 + - 2021.08.27.13.48.56_veh-08_02148_02235 + - 2021.08.27.13.48.56_veh-08_02322_02550 + - 2021.08.27.13.48.56_veh-08_02561_02719 + - 2021.08.27.14.14.40_veh-45_00090_00162 + - 2021.08.27.14.14.40_veh-45_00199_00531 + - 2021.08.27.14.14.40_veh-45_00582_01089 + - 2021.08.27.14.14.40_veh-45_01141_01554 + - 2021.08.27.14.14.40_veh-45_01590_01703 + - 2021.08.27.14.14.40_veh-45_01790_02016 + - 2021.08.27.14.14.40_veh-45_02088_02252 + - 2021.08.27.14.14.40_veh-45_02267_02937 + - 2021.08.27.14.14.40_veh-45_02956_03065 + - 2021.08.27.14.14.40_veh-45_03089_03203 + - 2021.08.27.14.14.40_veh-45_03333_03436 + - 2021.08.27.14.24.38_veh-42_00028_00101 + - 2021.08.27.14.24.38_veh-42_00120_00224 + - 2021.08.27.14.24.38_veh-42_00262_00839 + - 2021.08.27.14.24.38_veh-42_00850_01784 + - 2021.08.27.14.24.38_veh-42_01808_02213 + - 2021.08.27.14.24.38_veh-42_02231_02377 + - 2021.08.27.14.32.45_veh-28_00245_00368 + - 2021.08.27.14.32.45_veh-28_00417_00587 + - 2021.08.27.14.32.45_veh-28_00612_00748 + - 2021.08.27.14.32.45_veh-28_00978_01166 + - 2021.08.27.14.32.45_veh-28_01490_01553 + - 2021.08.27.14.37.47_veh-08_00016_00202 + - 2021.08.27.14.37.47_veh-08_00225_00426 + - 2021.08.27.14.37.47_veh-08_00437_00526 + - 2021.08.27.14.37.47_veh-08_00545_00760 + - 2021.08.27.14.37.47_veh-08_00786_00850 + - 2021.08.27.14.37.47_veh-08_00876_00957 + - 2021.08.27.14.37.47_veh-08_00986_01258 + - 2021.08.27.14.37.47_veh-08_01291_01597 + - 2021.08.27.14.37.47_veh-08_01620_01868 + - 2021.08.27.14.37.47_veh-08_01899_02002 + - 2021.08.27.14.37.47_veh-08_02015_02177 + - 2021.08.27.14.37.47_veh-08_02201_02277 + - 2021.08.27.14.37.47_veh-08_02300_02620 + - 2021.08.27.15.03.22_veh-28_00082_00227 + - 2021.08.27.15.03.22_veh-28_00242_00312 + - 2021.08.27.15.03.22_veh-28_00483_00589 + - 2021.08.27.15.03.22_veh-28_00765_00995 + - 2021.08.27.15.03.22_veh-28_01006_01575 + - 2021.08.27.16.43.13_veh-08_00145_00527 + - 2021.08.27.16.43.13_veh-08_00565_00794 + - 2021.08.27.16.43.13_veh-08_00805_01028 + - 2021.08.27.16.43.13_veh-08_01263_01337 + - 2021.08.27.16.43.13_veh-08_01379_01506 + - 2021.08.27.16.43.13_veh-08_01530_01604 + - 2021.08.27.16.46.47_veh-45_00098_00785 + - 2021.08.27.16.46.47_veh-45_00830_00910 + - 2021.08.27.16.46.47_veh-45_00958_01474 + - 2021.08.27.16.46.47_veh-45_01497_01755 + - 2021.08.27.16.46.47_veh-45_01810_02137 + - 2021.08.27.16.46.47_veh-45_02244_02729 + - 2021.08.27.17.45.33_veh-40_00025_00124 + - 2021.08.27.17.45.33_veh-40_00291_00373 + - 2021.08.27.17.45.33_veh-40_00586_00981 + - 2021.08.27.17.45.33_veh-40_00992_01134 + - 2021.08.27.17.45.33_veh-40_01179_01259 + - 2021.08.27.18.20.07_veh-40_00015_00122 + - 2021.08.27.18.20.07_veh-40_00148_00222 + - 2021.08.27.18.20.07_veh-40_00280_00388 + - 2021.08.27.18.20.07_veh-40_00413_00503 + - 2021.08.27.18.20.07_veh-40_00638_00722 + - 2021.08.27.18.20.07_veh-40_00788_00958 + - 2021.08.27.18.20.07_veh-40_01054_01156 + - 2021.08.27.18.20.07_veh-40_01228_01447 + - 2021.08.27.18.20.07_veh-40_01458_01568 + - 2021.08.27.18.20.07_veh-40_01609_01734 + - 2021.08.27.18.20.07_veh-40_01813_01896 + - 2021.08.27.18.20.07_veh-40_01984_02085 + - 2021.08.27.18.20.07_veh-40_02164_02845 + - 2021.08.30.07.00.41_veh-49_00016_00374 + - 2021.08.30.07.00.41_veh-49_00432_00946 + - 2021.08.30.07.00.41_veh-49_00974_01089 + - 2021.08.30.07.00.41_veh-49_01100_01548 + - 2021.08.30.07.18.25_veh-51_00017_00106 + - 2021.08.30.07.18.25_veh-51_00118_00339 + - 2021.08.30.07.18.25_veh-51_00402_00617 + - 2021.08.30.07.18.25_veh-51_00629_00816 + - 2021.08.30.07.18.25_veh-51_01000_01358 + - 2021.08.30.07.18.25_veh-51_01399_01592 + - 2021.08.30.07.18.25_veh-51_01640_01731 + - 2021.08.30.07.38.06_veh-49_00030_00398 + - 2021.08.30.07.38.06_veh-49_00411_00509 + - 2021.08.30.07.38.06_veh-49_00557_00664 + - 2021.08.30.07.38.06_veh-49_00694_01015 + - 2021.08.30.07.38.06_veh-49_01051_01331 + - 2021.08.30.07.38.06_veh-49_01352_01496 + - 2021.08.30.07.38.06_veh-49_01619_01723 + - 2021.08.30.07.59.13_veh-51_00023_00101 + - 2021.08.30.07.59.13_veh-51_00175_00498 + - 2021.08.30.07.59.13_veh-51_00533_00606 + - 2021.08.30.07.59.13_veh-51_00700_01025 + - 2021.08.30.07.59.13_veh-51_01064_01219 + - 2021.08.30.07.59.13_veh-51_01272_01413 + - 2021.08.30.07.59.13_veh-51_01603_01666 + - 2021.08.30.08.18.56_veh-49_00084_00208 + - 2021.08.30.08.18.56_veh-49_00219_00348 + - 2021.08.30.08.18.56_veh-49_00382_00554 + - 2021.08.30.08.18.56_veh-49_00600_00692 + - 2021.08.30.08.18.56_veh-49_00788_00882 + - 2021.08.30.08.18.56_veh-49_00893_01003 + - 2021.08.30.08.18.56_veh-49_01072_01181 + - 2021.08.30.08.18.56_veh-49_01225_01355 + - 2021.08.30.08.18.56_veh-49_01484_01642 + - 2021.08.30.08.35.28_veh-51_00111_00401 + - 2021.08.30.08.35.28_veh-51_00503_00736 + - 2021.08.30.08.35.28_veh-51_00749_01030 + - 2021.08.30.08.35.28_veh-51_01041_01214 + - 2021.08.30.08.35.28_veh-51_01280_01366 + - 2021.08.30.08.35.28_veh-51_01475_01633 + - 2021.08.30.08.35.28_veh-51_01680_01815 + - 2021.08.30.08.54.37_veh-49_00085_00152 + - 2021.08.30.08.54.37_veh-49_00164_00336 + - 2021.08.30.08.54.37_veh-49_00368_00936 + - 2021.08.30.08.54.37_veh-49_00951_01054 + - 2021.08.30.08.54.37_veh-49_01065_01388 + - 2021.08.30.08.54.37_veh-49_01518_01760 + - 2021.08.30.13.08.03_veh-08_00016_00140 + - 2021.08.30.13.08.03_veh-08_00207_00494 + - 2021.08.30.13.08.03_veh-08_00505_00679 + - 2021.08.30.13.08.03_veh-08_00741_01280 + - 2021.08.30.13.08.03_veh-08_01302_01607 + - 2021.08.30.13.08.03_veh-08_01643_01900 + - 2021.08.30.13.47.20_veh-08_00060_00127 + - 2021.08.30.13.47.20_veh-08_00150_00344 + - 2021.08.30.13.47.20_veh-08_00359_00489 + - 2021.08.30.13.47.20_veh-08_00533_01152 + - 2021.08.30.13.47.20_veh-08_01171_01317 + - 2021.08.30.13.47.20_veh-08_01338_01823 + - 2021.08.30.14.29.08_veh-45_00185_00385 + - 2021.08.30.14.29.08_veh-45_00408_00692 + - 2021.08.30.14.29.08_veh-45_00754_00883 + - 2021.08.30.14.29.08_veh-45_00905_01077 + - 2021.08.30.14.29.08_veh-45_01105_01737 + - 2021.08.30.14.29.08_veh-45_01748_01919 + - 2021.08.30.14.29.08_veh-45_01971_02180 + - 2021.08.30.14.29.08_veh-45_02192_02406 + - 2021.08.30.14.29.08_veh-45_02418_02502 + - 2021.08.30.14.29.08_veh-45_02531_02827 + - 2021.08.30.14.29.08_veh-45_02869_02956 + - 2021.08.30.14.36.46_veh-08_00213_00449 + - 2021.08.30.14.36.46_veh-08_00504_00855 + - 2021.08.30.14.36.46_veh-08_00873_01639 + - 2021.08.30.14.36.46_veh-08_01683_01834 + - 2021.08.30.14.41.24_veh-42_00403_00473 + - 2021.08.30.15.12.56_veh-08_00022_00084 + - 2021.08.30.15.12.56_veh-08_00178_00264 + - 2021.08.30.15.12.56_veh-08_00275_00407 + - 2021.08.30.15.12.56_veh-08_00418_01021 + - 2021.08.30.15.12.56_veh-08_01038_01189 + - 2021.08.30.15.12.56_veh-08_01484_01591 + - 2021.08.30.15.12.56_veh-08_01706_01772 + - 2021.08.30.16.39.44_veh-45_00185_00305 + - 2021.08.30.16.39.44_veh-45_00418_00506 + - 2021.08.30.16.39.44_veh-45_00524_00593 + - 2021.08.30.16.39.44_veh-45_00618_00842 + - 2021.08.30.16.39.44_veh-45_00866_01142 + - 2021.08.30.16.39.44_veh-45_01259_01345 + - 2021.08.30.16.39.44_veh-45_01506_01569 + - 2021.08.30.16.39.44_veh-45_01665_01775 + - 2021.08.30.16.39.44_veh-45_01827_02061 + - 2021.08.30.16.39.44_veh-45_02086_02252 + - 2021.08.30.16.39.44_veh-45_02438_02499 + - 2021.08.30.16.39.44_veh-45_02636_02740 + - 2021.08.30.16.39.44_veh-45_02840_02916 + - 2021.08.30.16.39.44_veh-45_02927_03196 + - 2021.08.30.17.40.28_veh-45_00015_00344 + - 2021.08.30.17.40.28_veh-45_00405_00836 + - 2021.08.30.17.40.28_veh-45_01190_01325 + - 2021.08.30.17.40.28_veh-45_01374_01488 + - 2021.08.30.17.40.28_veh-45_01511_02028 + - 2021.08.30.17.40.28_veh-45_02056_02290 + - 2021.08.30.17.40.28_veh-45_02407_02500 + - 2021.08.30.17.40.28_veh-45_02625_02745 + - 2021.08.30.17.40.28_veh-45_03015_03120 + - 2021.08.30.18.54.11_veh-45_00392_00764 + - 2021.08.30.18.54.11_veh-45_00816_00964 + - 2021.08.30.18.54.11_veh-45_01003_01069 + - 2021.08.30.18.54.11_veh-45_01093_01375 + - 2021.08.30.18.54.11_veh-45_01397_01597 + - 2021.08.30.18.54.11_veh-45_01737_02031 + - 2021.08.30.18.54.11_veh-45_02176_02285 + - 2021.08.30.18.54.11_veh-45_02627_02763 + - 2021.08.30.19.47.46_veh-45_00076_00285 + - 2021.08.30.19.47.46_veh-45_00307_00550 + - 2021.08.30.19.47.46_veh-45_00610_00671 + - 2021.08.30.19.47.46_veh-45_00682_00794 + - 2021.08.30.19.47.46_veh-45_00886_01048 + - 2021.08.30.19.47.46_veh-45_01143_01449 + - 2021.08.30.19.47.46_veh-45_01554_01745 + - 2021.08.30.19.47.46_veh-45_01766_01970 + - 2021.08.30.19.47.46_veh-45_02074_02173 + - 2021.08.30.19.47.46_veh-45_02191_02255 + - 2021.08.30.19.47.46_veh-45_02266_02349 + - 2021.08.30.19.47.46_veh-45_02478_02634 + - 2021.08.30.19.47.46_veh-45_02658_02788 + - 2021.08.30.19.47.46_veh-45_02841_02965 + - 2021.08.31.06.51.16_veh-51_00016_00181 + - 2021.08.31.06.51.16_veh-51_00221_00307 + - 2021.08.31.06.51.16_veh-51_00319_00735 + - 2021.08.31.06.51.16_veh-51_00746_00946 + - 2021.08.31.06.51.16_veh-51_00959_01137 + - 2021.08.31.06.51.16_veh-51_01176_01301 + - 2021.08.31.06.51.16_veh-51_01336_01766 + - 2021.08.31.08.01.03_veh-49_00016_00308 + - 2021.08.31.08.01.03_veh-49_00381_00685 + - 2021.08.31.08.01.03_veh-49_00734_00951 + - 2021.08.31.08.01.03_veh-49_00962_01241 + - 2021.08.31.08.01.03_veh-49_01287_01535 + - 2021.08.31.08.01.03_veh-49_01631_01752 + - 2021.08.31.08.01.03_veh-49_01773_01851 + - 2021.08.31.08.42.55_veh-49_00057_00194 + - 2021.08.31.08.42.55_veh-49_00206_00614 + - 2021.08.31.08.42.55_veh-49_00647_00874 + - 2021.08.31.08.42.55_veh-49_01015_01164 + - 2021.08.31.08.42.55_veh-49_01295_01389 + - 2021.08.31.08.42.55_veh-49_01465_01792 + - 2021.08.31.14.55.32_veh-08_00051_00283 + - 2021.08.31.14.55.32_veh-08_00305_00531 + - 2021.08.31.14.55.32_veh-08_00589_00779 + - 2021.08.31.14.55.32_veh-08_00808_01195 + - 2021.08.31.14.55.32_veh-08_01213_01374 + - 2021.08.31.14.55.32_veh-08_01397_01474 + - 2021.08.31.14.55.32_veh-08_01493_01713 + - 2021.09.01.03.05.10_veh-49_00016_00244 + - 2021.09.01.03.05.10_veh-49_00256_00377 + - 2021.09.01.03.05.10_veh-49_00388_00573 + - 2021.09.01.03.05.10_veh-49_00587_00728 + - 2021.09.01.03.05.10_veh-49_00743_00942 + - 2021.09.01.03.05.10_veh-49_00966_01050 + - 2021.09.01.03.05.10_veh-49_01083_01249 + - 2021.09.01.03.05.10_veh-49_01302_01430 + - 2021.09.01.03.05.10_veh-49_01441_01687 + - 2021.09.01.07.19.19_veh-51_00016_00313 + - 2021.09.01.07.19.19_veh-51_00366_00461 + - 2021.09.01.07.19.19_veh-51_00492_00582 + - 2021.09.01.07.19.19_veh-51_00594_00714 + - 2021.09.01.07.19.19_veh-51_00729_00834 + - 2021.09.01.07.19.19_veh-51_00851_01335 + - 2021.09.01.07.19.19_veh-51_01383_01715 + - 2021.09.01.07.55.11_veh-51_00016_00077 + - 2021.09.01.07.55.11_veh-51_00127_00305 + - 2021.09.01.07.55.11_veh-51_00354_01020 + - 2021.09.01.07.55.11_veh-51_01129_01382 + - 2021.09.01.07.55.11_veh-51_01394_01503 + - 2021.09.01.07.55.11_veh-51_01528_01590 + - 2021.09.01.07.55.11_veh-51_01615_01679 + - 2021.09.01.08.42.47_veh-51_00074_00184 + - 2021.09.01.08.42.47_veh-51_00209_00324 + - 2021.09.01.08.42.47_veh-51_00348_00636 + - 2021.09.01.08.42.47_veh-51_00649_00946 + - 2021.09.01.08.42.47_veh-51_00963_01457 + - 2021.09.01.08.42.47_veh-51_01471_01576 + - 2021.09.01.11.35.51_veh-40_00019_00168 + - 2021.09.01.11.35.51_veh-40_00179_00240 + - 2021.09.01.11.35.51_veh-40_00251_00345 + - 2021.09.01.11.35.51_veh-40_00389_00834 + - 2021.09.01.11.35.51_veh-40_00845_01161 + - 2021.09.01.11.35.51_veh-40_01474_01677 + - 2021.09.01.12.09.01_veh-40_00005_00147 + - 2021.09.01.12.09.01_veh-40_00183_00244 + - 2021.09.01.12.09.01_veh-40_00284_00512 + - 2021.09.01.12.09.01_veh-40_00527_00714 + - 2021.09.01.12.09.01_veh-40_00725_00884 + - 2021.09.01.12.09.01_veh-40_00945_01012 + - 2021.09.01.12.09.01_veh-40_01042_01314 + - 2021.09.01.12.09.01_veh-40_01326_01537 + - 2021.09.01.12.09.01_veh-40_01563_01628 + - 2021.09.01.12.09.01_veh-40_01654_01775 + - 2021.09.01.12.45.08_veh-40_00016_00128 + - 2021.09.01.12.45.08_veh-40_00455_00712 + - 2021.09.01.12.45.08_veh-40_00772_00845 + - 2021.09.01.12.45.08_veh-40_01005_01105 + - 2021.09.01.12.45.08_veh-40_01172_01335 + - 2021.09.01.12.45.08_veh-40_01418_01512 + - 2021.09.01.12.45.08_veh-40_01527_01737 + - 2021.09.01.13.17.48_veh-40_00182_00281 + - 2021.09.01.13.17.48_veh-40_00361_00478 + - 2021.09.01.13.17.48_veh-40_00490_01142 + - 2021.09.01.13.17.48_veh-40_01168_01250 + - 2021.09.01.13.17.48_veh-40_01529_01622 + - 2021.09.01.13.51.23_veh-40_00021_00246 + - 2021.09.01.13.51.23_veh-40_00312_00414 + - 2021.09.01.13.51.23_veh-40_00615_00798 + - 2021.09.01.13.51.23_veh-40_00810_00951 + - 2021.09.01.13.51.23_veh-40_00962_01574 + - 2021.09.01.13.51.23_veh-40_01587_01684 + - 2021.09.01.14.26.59_veh-40_00016_00137 + - 2021.09.01.14.26.59_veh-40_00348_00486 + - 2021.09.01.14.26.59_veh-40_00534_00646 + - 2021.09.01.14.26.59_veh-40_00809_00889 + - 2021.09.01.14.26.59_veh-40_00900_01360 + - 2021.09.01.14.26.59_veh-40_01371_01477 + - 2021.09.01.14.26.59_veh-40_01557_01753 + - 2021.09.01.16.59.08_veh-39_00015_00124 + - 2021.09.01.16.59.08_veh-39_00154_00218 + - 2021.09.01.16.59.08_veh-39_00309_00399 + - 2021.09.01.16.59.08_veh-39_00424_00538 + - 2021.09.01.16.59.08_veh-39_00610_00910 + - 2021.09.01.16.59.08_veh-39_01172_01721 + - 2021.09.02.02.33.00_veh-51_00016_00265 + - 2021.09.02.02.33.00_veh-51_00276_00365 + - 2021.09.02.02.33.00_veh-51_00378_00518 + - 2021.09.02.02.33.00_veh-51_00559_00805 + - 2021.09.02.02.33.00_veh-51_00822_00950 + - 2021.09.02.02.33.00_veh-51_01028_01183 + - 2021.09.02.02.33.00_veh-51_01194_01423 + - 2021.09.02.02.33.00_veh-51_01435_01561 + - 2021.09.02.02.33.00_veh-51_01595_01831 + - 2021.09.02.02.36.16_veh-49_00082_00228 + - 2021.09.02.02.36.16_veh-49_00242_00389 + - 2021.09.02.02.36.16_veh-49_00400_00493 + - 2021.09.02.02.36.16_veh-49_00584_00808 + - 2021.09.02.02.36.16_veh-49_00853_00994 + - 2021.09.02.02.36.16_veh-49_01079_01147 + - 2021.09.02.02.36.16_veh-49_01174_01694 + - 2021.09.02.02.55.40_veh-53_00005_00542 + - 2021.09.02.02.55.40_veh-53_00627_00971 + - 2021.09.02.02.55.40_veh-53_00982_01083 + - 2021.09.02.02.55.40_veh-53_01111_01273 + - 2021.09.02.02.55.40_veh-53_01320_01455 + - 2021.09.02.02.55.40_veh-53_01640_01723 + - 2021.09.02.02.55.40_veh-53_01766_01860 + - 2021.09.02.02.55.40_veh-53_01872_02090 + - 2021.09.02.03.09.11_veh-49_00016_00151 + - 2021.09.02.03.09.11_veh-49_00201_00478 + - 2021.09.02.03.09.11_veh-49_00535_00660 + - 2021.09.02.03.09.11_veh-49_00709_01068 + - 2021.09.02.03.09.11_veh-49_01131_01523 + - 2021.09.02.03.09.11_veh-49_01568_01704 + - 2021.09.02.03.09.11_veh-49_01715_01856 + - 2021.09.02.03.15.44_veh-51_00016_00371 + - 2021.09.02.03.15.44_veh-51_00422_00679 + - 2021.09.02.03.15.44_veh-51_00714_00854 + - 2021.09.02.03.15.44_veh-51_00968_01108 + - 2021.09.02.03.15.44_veh-51_01119_01244 + - 2021.09.02.03.15.44_veh-51_01350_01495 + - 2021.09.02.03.15.44_veh-51_01506_01604 + - 2021.09.02.03.15.44_veh-51_01659_01770 + - 2021.09.02.03.44.09_veh-49_00032_00181 + - 2021.09.02.03.44.09_veh-49_00196_00287 + - 2021.09.02.03.44.09_veh-49_00317_00455 + - 2021.09.02.03.44.09_veh-49_00510_00580 + - 2021.09.02.03.44.09_veh-49_00627_00767 + - 2021.09.02.03.44.09_veh-49_00847_00974 + - 2021.09.02.03.44.09_veh-49_00996_01387 + - 2021.09.02.03.44.09_veh-49_01399_01721 + - 2021.09.02.07.06.50_veh-53_00016_00403 + - 2021.09.02.07.06.50_veh-53_00498_00578 + - 2021.09.02.07.06.50_veh-53_00590_00805 + - 2021.09.02.07.06.50_veh-53_00871_00974 + - 2021.09.02.07.06.50_veh-53_00987_01368 + - 2021.09.02.07.06.50_veh-53_01407_01549 + - 2021.09.02.07.06.50_veh-53_01637_01838 + - 2021.09.02.07.45.36_veh-53_00029_00209 + - 2021.09.02.07.45.36_veh-53_00236_00304 + - 2021.09.02.07.45.36_veh-53_00316_00445 + - 2021.09.02.07.45.36_veh-53_00457_00604 + - 2021.09.02.07.45.36_veh-53_00625_00828 + - 2021.09.02.07.45.36_veh-53_00954_01595 + - 2021.09.02.07.45.36_veh-53_01612_01735 + - 2021.09.02.07.45.36_veh-53_01748_01830 + - 2021.09.02.07.47.07_veh-51_00016_00234 + - 2021.09.02.07.47.07_veh-51_00335_00399 + - 2021.09.02.07.47.07_veh-51_00519_00624 + - 2021.09.02.07.47.07_veh-51_00668_00769 + - 2021.09.02.07.47.07_veh-51_00798_00965 + - 2021.09.02.07.47.07_veh-51_00976_01338 + - 2021.09.02.07.47.07_veh-51_01379_01683 + - 2021.09.02.07.47.07_veh-51_01695_01888 + - 2021.09.02.08.24.34_veh-51_00016_00236 + - 2021.09.02.08.24.34_veh-51_00260_00509 + - 2021.09.02.08.24.34_veh-51_00530_00671 + - 2021.09.02.08.24.34_veh-51_00683_01303 + - 2021.09.02.08.24.34_veh-51_01316_01731 + - 2021.09.02.08.25.34_veh-53_00016_00307 + - 2021.09.02.08.25.34_veh-53_00318_00423 + - 2021.09.02.08.25.34_veh-53_00456_00624 + - 2021.09.02.08.25.34_veh-53_00653_01123 + - 2021.09.02.08.25.34_veh-53_01153_01352 + - 2021.09.02.08.25.34_veh-53_01364_01459 + - 2021.09.02.08.25.34_veh-53_01530_01897 + - 2021.09.02.09.01.05_veh-51_00016_00208 + - 2021.09.02.09.01.05_veh-51_00354_00551 + - 2021.09.02.09.01.05_veh-51_00610_00716 + - 2021.09.02.09.01.05_veh-51_00756_01189 + - 2021.09.02.09.01.05_veh-51_01288_01439 + - 2021.09.02.09.01.05_veh-51_01462_01731 + - 2021.09.02.12.54.17_veh-08_00014_00106 + - 2021.09.02.12.54.17_veh-08_00129_00198 + - 2021.09.02.12.54.17_veh-08_00225_00316 + - 2021.09.02.12.54.17_veh-08_00341_00924 + - 2021.09.02.12.54.17_veh-08_00942_01042 + - 2021.09.02.12.54.17_veh-08_01067_01543 + - 2021.09.02.12.54.17_veh-08_01564_01723 + - 2021.09.02.12.54.17_veh-08_01810_01911 + - 2021.09.02.12.54.17_veh-08_01951_02174 + - 2021.09.02.12.54.17_veh-08_02291_02457 + - 2021.09.02.12.54.17_veh-08_02556_03025 + - 2021.09.02.12.54.17_veh-08_03043_03130 + - 2021.09.02.12.54.17_veh-08_03160_03231 + - 2021.09.02.13.11.17_veh-40_00029_00263 + - 2021.09.02.13.11.17_veh-40_00276_00361 + - 2021.09.02.13.11.17_veh-40_00496_01093 + - 2021.09.02.13.11.17_veh-40_01138_01210 + - 2021.09.02.13.11.17_veh-40_01507_01642 + - 2021.09.02.13.53.58_veh-40_00077_00339 + - 2021.09.02.13.53.58_veh-40_00444_00718 + - 2021.09.02.13.53.58_veh-40_00816_00969 + - 2021.09.02.13.53.58_veh-40_00993_01244 + - 2021.09.02.13.53.58_veh-40_01315_01392 + - 2021.09.02.13.53.58_veh-40_01442_01551 + - 2021.09.02.13.53.58_veh-40_01606_01670 + - 2021.09.02.13.53.58_veh-40_01718_01792 + - 2021.09.02.14.10.27_veh-08_00008_00140 + - 2021.09.02.14.10.27_veh-08_00168_00649 + - 2021.09.02.14.10.27_veh-08_00671_00939 + - 2021.09.02.14.10.27_veh-08_00982_01561 + - 2021.09.02.14.10.27_veh-08_01583_02015 + - 2021.09.02.14.10.27_veh-08_02043_02167 + - 2021.09.02.14.10.27_veh-08_02190_02633 + - 2021.09.02.14.10.27_veh-08_02653_02840 + - 2021.09.02.14.10.27_veh-08_02851_02977 + - 2021.09.02.14.10.27_veh-08_02999_03260 + - 2021.09.02.14.28.39_veh-40_00239_00503 + - 2021.09.02.14.28.39_veh-40_00642_00780 + - 2021.09.02.14.28.39_veh-40_00958_01115 + - 2021.09.02.14.28.39_veh-40_01348_01424 + - 2021.09.02.14.28.39_veh-40_01451_01521 + - 2021.09.02.14.28.39_veh-40_01563_01689 + - 2021.09.02.15.02.56_veh-40_00126_00208 + - 2021.09.02.15.02.56_veh-40_00706_00905 + - 2021.09.02.15.02.56_veh-40_01055_01146 + - 2021.09.02.15.02.56_veh-40_01169_01268 + - 2021.09.02.15.02.56_veh-40_01471_01684 + - 2021.09.02.15.07.50_veh-08_00016_00379 + - 2021.09.02.15.07.50_veh-08_00401_00733 + - 2021.09.02.15.07.50_veh-08_00834_00967 + - 2021.09.02.15.07.50_veh-08_01111_01191 + - 2021.09.02.15.07.50_veh-08_01395_01514 + - 2021.09.02.15.07.50_veh-08_01667_01731 + - 2021.09.02.17.04.02_veh-08_00027_00091 + - 2021.09.02.17.04.02_veh-08_00210_00353 + - 2021.09.02.17.04.02_veh-08_00375_00658 + - 2021.09.02.17.04.02_veh-08_00677_00744 + - 2021.09.02.17.04.02_veh-08_00769_01435 + - 2021.09.02.17.04.02_veh-08_01458_01760 + - 2021.09.02.17.04.02_veh-08_01783_02096 + - 2021.09.02.17.04.02_veh-08_02290_02393 + - 2021.09.02.17.04.02_veh-08_02668_02776 + - 2021.09.02.17.04.02_veh-08_02800_02888 + - 2021.09.02.17.04.02_veh-08_02903_03016 + - 2021.09.02.17.04.02_veh-08_03092_03216 + - 2021.09.02.17.04.02_veh-08_03338_03411 + - 2021.09.02.17.40.11_veh-40_00016_00151 + - 2021.09.02.17.40.11_veh-40_00164_00283 + - 2021.09.02.17.40.11_veh-40_00368_00505 + - 2021.09.02.17.40.11_veh-40_00555_00732 + - 2021.09.02.17.40.11_veh-40_00804_00868 + - 2021.09.02.17.40.11_veh-40_00897_01119 + - 2021.09.02.17.40.11_veh-40_01323_01417 + - 2021.09.02.17.40.11_veh-40_01506_01585 + - 2021.09.02.18.03.07_veh-39_00148_00209 + - 2021.09.02.18.03.07_veh-39_00310_00537 + - 2021.09.02.18.03.07_veh-39_00548_00762 + - 2021.09.02.18.03.07_veh-39_00774_00992 + - 2021.09.02.18.03.07_veh-39_01104_01274 + - 2021.09.02.18.03.07_veh-39_01287_01372 + - 2021.09.02.18.03.07_veh-39_01395_01519 + - 2021.09.02.18.03.07_veh-39_01535_01809 + - 2021.09.02.18.12.27_veh-40_00056_00167 + - 2021.09.02.18.12.27_veh-40_00196_00450 + - 2021.09.02.18.12.27_veh-40_00696_00778 + - 2021.09.02.18.12.27_veh-40_00896_01157 + - 2021.09.02.18.12.27_veh-40_01201_01318 + - 2021.09.02.18.43.39_veh-40_00247_00453 + - 2021.09.02.18.43.39_veh-40_00464_00625 + - 2021.09.02.18.43.39_veh-40_00717_00825 + - 2021.09.02.18.43.39_veh-40_00924_01300 + - 2021.09.02.18.43.39_veh-40_01408_01656 + - 2021.09.02.18.48.06_veh-39_00015_00570 + - 2021.09.02.18.48.06_veh-39_00600_00791 + - 2021.09.02.18.48.06_veh-39_00803_00914 + - 2021.09.02.18.48.06_veh-39_01089_01356 + - 2021.09.02.18.48.06_veh-39_01395_01498 + - 2021.09.02.18.48.06_veh-39_01591_01702 + - 2021.09.02.19.26.01_veh-39_00016_00083 + - 2021.09.02.19.26.01_veh-39_00106_00170 + - 2021.09.02.19.26.01_veh-39_00272_00360 + - 2021.09.02.19.26.01_veh-39_00450_00948 + - 2021.09.02.19.26.01_veh-39_00990_01058 + - 2021.09.02.19.26.01_veh-39_01069_01147 + - 2021.09.02.19.26.01_veh-39_01209_01430 + - 2021.09.02.19.26.01_veh-39_01442_01526 + - 2021.09.02.19.26.01_veh-39_01572_01850 + - 2021.09.02.19.26.01_veh-39_01902_01973 + - 2021.09.02.19.27.43_veh-40_00054_00216 + - 2021.09.02.19.27.43_veh-40_00243_00469 + - 2021.09.02.19.27.43_veh-40_00563_00633 + - 2021.09.02.19.27.43_veh-40_00884_01011 + - 2021.09.02.19.27.43_veh-40_01067_01140 + - 2021.09.02.19.27.43_veh-40_01189_01273 + - 2021.09.02.19.27.43_veh-40_01325_01403 + - 2021.09.02.19.27.43_veh-40_01468_01616 + - 2021.09.03.02.59.13_veh-53_00016_00234 + - 2021.09.03.02.59.13_veh-53_00258_00331 + - 2021.09.03.02.59.13_veh-53_00492_00593 + - 2021.09.03.02.59.13_veh-53_00765_00927 + - 2021.09.03.02.59.13_veh-53_01044_01628 + - 2021.09.03.02.59.13_veh-53_01669_01731 + - 2021.09.03.02.59.13_veh-53_01742_01859 + - 2021.09.03.03.37.14_veh-53_00060_00148 + - 2021.09.03.03.37.14_veh-53_00174_00452 + - 2021.09.03.03.37.14_veh-53_00506_00671 + - 2021.09.03.03.37.14_veh-53_00683_00942 + - 2021.09.03.03.37.14_veh-53_01062_01156 + - 2021.09.03.03.37.14_veh-53_01192_01577 + - 2021.09.03.05.20.45_veh-51_00032_00154 + - 2021.09.03.05.20.45_veh-51_00167_00342 + - 2021.09.03.05.20.45_veh-51_00415_00570 + - 2021.09.03.05.20.45_veh-51_00701_00785 + - 2021.09.03.05.20.45_veh-51_00797_00966 + - 2021.09.03.05.20.45_veh-51_01017_01303 + - 2021.09.03.05.20.45_veh-51_01326_01737 + - 2021.09.03.05.36.38_veh-53_00178_00318 + - 2021.09.03.05.36.38_veh-53_00329_00738 + - 2021.09.03.05.36.38_veh-53_00785_01083 + - 2021.09.03.05.36.38_veh-53_01199_01371 + - 2021.09.03.05.36.38_veh-53_01453_01535 + - 2021.09.03.05.36.38_veh-53_01560_01797 + - 2021.09.03.06.04.17_veh-51_00025_00434 + - 2021.09.03.06.04.17_veh-51_00473_00548 + - 2021.09.03.06.04.17_veh-51_00588_00682 + - 2021.09.03.06.04.17_veh-51_00693_00756 + - 2021.09.03.06.04.17_veh-51_01105_01306 + - 2021.09.03.06.04.17_veh-51_01317_01607 + - 2021.09.03.06.13.55_veh-53_00046_00152 + - 2021.09.03.06.13.55_veh-53_00233_00838 + - 2021.09.03.06.13.55_veh-53_00870_01211 + - 2021.09.03.06.13.55_veh-53_01272_01488 + - 2021.09.03.06.13.55_veh-53_01509_01620 + - 2021.09.03.06.13.55_veh-53_01648_01991 + - 2021.09.03.06.49.38_veh-51_00026_00186 + - 2021.09.03.06.49.38_veh-51_00213_00593 + - 2021.09.03.06.49.38_veh-51_00647_00816 + - 2021.09.03.06.49.38_veh-51_00827_00925 + - 2021.09.03.06.49.38_veh-51_01055_01128 + - 2021.09.03.06.49.38_veh-51_01197_01293 + - 2021.09.03.06.49.38_veh-51_01306_01388 + - 2021.09.03.06.49.38_veh-51_01471_01582 + - 2021.09.03.06.49.38_veh-51_01601_01677 + - 2021.09.03.07.05.12_veh-53_00038_00717 + - 2021.09.03.07.05.12_veh-53_00758_00867 + - 2021.09.03.07.05.12_veh-53_00898_01259 + - 2021.09.03.07.05.12_veh-53_01271_01557 + - 2021.09.03.07.05.12_veh-53_01568_01788 + - 2021.09.03.07.38.19_veh-51_00016_00165 + - 2021.09.03.07.38.19_veh-51_00215_00281 + - 2021.09.03.07.38.19_veh-51_00317_00613 + - 2021.09.03.07.38.19_veh-51_00638_01791 + - 2021.09.03.07.38.58_veh-53_00035_00343 + - 2021.09.03.07.38.58_veh-53_00390_00451 + - 2021.09.03.07.38.58_veh-53_00473_00598 + - 2021.09.03.07.38.58_veh-53_00609_00698 + - 2021.09.03.07.38.58_veh-53_00765_01051 + - 2021.09.03.07.38.58_veh-53_01078_01256 + - 2021.09.03.07.38.58_veh-53_01283_01587 + - 2021.09.03.07.38.58_veh-53_01625_01772 + - 2021.09.03.08.13.30_veh-53_00020_00273 + - 2021.09.03.08.13.30_veh-53_00288_00422 + - 2021.09.03.08.13.30_veh-53_00558_00775 + - 2021.09.03.08.13.30_veh-53_00818_01064 + - 2021.09.03.08.13.30_veh-53_01077_01223 + - 2021.09.03.08.13.30_veh-53_01249_01507 + - 2021.09.03.08.13.30_veh-53_01520_01705 + - 2021.09.03.08.13.30_veh-53_01716_01913 + - 2021.09.03.08.21.32_veh-51_00016_00116 + - 2021.09.03.08.21.32_veh-51_00167_00326 + - 2021.09.03.08.21.32_veh-51_00372_00614 + - 2021.09.03.08.21.32_veh-51_00630_00694 + - 2021.09.03.08.21.32_veh-51_00712_00817 + - 2021.09.03.08.21.32_veh-51_00856_01011 + - 2021.09.03.08.21.32_veh-51_01035_01285 + - 2021.09.03.08.21.32_veh-51_01320_01739 + - 2021.09.03.11.38.11_veh-40_00023_00083 + - 2021.09.03.11.38.11_veh-40_00297_00494 + - 2021.09.03.11.38.11_veh-40_00505_00871 + - 2021.09.03.11.38.11_veh-40_01035_01123 + - 2021.09.03.11.38.11_veh-40_01207_01323 + - 2021.09.03.11.38.11_veh-40_01334_01427 + - 2021.09.03.11.38.11_veh-40_01496_01630 + - 2021.09.03.13.35.39_veh-39_00019_00142 + - 2021.09.03.13.35.39_veh-39_00333_00507 + - 2021.09.03.13.35.39_veh-39_00537_00685 + - 2021.09.03.13.35.39_veh-39_00843_00945 + - 2021.09.03.13.35.39_veh-39_00957_01215 + - 2021.09.03.13.35.39_veh-39_01243_01638 + - 2021.09.03.13.35.39_veh-39_01649_01711 + - 2021.09.03.13.35.39_veh-39_01736_01853 + - 2021.09.03.14.08.21_veh-48_00364_00533 + - 2021.09.03.14.08.21_veh-48_00595_01149 + - 2021.09.03.14.11.45_veh-40_00073_00169 + - 2021.09.03.14.11.45_veh-40_00236_00445 + - 2021.09.03.14.11.45_veh-40_00457_00873 + - 2021.09.03.14.11.45_veh-40_00894_01202 + - 2021.09.03.14.11.45_veh-40_01248_01397 + - 2021.09.03.14.16.10_veh-08_00122_00566 + - 2021.09.03.14.16.10_veh-08_00577_00751 + - 2021.09.03.14.16.10_veh-08_00762_00968 + - 2021.09.03.14.16.10_veh-08_01016_01133 + - 2021.09.03.14.16.10_veh-08_01170_01279 + - 2021.09.03.14.16.10_veh-08_01290_01490 + - 2021.09.03.14.16.10_veh-08_01619_01797 + - 2021.09.03.14.16.10_veh-08_01944_02312 + - 2021.09.03.14.16.10_veh-08_02323_02533 + - 2021.09.03.14.16.10_veh-08_02551_02654 + - 2021.09.03.14.16.10_veh-08_02787_02938 + - 2021.09.03.14.16.10_veh-08_03001_03154 + - 2021.09.03.14.16.10_veh-08_03178_03345 + - 2021.09.03.14.42.51_veh-40_00016_00109 + - 2021.09.03.14.42.51_veh-40_00156_00262 + - 2021.09.03.14.42.51_veh-40_00377_00522 + - 2021.09.03.14.42.51_veh-40_00757_01000 + - 2021.09.03.14.42.51_veh-40_01023_01439 + - 2021.09.03.14.42.51_veh-40_01478_01551 + - 2021.09.03.14.42.51_veh-40_01606_01732 + - 2021.09.03.16.25.50_veh-42_00016_00340 + - 2021.09.03.16.25.50_veh-42_00397_00570 + - 2021.09.03.16.25.50_veh-42_00588_00845 + - 2021.09.03.16.25.50_veh-42_00857_00960 + - 2021.09.03.16.25.50_veh-42_00979_01436 + - 2021.09.03.16.25.50_veh-42_01447_01647 + - 2021.09.03.16.25.50_veh-42_01777_01900 + - 2021.09.03.16.38.35_veh-08_00026_00837 + - 2021.09.03.16.38.35_veh-08_00856_01045 + - 2021.09.03.16.38.35_veh-08_01127_01862 + - 2021.09.03.16.38.35_veh-08_01900_02526 + - 2021.09.03.16.38.35_veh-08_02555_02938 + - 2021.09.03.16.38.35_veh-08_02964_03280 + - 2021.09.03.16.38.35_veh-08_03417_03500 + - 2021.09.03.17.02.10_veh-42_00089_00175 + - 2021.09.03.17.02.10_veh-42_00245_00336 + - 2021.09.03.17.02.10_veh-42_00363_00477 + - 2021.09.03.17.02.10_veh-42_00519_01004 + - 2021.09.03.17.02.10_veh-42_01034_01107 + - 2021.09.03.17.02.10_veh-42_01140_01339 + - 2021.09.03.17.02.10_veh-42_01361_01619 + - 2021.09.03.17.02.10_veh-42_01642_01785 + - 2021.09.03.17.02.10_veh-42_01804_02024 + - 2021.09.03.17.35.53_veh-40_00015_00268 + - 2021.09.03.17.35.53_veh-40_00304_00568 + - 2021.09.03.17.35.53_veh-40_00593_00691 + - 2021.09.03.17.35.53_veh-40_00702_00818 + - 2021.09.03.17.35.53_veh-40_00829_01084 + - 2021.09.03.17.35.53_veh-40_01114_01270 + - 2021.09.03.17.40.20_veh-42_00142_00931 + - 2021.09.03.17.40.20_veh-42_00950_01784 + - 2021.09.03.17.40.20_veh-42_01861_02070 + - 2021.09.03.18.11.54_veh-40_00015_00289 + - 2021.09.03.18.11.54_veh-40_00302_00380 + - 2021.09.03.18.11.54_veh-40_00429_00554 + - 2021.09.03.18.11.54_veh-40_00586_00701 + - 2021.09.03.18.11.54_veh-40_00823_00922 + - 2021.09.03.18.11.54_veh-40_01173_01596 + - 2021.09.03.18.11.54_veh-40_01737_01810 + - 2021.09.03.18.32.35_veh-39_00084_00168 + - 2021.09.03.18.32.35_veh-39_00198_00279 + - 2021.09.03.18.32.35_veh-39_00343_00504 + - 2021.09.03.18.32.35_veh-39_00559_01142 + - 2021.09.03.18.32.35_veh-39_01157_01294 + - 2021.09.03.18.32.35_veh-39_01549_01700 + - 2021.09.06.01.44.26_veh-51_00021_00175 + - 2021.09.06.01.44.26_veh-51_00308_00385 + - 2021.09.06.01.44.26_veh-51_00484_00632 + - 2021.09.06.01.44.26_veh-51_00709_00808 + - 2021.09.06.01.44.26_veh-51_00819_00956 + - 2021.09.06.01.44.26_veh-51_00994_01298 + - 2021.09.06.01.44.26_veh-51_01310_01409 + - 2021.09.06.01.44.26_veh-51_01437_01616 + - 2021.09.06.01.44.26_veh-51_01655_01782 + - 2021.09.06.02.21.00_veh-51_00144_00673 + - 2021.09.06.02.21.00_veh-51_00708_00906 + - 2021.09.06.02.21.00_veh-51_00959_01027 + - 2021.09.06.02.21.00_veh-51_01064_01262 + - 2021.09.06.02.21.00_veh-51_01296_01643 + - 2021.09.06.02.59.10_veh-51_00016_00077 + - 2021.09.06.02.59.10_veh-51_00388_00509 + - 2021.09.06.02.59.10_veh-51_00521_00762 + - 2021.09.06.02.59.10_veh-51_00783_00928 + - 2021.09.06.02.59.10_veh-51_01013_01240 + - 2021.09.06.02.59.10_veh-51_01333_01502 + - 2021.09.06.02.59.10_veh-51_01615_01708 + - 2021.09.06.03.27.22_veh-53_00016_00327 + - 2021.09.06.03.27.22_veh-53_00338_00440 + - 2021.09.06.03.27.22_veh-53_00463_00783 + - 2021.09.06.03.27.22_veh-53_00803_01004 + - 2021.09.06.03.27.22_veh-53_01016_01080 + - 2021.09.06.03.27.22_veh-53_01213_01295 + - 2021.09.06.03.27.22_veh-53_01347_01503 + - 2021.09.06.03.27.22_veh-53_01551_01888 + - 2021.09.06.03.35.43_veh-51_00116_00257 + - 2021.09.06.03.35.43_veh-51_00268_00406 + - 2021.09.06.03.35.43_veh-51_00417_00662 + - 2021.09.06.03.35.43_veh-51_00717_00832 + - 2021.09.06.03.35.43_veh-51_00868_01210 + - 2021.09.06.03.35.43_veh-51_01222_01475 + - 2021.09.06.03.35.43_veh-51_01488_01737 + - 2021.09.06.04.06.26_veh-53_00110_00224 + - 2021.09.06.04.06.26_veh-53_00240_00313 + - 2021.09.06.04.06.26_veh-53_00394_00846 + - 2021.09.06.04.06.26_veh-53_00857_01154 + - 2021.09.06.04.06.26_veh-53_01225_01416 + - 2021.09.06.04.06.26_veh-53_01427_01660 + - 2021.09.06.04.06.26_veh-53_01672_01867 + - 2021.09.06.04.06.26_veh-53_01900_02261 + - 2021.09.06.05.56.29_veh-51_00251_00315 + - 2021.09.06.05.56.29_veh-51_00440_00622 + - 2021.09.06.05.56.29_veh-51_00658_00805 + - 2021.09.06.05.56.29_veh-51_00825_00944 + - 2021.09.06.05.56.29_veh-51_00955_01166 + - 2021.09.06.05.56.29_veh-51_01183_01685 + - 2021.09.06.05.56.29_veh-51_01700_01840 + - 2021.09.06.06.22.57_veh-53_00016_00464 + - 2021.09.06.06.22.57_veh-53_00499_00582 + - 2021.09.06.06.22.57_veh-53_00622_00738 + - 2021.09.06.06.22.57_veh-53_00749_00842 + - 2021.09.06.06.22.57_veh-53_00853_01761 + - 2021.09.06.06.22.57_veh-53_01821_01921 + - 2021.09.06.06.32.43_veh-51_00016_00116 + - 2021.09.06.06.32.43_veh-51_00127_00372 + - 2021.09.06.06.32.43_veh-51_00498_00586 + - 2021.09.06.06.32.43_veh-51_00774_00928 + - 2021.09.06.06.32.43_veh-51_01025_01117 + - 2021.09.06.06.32.43_veh-51_01152_01292 + - 2021.09.06.06.32.43_veh-51_01335_01404 + - 2021.09.06.06.32.43_veh-51_01415_01482 + - 2021.09.06.06.32.43_veh-51_01609_01767 + - 2021.09.06.07.03.16_veh-53_00027_00287 + - 2021.09.06.07.03.16_veh-53_00320_00491 + - 2021.09.06.07.03.16_veh-53_00523_00828 + - 2021.09.06.07.03.16_veh-53_00850_01026 + - 2021.09.06.07.03.16_veh-53_01073_01591 + - 2021.09.06.07.03.16_veh-53_01653_01732 + - 2021.09.06.07.12.46_veh-51_00016_00085 + - 2021.09.06.07.12.46_veh-51_00140_00265 + - 2021.09.06.07.12.46_veh-51_00328_00457 + - 2021.09.06.07.12.46_veh-51_00468_00650 + - 2021.09.06.07.12.46_veh-51_00662_00829 + - 2021.09.06.07.12.46_veh-51_00885_01516 + - 2021.09.06.07.12.46_veh-51_01600_01674 + - 2021.09.06.07.45.37_veh-53_00084_00308 + - 2021.09.06.07.45.37_veh-53_00361_00459 + - 2021.09.06.07.45.37_veh-53_00486_01129 + - 2021.09.06.07.45.37_veh-53_01140_01580 + - 2021.09.06.07.45.37_veh-53_01605_01717 + - 2021.09.06.07.45.37_veh-53_01731_01907 + - 2021.09.07.01.55.00_veh-51_00016_00340 + - 2021.09.07.01.55.00_veh-51_00378_00476 + - 2021.09.07.01.55.00_veh-51_00518_00622 + - 2021.09.07.01.55.00_veh-51_00633_00732 + - 2021.09.07.01.55.00_veh-51_00765_01383 + - 2021.09.07.01.55.00_veh-51_01421_01550 + - 2021.09.07.01.55.00_veh-51_01561_01904 + - 2021.09.07.02.31.43_veh-51_00016_00365 + - 2021.09.07.02.31.43_veh-51_00386_00479 + - 2021.09.07.02.31.43_veh-51_00491_00638 + - 2021.09.07.02.31.43_veh-51_00683_00945 + - 2021.09.07.02.31.43_veh-51_00961_01714 + - 2021.09.07.02.31.43_veh-51_01768_02102 + - 2021.09.07.03.13.47_veh-51_00016_00396 + - 2021.09.07.03.13.47_veh-51_00442_00572 + - 2021.09.07.03.13.47_veh-51_00593_00737 + - 2021.09.07.03.13.47_veh-51_00768_01017 + - 2021.09.07.03.13.47_veh-51_01040_01358 + - 2021.09.07.03.13.47_veh-51_01374_01511 + - 2021.09.07.03.13.47_veh-51_01525_01658 + - 2021.09.07.03.13.47_veh-51_01680_01864 + - 2021.09.07.04.01.34_veh-51_00106_00189 + - 2021.09.07.04.01.34_veh-51_00240_00311 + - 2021.09.07.04.01.34_veh-51_00323_00461 + - 2021.09.07.04.01.34_veh-51_00516_00608 + - 2021.09.07.04.01.34_veh-51_00630_00843 + - 2021.09.07.04.01.34_veh-51_00881_01061 + - 2021.09.07.04.01.34_veh-51_01117_01397 + - 2021.09.07.04.01.34_veh-51_01408_01493 + - 2021.09.07.04.01.34_veh-51_01505_01858 + - 2021.09.07.05.45.19_veh-51_00031_00343 + - 2021.09.07.05.45.19_veh-51_00385_00529 + - 2021.09.07.05.45.19_veh-51_00581_00679 + - 2021.09.07.05.45.19_veh-51_00714_00789 + - 2021.09.07.05.45.19_veh-51_00817_01682 + - 2021.09.07.06.15.12_veh-49_00043_00507 + - 2021.09.07.06.15.12_veh-49_00570_00677 + - 2021.09.07.06.15.12_veh-49_00689_00823 + - 2021.09.07.06.15.12_veh-49_00836_00900 + - 2021.09.07.06.15.12_veh-49_00927_01075 + - 2021.09.07.06.15.12_veh-49_01094_01203 + - 2021.09.07.06.15.12_veh-49_01217_01300 + - 2021.09.07.06.15.12_veh-49_01322_01419 + - 2021.09.07.06.15.12_veh-49_01579_01702 + - 2021.09.07.06.21.22_veh-51_00016_00747 + - 2021.09.07.06.21.22_veh-51_00788_00946 + - 2021.09.07.06.21.22_veh-51_00973_01067 + - 2021.09.07.06.21.22_veh-51_01175_01282 + - 2021.09.07.06.21.22_veh-51_01370_01823 + - 2021.09.07.06.21.22_veh-51_01834_01909 + - 2021.09.07.06.56.13_veh-49_00016_00108 + - 2021.09.07.06.56.13_veh-49_00119_00225 + - 2021.09.07.06.56.13_veh-49_00273_00408 + - 2021.09.07.06.56.13_veh-49_00441_00778 + - 2021.09.07.06.56.13_veh-49_00850_00934 + - 2021.09.07.06.56.13_veh-49_00946_01403 + - 2021.09.07.06.56.13_veh-49_01540_01637 + - 2021.09.07.06.56.13_veh-49_01651_01765 + - 2021.09.07.07.21.50_veh-51_00016_00265 + - 2021.09.07.07.21.50_veh-51_00290_00380 + - 2021.09.07.07.21.50_veh-51_00430_00759 + - 2021.09.07.07.21.50_veh-51_00771_00899 + - 2021.09.07.07.21.50_veh-51_00912_01082 + - 2021.09.07.07.21.50_veh-51_01093_01596 + - 2021.09.07.07.21.50_veh-51_01614_01831 + - 2021.09.07.07.33.30_veh-49_00016_00137 + - 2021.09.07.07.33.30_veh-49_00170_00315 + - 2021.09.07.07.33.30_veh-49_00328_00509 + - 2021.09.07.07.33.30_veh-49_00562_00860 + - 2021.09.07.07.33.30_veh-49_00875_01180 + - 2021.09.07.07.33.30_veh-49_01191_01440 + - 2021.09.07.07.33.30_veh-49_01451_01572 + - 2021.09.07.07.33.30_veh-49_01691_01817 + - 2021.09.07.07.33.30_veh-49_01899_01965 + - 2021.09.07.07.33.30_veh-49_01976_02052 + - 2021.09.07.07.58.13_veh-51_00177_00291 + - 2021.09.07.07.58.13_veh-51_00313_00422 + - 2021.09.07.07.58.13_veh-51_00433_00591 + - 2021.09.07.07.58.13_veh-51_00648_00915 + - 2021.09.07.07.58.13_veh-51_00959_01160 + - 2021.09.07.07.58.13_veh-51_01205_01425 + - 2021.09.07.07.58.13_veh-51_01436_01572 + - 2021.09.07.07.58.13_veh-51_01583_01695 + - 2021.09.07.07.58.13_veh-51_01706_01872 + - 2021.09.07.08.12.04_veh-49_00057_00164 + - 2021.09.07.08.12.04_veh-49_00176_00402 + - 2021.09.07.08.12.04_veh-49_00420_00564 + - 2021.09.07.08.12.04_veh-49_00609_00793 + - 2021.09.07.08.12.04_veh-49_00808_00954 + - 2021.09.07.08.12.04_veh-49_01004_01145 + - 2021.09.07.08.12.04_veh-49_01168_01490 + - 2021.09.07.08.12.04_veh-49_01506_01637 + - 2021.09.07.08.12.04_veh-49_01672_01785 + - 2021.09.07.08.12.04_veh-49_01859_01973 + - 2021.09.07.08.34.05_veh-51_00016_00209 + - 2021.09.07.08.34.05_veh-51_00426_00727 + - 2021.09.07.08.34.05_veh-51_00750_01325 + - 2021.09.07.08.34.05_veh-51_01426_01719 + - 2021.09.07.08.34.05_veh-51_01772_02039 + - 2021.09.07.08.34.05_veh-51_02053_02336 + - 2021.09.07.09.00.01_veh-49_00016_00244 + - 2021.09.07.09.00.01_veh-49_00259_00328 + - 2021.09.07.09.00.01_veh-49_00340_00436 + - 2021.09.07.09.00.01_veh-49_00450_00657 + - 2021.09.07.09.00.01_veh-49_00668_00908 + - 2021.09.07.09.00.01_veh-49_01017_01095 + - 2021.09.07.09.00.01_veh-49_01152_01403 + - 2021.09.07.09.00.01_veh-49_01416_01510 + - 2021.09.07.09.00.01_veh-49_01594_01785 + - 2021.09.07.13.06.36_veh-42_00065_00174 + - 2021.09.07.13.06.36_veh-42_00266_00935 + - 2021.09.07.13.06.36_veh-42_00954_01243 + - 2021.09.07.13.06.36_veh-42_01306_01697 + - 2021.09.07.13.06.36_veh-42_01795_01987 + - 2021.09.07.13.26.54_veh-40_00015_00150 + - 2021.09.07.13.26.54_veh-40_00329_00401 + - 2021.09.07.13.26.54_veh-40_00511_00643 + - 2021.09.07.13.26.54_veh-40_00655_00799 + - 2021.09.07.13.26.54_veh-40_00822_01021 + - 2021.09.07.13.26.54_veh-40_01140_01303 + - 2021.09.07.13.26.54_veh-40_01476_01650 + - 2021.09.07.13.44.33_veh-39_00016_00285 + - 2021.09.07.13.44.33_veh-39_00309_00484 + - 2021.09.07.13.44.33_veh-39_00511_00595 + - 2021.09.07.13.44.33_veh-39_00660_00854 + - 2021.09.07.13.44.33_veh-39_00866_01082 + - 2021.09.07.13.44.33_veh-39_01094_01189 + - 2021.09.07.13.44.33_veh-39_01402_01566 + - 2021.09.07.13.44.33_veh-39_01645_01777 + - 2021.09.07.13.44.33_veh-39_01788_02210 + - 2021.09.07.14.03.48_veh-40_00016_00153 + - 2021.09.07.14.03.48_veh-40_00164_00246 + - 2021.09.07.14.03.48_veh-40_00263_00535 + - 2021.09.07.14.03.48_veh-40_00634_00694 + - 2021.09.07.14.03.48_veh-40_00804_00875 + - 2021.09.07.14.03.48_veh-40_01054_01480 + - 2021.09.07.14.03.48_veh-40_01530_01702 + - 2021.09.07.14.03.48_veh-40_01728_01814 + - 2021.09.07.14.03.48_veh-40_01868_01945 + - 2021.09.07.14.30.36_veh-39_00017_00354 + - 2021.09.07.14.30.36_veh-39_00613_00858 + - 2021.09.07.14.30.36_veh-39_00870_01054 + - 2021.09.07.14.30.36_veh-39_01065_01406 + - 2021.09.07.14.30.36_veh-39_01459_01589 + - 2021.09.07.14.30.36_veh-39_01601_01717 + - 2021.09.07.14.30.36_veh-39_01728_01837 + - 2021.09.07.14.51.48_veh-40_00252_00408 + - 2021.09.07.14.51.48_veh-40_00429_00633 + - 2021.09.07.14.51.48_veh-40_00719_01023 + - 2021.09.07.14.51.48_veh-40_01129_01423 + - 2021.09.07.14.51.48_veh-40_01472_01584 + - 2021.09.07.15.09.25_veh-39_00016_00383 + - 2021.09.07.15.09.25_veh-39_00520_00606 + - 2021.09.07.15.09.25_veh-39_00695_01006 + - 2021.09.07.15.09.25_veh-39_01017_01284 + - 2021.09.07.15.09.25_veh-39_01312_01424 + - 2021.09.07.15.09.25_veh-39_01526_01603 + - 2021.09.07.15.09.25_veh-39_01645_01826 + - 2021.09.07.15.28.24_veh-40_00044_00148 + - 2021.09.07.15.28.24_veh-40_00160_00361 + - 2021.09.07.15.28.24_veh-40_00582_01059 + - 2021.09.07.15.28.24_veh-40_01073_01155 + - 2021.09.07.15.28.24_veh-40_01168_01343 + - 2021.09.07.15.28.24_veh-40_01471_01601 + - 2021.09.07.18.32.07_veh-39_00015_00086 + - 2021.09.07.18.32.07_veh-39_00128_00287 + - 2021.09.07.18.32.07_veh-39_00360_00578 + - 2021.09.07.18.32.07_veh-39_00589_01013 + - 2021.09.07.18.32.07_veh-39_01024_01162 + - 2021.09.07.18.32.07_veh-39_01173_01337 + - 2021.09.07.18.32.07_veh-39_01367_01448 + - 2021.09.07.18.32.07_veh-39_01460_01644 + - 2021.09.07.18.32.07_veh-39_01672_01793 + - 2021.09.07.19.49.48_veh-39_00013_00325 + - 2021.09.07.19.49.48_veh-39_00337_01058 + - 2021.09.07.19.49.48_veh-39_01070_01161 + - 2021.09.07.19.49.48_veh-39_01397_01643 + - 2021.09.07.19.49.48_veh-39_01654_01831 + - 2021.09.07.20.27.01_veh-39_00019_00395 + - 2021.09.07.20.27.01_veh-39_00407_00994 + - 2021.09.07.20.27.01_veh-39_01050_01162 + - 2021.09.07.20.27.01_veh-39_01354_01431 + - 2021.09.08.02.30.38_veh-51_00016_00214 + - 2021.09.08.02.30.38_veh-51_00235_00369 + - 2021.09.08.02.30.38_veh-51_00427_00607 + - 2021.09.08.02.30.38_veh-51_00704_00778 + - 2021.09.08.02.30.38_veh-51_00834_01262 + - 2021.09.08.02.30.38_veh-51_01299_01387 + - 2021.09.08.02.30.38_veh-51_01408_01799 + - 2021.09.08.03.13.47_veh-51_00061_00298 + - 2021.09.08.03.13.47_veh-51_00360_00795 + - 2021.09.08.03.13.47_veh-51_00857_00936 + - 2021.09.08.03.13.47_veh-51_00998_01598 + - 2021.09.08.03.13.47_veh-51_01610_01681 + - 2021.09.08.03.54.54_veh-51_00016_00383 + - 2021.09.08.03.54.54_veh-51_00407_00555 + - 2021.09.08.03.54.54_veh-51_00621_00710 + - 2021.09.08.03.54.54_veh-51_00756_00863 + - 2021.09.08.03.54.54_veh-51_00986_01063 + - 2021.09.08.03.54.54_veh-51_01109_01613 + - 2021.09.09.01.35.40_veh-51_00016_00182 + - 2021.09.09.01.35.40_veh-51_00253_00414 + - 2021.09.09.01.35.40_veh-51_00466_00546 + - 2021.09.09.01.35.40_veh-51_00709_00798 + - 2021.09.09.01.35.40_veh-51_00867_01023 + - 2021.09.09.01.35.40_veh-51_01112_01204 + - 2021.09.09.01.35.40_veh-51_01296_01428 + - 2021.09.09.01.35.40_veh-51_01440_01577 + - 2021.09.09.01.35.40_veh-51_01626_01771 + - 2021.09.09.01.39.41_veh-49_00077_00470 + - 2021.09.09.01.39.41_veh-49_00574_00746 + - 2021.09.09.01.39.41_veh-49_00787_01443 + - 2021.09.09.01.39.41_veh-49_01480_02036 + - 2021.09.09.02.16.48_veh-49_00029_00500 + - 2021.09.09.02.16.48_veh-49_00514_00699 + - 2021.09.09.02.16.48_veh-49_00710_00882 + - 2021.09.09.02.16.48_veh-49_00894_01188 + - 2021.09.09.02.16.48_veh-49_01333_01612 + - 2021.09.09.02.16.48_veh-49_01624_01689 + - 2021.09.09.02.16.48_veh-49_01700_01806 + - 2021.09.09.02.17.08_veh-51_00016_00162 + - 2021.09.09.02.17.08_veh-51_00236_00455 + - 2021.09.09.02.17.08_veh-51_00480_00677 + - 2021.09.09.02.17.08_veh-51_00791_00998 + - 2021.09.09.02.17.08_veh-51_01081_01450 + - 2021.09.09.02.17.08_veh-51_01468_01721 + - 2021.09.09.02.17.08_veh-51_01748_01833 + - 2021.09.09.02.51.02_veh-49_00016_00196 + - 2021.09.09.02.51.02_veh-49_00251_00314 + - 2021.09.09.02.51.02_veh-49_00327_00642 + - 2021.09.09.02.51.02_veh-49_00655_00841 + - 2021.09.09.02.51.02_veh-49_01026_01292 + - 2021.09.09.02.51.02_veh-49_01439_01562 + - 2021.09.09.02.51.02_veh-49_01600_01679 + - 2021.09.09.03.00.29_veh-51_00016_00077 + - 2021.09.09.03.00.29_veh-51_00090_00225 + - 2021.09.09.03.00.29_veh-51_00236_00795 + - 2021.09.09.03.00.29_veh-51_00807_00947 + - 2021.09.09.03.00.29_veh-51_00959_01141 + - 2021.09.09.03.00.29_veh-51_01172_01453 + - 2021.09.09.03.00.29_veh-51_01464_01699 + - 2021.09.09.03.00.29_veh-51_01710_01785 + - 2021.09.09.03.32.50_veh-49_00118_00220 + - 2021.09.09.03.32.50_veh-49_00346_00472 + - 2021.09.09.03.32.50_veh-49_00520_00680 + - 2021.09.09.03.32.50_veh-49_00748_00866 + - 2021.09.09.03.32.50_veh-49_00902_01063 + - 2021.09.09.03.32.50_veh-49_01084_01380 + - 2021.09.09.03.32.50_veh-49_01420_01732 + - 2021.09.09.03.32.50_veh-49_01744_01806 + - 2021.09.09.05.40.08_veh-49_00089_00879 + - 2021.09.09.05.40.08_veh-49_00992_01120 + - 2021.09.09.05.40.08_veh-49_01205_01273 + - 2021.09.09.05.40.08_veh-49_01421_01683 + - 2021.09.09.06.14.16_veh-49_00090_00343 + - 2021.09.09.06.14.16_veh-49_00354_00494 + - 2021.09.09.06.14.16_veh-49_00516_00693 + - 2021.09.09.06.14.16_veh-49_00734_00875 + - 2021.09.09.06.14.16_veh-49_00897_01033 + - 2021.09.09.06.14.16_veh-49_01081_01274 + - 2021.09.09.06.14.16_veh-49_01326_01466 + - 2021.09.09.06.14.16_veh-49_01514_01600 + - 2021.09.09.06.14.16_veh-49_01633_01820 + - 2021.09.09.07.00.44_veh-49_00016_00229 + - 2021.09.09.07.00.44_veh-49_00241_00424 + - 2021.09.09.07.00.44_veh-49_00437_00499 + - 2021.09.09.07.00.44_veh-49_00569_00935 + - 2021.09.09.07.00.44_veh-49_00946_01150 + - 2021.09.09.07.00.44_veh-49_01174_01391 + - 2021.09.09.07.00.44_veh-49_01495_01590 + - 2021.09.09.07.00.44_veh-49_01638_01938 + - 2021.09.09.07.36.27_veh-49_00016_00260 + - 2021.09.09.07.36.27_veh-49_00394_00508 + - 2021.09.09.07.36.27_veh-49_00526_00619 + - 2021.09.09.07.36.27_veh-49_00640_00905 + - 2021.09.09.07.36.27_veh-49_00929_01070 + - 2021.09.09.07.36.27_veh-49_01085_01249 + - 2021.09.09.07.36.27_veh-49_01475_01584 + - 2021.09.09.07.36.27_veh-49_01597_01661 + - 2021.09.09.08.10.20_veh-49_00048_00120 + - 2021.09.09.08.10.20_veh-49_00142_00220 + - 2021.09.09.08.10.20_veh-49_00232_00361 + - 2021.09.09.08.10.20_veh-49_00372_00479 + - 2021.09.09.08.10.20_veh-49_00602_00716 + - 2021.09.09.08.10.20_veh-49_00733_00919 + - 2021.09.09.08.10.20_veh-49_00938_01191 + - 2021.09.09.08.10.20_veh-49_01204_01383 + - 2021.09.09.08.10.20_veh-49_01459_01536 + - 2021.09.09.08.10.20_veh-49_01667_01780 + - 2021.09.09.13.32.12_veh-43_00026_00133 + - 2021.09.09.13.32.12_veh-43_00175_00627 + - 2021.09.09.13.32.12_veh-43_00646_01672 + - 2021.09.09.13.32.12_veh-43_01691_02260 + - 2021.09.09.13.32.12_veh-43_02295_02890 + - 2021.09.09.13.32.12_veh-43_03035_03113 + - 2021.09.09.13.32.12_veh-43_03257_03345 + - 2021.09.09.14.34.34_veh-43_00093_00870 + - 2021.09.09.14.34.34_veh-43_00889_01053 + - 2021.09.09.14.34.34_veh-43_01138_01736 + - 2021.09.09.14.34.34_veh-43_01759_02430 + - 2021.09.09.14.34.34_veh-43_02453_02796 + - 2021.09.09.16.51.32_veh-42_00028_00124 + - 2021.09.09.16.51.32_veh-42_00161_00562 + - 2021.09.09.16.51.32_veh-42_00959_01037 + - 2021.09.09.16.51.32_veh-42_01098_01163 + - 2021.09.09.16.51.32_veh-42_01586_01647 + - 2021.09.09.17.29.55_veh-42_00016_00151 + - 2021.09.09.17.29.55_veh-42_00187_00531 + - 2021.09.09.17.29.55_veh-42_00553_00824 + - 2021.09.09.17.29.55_veh-42_00858_01275 + - 2021.09.09.17.29.55_veh-42_01531_01608 + - 2021.09.09.17.29.55_veh-42_01635_01776 + - 2021.09.09.18.12.06_veh-42_00036_00389 + - 2021.09.09.18.12.06_veh-42_00446_01239 + - 2021.09.09.18.12.06_veh-42_01268_01696 + - 2021.09.09.18.47.17_veh-45_00027_00129 + - 2021.09.09.18.47.17_veh-45_00144_00620 + - 2021.09.09.18.47.17_veh-45_00653_00715 + - 2021.09.09.18.47.17_veh-45_00740_01166 + - 2021.09.09.18.47.17_veh-45_01201_01645 + - 2021.09.09.18.47.17_veh-45_01748_01928 + - 2021.09.09.18.47.17_veh-45_02016_02078 + - 2021.09.09.18.47.17_veh-45_02115_02605 + - 2021.09.09.18.47.17_veh-45_02725_02871 + - 2021.09.09.18.47.17_veh-45_02938_03061 + - 2021.09.09.18.47.17_veh-45_03147_03223 + - 2021.09.09.18.47.17_veh-45_03246_03438 + - 2021.09.09.19.17.35_veh-42_00016_00998 + - 2021.09.09.19.17.35_veh-42_01051_01399 + - 2021.09.09.19.17.35_veh-42_01464_01542 + - 2021.09.09.19.47.56_veh-45_00016_00398 + - 2021.09.09.19.47.56_veh-45_00434_01049 + - 2021.09.09.19.47.56_veh-45_01177_01260 + - 2021.09.09.19.47.56_veh-45_01379_01541 + - 2021.09.09.19.47.56_veh-45_01645_02084 + - 2021.09.09.19.47.56_veh-45_02121_02426 + - 2021.09.09.20.07.29_veh-42_00015_00076 + - 2021.09.09.20.07.29_veh-42_00233_00302 + - 2021.09.09.20.07.29_veh-42_00374_00455 + - 2021.09.09.20.07.29_veh-42_00466_00828 + - 2021.09.09.20.07.29_veh-42_00902_00962 + - 2021.09.09.20.07.29_veh-42_00973_01048 + - 2021.09.09.20.07.29_veh-42_01059_01133 + - 2021.09.09.20.07.29_veh-42_01144_01223 + - 2021.09.09.20.07.29_veh-42_01234_01340 + - 2021.09.09.20.07.29_veh-42_01411_01488 + - 2021.09.09.20.07.29_veh-42_01499_01628 + - 2021.09.09.20.07.29_veh-42_01817_01931 + - 2021.09.09.20.07.29_veh-42_02581_02710 + - 2021.09.09.20.07.29_veh-42_02744_02821 + - 2021.09.10.03.54.15_veh-51_00062_00280 + - 2021.09.10.03.54.15_veh-51_00326_00716 + - 2021.09.10.03.54.15_veh-51_00802_01164 + - 2021.09.10.03.54.15_veh-51_01218_01291 + - 2021.09.10.03.54.15_veh-51_01305_02133 + - 2021.09.10.05.48.49_veh-49_00049_00217 + - 2021.09.10.05.48.49_veh-49_00266_00720 + - 2021.09.10.05.48.49_veh-49_00731_00955 + - 2021.09.10.05.48.49_veh-49_00977_01106 + - 2021.09.10.05.48.49_veh-49_01190_01543 + - 2021.09.10.05.48.49_veh-49_01559_01909 + - 2021.09.10.06.18.56_veh-51_00016_00332 + - 2021.09.10.06.18.56_veh-51_00430_00523 + - 2021.09.10.06.18.56_veh-51_00631_01147 + - 2021.09.10.06.18.56_veh-51_01199_01763 + - 2021.09.10.06.21.57_veh-52_00016_00131 + - 2021.09.10.06.21.57_veh-52_00152_00265 + - 2021.09.10.06.21.57_veh-52_00320_00491 + - 2021.09.10.06.21.57_veh-52_00527_01512 + - 2021.09.10.06.21.57_veh-52_01523_01658 + - 2021.09.10.06.24.49_veh-49_00016_00095 + - 2021.09.10.06.24.49_veh-49_00151_00777 + - 2021.09.10.06.24.49_veh-49_00809_00872 + - 2021.09.10.06.24.49_veh-49_00928_01108 + - 2021.09.10.06.24.49_veh-49_01123_01359 + - 2021.09.10.06.24.49_veh-49_01484_01581 + - 2021.09.10.06.56.28_veh-52_00016_00376 + - 2021.09.10.06.56.28_veh-52_00418_00541 + - 2021.09.10.06.56.28_veh-52_00565_00656 + - 2021.09.10.06.56.28_veh-52_00797_01137 + - 2021.09.10.06.56.28_veh-52_01149_01240 + - 2021.09.10.06.56.28_veh-52_01251_01360 + - 2021.09.10.06.56.28_veh-52_01400_01608 + - 2021.09.10.06.56.28_veh-52_01627_01736 + - 2021.09.10.07.02.31_veh-51_00091_00253 + - 2021.09.10.07.02.31_veh-51_00408_00579 + - 2021.09.10.07.02.31_veh-51_00624_00747 + - 2021.09.10.07.02.31_veh-51_00758_00834 + - 2021.09.10.07.02.31_veh-51_00845_01117 + - 2021.09.10.07.02.31_veh-51_01129_01229 + - 2021.09.10.07.02.31_veh-51_01242_01562 + - 2021.09.10.07.02.31_veh-51_01673_01853 + - 2021.09.10.07.07.06_veh-49_00016_00141 + - 2021.09.10.07.07.06_veh-49_00154_00332 + - 2021.09.10.07.07.06_veh-49_00359_00738 + - 2021.09.10.07.07.06_veh-49_00761_01085 + - 2021.09.10.07.07.06_veh-49_01183_01354 + - 2021.09.10.07.07.06_veh-49_01530_01806 + - 2021.09.10.07.30.47_veh-52_00031_00144 + - 2021.09.10.07.30.47_veh-52_00200_00305 + - 2021.09.10.07.30.47_veh-52_00327_00518 + - 2021.09.10.07.30.47_veh-52_00594_00715 + - 2021.09.10.07.30.47_veh-52_00767_01207 + - 2021.09.10.07.30.47_veh-52_01266_01708 + - 2021.09.10.08.00.27_veh-51_00016_00382 + - 2021.09.10.08.00.27_veh-51_00492_00563 + - 2021.09.10.08.00.27_veh-51_00577_00839 + - 2021.09.10.08.00.27_veh-51_00862_01031 + - 2021.09.10.08.00.27_veh-51_01043_01284 + - 2021.09.10.08.00.27_veh-51_01315_01711 + - 2021.09.10.13.16.14_veh-39_00016_00116 + - 2021.09.10.13.16.14_veh-39_00128_00206 + - 2021.09.10.13.16.14_veh-39_00314_00450 + - 2021.09.10.13.16.14_veh-39_00482_00655 + - 2021.09.10.13.16.14_veh-39_00672_00808 + - 2021.09.10.13.16.14_veh-39_00832_00969 + - 2021.09.10.13.16.14_veh-39_00985_01084 + - 2021.09.10.13.16.14_veh-39_01119_01322 + - 2021.09.10.13.16.14_veh-39_01355_01600 + - 2021.09.10.13.55.04_veh-39_00015_00125 + - 2021.09.10.13.55.04_veh-39_00254_00341 + - 2021.09.10.13.55.04_veh-39_00363_00454 + - 2021.09.10.13.55.04_veh-39_00547_00614 + - 2021.09.10.13.55.04_veh-39_00639_00805 + - 2021.09.10.13.55.04_veh-39_00816_00959 + - 2021.09.10.13.55.04_veh-39_00972_01040 + - 2021.09.10.13.55.04_veh-39_01105_01209 + - 2021.09.10.13.55.04_veh-39_01220_01297 + - 2021.09.10.13.55.04_veh-39_01332_01397 + - 2021.09.10.13.55.04_veh-39_01464_01672 + - 2021.09.10.13.55.04_veh-39_01704_01776 + - 2021.09.10.13.58.49_veh-42_00016_00107 + - 2021.09.10.13.58.49_veh-42_00119_00710 + - 2021.09.10.13.58.49_veh-42_00729_01085 + - 2021.09.10.13.58.49_veh-42_01113_01188 + - 2021.09.10.13.58.49_veh-42_01246_01330 + - 2021.09.10.13.58.49_veh-42_01341_01452 + - 2021.09.10.13.58.49_veh-42_01475_01743 + - 2021.09.10.13.58.49_veh-42_01774_02175 + - 2021.09.10.13.58.49_veh-42_02196_02443 + - 2021.09.10.13.58.49_veh-42_02466_02539 + - 2021.09.10.14.26.51_veh-45_00045_00137 + - 2021.09.10.14.26.51_veh-45_00148_00318 + - 2021.09.10.14.26.51_veh-45_00329_00688 + - 2021.09.10.14.26.51_veh-45_00718_01060 + - 2021.09.10.14.26.51_veh-45_01229_01296 + - 2021.09.10.14.26.51_veh-45_01342_01541 + - 2021.09.10.14.44.55_veh-42_00031_00158 + - 2021.09.10.14.44.55_veh-42_00243_00683 + - 2021.09.10.14.44.55_veh-42_00694_00971 + - 2021.09.10.14.44.55_veh-42_01037_01315 + - 2021.09.10.14.44.55_veh-42_01340_01591 + - 2021.09.10.14.44.55_veh-42_01614_01799 + - 2021.09.10.14.44.55_veh-42_01810_01966 + - 2021.09.10.14.44.55_veh-42_01990_02149 + - 2021.09.10.14.44.55_veh-42_02160_02248 + - 2021.09.10.14.44.55_veh-42_02410_02472 + - 2021.09.10.14.44.55_veh-42_02529_02595 + - 2021.09.10.14.44.55_veh-42_02607_02762 + - 2021.09.10.15.00.33_veh-45_00040_00245 + - 2021.09.10.15.00.33_veh-45_00264_00358 + - 2021.09.10.15.00.33_veh-45_00402_00469 + - 2021.09.10.15.00.33_veh-45_00596_00800 + - 2021.09.10.15.00.33_veh-45_00997_01078 + - 2021.09.10.15.00.33_veh-45_01265_01432 + - 2021.09.10.15.00.33_veh-45_01495_01585 + - 2021.09.10.15.00.33_veh-45_01728_01886 + - 2021.09.10.15.10.09_veh-39_00016_00129 + - 2021.09.10.15.10.09_veh-39_00250_00399 + - 2021.09.10.15.10.09_veh-39_00446_00546 + - 2021.09.10.15.10.09_veh-39_00586_00676 + - 2021.09.10.15.10.09_veh-39_00725_00785 + - 2021.09.10.15.10.09_veh-39_01023_01255 + - 2021.09.10.15.10.09_veh-39_01273_01400 + - 2021.09.10.15.10.09_veh-39_01506_01600 + - 2021.09.10.15.10.09_veh-39_01612_01679 + - 2021.09.10.17.09.03_veh-42_00016_00105 + - 2021.09.10.17.09.03_veh-42_00116_00277 + - 2021.09.10.17.09.03_veh-42_00298_00768 + - 2021.09.10.17.09.03_veh-42_00818_01092 + - 2021.09.10.17.09.03_veh-42_01128_02369 + - 2021.09.10.17.09.03_veh-42_02391_02973 + - 2021.09.10.17.26.51_veh-39_00016_00215 + - 2021.09.10.17.26.51_veh-39_00270_00478 + - 2021.09.10.17.26.51_veh-39_00493_00963 + - 2021.09.10.17.26.51_veh-39_00984_01066 + - 2021.09.10.17.26.51_veh-39_01077_01143 + - 2021.09.10.17.26.51_veh-39_01201_01411 + - 2021.09.10.17.26.51_veh-39_01515_01778 + - 2021.09.10.18.03.24_veh-42_00067_01025 + - 2021.09.10.18.03.24_veh-42_01149_01310 + - 2021.09.10.18.03.24_veh-42_01371_01489 + - 2021.09.10.18.03.24_veh-42_01572_02075 + - 2021.09.10.18.03.24_veh-42_02099_02417 + - 2021.09.10.18.03.24_veh-42_02463_02576 + - 2021.09.10.18.03.24_veh-42_02596_02778 + - 2021.09.10.18.03.24_veh-42_02833_03385 + - 2021.09.10.18.03.24_veh-42_03480_03593 + - 2021.09.10.18.04.45_veh-39_00047_00174 + - 2021.09.10.18.04.45_veh-39_00404_00526 + - 2021.09.10.18.04.45_veh-39_00568_00876 + - 2021.09.10.18.04.45_veh-39_00907_01047 + - 2021.09.10.18.04.45_veh-39_01077_01259 + - 2021.09.10.18.04.45_veh-39_01313_01565 + - 2021.09.10.19.22.47_veh-42_00042_00138 + - 2021.09.10.19.22.47_veh-42_00173_00921 + - 2021.09.10.19.22.47_veh-42_00950_01051 + - 2021.09.10.19.22.47_veh-42_01062_02421 + - 2021.09.10.19.51.48_veh-39_00073_00264 + - 2021.09.10.19.51.48_veh-39_00340_00504 + - 2021.09.10.19.51.48_veh-39_00517_00810 + - 2021.09.10.19.51.48_veh-39_00823_00967 + - 2021.09.10.19.51.48_veh-39_00997_01252 + - 2021.09.10.19.51.48_veh-39_01266_01350 + - 2021.09.10.19.51.48_veh-39_01374_01451 + - 2021.09.10.20.06.13_veh-42_00032_01034 + - 2021.09.10.20.06.13_veh-42_01090_01664 + - 2021.09.10.20.06.13_veh-42_01793_01919 + - 2021.09.13.13.20.43_veh-45_00102_00230 + - 2021.09.13.13.20.43_veh-45_00291_00504 + - 2021.09.13.13.20.43_veh-45_00537_00674 + - 2021.09.13.13.20.43_veh-45_00721_00828 + - 2021.09.13.13.20.43_veh-45_00898_01049 + - 2021.09.13.13.20.43_veh-45_01110_01801 + - 2021.09.13.13.20.43_veh-45_02039_02166 + - 2021.09.13.13.20.43_veh-45_02247_02392 + - 2021.09.13.13.20.43_veh-45_02418_02734 + - 2021.09.13.13.20.43_veh-45_02765_02834 + - 2021.09.13.13.20.43_veh-45_02877_03335 + - 2021.09.13.13.20.43_veh-45_03358_03519 + - 2021.09.13.14.24.27_veh-45_00016_00108 + - 2021.09.13.14.24.27_veh-45_00131_00396 + - 2021.09.13.14.24.27_veh-45_00516_00591 + - 2021.09.13.14.24.27_veh-45_00765_00868 + - 2021.09.13.14.24.27_veh-45_00963_01115 + - 2021.09.13.14.24.27_veh-45_01126_01780 + - 2021.09.13.14.24.27_veh-45_01804_02112 + - 2021.09.13.14.24.27_veh-45_02136_02244 + - 2021.09.13.14.24.27_veh-45_02264_02424 + - 2021.09.13.14.24.27_veh-45_02488_02841 + - 2021.09.13.14.24.27_veh-45_02987_03098 + - 2021.09.13.18.55.23_veh-45_00096_00161 + - 2021.09.13.18.55.23_veh-45_00208_00352 + - 2021.09.13.18.55.23_veh-45_00424_00626 + - 2021.09.13.18.55.23_veh-45_00709_00841 + - 2021.09.13.18.55.23_veh-45_00880_01102 + - 2021.09.13.18.55.23_veh-45_01137_01272 + - 2021.09.13.18.55.23_veh-45_01374_01434 + - 2021.09.13.18.55.23_veh-45_01531_01607 + - 2021.09.13.18.55.23_veh-45_01635_01757 + - 2021.09.13.18.55.23_veh-45_01768_01842 + - 2021.09.13.18.55.23_veh-45_01858_02014 + - 2021.09.13.18.55.23_veh-45_02099_02822 + - 2021.09.13.18.55.23_veh-45_02833_02990 + - 2021.09.13.18.55.23_veh-45_03008_03274 + - 2021.09.13.19.54.06_veh-45_00016_00242 + - 2021.09.13.19.54.06_veh-45_00388_00454 + - 2021.09.13.19.54.06_veh-45_00564_00735 + - 2021.09.13.19.54.06_veh-45_00781_00843 + - 2021.09.13.19.54.06_veh-45_00884_01006 + - 2021.09.13.19.54.06_veh-45_01097_01852 + - 2021.09.13.19.54.06_veh-45_01864_02254 + - 2021.09.13.19.54.06_veh-45_02383_02486 + - 2021.09.13.19.54.06_veh-45_02619_02697 + - 2021.09.13.19.54.06_veh-45_02890_02967 + - 2021.09.13.19.54.06_veh-45_02984_03132 + - 2021.09.13.19.54.06_veh-45_03253_03386 + - 2021.09.13.21.07.09_veh-45_00035_00106 + - 2021.09.13.21.07.09_veh-45_00187_00339 + - 2021.09.13.21.07.09_veh-45_00362_00450 + - 2021.09.13.21.07.09_veh-45_00503_00734 + - 2021.09.13.21.07.09_veh-45_00809_00895 + - 2021.09.13.21.07.09_veh-45_00921_01061 + - 2021.09.13.21.07.09_veh-45_01127_01268 + - 2021.09.14.02.25.16_veh-51_00016_00266 + - 2021.09.14.02.25.16_veh-51_00324_00484 + - 2021.09.14.02.25.16_veh-51_00531_00622 + - 2021.09.14.02.25.16_veh-51_00681_00808 + - 2021.09.14.02.25.16_veh-51_00842_01187 + - 2021.09.14.02.25.16_veh-51_01283_01762 + - 2021.09.14.03.07.08_veh-51_00072_00300 + - 2021.09.14.03.07.08_veh-51_00346_00708 + - 2021.09.14.03.07.08_veh-51_00751_01109 + - 2021.09.14.03.07.08_veh-51_01182_01299 + - 2021.09.14.03.07.08_veh-51_01310_01433 + - 2021.09.14.03.07.08_veh-51_01524_01869 + - 2021.09.14.06.39.45_veh-51_00016_00184 + - 2021.09.14.06.39.45_veh-51_00207_00383 + - 2021.09.14.06.39.45_veh-51_00426_00516 + - 2021.09.14.06.39.45_veh-51_00557_00666 + - 2021.09.14.06.39.45_veh-51_00729_01316 + - 2021.09.14.06.39.45_veh-51_01353_01669 + - 2021.09.14.07.16.56_veh-51_00029_00405 + - 2021.09.14.07.16.56_veh-51_00451_00547 + - 2021.09.14.07.16.56_veh-51_00571_00907 + - 2021.09.14.07.16.56_veh-51_01005_01123 + - 2021.09.14.07.16.56_veh-51_01194_01258 + - 2021.09.14.07.16.56_veh-51_01281_01785 + - 2021.09.14.07.57.07_veh-51_00107_00602 + - 2021.09.14.07.57.07_veh-51_00684_01015 + - 2021.09.14.07.57.07_veh-51_01035_01599 + - 2021.09.14.07.57.07_veh-51_01616_01721 + - 2021.09.14.08.32.27_veh-51_00005_00218 + - 2021.09.14.08.32.27_veh-51_00262_00355 + - 2021.09.14.08.32.27_veh-51_00366_00431 + - 2021.09.14.08.32.27_veh-51_00442_00619 + - 2021.09.14.08.32.27_veh-51_00662_00730 + - 2021.09.14.08.32.27_veh-51_00762_01350 + - 2021.09.14.08.32.27_veh-51_01405_01466 + - 2021.09.14.08.32.27_veh-51_01477_01830 + - 2021.09.14.09.05.58_veh-51_00016_00218 + - 2021.09.14.09.05.58_veh-51_00319_00432 + - 2021.09.14.09.05.58_veh-51_00444_00906 + - 2021.09.14.09.05.58_veh-51_00932_01084 + - 2021.09.14.09.05.58_veh-51_01200_01312 + - 2021.09.14.09.05.58_veh-51_01395_01498 + - 2021.09.14.09.05.58_veh-51_01539_01721 + - 2021.09.14.11.51.00_veh-28_00099_00193 + - 2021.09.14.11.51.00_veh-28_00245_00460 + - 2021.09.14.11.51.00_veh-28_00471_00893 + - 2021.09.14.11.51.00_veh-28_00959_01025 + - 2021.09.14.12.36.28_veh-28_00015_00124 + - 2021.09.14.12.36.28_veh-28_00323_00475 + - 2021.09.14.12.36.28_veh-28_00613_00688 + - 2021.09.14.12.36.28_veh-28_00699_01194 + - 2021.09.14.12.36.28_veh-28_01223_01306 + - 2021.09.14.12.36.28_veh-28_01330_01577 + - 2021.09.14.13.09.53_veh-28_00016_00102 + - 2021.09.14.13.09.53_veh-28_00257_00394 + - 2021.09.14.13.09.53_veh-28_00422_00784 + - 2021.09.14.13.09.53_veh-28_00796_00895 + - 2021.09.14.13.09.53_veh-28_01043_01410 + - 2021.09.14.13.09.53_veh-28_01421_01808 + - 2021.09.14.13.10.57_veh-39_00105_00192 + - 2021.09.14.13.10.57_veh-39_00243_00345 + - 2021.09.14.13.10.57_veh-39_00358_00594 + - 2021.09.14.13.10.57_veh-39_00617_00710 + - 2021.09.14.13.10.57_veh-39_00776_00865 + - 2021.09.14.13.10.57_veh-39_00876_01052 + - 2021.09.14.13.10.57_veh-39_01079_01184 + - 2021.09.14.13.10.57_veh-39_01516_01779 + - 2021.09.14.13.47.58_veh-39_00015_00126 + - 2021.09.14.13.47.58_veh-39_00264_00408 + - 2021.09.14.13.47.58_veh-39_00432_00608 + - 2021.09.14.13.47.58_veh-39_00750_00903 + - 2021.09.14.13.47.58_veh-39_00930_01061 + - 2021.09.14.13.47.58_veh-39_01115_01285 + - 2021.09.14.13.47.58_veh-39_01329_01413 + - 2021.09.14.13.47.58_veh-39_01520_01716 + - 2021.09.14.13.47.58_veh-39_01788_01917 + - 2021.09.14.14.03.35_veh-28_00133_00340 + - 2021.09.14.14.03.35_veh-28_00394_00815 + - 2021.09.14.14.03.35_veh-28_00887_00956 + - 2021.09.14.14.03.35_veh-28_00968_01460 + - 2021.09.14.14.24.04_veh-39_00037_00174 + - 2021.09.14.14.24.04_veh-39_00190_00253 + - 2021.09.14.14.24.04_veh-39_00355_00431 + - 2021.09.14.14.24.04_veh-39_00476_00572 + - 2021.09.14.14.24.04_veh-39_00730_01566 + - 2021.09.14.14.34.34_veh-28_00112_00289 + - 2021.09.14.14.34.34_veh-28_00476_00802 + - 2021.09.14.14.34.34_veh-28_00825_00902 + - 2021.09.14.14.34.34_veh-28_00982_01049 + - 2021.09.14.14.34.34_veh-28_01144_01733 + - 2021.09.14.14.57.08_veh-39_00019_00091 + - 2021.09.14.14.57.08_veh-39_00103_00267 + - 2021.09.14.14.57.08_veh-39_00422_00497 + - 2021.09.14.14.57.08_veh-39_00645_00957 + - 2021.09.14.14.57.08_veh-39_00981_01089 + - 2021.09.14.14.57.08_veh-39_01114_01208 + - 2021.09.14.14.57.08_veh-39_01743_01808 + - 2021.09.14.15.07.04_veh-28_00178_00268 + - 2021.09.14.15.07.04_veh-28_00310_00418 + - 2021.09.14.15.07.04_veh-28_00430_00493 + - 2021.09.14.15.07.04_veh-28_00562_00820 + - 2021.09.14.15.07.04_veh-28_00872_00966 + - 2021.09.14.15.07.04_veh-28_01216_01351 + - 2021.09.14.15.07.04_veh-28_01363_01551 + - 2021.09.14.15.07.04_veh-28_01583_01700 + - 2021.09.14.15.39.07_veh-28_00005_00095 + - 2021.09.14.15.39.07_veh-28_00165_00286 + - 2021.09.14.15.39.07_veh-28_00321_00579 + - 2021.09.14.15.39.07_veh-28_00616_00722 + - 2021.09.14.15.39.07_veh-28_00969_01548 + - 2021.09.14.15.39.07_veh-28_01560_01784 + - 2021.09.14.16.12.27_veh-28_00388_00575 + - 2021.09.14.18.45.46_veh-28_00086_00155 + - 2021.09.14.18.45.46_veh-28_00213_00286 + - 2021.09.14.18.45.46_veh-28_00309_00456 + - 2021.09.14.18.45.46_veh-28_00579_00682 + - 2021.09.14.18.45.46_veh-28_00718_00836 + - 2021.09.14.18.45.46_veh-28_00847_01265 + - 2021.09.14.18.45.46_veh-28_01329_01447 + - 2021.09.14.18.45.46_veh-28_01842_01924 + - 2021.09.14.18.45.46_veh-28_01961_02082 + - 2021.09.14.18.45.46_veh-28_02165_02247 + - 2021.09.14.18.52.36_veh-39_00016_00254 + - 2021.09.14.18.52.36_veh-39_00277_00421 + - 2021.09.14.18.52.36_veh-39_00461_00647 + - 2021.09.14.18.52.36_veh-39_00700_01239 + - 2021.09.14.18.52.36_veh-39_01304_01415 + - 2021.09.14.18.52.36_veh-39_01444_01537 + - 2021.09.14.18.52.36_veh-39_01566_01727 + - 2021.09.14.18.52.36_veh-39_01908_02186 + - 2021.09.14.19.35.02_veh-39_00016_00144 + - 2021.09.14.19.35.02_veh-39_00204_00344 + - 2021.09.14.19.35.02_veh-39_00460_00601 + - 2021.09.14.19.35.02_veh-39_00618_00685 + - 2021.09.14.19.35.02_veh-39_00773_00876 + - 2021.09.14.19.35.02_veh-39_00967_01165 + - 2021.09.14.19.35.02_veh-39_01302_01657 + - 2021.09.14.19.35.02_veh-39_01684_01766 + - 2021.09.14.19.35.02_veh-39_01795_01912 + - 2021.09.14.19.35.02_veh-39_01958_02026 + - 2021.09.14.19.35.02_veh-39_02379_02469 + - 2021.09.14.19.35.02_veh-39_02497_02763 + - 2021.09.15.02.49.19_veh-53_00016_00088 + - 2021.09.15.02.49.19_veh-53_00129_00221 + - 2021.09.15.02.49.19_veh-53_00232_00383 + - 2021.09.15.02.49.19_veh-53_00431_00591 + - 2021.09.15.02.49.19_veh-53_00608_00754 + - 2021.09.15.02.49.19_veh-53_00772_00888 + - 2021.09.15.02.49.19_veh-53_00925_01029 + - 2021.09.15.02.49.19_veh-53_01085_01309 + - 2021.09.15.02.49.19_veh-53_01334_01442 + - 2021.09.15.02.49.19_veh-53_01494_01978 + - 2021.09.15.07.22.51_veh-49_00016_00341 + - 2021.09.15.07.22.51_veh-49_00478_00624 + - 2021.09.15.07.22.51_veh-49_00635_00863 + - 2021.09.15.07.22.51_veh-49_00884_01401 + - 2021.09.15.07.22.51_veh-49_01439_01863 + - 2021.09.15.07.34.38_veh-51_00027_00555 + - 2021.09.15.07.34.38_veh-51_00571_00709 + - 2021.09.15.07.34.38_veh-51_00735_01040 + - 2021.09.15.07.34.38_veh-51_01126_01460 + - 2021.09.15.07.34.38_veh-51_01531_01655 + - 2021.09.15.07.34.38_veh-51_01667_01757 + - 2021.09.15.08.03.05_veh-49_00022_00293 + - 2021.09.15.08.03.05_veh-49_00333_00398 + - 2021.09.15.08.03.05_veh-49_00584_00697 + - 2021.09.15.08.03.05_veh-49_00789_01265 + - 2021.09.15.08.03.05_veh-49_01305_01454 + - 2021.09.15.08.03.05_veh-49_01485_01729 + - 2021.09.15.08.09.44_veh-51_00051_00199 + - 2021.09.15.08.09.44_veh-51_00242_00461 + - 2021.09.15.08.09.44_veh-51_00707_01148 + - 2021.09.15.08.09.44_veh-51_01180_01457 + - 2021.09.15.08.09.44_veh-51_01584_01743 + - 2021.09.15.08.35.19_veh-49_00016_00737 + - 2021.09.15.08.35.19_veh-49_00773_00878 + - 2021.09.15.08.35.19_veh-49_00901_01023 + - 2021.09.15.08.35.19_veh-49_01064_01130 + - 2021.09.15.08.35.19_veh-49_01141_01289 + - 2021.09.15.08.35.19_veh-49_01303_01474 + - 2021.09.15.08.35.19_veh-49_01495_01932 + - 2021.09.15.08.44.21_veh-51_00016_00207 + - 2021.09.15.08.44.21_veh-51_00234_00589 + - 2021.09.15.08.44.21_veh-51_00675_00825 + - 2021.09.15.08.44.21_veh-51_00871_00933 + - 2021.09.15.08.44.21_veh-51_00990_01305 + - 2021.09.15.08.44.21_veh-51_01367_01463 + - 2021.09.15.08.44.21_veh-51_01508_01695 + - 2021.09.15.11.49.23_veh-28_00081_00237 + - 2021.09.15.11.49.23_veh-28_00280_00506 + - 2021.09.15.11.49.23_veh-28_00520_00669 + - 2021.09.15.11.49.23_veh-28_00767_00955 + - 2021.09.15.11.49.23_veh-28_01108_01493 + - 2021.09.15.11.49.23_veh-28_01869_02000 + - 2021.09.15.11.49.23_veh-28_02024_02091 + - 2021.09.15.11.49.23_veh-28_02192_02253 + - 2021.09.15.12.32.43_veh-28_00015_00093 + - 2021.09.15.12.32.43_veh-28_00202_00323 + - 2021.09.15.12.32.43_veh-28_00417_00527 + - 2021.09.15.12.32.43_veh-28_00625_00697 + - 2021.09.15.12.32.43_veh-28_00708_00866 + - 2021.09.15.12.32.43_veh-28_00973_01056 + - 2021.09.15.12.32.43_veh-28_01070_01157 + - 2021.09.15.12.32.43_veh-28_01238_01314 + - 2021.09.15.12.32.43_veh-28_01410_01501 + - 2021.09.15.12.32.43_veh-28_01513_01697 + - 2021.09.15.12.32.43_veh-28_02111_02342 + - 2021.09.15.12.49.18_veh-45_00179_00763 + - 2021.09.15.12.49.18_veh-45_00916_01109 + - 2021.09.15.12.49.18_veh-45_01155_01320 + - 2021.09.15.12.49.18_veh-45_01506_01599 + - 2021.09.15.12.49.18_veh-45_01738_01800 + - 2021.09.15.12.49.18_veh-45_01823_01896 + - 2021.09.15.13.06.21_veh-42_00016_00158 + - 2021.09.15.13.06.21_veh-42_00169_00749 + - 2021.09.15.13.06.21_veh-42_00834_01108 + - 2021.09.15.13.06.21_veh-42_01119_01413 + - 2021.09.15.13.06.21_veh-42_01435_01733 + - 2021.09.15.13.06.21_veh-42_01917_02000 + - 2021.09.15.13.06.21_veh-42_02037_02107 + - 2021.09.15.13.06.21_veh-42_02158_02283 + - 2021.09.15.13.06.21_veh-42_02310_02429 + - 2021.09.15.13.06.21_veh-42_02452_03092 + - 2021.09.15.13.06.21_veh-42_03166_03240 + - 2021.09.15.13.06.21_veh-42_03263_03326 + - 2021.09.15.13.06.21_veh-42_03355_03422 + - 2021.09.15.13.12.49_veh-39_00022_00104 + - 2021.09.15.13.12.49_veh-39_00135_00467 + - 2021.09.15.13.12.49_veh-39_00541_00634 + - 2021.09.15.13.12.49_veh-39_00645_00802 + - 2021.09.15.13.12.49_veh-39_01049_01301 + - 2021.09.15.13.12.49_veh-39_01329_01520 + - 2021.09.15.13.12.49_veh-39_01532_01687 + - 2021.09.15.13.16.40_veh-28_00088_00157 + - 2021.09.15.13.16.40_veh-28_00180_00257 + - 2021.09.15.13.16.40_veh-28_00366_00631 + - 2021.09.15.13.16.40_veh-28_00642_01267 + - 2021.09.15.13.16.40_veh-28_01343_01432 + - 2021.09.15.13.16.40_veh-28_01473_01612 + - 2021.09.15.13.16.40_veh-28_01817_01902 + - 2021.09.15.13.16.40_veh-28_02072_02166 + - 2021.09.15.13.16.40_veh-28_02198_02321 + - 2021.09.15.13.26.07_veh-45_00088_00251 + - 2021.09.15.13.26.07_veh-45_00278_00999 + - 2021.09.15.13.26.07_veh-45_01077_01297 + - 2021.09.15.13.26.07_veh-45_01436_01641 + - 2021.09.15.13.26.07_veh-45_01799_01907 + - 2021.09.15.13.26.07_veh-45_02081_02187 + - 2021.09.15.13.52.55_veh-39_00016_00122 + - 2021.09.15.13.52.55_veh-39_00134_00215 + - 2021.09.15.13.52.55_veh-39_00371_00631 + - 2021.09.15.13.52.55_veh-39_00643_00807 + - 2021.09.15.13.52.55_veh-39_00818_01335 + - 2021.09.15.13.52.55_veh-39_01385_01446 + - 2021.09.15.14.00.15_veh-28_00288_00408 + - 2021.09.15.14.00.15_veh-28_00420_00578 + - 2021.09.15.14.00.15_veh-28_00770_00852 + - 2021.09.15.14.00.15_veh-28_00895_00981 + - 2021.09.15.14.00.15_veh-28_01274_01543 + - 2021.09.15.14.00.15_veh-28_01611_01874 + - 2021.09.15.14.00.15_veh-28_01953_02255 + - 2021.09.15.14.18.26_veh-45_00020_00194 + - 2021.09.15.14.18.26_veh-45_00247_00684 + - 2021.09.15.14.18.26_veh-45_00737_00976 + - 2021.09.15.14.18.26_veh-45_00987_01261 + - 2021.09.15.14.18.26_veh-45_01302_01795 + - 2021.09.15.14.18.26_veh-45_01814_01926 + - 2021.09.15.14.18.26_veh-45_02082_02171 + - 2021.09.15.14.27.22_veh-39_00038_00414 + - 2021.09.15.14.27.22_veh-39_00473_00568 + - 2021.09.15.14.27.22_veh-39_00580_00654 + - 2021.09.15.14.27.22_veh-39_00665_00745 + - 2021.09.15.14.27.22_veh-39_00756_00838 + - 2021.09.15.14.27.22_veh-39_00868_01125 + - 2021.09.15.14.27.22_veh-39_01166_01252 + - 2021.09.15.14.27.22_veh-39_01281_01346 + - 2021.09.15.14.27.22_veh-39_01420_01480 + - 2021.09.15.14.27.22_veh-39_01491_01763 + - 2021.09.15.14.30.33_veh-42_00022_00436 + - 2021.09.15.14.30.33_veh-42_00503_00575 + - 2021.09.15.14.30.33_veh-42_00643_00919 + - 2021.09.15.14.30.33_veh-42_00990_01457 + - 2021.09.15.14.30.33_veh-42_01482_01675 + - 2021.09.15.14.30.33_veh-42_01686_01777 + - 2021.09.15.14.30.33_veh-42_01821_01974 + - 2021.09.15.14.30.33_veh-42_02003_02070 + - 2021.09.15.14.30.33_veh-42_02081_02170 + - 2021.09.15.14.30.33_veh-42_02192_02284 + - 2021.09.15.14.30.33_veh-42_02304_02447 + - 2021.09.15.14.30.33_veh-42_02562_02982 + - 2021.09.15.14.30.33_veh-42_03011_03336 + - 2021.09.15.14.50.05_veh-28_00083_00152 + - 2021.09.15.14.50.05_veh-28_00182_00253 + - 2021.09.15.14.50.05_veh-28_00389_00508 + - 2021.09.15.14.50.05_veh-28_00578_00896 + - 2021.09.15.14.50.05_veh-28_01187_01281 + - 2021.09.15.14.50.05_veh-28_01392_01458 + - 2021.09.15.14.50.05_veh-28_01511_01690 + - 2021.09.15.14.50.05_veh-28_01740_01833 + - 2021.09.15.14.50.05_veh-28_02133_02222 + - 2021.09.15.14.57.57_veh-45_00131_00294 + - 2021.09.15.14.57.57_veh-45_00346_01183 + - 2021.09.15.14.57.57_veh-45_01247_01413 + - 2021.09.15.14.57.57_veh-45_01461_01971 + - 2021.09.15.14.57.57_veh-45_02069_02157 + - 2021.09.15.14.57.57_veh-45_02327_02419 + - 2021.09.15.15.02.19_veh-39_00105_00203 + - 2021.09.15.15.02.19_veh-39_00214_00558 + - 2021.09.15.15.02.19_veh-39_00856_01095 + - 2021.09.15.15.02.19_veh-39_01107_01666 + - 2021.09.15.15.34.53_veh-28_00030_00128 + - 2021.09.15.15.34.53_veh-28_00365_00501 + - 2021.09.15.15.34.53_veh-28_00512_01084 + - 2021.09.15.15.34.53_veh-28_01133_01234 + - 2021.09.15.15.34.53_veh-28_01303_01395 + - 2021.09.15.15.34.53_veh-28_01533_01596 + - 2021.09.15.15.34.53_veh-28_01639_01805 + - 2021.09.15.15.34.53_veh-28_01820_02314 + - 2021.09.15.16.17.26_veh-28_00586_00712 + - 2021.09.15.16.17.26_veh-28_00772_00880 + - 2021.09.15.16.17.26_veh-28_00937_01074 + - 2021.09.15.16.17.26_veh-28_01085_01182 + - 2021.09.15.16.17.26_veh-28_01370_01439 + - 2021.09.15.16.17.26_veh-28_01450_01544 + - 2021.09.15.16.17.26_veh-28_01581_01740 + - 2021.09.15.16.51.15_veh-28_00005_00160 + - 2021.09.15.16.51.15_veh-28_00176_00329 + - 2021.09.15.16.51.15_veh-28_00357_00430 + - 2021.09.15.16.51.15_veh-28_01225_01302 + - 2021.09.15.16.51.15_veh-28_01468_01533 + - 2021.09.15.16.51.15_veh-28_01698_01775 + - 2021.09.15.17.01.41_veh-45_00015_00145 + - 2021.09.15.17.01.41_veh-45_00283_00398 + - 2021.09.15.17.01.41_veh-45_00425_01226 + - 2021.09.15.17.01.41_veh-45_01244_01395 + - 2021.09.15.17.01.41_veh-45_01468_01785 + - 2021.09.15.17.01.41_veh-45_01829_01938 + - 2021.09.15.17.41.38_veh-45_00011_00436 + - 2021.09.15.17.41.38_veh-45_00464_00986 + - 2021.09.15.17.41.38_veh-45_01009_01081 + - 2021.09.15.17.41.38_veh-45_01220_01289 + - 2021.09.15.17.41.38_veh-45_01466_01561 + - 2021.09.15.17.41.38_veh-45_01721_01814 + - 2021.09.15.18.28.05_veh-45_00196_00273 + - 2021.09.15.18.28.05_veh-45_00325_00528 + - 2021.09.15.18.28.05_veh-45_00561_01614 + - 2021.09.15.18.28.05_veh-45_01632_01720 + - 2021.09.15.18.28.05_veh-45_01731_01831 + - 2021.09.16.12.20.58_veh-28_00015_00090 + - 2021.09.16.12.20.58_veh-28_00134_00251 + - 2021.09.16.12.20.58_veh-28_00277_00356 + - 2021.09.16.12.20.58_veh-28_00499_00620 + - 2021.09.16.17.56.05_veh-28_00015_00137 + - 2021.09.16.17.56.05_veh-28_00352_00427 + - 2021.09.16.17.56.05_veh-28_00438_00628 + - 2021.09.16.17.56.05_veh-28_00698_00808 + - 2021.09.16.17.56.05_veh-28_00838_01096 + - 2021.09.16.17.56.05_veh-28_01120_01248 + - 2021.09.16.17.56.05_veh-28_01372_01558 + - 2021.09.16.17.56.05_veh-28_01593_01655 + - 2021.09.16.17.56.05_veh-28_01696_01792 + - 2021.09.16.17.56.05_veh-28_01803_02244 + - 2021.09.16.18.40.39_veh-28_00150_00303 + - 2021.09.16.18.40.39_veh-28_00467_00570 + - 2021.09.16.18.40.39_veh-28_00666_00807 + - 2021.09.16.18.40.39_veh-28_01032_01093 + - 2021.09.16.18.40.39_veh-28_01116_01303 + - 2021.09.16.18.40.39_veh-28_01342_01466 + - 2021.09.16.18.40.39_veh-28_01541_01799 + - 2021.09.16.18.40.39_veh-28_01871_01946 + - 2021.09.16.18.40.39_veh-28_02107_02255 + - 2021.09.17.11.45.23_veh-28_00015_00120 + - 2021.09.17.11.45.23_veh-28_00263_00344 + - 2021.09.17.11.45.23_veh-28_00377_00525 + - 2021.09.17.11.45.23_veh-28_00536_00876 + - 2021.09.17.11.45.23_veh-28_01149_01238 + - 2021.09.17.11.45.23_veh-28_01250_01357 + - 2021.09.17.11.45.23_veh-28_01451_01532 + - 2021.09.17.11.45.23_veh-28_01594_01754 + - 2021.09.17.12.23.40_veh-28_00149_00310 + - 2021.09.17.12.23.40_veh-28_00321_00409 + - 2021.09.17.12.23.40_veh-28_00493_00609 + - 2021.09.17.12.23.40_veh-28_00636_00708 + - 2021.09.17.12.23.40_veh-28_00719_00860 + - 2021.09.17.12.23.40_veh-28_00871_01129 + - 2021.09.17.12.23.40_veh-28_01492_01565 + - 2021.09.17.12.23.40_veh-28_01651_01753 + - 2021.09.17.12.58.10_veh-45_00028_00151 + - 2021.09.17.12.58.10_veh-45_00473_00641 + - 2021.09.17.12.58.10_veh-45_00693_00915 + - 2021.09.17.12.58.10_veh-45_01052_01117 + - 2021.09.17.12.58.10_veh-45_01150_01912 + - 2021.09.17.12.58.10_veh-45_01935_02062 + - 2021.09.17.12.58.10_veh-45_02654_02976 + - 2021.09.17.12.58.10_veh-45_02999_03169 + - 2021.09.17.12.58.10_veh-45_03273_03368 + - 2021.09.17.13.27.08_veh-42_00039_00128 + - 2021.09.17.13.27.08_veh-42_00224_00365 + - 2021.09.17.13.27.08_veh-42_00434_01037 + - 2021.09.17.13.27.08_veh-42_01062_01265 + - 2021.09.17.13.27.08_veh-42_01295_01490 + - 2021.09.17.13.47.10_veh-28_00020_00143 + - 2021.09.17.13.47.10_veh-28_00172_00294 + - 2021.09.17.13.47.10_veh-28_00560_00956 + - 2021.09.17.13.47.10_veh-28_01059_01121 + - 2021.09.17.13.47.10_veh-28_01155_01549 + - 2021.09.17.13.47.10_veh-28_01561_01762 + - 2021.09.17.13.47.10_veh-28_01975_02107 + - 2021.09.17.14.16.10_veh-42_00022_00109 + - 2021.09.17.14.16.10_veh-42_00206_00278 + - 2021.09.17.14.16.10_veh-42_00351_00579 + - 2021.09.17.14.16.10_veh-42_00590_00737 + - 2021.09.17.14.16.10_veh-42_00755_00870 + - 2021.09.17.14.16.10_veh-42_00933_01037 + - 2021.09.17.14.16.10_veh-42_01087_01281 + - 2021.09.17.14.16.10_veh-42_01303_01376 + - 2021.09.17.14.16.24_veh-45_00253_01317 + - 2021.09.17.14.16.24_veh-45_01340_01767 + - 2021.09.17.14.16.24_veh-45_01790_01961 + - 2021.09.17.14.16.24_veh-45_01972_02284 + - 2021.09.17.14.16.24_veh-45_02378_02497 + - 2021.09.17.14.16.24_veh-45_02522_02685 + - 2021.09.17.14.16.24_veh-45_02729_03014 + - 2021.09.17.14.28.18_veh-28_00165_00278 + - 2021.09.17.14.28.18_veh-28_00289_00357 + - 2021.09.17.14.28.18_veh-28_00403_00529 + - 2021.09.17.14.28.18_veh-28_00687_01125 + - 2021.09.17.14.28.18_veh-28_01221_01311 + - 2021.09.17.14.28.18_veh-28_01553_01690 + - 2021.09.17.14.28.18_veh-28_01724_01981 + - 2021.09.17.14.28.18_veh-28_02164_02257 + - 2021.09.17.14.49.23_veh-42_00135_00310 + - 2021.09.17.14.49.23_veh-42_00333_00624 + - 2021.09.17.14.49.23_veh-42_00690_00846 + - 2021.09.17.14.49.23_veh-42_00941_01023 + - 2021.09.17.14.49.23_veh-42_01181_01300 + - 2021.09.17.14.49.23_veh-42_01352_01463 + - 2021.09.17.14.49.23_veh-42_01486_01773 + - 2021.09.17.14.49.23_veh-42_01802_01942 + - 2021.09.17.14.49.23_veh-42_01963_02102 + - 2021.09.17.14.49.23_veh-42_02134_02209 + - 2021.09.17.14.49.23_veh-42_02280_02468 + - 2021.09.17.14.49.23_veh-42_02490_02635 + - 2021.09.17.14.49.23_veh-42_02715_02860 + - 2021.09.17.16.35.20_veh-45_00031_00099 + - 2021.09.17.16.35.20_veh-45_00226_00337 + - 2021.09.17.16.35.20_veh-45_00394_00540 + - 2021.09.17.16.35.20_veh-45_00698_00846 + - 2021.09.17.16.35.20_veh-45_01041_01191 + - 2021.09.17.16.35.20_veh-45_01218_01381 + - 2021.09.17.16.35.20_veh-45_01400_01477 + - 2021.09.17.16.35.20_veh-45_01509_01782 + - 2021.09.17.16.35.20_veh-45_02008_02115 + - 2021.09.17.16.35.20_veh-45_02292_02449 + - 2021.09.17.16.35.20_veh-45_02460_02539 + - 2021.09.17.16.35.20_veh-45_02564_02920 + - 2021.09.17.16.35.20_veh-45_02942_03004 + - 2021.09.17.16.35.20_veh-45_03025_03426 + - 2021.09.17.17.36.45_veh-45_00080_00288 + - 2021.09.17.17.36.45_veh-45_00338_00529 + - 2021.09.17.17.36.45_veh-45_00541_00814 + - 2021.09.17.17.36.45_veh-45_00837_01106 + - 2021.09.17.17.36.45_veh-45_01123_01184 + - 2021.09.17.18.16.32_veh-45_00016_00093 + - 2021.09.17.18.16.32_veh-45_00213_00869 + - 2021.09.17.18.16.32_veh-45_00893_01174 + - 2021.09.17.18.16.32_veh-45_01298_01365 + - 2021.09.17.18.16.32_veh-45_01447_01769 + - 2021.09.17.18.16.32_veh-45_02010_02121 + - 2021.09.17.18.16.32_veh-45_02155_02826 + - 2021.09.17.18.16.32_veh-45_02859_03225 + - 2021.09.17.18.16.32_veh-45_03240_03442 + - 2021.09.17.18.42.25_veh-08_00029_00784 + - 2021.09.17.18.42.25_veh-08_00847_01426 + - 2021.09.17.18.42.25_veh-08_01484_01749 + - 2021.09.17.18.42.25_veh-08_01760_02084 + - 2021.09.17.18.42.25_veh-08_02107_02454 + - 2021.09.17.18.42.25_veh-08_02465_02551 + - 2021.09.17.18.42.25_veh-08_02595_02819 + - 2021.09.17.19.20.02_veh-45_00046_00248 + - 2021.09.17.19.20.02_veh-45_00294_00395 + - 2021.09.17.19.20.02_veh-45_00427_00498 + - 2021.09.17.19.20.02_veh-45_00559_00692 + - 2021.09.17.19.20.02_veh-45_00721_00870 + - 2021.09.17.19.20.02_veh-45_00890_01067 + - 2021.09.17.19.20.02_veh-45_01091_01551 + - 2021.09.17.19.20.02_veh-45_01571_01654 + - 2021.09.17.19.20.02_veh-45_01707_02104 + - 2021.09.17.19.20.02_veh-45_02127_02479 + - 2021.09.17.19.20.02_veh-45_02502_02918 + - 2021.09.17.19.20.02_veh-45_03101_03221 + - 2021.09.17.19.20.02_veh-45_03274_03401 + - 2021.09.17.19.38.59_veh-08_00016_00115 + - 2021.09.17.19.38.59_veh-08_00199_01050 + - 2021.09.17.19.38.59_veh-08_01073_01512 + - 2021.09.17.19.38.59_veh-08_01524_02752 + - 2021.09.17.20.30.55_veh-08_00016_00390 + - 2021.09.17.20.30.55_veh-08_00419_00670 + - 2021.09.17.20.30.55_veh-08_00701_01555 + - 2021.09.17.20.30.55_veh-08_01566_02359 + - 2021.09.17.20.30.55_veh-08_02379_02544 + - 2021.09.17.20.30.55_veh-08_02644_02784 + - 2021.09.17.20.31.03_veh-45_00241_00454 + - 2021.09.17.20.31.03_veh-45_00476_00993 + - 2021.09.17.20.31.03_veh-45_01038_01394 + - 2021.09.17.20.31.03_veh-45_01405_01571 + - 2021.09.17.20.31.03_veh-45_01979_02085 + - 2021.09.20.05.27.41_veh-51_00063_00194 + - 2021.09.20.05.27.41_veh-51_00242_00485 + - 2021.09.20.05.27.41_veh-51_00613_00777 + - 2021.09.20.05.27.41_veh-51_00820_00987 + - 2021.09.20.05.27.41_veh-51_01001_01671 + - 2021.09.20.05.32.32_veh-49_00019_00175 + - 2021.09.20.05.32.32_veh-49_00250_00724 + - 2021.09.20.05.32.32_veh-49_00765_00943 + - 2021.09.20.05.32.32_veh-49_00958_01187 + - 2021.09.20.05.32.32_veh-49_01220_01386 + - 2021.09.20.05.32.32_veh-49_01397_01489 + - 2021.09.20.05.32.32_veh-49_01539_01798 + - 2021.09.20.05.32.32_veh-49_01823_01975 + - 2021.09.20.06.01.40_veh-51_00094_00483 + - 2021.09.20.06.01.40_veh-51_00565_00756 + - 2021.09.20.06.01.40_veh-51_00773_01197 + - 2021.09.20.06.01.40_veh-51_01267_01519 + - 2021.09.20.06.01.40_veh-51_01530_01748 + - 2021.09.20.06.09.46_veh-49_00104_00249 + - 2021.09.20.06.09.46_veh-49_00273_00437 + - 2021.09.20.06.09.46_veh-49_00474_00586 + - 2021.09.20.06.09.46_veh-49_00634_00711 + - 2021.09.20.06.09.46_veh-49_00738_00990 + - 2021.09.20.06.09.46_veh-49_01019_02158 + - 2021.09.20.06.51.19_veh-51_00082_00628 + - 2021.09.20.06.51.19_veh-51_00701_00840 + - 2021.09.20.06.51.19_veh-51_00905_00969 + - 2021.09.20.06.51.19_veh-51_01014_01139 + - 2021.09.20.06.51.19_veh-51_01225_01327 + - 2021.09.20.06.51.19_veh-51_01364_01776 + - 2021.09.20.07.00.11_veh-49_00169_00439 + - 2021.09.20.07.00.11_veh-49_00516_00687 + - 2021.09.20.07.00.11_veh-49_00723_01002 + - 2021.09.20.07.00.11_veh-49_01052_01193 + - 2021.09.20.07.00.11_veh-49_01204_01757 + - 2021.09.20.07.30.53_veh-51_00016_00276 + - 2021.09.20.07.30.53_veh-51_00313_00483 + - 2021.09.20.07.30.53_veh-51_00582_00646 + - 2021.09.20.07.30.53_veh-51_00711_00834 + - 2021.09.20.07.30.53_veh-51_00880_01019 + - 2021.09.20.07.30.53_veh-51_01071_01383 + - 2021.09.20.07.30.53_veh-51_01409_01780 + - 2021.09.20.07.35.30_veh-49_00008_00170 + - 2021.09.20.07.35.30_veh-49_00206_00419 + - 2021.09.20.07.35.30_veh-49_00454_00730 + - 2021.09.20.07.35.30_veh-49_00803_00955 + - 2021.09.20.07.35.30_veh-49_00979_01127 + - 2021.09.20.07.35.30_veh-49_01138_01199 + - 2021.09.20.07.35.30_veh-49_01211_01301 + - 2021.09.20.07.35.30_veh-49_01321_01501 + - 2021.09.20.07.35.30_veh-49_01513_01844 + - 2021.09.20.08.04.33_veh-51_00081_00208 + - 2021.09.20.08.04.33_veh-51_00242_00412 + - 2021.09.20.08.04.33_veh-51_00457_00607 + - 2021.09.20.08.04.33_veh-51_00645_00766 + - 2021.09.20.08.04.33_veh-51_00815_00883 + - 2021.09.20.08.04.33_veh-51_00896_00998 + - 2021.09.20.08.04.33_veh-51_01016_01087 + - 2021.09.20.08.04.33_veh-51_01101_01442 + - 2021.09.20.08.04.33_veh-51_01453_01700 + - 2021.09.20.08.09.06_veh-49_00050_00234 + - 2021.09.20.08.09.06_veh-49_00281_00481 + - 2021.09.20.08.09.06_veh-49_00504_00820 + - 2021.09.20.08.09.06_veh-49_00872_00945 + - 2021.09.20.08.09.06_veh-49_01024_01096 + - 2021.09.20.08.09.06_veh-49_01142_01507 + - 2021.09.20.08.09.06_veh-49_01518_01580 + - 2021.09.20.12.58.53_veh-42_00016_00125 + - 2021.09.20.12.58.53_veh-42_00221_00325 + - 2021.09.20.12.58.53_veh-42_00371_00667 + - 2021.09.20.12.58.53_veh-42_00699_00888 + - 2021.09.20.12.58.53_veh-42_00998_01463 + - 2021.09.20.12.58.53_veh-42_01503_01620 + - 2021.09.20.12.58.53_veh-42_01648_01873 + - 2021.09.20.12.58.53_veh-42_01902_02217 + - 2021.09.20.12.58.53_veh-42_02230_02361 + - 2021.09.20.12.58.53_veh-42_02440_02598 + - 2021.09.20.13.46.45_veh-42_00252_00316 + - 2021.09.20.13.46.45_veh-42_00401_00526 + - 2021.09.20.13.46.45_veh-42_00548_00790 + - 2021.09.20.13.46.45_veh-42_00822_01075 + - 2021.09.20.13.46.45_veh-42_01157_01690 + - 2021.09.20.13.46.45_veh-42_01712_02157 + - 2021.09.20.13.46.45_veh-42_02176_02268 + - 2021.09.20.13.46.45_veh-42_02535_02599 + - 2021.09.20.14.04.18_veh-08_00156_00218 + - 2021.09.20.14.04.18_veh-08_00245_00313 + - 2021.09.20.14.04.18_veh-08_00338_00407 + - 2021.09.20.14.04.18_veh-08_00479_00566 + - 2021.09.20.14.04.18_veh-08_00577_00779 + - 2021.09.20.14.04.18_veh-08_00801_01086 + - 2021.09.20.14.04.18_veh-08_01165_02197 + - 2021.09.20.14.04.18_veh-08_02300_02496 + - 2021.09.20.14.14.58_veh-28_00250_00331 + - 2021.09.20.14.14.58_veh-28_00372_00438 + - 2021.09.20.14.14.58_veh-28_00546_00670 + - 2021.09.20.14.14.58_veh-28_00694_01178 + - 2021.09.20.14.14.58_veh-28_01234_01332 + - 2021.09.20.14.14.58_veh-28_01344_01422 + - 2021.09.20.14.14.58_veh-28_01471_01631 + - 2021.09.20.14.38.07_veh-42_00122_00182 + - 2021.09.20.14.38.07_veh-42_00209_00309 + - 2021.09.20.14.38.07_veh-42_00379_00742 + - 2021.09.20.14.38.07_veh-42_00760_00955 + - 2021.09.20.14.38.07_veh-42_00980_01099 + - 2021.09.20.14.38.07_veh-42_01123_01320 + - 2021.09.20.14.38.07_veh-42_01338_01724 + - 2021.09.20.14.38.07_veh-42_01816_02113 + - 2021.09.20.14.38.07_veh-42_02132_02380 + - 2021.09.20.14.38.07_veh-42_02391_02463 + - 2021.09.20.14.38.07_veh-42_02474_02577 + - 2021.09.20.14.38.07_veh-42_02732_02824 + - 2021.09.20.14.50.11_veh-08_00016_01146 + - 2021.09.20.14.50.11_veh-08_01166_01238 + - 2021.09.20.14.50.11_veh-08_01265_01355 + - 2021.09.20.14.50.11_veh-08_01514_01640 + - 2021.09.20.14.50.32_veh-28_00037_00153 + - 2021.09.20.14.50.32_veh-28_00212_00476 + - 2021.09.20.14.50.32_veh-28_00657_00732 + - 2021.09.20.14.50.32_veh-28_00926_01130 + - 2021.09.20.14.50.32_veh-28_01193_01255 + - 2021.09.20.14.50.32_veh-28_01375_01585 + - 2021.09.20.14.50.32_veh-28_01596_01725 + - 2021.09.20.14.50.32_veh-28_01736_01869 + - 2021.09.20.15.31.58_veh-28_00106_00278 + - 2021.09.20.15.31.58_veh-28_00310_00383 + - 2021.09.20.15.31.58_veh-28_00469_01019 + - 2021.09.20.15.31.58_veh-28_01048_01187 + - 2021.09.20.15.31.58_veh-28_01212_01373 + - 2021.09.20.15.31.58_veh-28_01491_01645 + - 2021.09.20.17.01.23_veh-08_00252_00531 + - 2021.09.20.17.01.23_veh-08_00594_00708 + - 2021.09.20.17.01.23_veh-08_00764_00942 + - 2021.09.20.17.01.23_veh-08_00974_01766 + - 2021.09.20.17.01.23_veh-08_01943_02041 + - 2021.09.20.17.42.50_veh-08_00322_00551 + - 2021.09.20.17.42.50_veh-08_00585_00680 + - 2021.09.20.17.42.50_veh-08_00702_00908 + - 2021.09.20.17.42.50_veh-08_00931_01048 + - 2021.09.20.17.42.50_veh-08_01078_01775 + - 2021.09.20.18.02.54_veh-28_00040_00119 + - 2021.09.20.18.02.54_veh-28_00132_00201 + - 2021.09.20.18.02.54_veh-28_00323_00477 + - 2021.09.20.18.02.54_veh-28_00504_01168 + - 2021.09.20.18.02.54_veh-28_01244_01399 + - 2021.09.20.18.02.54_veh-28_01508_01622 + - 2021.09.20.18.02.54_veh-28_01668_01761 + - 2021.09.20.18.15.46_veh-08_00078_00230 + - 2021.09.20.18.15.46_veh-08_00448_00546 + - 2021.09.20.18.15.46_veh-08_00796_01182 + - 2021.09.20.18.15.46_veh-08_01197_01333 + - 2021.09.20.18.15.46_veh-08_01355_01523 + - 2021.09.20.18.15.46_veh-08_01534_01667 + - 2021.09.20.18.15.46_veh-08_01820_01912 + - 2021.09.20.18.39.40_veh-28_00016_00079 + - 2021.09.20.18.39.40_veh-28_00091_00437 + - 2021.09.20.18.39.40_veh-28_00448_00553 + - 2021.09.20.18.39.40_veh-28_00627_00776 + - 2021.09.20.18.39.40_veh-28_00834_00912 + - 2021.09.20.18.39.40_veh-28_01024_01143 + - 2021.09.20.18.39.40_veh-28_01257_01486 + - 2021.09.20.18.55.11_veh-08_00069_00483 + - 2021.09.20.18.55.11_veh-08_00514_00622 + - 2021.09.20.18.55.11_veh-08_00649_00828 + - 2021.09.20.18.55.11_veh-08_00839_01047 + - 2021.09.20.18.55.11_veh-08_01058_01373 + - 2021.09.20.18.55.11_veh-08_01713_01826 + - 2021.09.20.19.14.01_veh-28_00045_00139 + - 2021.09.20.19.14.01_veh-28_00260_00388 + - 2021.09.20.19.14.01_veh-28_00415_00714 + - 2021.09.20.19.14.01_veh-28_00727_00870 + - 2021.09.20.19.14.01_veh-28_00893_00981 + - 2021.09.20.19.14.01_veh-28_01013_01134 + - 2021.09.20.19.14.01_veh-28_01305_01415 + - 2021.09.20.19.14.01_veh-28_01430_01611 + - 2021.09.20.19.14.01_veh-28_01623_01705 + - 2021.09.20.19.38.32_veh-08_00032_00111 + - 2021.09.20.19.38.32_veh-08_00236_01202 + - 2021.09.20.19.38.32_veh-08_01264_01548 + - 2021.09.20.19.38.32_veh-08_01559_01704 + - 2021.09.20.19.38.32_veh-08_01727_02198 + - 2021.09.20.19.38.32_veh-08_02246_02569 + - 2021.09.20.19.38.32_veh-08_02581_02803 + - 2021.09.20.19.49.44_veh-28_00076_00171 + - 2021.09.20.19.49.44_veh-28_00423_01298 + - 2021.09.20.20.32.00_veh-08_00211_00332 + - 2021.09.20.20.32.00_veh-08_00399_00717 + - 2021.09.20.20.32.00_veh-08_00746_01631 + - 2021.09.20.20.32.00_veh-08_01655_01720 + - 2021.09.20.20.32.00_veh-08_01745_01991 + - 2021.09.20.20.32.00_veh-08_02014_02781 + - 2021.09.21.06.44.00_veh-49_00042_00342 + - 2021.09.21.06.44.00_veh-49_00378_00532 + - 2021.09.21.06.44.00_veh-49_00583_00711 + - 2021.09.21.06.44.00_veh-49_00722_00788 + - 2021.09.21.06.44.00_veh-49_00872_01469 + - 2021.09.21.06.44.00_veh-49_01499_01745 + - 2021.09.21.06.44.00_veh-49_01800_01868 + - 2021.09.21.06.44.00_veh-49_01879_01951 + - 2021.09.21.06.50.48_veh-51_00016_00233 + - 2021.09.21.06.50.48_veh-51_00275_00647 + - 2021.09.21.06.50.48_veh-51_00658_00857 + - 2021.09.21.06.50.48_veh-51_00945_01042 + - 2021.09.21.06.50.48_veh-51_01053_01170 + - 2021.09.21.06.50.48_veh-51_01182_01244 + - 2021.09.21.06.50.48_veh-51_01267_01484 + - 2021.09.21.06.50.48_veh-51_01500_01790 + - 2021.09.21.07.20.21_veh-49_00024_00190 + - 2021.09.21.07.20.21_veh-49_00207_00359 + - 2021.09.21.07.20.21_veh-49_00374_00568 + - 2021.09.21.07.20.21_veh-49_00605_00905 + - 2021.09.21.07.20.21_veh-49_01052_01170 + - 2021.09.21.07.20.21_veh-49_01182_01262 + - 2021.09.21.07.20.21_veh-49_01274_01505 + - 2021.09.21.07.20.21_veh-49_01547_01861 + - 2021.09.21.07.25.24_veh-51_00029_00299 + - 2021.09.21.07.25.24_veh-51_00322_00561 + - 2021.09.21.07.25.24_veh-51_00609_00828 + - 2021.09.21.07.25.24_veh-51_00840_01157 + - 2021.09.21.07.25.24_veh-51_01181_01580 + - 2021.09.21.07.25.24_veh-51_01600_01679 + - 2021.09.21.07.57.15_veh-49_00058_00400 + - 2021.09.21.07.57.15_veh-49_00451_00853 + - 2021.09.21.07.57.15_veh-49_00880_01047 + - 2021.09.21.07.57.15_veh-49_01131_01192 + - 2021.09.21.07.57.15_veh-49_01258_01355 + - 2021.09.21.07.57.15_veh-49_01457_01524 + - 2021.09.21.07.57.15_veh-49_01612_01743 + - 2021.09.21.07.57.15_veh-49_01882_01977 + - 2021.09.21.08.07.02_veh-51_00017_00464 + - 2021.09.21.08.07.02_veh-51_00589_00709 + - 2021.09.21.08.07.02_veh-51_00757_01318 + - 2021.09.21.08.07.02_veh-51_01379_01561 + - 2021.09.21.08.07.02_veh-51_01573_01707 + - 2021.09.21.08.07.02_veh-51_01747_01882 + - 2021.09.21.08.34.39_veh-49_00063_00191 + - 2021.09.21.08.34.39_veh-49_00248_00358 + - 2021.09.21.08.34.39_veh-49_00416_00717 + - 2021.09.21.08.34.39_veh-49_00744_00807 + - 2021.09.21.08.34.39_veh-49_00835_01118 + - 2021.09.21.08.34.39_veh-49_01265_01454 + - 2021.09.21.08.34.39_veh-49_01479_01720 + - 2021.09.21.08.34.39_veh-49_01782_01864 + - 2021.09.21.08.43.27_veh-51_00016_00186 + - 2021.09.21.08.43.27_veh-51_00291_00389 + - 2021.09.21.08.43.27_veh-51_00413_00533 + - 2021.09.21.08.43.27_veh-51_00562_00676 + - 2021.09.21.08.43.27_veh-51_00757_00839 + - 2021.09.21.08.43.27_veh-51_00882_01139 + - 2021.09.21.08.43.27_veh-51_01208_01315 + - 2021.09.21.08.43.27_veh-51_01501_01800 + - 2021.09.21.13.35.38_veh-28_00016_00140 + - 2021.09.21.13.35.38_veh-28_00153_00262 + - 2021.09.21.13.35.38_veh-28_00343_00486 + - 2021.09.21.13.35.38_veh-28_00497_00997 + - 2021.09.21.13.35.38_veh-28_01024_01190 + - 2021.09.21.13.35.38_veh-28_01203_01275 + - 2021.09.21.13.35.38_veh-28_01353_01457 + - 2021.09.21.13.35.38_veh-28_01469_01592 + - 2021.09.21.14.46.05_veh-28_00028_00141 + - 2021.09.21.14.46.05_veh-28_00289_00496 + - 2021.09.21.14.46.05_veh-28_00537_00597 + - 2021.09.21.14.46.05_veh-28_00626_01005 + - 2021.09.21.14.46.05_veh-28_01118_01182 + - 2021.09.21.14.46.05_veh-28_01221_01340 + - 2021.09.21.14.46.05_veh-28_01366_01555 + - 2021.09.21.16.42.24_veh-08_00517_00688 + - 2021.09.21.16.42.24_veh-08_00857_00944 + - 2021.09.21.16.42.24_veh-08_01083_01215 + - 2021.09.21.16.42.24_veh-08_01243_01526 + - 2021.09.21.16.42.24_veh-08_01600_01735 + - 2021.09.21.16.42.24_veh-08_01761_02092 + - 2021.09.21.16.42.24_veh-08_02115_02448 + - 2021.09.21.16.42.24_veh-08_02474_02610 + - 2021.09.21.16.42.24_veh-08_02630_02751 + - 2021.09.21.16.42.24_veh-08_02986_03066 + - 2021.09.21.17.53.12_veh-08_00363_00445 + - 2021.09.21.17.53.12_veh-08_00458_00526 + - 2021.09.21.17.53.12_veh-08_00549_00614 + - 2021.09.21.17.53.12_veh-08_00933_01331 + - 2021.09.21.17.53.12_veh-08_01345_01456 + - 2021.09.21.17.53.12_veh-08_01467_01534 + - 2021.09.21.17.53.12_veh-08_01609_01696 + - 2021.09.21.17.53.12_veh-08_01763_01841 + - 2021.09.21.17.53.12_veh-08_01885_02099 + - 2021.09.21.17.53.12_veh-08_02162_02346 + - 2021.09.21.17.53.12_veh-08_02362_02425 + - 2021.09.21.17.53.12_veh-08_02449_02583 + - 2021.09.21.17.53.12_veh-08_02608_02805 + - 2021.09.21.17.53.12_veh-08_02816_03170 + - 2021.09.21.17.53.12_veh-08_03196_03372 + - 2021.09.21.18.07.37_veh-45_00016_00092 + - 2021.09.21.18.07.37_veh-45_00118_00178 + - 2021.09.21.18.07.37_veh-45_00201_00262 + - 2021.09.21.18.07.37_veh-45_00286_00391 + - 2021.09.21.18.07.37_veh-45_00438_00626 + - 2021.09.21.18.07.37_veh-45_00652_00895 + - 2021.09.21.18.07.37_veh-45_00914_01090 + - 2021.09.21.18.07.37_veh-45_01141_01324 + - 2021.09.21.18.07.37_veh-45_01346_01639 + - 2021.09.21.18.07.37_veh-45_01666_01816 + - 2021.09.21.18.07.37_veh-45_01933_02017 + - 2021.09.21.18.07.37_veh-45_02117_02288 + - 2021.09.21.18.07.37_veh-45_02407_02541 + - 2021.09.21.18.11.36_veh-28_00015_00145 + - 2021.09.21.18.11.36_veh-28_00292_00411 + - 2021.09.21.18.11.36_veh-28_00487_00721 + - 2021.09.21.18.11.36_veh-28_00732_01598 + - 2021.09.21.18.11.36_veh-28_01610_01737 + - 2021.09.21.18.54.31_veh-45_00016_00108 + - 2021.09.21.18.54.31_veh-45_00132_00212 + - 2021.09.21.18.54.31_veh-45_00236_00572 + - 2021.09.21.18.54.31_veh-45_00595_00815 + - 2021.09.21.18.54.31_veh-45_00894_01246 + - 2021.09.21.18.54.31_veh-45_01367_01493 + - 2021.09.21.18.54.31_veh-45_01637_02127 + - 2021.09.21.18.54.31_veh-45_02138_02345 + - 2021.09.21.18.54.31_veh-45_02364_02447 + - 2021.09.21.18.54.31_veh-45_02502_02583 + - 2021.09.21.19.31.01_veh-28_00015_00188 + - 2021.09.21.19.31.01_veh-28_00215_00290 + - 2021.09.21.19.31.01_veh-28_00354_00629 + - 2021.09.21.19.31.01_veh-28_00640_00702 + - 2021.09.21.19.31.01_veh-28_00797_01241 + - 2021.09.21.19.31.01_veh-28_01273_01358 + - 2021.09.21.19.31.01_veh-28_01414_01491 + - 2021.09.21.19.41.31_veh-45_00015_00235 + - 2021.09.21.19.41.31_veh-45_00285_00503 + - 2021.09.21.19.41.31_veh-45_00522_00582 + - 2021.09.21.19.41.31_veh-45_00608_01295 + - 2021.09.21.19.41.31_veh-45_01431_01572 + - 2021.09.21.19.41.31_veh-45_01642_01766 + - 2021.09.21.19.41.31_veh-45_01828_02370 + - 2021.09.21.19.41.31_veh-45_02416_02592 + - 2021.09.21.20.04.35_veh-08_00344_00719 + - 2021.09.21.20.04.35_veh-08_00730_01024 + - 2021.09.21.20.04.35_veh-08_01047_01447 + - 2021.09.21.20.04.35_veh-08_01465_01640 + - 2021.09.21.20.04.35_veh-08_01935_02511 + - 2021.09.21.20.04.35_veh-08_02530_03191 + - 2021.09.21.20.04.35_veh-08_03266_03333 + - 2021.09.21.20.04.35_veh-08_03344_03472 + - 2021.09.21.20.37.06_veh-45_00016_00080 + - 2021.09.21.20.37.06_veh-45_00155_00357 + - 2021.09.21.20.37.06_veh-45_00379_00688 + - 2021.09.21.20.37.06_veh-45_00710_00958 + - 2021.09.21.20.37.06_veh-45_01013_01084 + - 2021.09.21.20.37.06_veh-45_01102_01228 + - 2021.09.21.20.37.06_veh-45_01268_01566 + - 2021.09.21.20.37.06_veh-45_01589_01678 + - 2021.09.21.20.37.06_veh-45_01696_01802 + - 2021.09.21.20.37.06_veh-45_01871_01958 + - 2021.09.23.13.07.52_veh-45_00355_00848 + - 2021.09.23.13.07.52_veh-45_00951_01100 + - 2021.09.23.13.07.52_veh-45_01211_01750 + - 2021.09.23.13.07.52_veh-45_01855_01969 + - 2021.09.23.13.07.52_veh-45_02125_02232 + - 2021.09.23.13.07.52_veh-45_02341_02549 + - 2021.09.23.13.54.40_veh-45_00068_00226 + - 2021.09.23.13.54.40_veh-45_00336_00398 + - 2021.09.23.13.54.40_veh-45_00472_00747 + - 2021.09.23.13.54.40_veh-45_00788_00903 + - 2021.09.23.13.54.40_veh-45_00929_01047 + - 2021.09.23.13.54.40_veh-45_01075_01256 + - 2021.09.23.13.54.40_veh-45_01383_01932 + - 2021.09.23.13.54.40_veh-45_02026_02129 + - 2021.09.23.13.54.40_veh-45_02221_02295 + - 2021.09.23.14.44.24_veh-45_00151_00217 + - 2021.09.23.14.44.24_veh-45_00246_00328 + - 2021.09.23.14.44.24_veh-45_00353_01052 + - 2021.09.23.14.44.24_veh-45_01116_01383 + - 2021.09.23.14.44.24_veh-45_01406_01497 + - 2021.09.23.14.44.24_veh-45_01525_02132 + - 2021.09.23.14.44.24_veh-45_02179_02379 + - 2021.09.23.14.44.24_veh-45_02409_02720 + - 2021.09.23.17.03.56_veh-45_00007_00143 + - 2021.09.23.17.03.56_veh-45_00277_00348 + - 2021.09.23.17.03.56_veh-45_00376_00623 + - 2021.09.23.17.03.56_veh-45_00645_00872 + - 2021.09.23.17.03.56_veh-45_00891_01489 + - 2021.09.23.17.03.56_veh-45_01512_01822 + - 2021.09.23.17.03.56_veh-45_01854_02115 + - 2021.09.23.17.03.56_veh-45_02200_02471 + - 2021.09.23.17.03.56_veh-45_02539_02937 + - 2021.09.23.17.57.13_veh-45_00008_00081 + - 2021.09.23.17.57.13_veh-45_00185_00248 + - 2021.09.23.17.57.13_veh-45_00260_00379 + - 2021.09.23.17.57.13_veh-45_00394_00511 + - 2021.09.23.17.57.13_veh-45_00596_00784 + - 2021.09.23.17.57.13_veh-45_00795_01020 + - 2021.09.23.17.57.13_veh-45_01039_01679 + - 2021.09.23.17.57.13_veh-45_01746_02191 + - 2021.09.23.17.57.13_veh-45_02202_02830 + - 2021.09.23.17.57.13_veh-45_02849_02930 + - 2021.09.23.18.34.30_veh-28_00163_00286 + - 2021.09.23.18.34.30_veh-28_00298_00965 + - 2021.09.23.18.34.30_veh-28_00978_01045 + - 2021.09.23.18.34.30_veh-28_01093_01401 + - 2021.09.23.18.34.30_veh-28_01417_01497 + - 2021.09.23.18.34.30_veh-28_01532_01667 + - 2021.09.23.18.57.19_veh-45_00016_00117 + - 2021.09.23.18.57.19_veh-45_00428_00826 + - 2021.09.23.18.57.19_veh-45_00853_01131 + - 2021.09.23.18.57.19_veh-45_01155_01723 + - 2021.09.23.18.57.19_veh-45_01763_02053 + - 2021.09.23.18.57.19_veh-45_02075_02318 + - 2021.09.23.18.57.19_veh-45_02403_02802 + - 2021.09.23.18.57.19_veh-45_02915_03011 + - 2021.09.23.19.11.12_veh-28_00025_00122 + - 2021.09.23.19.11.12_veh-28_00316_00439 + - 2021.09.23.19.11.12_veh-28_00555_00790 + - 2021.09.23.19.11.12_veh-28_00802_00909 + - 2021.09.23.19.11.12_veh-28_01112_01174 + - 2021.09.23.19.11.12_veh-28_01342_01447 + - 2021.09.23.19.11.12_veh-28_01678_01753 + - 2021.09.23.19.52.54_veh-45_00021_00168 + - 2021.09.23.19.52.54_veh-45_00192_00614 + - 2021.09.23.19.52.54_veh-45_00625_00830 + - 2021.09.23.19.52.54_veh-45_00849_01164 + - 2021.09.23.19.52.54_veh-45_01210_01479 + - 2021.09.23.19.52.54_veh-45_01490_01776 + - 2021.09.23.19.52.54_veh-45_01828_01902 + - 2021.09.23.19.52.54_veh-45_01923_02003 + - 2021.09.23.19.52.54_veh-45_02051_02116 + - 2021.09.23.20.37.33_veh-45_00075_00139 + - 2021.09.23.20.37.33_veh-45_00248_00379 + - 2021.09.23.20.37.33_veh-45_00487_01007 + - 2021.09.23.20.37.33_veh-45_01103_01309 + - 2021.09.23.20.37.33_veh-45_01455_01672 + - 2021.09.23.20.37.33_veh-45_01722_02000 + - 2021.09.23.20.37.33_veh-45_02087_02313 + - 2021.09.24.01.30.33_veh-53_00016_00513 + - 2021.09.24.01.30.33_veh-53_00551_01091 + - 2021.09.24.01.30.33_veh-53_01132_01650 + - 2021.09.24.01.30.33_veh-53_01690_01939 + - 2021.09.24.01.30.59_veh-49_00016_00462 + - 2021.09.24.01.30.59_veh-49_00502_00614 + - 2021.09.24.01.30.59_veh-49_00640_00777 + - 2021.09.24.01.30.59_veh-49_00788_01421 + - 2021.09.24.01.30.59_veh-49_01446_01816 + - 2021.09.24.02.05.53_veh-49_00030_00175 + - 2021.09.24.02.05.53_veh-49_00215_00725 + - 2021.09.24.02.05.53_veh-49_00777_00964 + - 2021.09.24.02.05.53_veh-49_00976_01390 + - 2021.09.24.02.05.53_veh-49_01432_01567 + - 2021.09.24.02.05.53_veh-49_01665_01728 + - 2021.09.24.02.09.56_veh-51_00016_00452 + - 2021.09.24.02.09.56_veh-51_00620_00712 + - 2021.09.24.02.09.56_veh-51_00861_01487 + - 2021.09.24.02.09.56_veh-51_01526_01777 + - 2021.09.24.02.09.56_veh-51_01851_01937 + - 2021.09.24.02.18.51_veh-53_00016_00287 + - 2021.09.24.02.18.51_veh-53_00334_00524 + - 2021.09.24.02.18.51_veh-53_00563_01021 + - 2021.09.24.02.18.51_veh-53_01034_01113 + - 2021.09.24.02.18.51_veh-53_01128_01303 + - 2021.09.24.02.18.51_veh-53_01332_01413 + - 2021.09.24.02.18.51_veh-53_01458_02011 + - 2021.09.24.02.51.37_veh-49_00016_00208 + - 2021.09.24.02.51.37_veh-49_00221_00372 + - 2021.09.24.02.51.37_veh-49_00420_00637 + - 2021.09.24.02.51.37_veh-49_00650_01050 + - 2021.09.24.02.51.37_veh-49_01080_01218 + - 2021.09.24.02.51.37_veh-49_01275_01731 + - 2021.09.24.03.04.27_veh-53_00062_00403 + - 2021.09.24.03.04.27_veh-53_00424_00609 + - 2021.09.24.03.04.27_veh-53_00650_01200 + - 2021.09.24.03.04.27_veh-53_01238_01466 + - 2021.09.24.03.04.27_veh-53_01487_01559 + - 2021.09.24.03.04.27_veh-53_01571_01674 + - 2021.09.24.03.04.27_veh-53_01686_01782 + - 2021.09.24.03.25.03_veh-49_00062_00130 + - 2021.09.24.03.25.03_veh-49_00141_00705 + - 2021.09.24.03.25.03_veh-49_00731_00952 + - 2021.09.24.03.25.03_veh-49_01035_01104 + - 2021.09.24.03.25.03_veh-49_01163_01835 + - 2021.09.24.03.34.47_veh-51_00016_00181 + - 2021.09.24.03.34.47_veh-51_00217_00299 + - 2021.09.24.03.34.47_veh-51_00350_00619 + - 2021.09.24.03.34.47_veh-51_00680_00805 + - 2021.09.24.03.34.47_veh-51_00827_01227 + - 2021.09.24.03.34.47_veh-51_01337_01939 + - 2021.09.24.03.41.25_veh-53_00016_00669 + - 2021.09.24.03.41.25_veh-53_00703_00816 + - 2021.09.24.03.41.25_veh-53_00914_01317 + - 2021.09.24.03.41.25_veh-53_01351_01775 + - 2021.09.24.03.59.37_veh-49_00155_00382 + - 2021.09.24.03.59.37_veh-49_00393_00588 + - 2021.09.24.03.59.37_veh-49_00738_01235 + - 2021.09.24.03.59.37_veh-49_01281_01488 + - 2021.09.24.03.59.37_veh-49_01510_01875 + - 2021.09.24.05.42.43_veh-53_00016_00263 + - 2021.09.24.05.42.43_veh-53_00314_00496 + - 2021.09.24.05.42.43_veh-53_00534_00753 + - 2021.09.24.05.42.43_veh-53_00798_01869 + - 2021.09.24.05.44.10_veh-51_00016_00304 + - 2021.09.24.05.44.10_veh-51_00315_00447 + - 2021.09.24.05.44.10_veh-51_00563_00731 + - 2021.09.24.05.44.10_veh-51_00789_01091 + - 2021.09.24.05.44.10_veh-51_01142_01387 + - 2021.09.24.05.44.10_veh-51_01418_01670 + - 2021.09.24.05.44.10_veh-51_01696_01774 + - 2021.09.24.05.44.10_veh-51_01788_01966 + - 2021.09.24.06.20.13_veh-53_00060_00183 + - 2021.09.24.06.20.13_veh-53_00247_00618 + - 2021.09.24.06.20.13_veh-53_00646_00815 + - 2021.09.24.06.20.13_veh-53_00857_00917 + - 2021.09.24.06.20.13_veh-53_00964_01162 + - 2021.09.24.06.20.13_veh-53_01173_01265 + - 2021.09.24.06.20.13_veh-53_01339_01405 + - 2021.09.24.06.20.13_veh-53_01603_01755 + - 2021.09.24.06.28.45_veh-51_00016_00178 + - 2021.09.24.06.28.45_veh-51_00277_00352 + - 2021.09.24.06.28.45_veh-51_00637_00811 + - 2021.09.24.06.28.45_veh-51_00905_01187 + - 2021.09.24.06.28.45_veh-51_01240_01355 + - 2021.09.24.06.28.45_veh-51_01447_01530 + - 2021.09.24.06.28.45_veh-51_01612_01984 + - 2021.09.24.06.58.44_veh-53_00143_00223 + - 2021.09.24.06.58.44_veh-53_00295_00798 + - 2021.09.24.06.58.44_veh-53_00858_00941 + - 2021.09.24.06.58.44_veh-53_00980_01354 + - 2021.09.24.06.58.44_veh-53_01436_01677 + - 2021.09.24.06.58.44_veh-53_01700_01788 + - 2021.09.24.07.27.21_veh-51_00016_00079 + - 2021.09.24.07.27.21_veh-51_00100_00236 + - 2021.09.24.07.27.21_veh-51_00267_00882 + - 2021.09.24.07.27.21_veh-51_00899_01011 + - 2021.09.24.07.27.21_veh-51_01037_01194 + - 2021.09.24.07.27.21_veh-51_01230_01510 + - 2021.09.24.07.27.21_veh-51_01592_01735 + - 2021.09.24.07.33.06_veh-53_00016_00198 + - 2021.09.24.07.33.06_veh-53_00245_00614 + - 2021.09.24.07.33.06_veh-53_00641_00940 + - 2021.09.24.07.33.06_veh-53_01084_01252 + - 2021.09.24.07.33.06_veh-53_01289_01392 + - 2021.09.24.07.33.06_veh-53_01403_01494 + - 2021.09.24.07.33.06_veh-53_01577_01668 + - 2021.09.24.08.02.36_veh-51_00016_00222 + - 2021.09.24.08.02.36_veh-51_00294_00513 + - 2021.09.24.08.02.36_veh-51_00528_01094 + - 2021.09.24.08.02.36_veh-51_01154_01341 + - 2021.09.24.08.02.36_veh-51_01352_01525 + - 2021.09.24.08.02.36_veh-51_01538_01833 + - 2021.09.24.08.11.46_veh-53_00016_00403 + - 2021.09.24.08.11.46_veh-53_00433_00750 + - 2021.09.24.08.11.46_veh-53_00762_01164 + - 2021.09.24.08.11.46_veh-53_01187_01522 + - 2021.09.24.08.11.46_veh-53_01546_01860 + - 2021.09.24.14.23.05_veh-45_00117_00197 + - 2021.09.24.14.23.05_veh-45_00212_00576 + - 2021.09.24.14.23.05_veh-45_00598_00790 + - 2021.09.24.14.23.05_veh-45_00811_01131 + - 2021.09.24.14.23.05_veh-45_01175_01453 + - 2021.09.24.14.23.05_veh-45_01475_01930 + - 2021.09.24.14.23.05_veh-45_01950_02113 + - 2021.09.24.14.23.05_veh-45_02144_02442 + - 2021.09.24.14.23.05_veh-45_02453_02817 + - 2021.09.24.14.23.05_veh-45_02839_03207 + - 2021.09.24.14.23.05_veh-45_03261_03406 + - 2021.09.24.14.23.05_veh-45_03426_03612 + - 2021.09.24.14.23.05_veh-45_03746_03893 + - 2021.09.24.16.44.47_veh-28_00016_00151 + - 2021.09.24.16.44.47_veh-28_00323_00439 + - 2021.09.24.16.44.47_veh-28_00454_01329 + - 2021.09.24.16.44.47_veh-28_01352_01576 + - 2021.09.24.16.44.47_veh-28_01630_01704 + - 2021.09.24.18.01.39_veh-28_00240_00335 + - 2021.09.24.18.01.39_veh-28_00414_00706 + - 2021.09.24.18.01.39_veh-28_00818_00930 + - 2021.09.24.18.01.39_veh-28_00966_01161 + - 2021.09.24.18.01.39_veh-28_01293_01361 + - 2021.09.24.18.01.39_veh-28_01386_01485 + - 2021.09.24.18.01.39_veh-28_01541_01739 + - 2021.09.24.18.01.39_veh-28_01752_01891 + - 2021.09.24.18.40.38_veh-28_00047_00120 + - 2021.09.24.18.40.38_veh-28_00249_00334 + - 2021.09.24.18.40.38_veh-28_00345_00415 + - 2021.09.24.18.40.38_veh-28_00470_00532 + - 2021.09.24.18.40.38_veh-28_00656_00823 + - 2021.09.24.18.40.38_veh-28_00835_01289 + - 2021.09.24.18.40.38_veh-28_01339_01405 + - 2021.09.24.18.40.38_veh-28_01463_01532 + - 2021.09.24.19.05.37_veh-48_00089_00275 + - 2021.09.24.19.05.37_veh-48_00442_00663 + - 2021.09.24.19.05.37_veh-48_00675_00819 + - 2021.09.24.19.05.37_veh-48_00830_00916 + - 2021.09.24.19.14.31_veh-28_00041_00177 + - 2021.09.24.19.14.31_veh-28_00234_00346 + - 2021.09.24.19.14.31_veh-28_00357_00548 + - 2021.09.24.19.14.31_veh-28_00589_00803 + - 2021.09.24.19.14.31_veh-28_00844_01024 + - 2021.09.24.19.14.31_veh-28_01048_01496 + - 2021.09.24.19.14.31_veh-28_01564_01723 + - 2021.09.25.00.18.41_veh-53_00016_00213 + - 2021.09.25.00.18.41_veh-53_00244_00390 + - 2021.09.25.00.18.41_veh-53_00421_00837 + - 2021.09.25.00.18.41_veh-53_00850_00980 + - 2021.09.25.00.18.41_veh-53_01011_01079 + - 2021.09.25.00.18.41_veh-53_01189_01366 + - 2021.09.25.00.18.41_veh-53_01388_01594 + - 2021.09.25.00.18.41_veh-53_01607_01873 + - 2021.09.25.00.19.33_veh-50_00019_00336 + - 2021.09.25.00.19.33_veh-50_00358_00883 + - 2021.09.25.00.19.33_veh-50_01001_01138 + - 2021.09.25.00.19.33_veh-50_01305_01833 + - 2021.09.25.00.19.33_veh-50_01884_02024 + - 2021.09.25.00.19.33_veh-50_02046_02196 + - 2021.09.25.00.53.42_veh-53_00035_00218 + - 2021.09.25.00.53.42_veh-53_00241_00683 + - 2021.09.25.00.53.42_veh-53_00717_00912 + - 2021.09.25.00.53.42_veh-53_01003_01399 + - 2021.09.25.00.53.42_veh-53_01418_01725 + - 2021.09.25.00.53.42_veh-53_01744_01808 + - 2021.09.25.00.59.24_veh-50_00067_00244 + - 2021.09.25.00.59.24_veh-50_00385_00524 + - 2021.09.25.00.59.24_veh-50_00546_00606 + - 2021.09.25.00.59.24_veh-50_00617_00748 + - 2021.09.25.00.59.24_veh-50_00769_00970 + - 2021.09.25.00.59.24_veh-50_01006_01145 + - 2021.09.25.00.59.24_veh-50_01198_01415 + - 2021.09.25.00.59.24_veh-50_01515_01849 + - 2021.09.25.01.07.09_veh-51_00016_00248 + - 2021.09.25.01.07.09_veh-51_00408_00562 + - 2021.09.25.01.07.09_veh-51_00609_00701 + - 2021.09.25.01.07.09_veh-51_00713_00931 + - 2021.09.25.01.32.01_veh-53_00026_00508 + - 2021.09.25.01.32.01_veh-53_00524_00688 + - 2021.09.25.01.32.01_veh-53_00767_00907 + - 2021.09.25.01.32.01_veh-53_00959_01073 + - 2021.09.25.01.32.01_veh-53_01084_01162 + - 2021.09.25.01.32.01_veh-53_01185_01342 + - 2021.09.25.01.32.01_veh-53_01353_01651 + - 2021.09.25.01.32.01_veh-53_01671_01786 + - 2021.09.25.01.32.01_veh-53_01797_01932 + - 2021.09.25.01.35.31_veh-50_00021_00099 + - 2021.09.25.01.35.31_veh-50_00115_00433 + - 2021.09.25.01.35.31_veh-50_00444_00891 + - 2021.09.25.01.35.31_veh-50_00917_01834 + - 2021.09.25.01.35.31_veh-50_01846_02010 + - 2021.09.25.02.07.45_veh-53_00016_00512 + - 2021.09.25.02.07.45_veh-53_00536_00649 + - 2021.09.25.02.07.45_veh-53_00660_00789 + - 2021.09.25.02.07.45_veh-53_00858_00989 + - 2021.09.25.02.07.45_veh-53_01050_01416 + - 2021.09.25.02.07.45_veh-53_01440_01731 + - 2021.09.25.02.07.45_veh-53_01742_01816 + - 2021.09.25.02.16.18_veh-50_00023_00102 + - 2021.09.25.02.16.18_veh-50_00132_00265 + - 2021.09.25.02.16.18_veh-50_00289_00475 + - 2021.09.25.02.16.18_veh-50_00491_00620 + - 2021.09.25.02.16.18_veh-50_00711_00778 + - 2021.09.25.02.16.18_veh-50_00886_01226 + - 2021.09.25.02.16.18_veh-50_01275_01372 + - 2021.09.25.02.16.18_veh-50_01410_01561 + - 2021.09.25.02.16.18_veh-50_01614_01693 + - 2021.09.25.02.16.18_veh-50_01704_01766 + - 2021.09.25.02.46.17_veh-49_00010_00208 + - 2021.09.25.02.46.17_veh-49_00221_00575 + - 2021.09.25.02.46.17_veh-49_00587_01129 + - 2021.09.25.02.46.17_veh-49_01140_01425 + - 2021.09.25.02.46.17_veh-49_01449_01514 + - 2021.09.25.02.46.17_veh-49_01537_01657 + - 2021.09.25.02.46.17_veh-49_01692_01754 + - 2021.09.25.02.46.17_veh-49_01781_01862 + - 2021.09.25.02.54.53_veh-50_00015_00638 + - 2021.09.25.02.54.53_veh-50_00671_00764 + - 2021.09.25.02.54.53_veh-50_00788_01100 + - 2021.09.25.02.54.53_veh-50_01111_01187 + - 2021.09.25.02.54.53_veh-50_01266_01572 + - 2021.09.25.02.54.53_veh-50_01613_01747 + - 2021.09.25.02.54.53_veh-50_01767_01960 + - 2021.09.25.03.29.48_veh-49_00016_00124 + - 2021.09.25.03.29.48_veh-49_00177_00540 + - 2021.09.25.03.29.48_veh-49_00554_00695 + - 2021.09.25.03.29.48_veh-49_00718_00801 + - 2021.09.25.03.29.48_veh-49_00812_01134 + - 2021.09.25.03.29.48_veh-49_01245_01510 + - 2021.09.25.03.29.48_veh-49_01526_01594 + - 2021.09.25.03.29.48_veh-49_01615_01792 + - 2021.09.25.03.30.46_veh-50_00016_00296 + - 2021.09.25.03.30.46_veh-50_00337_00437 + - 2021.09.25.03.30.46_veh-50_00466_00573 + - 2021.09.25.03.30.46_veh-50_00623_00730 + - 2021.09.25.03.30.46_veh-50_00775_01051 + - 2021.09.25.03.30.46_veh-50_01073_01277 + - 2021.09.25.03.30.46_veh-50_01324_01501 + - 2021.09.25.03.30.46_veh-50_01536_01896 + - 2021.09.25.03.56.10_veh-53_00026_00117 + - 2021.09.25.03.56.10_veh-53_00129_00463 + - 2021.09.25.03.56.10_veh-53_00494_00665 + - 2021.09.25.03.56.10_veh-53_00680_00766 + - 2021.09.25.03.56.10_veh-53_00777_00934 + - 2021.09.25.03.56.10_veh-53_01012_01851 + - 2021.09.25.04.03.42_veh-49_00015_00263 + - 2021.09.25.04.03.42_veh-49_00350_00691 + - 2021.09.25.04.03.42_veh-49_00704_00984 + - 2021.09.25.04.03.42_veh-49_01016_01336 + - 2021.09.25.04.03.42_veh-49_01495_01677 + - 2021.09.25.04.03.42_veh-49_01690_02006 + - 2021.09.27.00.26.37_veh-53_00016_00446 + - 2021.09.27.00.26.37_veh-53_00480_00636 + - 2021.09.27.00.26.37_veh-53_00678_00774 + - 2021.09.27.00.26.37_veh-53_00785_00864 + - 2021.09.27.00.26.37_veh-53_00972_01395 + - 2021.09.27.00.26.37_veh-53_01426_01752 + - 2021.09.27.00.53.55_veh-51_00016_00398 + - 2021.09.27.00.53.55_veh-51_00595_00795 + - 2021.09.27.00.53.55_veh-51_00807_00908 + - 2021.09.27.00.53.55_veh-51_00919_01201 + - 2021.09.27.00.53.55_veh-51_01212_01337 + - 2021.09.27.00.53.55_veh-51_01387_01574 + - 2021.09.27.00.53.55_veh-51_01585_01770 + - 2021.09.27.00.53.55_veh-51_01783_01875 + - 2021.09.27.00.53.55_veh-51_01909_02023 + - 2021.09.27.00.59.11_veh-53_00016_00422 + - 2021.09.27.00.59.11_veh-53_00450_00527 + - 2021.09.27.00.59.11_veh-53_00554_00894 + - 2021.09.27.00.59.11_veh-53_00919_00986 + - 2021.09.27.00.59.11_veh-53_00998_01527 + - 2021.09.27.00.59.11_veh-53_01591_01763 + - 2021.09.27.01.02.20_veh-50_00016_00242 + - 2021.09.27.01.02.20_veh-50_00257_00423 + - 2021.09.27.01.02.20_veh-50_00434_00627 + - 2021.09.27.01.02.20_veh-50_00686_00778 + - 2021.09.27.01.02.20_veh-50_00816_01462 + - 2021.09.27.01.02.20_veh-50_01487_01737 + - 2021.09.27.01.32.22_veh-51_00016_00422 + - 2021.09.27.01.32.22_veh-51_00569_00635 + - 2021.09.27.01.32.22_veh-51_00648_00857 + - 2021.09.27.01.32.22_veh-51_00962_01143 + - 2021.09.27.01.32.22_veh-51_01207_01707 + - 2021.09.27.01.35.14_veh-50_00016_00195 + - 2021.09.27.01.35.14_veh-50_00219_00582 + - 2021.09.27.01.35.14_veh-50_00593_00711 + - 2021.09.27.01.35.14_veh-50_00807_01196 + - 2021.09.27.01.35.14_veh-50_01230_01521 + - 2021.09.27.01.35.14_veh-50_01574_01636 + - 2021.09.27.01.35.14_veh-50_01647_01766 + - 2021.09.27.01.35.14_veh-50_01777_02326 + - 2021.09.27.01.35.14_veh-50_02413_02488 + - 2021.09.27.01.39.29_veh-53_00008_00240 + - 2021.09.27.01.39.29_veh-53_00269_00453 + - 2021.09.27.01.39.29_veh-53_00567_00735 + - 2021.09.27.01.39.29_veh-53_00810_01160 + - 2021.09.27.01.39.29_veh-53_01216_01295 + - 2021.09.27.01.39.29_veh-53_01312_01423 + - 2021.09.27.01.39.29_veh-53_01528_01724 + - 2021.09.27.02.07.30_veh-51_00066_00423 + - 2021.09.27.02.07.30_veh-51_00450_00522 + - 2021.09.27.02.07.30_veh-51_00572_00848 + - 2021.09.27.02.07.30_veh-51_00871_01058 + - 2021.09.27.02.07.30_veh-51_01121_01286 + - 2021.09.27.02.07.30_veh-51_01298_01548 + - 2021.09.27.02.07.30_veh-51_01573_01636 + - 2021.09.27.02.07.30_veh-51_01647_01761 + - 2021.09.27.02.07.30_veh-51_01795_01957 + - 2021.09.27.02.14.28_veh-53_00016_00163 + - 2021.09.27.02.14.28_veh-53_00218_00357 + - 2021.09.27.02.14.28_veh-53_00428_00732 + - 2021.09.27.02.14.28_veh-53_00766_00883 + - 2021.09.27.02.14.28_veh-53_00977_01379 + - 2021.09.27.02.14.28_veh-53_01400_01779 + - 2021.09.27.02.25.35_veh-50_00016_00227 + - 2021.09.27.02.25.35_veh-50_00335_00401 + - 2021.09.27.02.25.35_veh-50_00416_00550 + - 2021.09.27.02.25.35_veh-50_00573_00711 + - 2021.09.27.02.25.35_veh-50_00732_00830 + - 2021.09.27.02.25.35_veh-50_00851_01142 + - 2021.09.27.02.25.35_veh-50_01153_01441 + - 2021.09.27.02.25.35_veh-50_01484_01597 + - 2021.09.27.02.25.35_veh-50_01614_02301 + - 2021.09.27.02.25.35_veh-50_02314_02392 + - 2021.09.27.02.44.44_veh-51_00016_00166 + - 2021.09.27.02.44.44_veh-51_00177_00326 + - 2021.09.27.02.44.44_veh-51_00457_01229 + - 2021.09.27.02.44.44_veh-51_01240_01331 + - 2021.09.27.02.44.44_veh-51_01375_01506 + - 2021.09.27.02.44.44_veh-51_01544_01831 + - 2021.09.27.03.01.16_veh-53_00016_00469 + - 2021.09.27.03.01.16_veh-53_00507_00742 + - 2021.09.27.03.01.16_veh-53_00789_00878 + - 2021.09.27.03.01.16_veh-53_00890_00961 + - 2021.09.27.03.01.16_veh-53_01069_01219 + - 2021.09.27.03.01.16_veh-53_01321_01530 + - 2021.09.27.03.01.16_veh-53_01585_01689 + - 2021.09.27.03.08.32_veh-49_00016_00229 + - 2021.09.27.03.08.32_veh-49_00246_00416 + - 2021.09.27.03.08.32_veh-49_00428_00573 + - 2021.09.27.03.08.32_veh-49_00641_00738 + - 2021.09.27.03.08.32_veh-49_00797_01414 + - 2021.09.27.03.08.32_veh-49_01499_01792 + - 2021.09.27.03.10.15_veh-50_00030_00184 + - 2021.09.27.03.10.15_veh-50_00226_00332 + - 2021.09.27.03.10.15_veh-50_00354_00461 + - 2021.09.27.03.10.15_veh-50_00486_00976 + - 2021.09.27.03.10.15_veh-50_01018_01086 + - 2021.09.27.03.10.15_veh-50_01140_01211 + - 2021.09.27.03.10.15_veh-50_01341_01900 + - 2021.09.27.03.10.15_veh-50_01934_02237 + - 2021.09.27.03.10.15_veh-50_02327_02412 + - 2021.09.27.03.10.15_veh-50_02647_02745 + - 2021.09.27.03.33.50_veh-53_00016_00083 + - 2021.09.27.03.33.50_veh-53_00109_00272 + - 2021.09.27.03.33.50_veh-53_00291_00587 + - 2021.09.27.03.33.50_veh-53_00694_01088 + - 2021.09.27.03.33.50_veh-53_01203_01471 + - 2021.09.27.03.33.50_veh-53_01496_01794 + - 2021.09.27.03.36.01_veh-51_00016_00085 + - 2021.09.27.03.36.01_veh-51_00114_00524 + - 2021.09.27.03.36.01_veh-51_00617_00813 + - 2021.09.27.03.36.01_veh-51_00883_01034 + - 2021.09.27.03.36.01_veh-51_01138_01216 + - 2021.09.27.03.36.01_veh-51_01494_01577 + - 2021.09.27.03.36.01_veh-51_01589_01738 + - 2021.09.27.03.45.53_veh-49_00015_00254 + - 2021.09.27.03.45.53_veh-49_00291_00397 + - 2021.09.27.03.45.53_veh-49_00573_00899 + - 2021.09.27.03.45.53_veh-49_00937_01221 + - 2021.09.27.03.45.53_veh-49_01233_01337 + - 2021.09.27.03.45.53_veh-49_01387_01846 + - 2021.09.27.04.05.07_veh-50_00005_00313 + - 2021.09.27.04.05.07_veh-50_00339_00650 + - 2021.09.27.04.05.07_veh-50_00661_00836 + - 2021.09.27.04.05.07_veh-50_00869_00968 + - 2021.09.27.04.05.07_veh-50_01004_01852 + - 2021.09.27.04.07.22_veh-53_00057_00214 + - 2021.09.27.04.07.22_veh-53_00248_00413 + - 2021.09.27.04.07.22_veh-53_00490_00642 + - 2021.09.27.04.07.22_veh-53_00693_01167 + - 2021.09.27.04.07.22_veh-53_01202_01327 + - 2021.09.27.04.07.22_veh-53_01373_01832 + - 2021.09.27.04.11.41_veh-51_00016_00092 + - 2021.09.27.04.11.41_veh-51_00110_00247 + - 2021.09.27.04.11.41_veh-51_00258_00365 + - 2021.09.27.04.11.41_veh-51_00376_00715 + - 2021.09.27.04.11.41_veh-51_00727_00953 + - 2021.09.27.04.11.41_veh-51_00997_01158 + - 2021.09.27.04.11.41_veh-51_01213_01349 + - 2021.09.27.04.11.41_veh-51_01377_01508 + - 2021.09.27.04.11.41_veh-51_01561_01876 + - 2021.09.27.05.48.55_veh-50_00016_00182 + - 2021.09.27.05.48.55_veh-50_00204_00376 + - 2021.09.27.05.48.55_veh-50_00388_00622 + - 2021.09.27.07.01.13_veh-53_00005_00280 + - 2021.09.27.07.01.13_veh-53_00325_00419 + - 2021.09.27.07.01.13_veh-53_00462_00532 + - 2021.09.27.07.01.13_veh-53_00543_00893 + - 2021.09.27.07.01.13_veh-53_01009_01091 + - 2021.09.27.07.01.13_veh-53_01119_01207 + - 2021.09.27.07.05.30_veh-50_00016_00307 + - 2021.09.27.07.05.30_veh-50_00339_00400 + - 2021.09.27.07.05.30_veh-50_00411_00507 + - 2021.09.27.07.05.30_veh-50_00526_00810 + - 2021.09.27.07.05.30_veh-50_00821_00913 + - 2021.09.27.07.05.30_veh-50_00932_01120 + - 2021.09.27.07.05.30_veh-50_01138_01402 + - 2021.09.27.07.05.30_veh-50_01433_01508 + - 2021.09.27.07.05.30_veh-50_01535_01891 + - 2021.09.27.07.05.30_veh-50_01904_02422 + - 2021.09.27.07.31.47_veh-52_00071_00292 + - 2021.09.27.07.31.47_veh-52_00339_00532 + - 2021.09.27.07.31.47_veh-52_00545_01104 + - 2021.09.27.07.31.47_veh-52_01117_01762 + - 2021.09.27.07.38.19_veh-53_00016_00576 + - 2021.09.27.07.38.19_veh-53_00603_00751 + - 2021.09.27.07.38.19_veh-53_00951_01035 + - 2021.09.27.07.38.19_veh-53_01154_01272 + - 2021.09.27.07.38.19_veh-53_01297_01481 + - 2021.09.27.07.38.19_veh-53_01529_01627 + - 2021.09.27.07.40.58_veh-49_00061_00636 + - 2021.09.27.07.40.58_veh-49_00672_00769 + - 2021.09.27.07.40.58_veh-49_00786_00892 + - 2021.09.27.07.40.58_veh-49_00929_01282 + - 2021.09.27.07.40.58_veh-49_01351_01633 + - 2021.09.27.07.42.51_veh-51_00029_00237 + - 2021.09.27.07.42.51_veh-51_00276_00400 + - 2021.09.27.07.42.51_veh-51_00445_00658 + - 2021.09.27.07.42.51_veh-51_00672_00856 + - 2021.09.27.07.42.51_veh-51_00888_01032 + - 2021.09.27.07.42.51_veh-51_01076_01220 + - 2021.09.27.07.42.51_veh-51_01280_01387 + - 2021.09.27.07.42.51_veh-51_01423_01669 + - 2021.09.27.07.42.51_veh-51_01698_01789 + - 2021.09.27.07.51.20_veh-50_00013_00090 + - 2021.09.27.07.51.20_veh-50_00122_00300 + - 2021.09.27.07.51.20_veh-50_00311_00415 + - 2021.09.27.07.51.20_veh-50_00450_00736 + - 2021.09.27.07.51.20_veh-50_00763_00920 + - 2021.09.27.07.51.20_veh-50_00972_01156 + - 2021.09.27.07.51.20_veh-50_01186_01264 + - 2021.09.27.07.51.20_veh-50_01293_02048 + - 2021.09.27.07.51.20_veh-50_02099_02372 + - 2021.09.27.07.51.20_veh-50_02398_02758 + - 2021.09.27.08.03.54_veh-52_00068_00190 + - 2021.09.27.08.03.54_veh-52_00245_00391 + - 2021.09.27.08.03.54_veh-52_00418_00593 + - 2021.09.27.08.03.54_veh-52_00694_00858 + - 2021.09.27.08.03.54_veh-52_00993_01227 + - 2021.09.27.08.03.54_veh-52_01244_01390 + - 2021.09.27.08.03.54_veh-52_01401_01518 + - 2021.09.27.08.03.54_veh-52_01551_01790 + - 2021.09.27.14.45.42_veh-44_00016_01082 + - 2021.09.27.14.45.42_veh-44_01103_02583 + - 2021.09.27.14.45.42_veh-44_02609_03216 + - 2021.09.27.14.45.42_veh-44_03236_03434 + - 2021.09.27.15.14.56_veh-28_00046_00155 + - 2021.09.27.15.14.56_veh-28_00218_00799 + - 2021.09.27.15.14.56_veh-28_00964_01216 + - 2021.09.27.15.14.56_veh-28_01278_01536 + - 2021.09.27.15.14.56_veh-28_01656_01806 + - 2021.09.27.15.14.56_veh-28_02030_02178 + - 2021.09.27.15.14.56_veh-28_02328_02471 + - 2021.09.27.15.14.56_veh-28_02500_02650 + - 2021.09.27.15.14.56_veh-28_02674_02745 + - 2021.09.27.17.06.43_veh-44_00039_00106 + - 2021.09.27.17.06.43_veh-44_00237_00336 + - 2021.09.27.17.06.43_veh-44_00367_00821 + - 2021.09.27.17.06.43_veh-44_00840_00946 + - 2021.09.27.17.06.43_veh-44_01021_01754 + - 2021.09.27.17.06.43_veh-44_01765_01929 + - 2021.09.27.17.06.43_veh-44_02104_02189 + - 2021.09.27.17.06.43_veh-44_02335_02445 + - 2021.09.27.17.24.22_veh-28_00044_00255 + - 2021.09.27.17.24.22_veh-28_00349_00508 + - 2021.09.27.17.24.22_veh-28_00519_01118 + - 2021.09.27.17.24.22_veh-28_01152_01394 + - 2021.09.27.17.24.22_veh-28_01492_01590 + - 2021.09.27.17.24.22_veh-28_01686_02029 + - 2021.09.27.17.24.22_veh-28_02339_02470 + - 2021.09.27.17.52.47_veh-44_00016_00742 + - 2021.09.27.17.52.47_veh-44_00763_00839 + - 2021.09.27.17.52.47_veh-44_00913_00985 + - 2021.09.27.17.52.47_veh-44_01131_01267 + - 2021.09.27.17.52.47_veh-44_01407_01524 + - 2021.09.27.17.52.47_veh-44_01631_02044 + - 2021.09.27.17.52.47_veh-44_02062_02160 + - 2021.09.27.17.52.47_veh-44_02192_02552 + - 2021.09.27.18.16.33_veh-28_00042_00195 + - 2021.09.27.18.16.33_veh-28_00223_00486 + - 2021.09.27.18.16.33_veh-28_00564_00842 + - 2021.09.27.18.16.33_veh-28_00875_01073 + - 2021.09.27.18.16.33_veh-28_01085_01361 + - 2021.09.27.18.16.33_veh-28_01385_01452 + - 2021.09.27.18.16.33_veh-28_01601_02196 + - 2021.09.27.18.16.33_veh-28_02281_02453 + - 2021.09.27.18.16.33_veh-28_02488_02551 + - 2021.09.27.18.16.33_veh-28_02632_02720 + - 2021.09.27.18.51.35_veh-44_00016_00103 + - 2021.09.27.18.51.35_veh-44_00246_00358 + - 2021.09.27.18.51.35_veh-44_00369_01255 + - 2021.09.27.18.51.35_veh-44_01266_01414 + - 2021.09.27.18.51.35_veh-44_01543_01638 + - 2021.09.27.18.51.35_veh-44_01817_01921 + - 2021.09.27.18.51.35_veh-44_02009_02370 + - 2021.09.27.18.51.35_veh-44_02405_02850 + - 2021.09.27.19.43.19_veh-44_00016_00587 + - 2021.09.27.19.43.19_veh-44_00607_00690 + - 2021.09.27.19.43.19_veh-44_00770_01582 + - 2021.09.27.19.50.50_veh-28_00041_00190 + - 2021.09.27.19.50.50_veh-28_00217_00429 + - 2021.09.27.19.50.50_veh-28_00521_00798 + - 2021.09.27.19.50.50_veh-28_00820_00890 + - 2021.09.27.19.50.50_veh-28_00946_01032 + - 2021.09.27.19.50.50_veh-28_01044_01241 + - 2021.09.27.19.50.50_veh-28_01280_01507 + - 2021.09.27.19.50.50_veh-28_01519_01675 + - 2021.09.27.19.50.50_veh-28_01726_02483 + - 2021.09.27.19.50.50_veh-28_02622_02730 + - 2021.09.28.00.35.22_veh-49_00016_00601 + - 2021.09.28.00.35.22_veh-49_00638_00869 + - 2021.09.28.00.35.22_veh-49_01071_01138 + - 2021.09.28.00.35.22_veh-49_01228_01318 + - 2021.09.28.00.35.22_veh-49_01339_01524 + - 2021.09.28.00.35.22_veh-49_01547_01658 + - 2021.09.28.00.37.22_veh-53_00016_00387 + - 2021.09.28.00.37.22_veh-53_00415_00851 + - 2021.09.28.00.37.22_veh-53_00893_00953 + - 2021.09.28.00.37.22_veh-53_00989_01251 + - 2021.09.28.00.37.22_veh-53_01349_01421 + - 2021.09.28.00.37.22_veh-53_01433_01890 + - 2021.09.28.00.58.30_veh-50_00016_00203 + - 2021.09.28.00.58.30_veh-50_00257_00333 + - 2021.09.28.00.58.30_veh-50_00395_00566 + - 2021.09.28.00.58.30_veh-50_00578_00709 + - 2021.09.28.00.58.30_veh-50_00778_01074 + - 2021.09.28.00.58.30_veh-50_01222_01330 + - 2021.09.28.00.58.30_veh-50_01341_01442 + - 2021.09.28.00.58.30_veh-50_01454_01524 + - 2021.09.28.00.58.30_veh-50_01552_01904 + - 2021.09.28.01.07.00_veh-49_00016_00372 + - 2021.09.28.01.07.00_veh-49_00407_00632 + - 2021.09.28.01.07.00_veh-49_00754_00959 + - 2021.09.28.01.07.00_veh-49_00977_01050 + - 2021.09.28.01.07.00_veh-49_01067_01423 + - 2021.09.28.01.07.00_veh-49_01443_01729 + - 2021.09.28.01.14.43_veh-53_00016_00500 + - 2021.09.28.01.14.43_veh-53_00525_00622 + - 2021.09.28.01.14.43_veh-53_00648_00797 + - 2021.09.28.01.14.43_veh-53_00808_01029 + - 2021.09.28.01.14.43_veh-53_01063_01186 + - 2021.09.28.01.14.43_veh-53_01199_01687 + - 2021.09.28.01.14.43_veh-53_01735_01818 + - 2021.09.28.01.36.44_veh-50_00026_00134 + - 2021.09.28.01.36.44_veh-50_00168_00246 + - 2021.09.28.01.36.44_veh-50_00299_00742 + - 2021.09.28.01.36.44_veh-50_00758_00853 + - 2021.09.28.01.36.44_veh-50_00895_01083 + - 2021.09.28.01.36.44_veh-50_01104_01451 + - 2021.09.28.01.36.44_veh-50_01463_01716 + - 2021.09.28.01.47.51_veh-49_00016_00115 + - 2021.09.28.01.47.51_veh-49_00245_00391 + - 2021.09.28.01.47.51_veh-49_00553_01127 + - 2021.09.28.01.47.51_veh-49_01139_01279 + - 2021.09.28.01.47.51_veh-49_01395_01575 + - 2021.09.28.01.47.51_veh-49_01586_01785 + - 2021.09.28.01.47.51_veh-49_01807_02111 + - 2021.09.28.01.50.04_veh-53_00028_00429 + - 2021.09.28.01.50.04_veh-53_00478_00619 + - 2021.09.28.01.50.04_veh-53_00658_00805 + - 2021.09.28.01.50.04_veh-53_00816_01000 + - 2021.09.28.01.50.04_veh-53_01024_01510 + - 2021.09.28.01.50.04_veh-53_01521_01644 + - 2021.09.28.01.50.04_veh-53_01676_01903 + - 2021.09.28.02.16.28_veh-50_00016_00194 + - 2021.09.28.02.16.28_veh-50_00389_00451 + - 2021.09.28.02.16.28_veh-50_00465_00722 + - 2021.09.28.02.16.28_veh-50_00742_00863 + - 2021.09.28.02.16.28_veh-50_00910_01010 + - 2021.09.28.02.16.28_veh-50_01022_01126 + - 2021.09.28.02.16.28_veh-50_01315_01689 + - 2021.09.28.02.16.28_veh-50_01722_01840 + - 2021.09.28.02.16.28_veh-50_01861_01964 + - 2021.09.28.02.26.27_veh-49_00016_00478 + - 2021.09.28.02.26.27_veh-49_00510_00729 + - 2021.09.28.02.26.27_veh-49_00778_00908 + - 2021.09.28.02.26.27_veh-49_00922_01020 + - 2021.09.28.02.26.27_veh-49_01063_01186 + - 2021.09.28.02.26.27_veh-49_01199_01514 + - 2021.09.28.02.26.27_veh-49_01565_01714 + - 2021.09.28.02.47.24_veh-53_00016_00162 + - 2021.09.28.02.47.24_veh-53_00241_00386 + - 2021.09.28.02.47.24_veh-53_00438_00693 + - 2021.09.28.02.47.24_veh-53_00769_01309 + - 2021.09.28.02.47.24_veh-53_01364_01464 + - 2021.09.28.02.47.24_veh-53_01512_01758 + - 2021.09.28.02.54.23_veh-50_00022_00183 + - 2021.09.28.02.54.23_veh-50_00216_00351 + - 2021.09.28.02.54.23_veh-50_00374_00542 + - 2021.09.28.02.54.23_veh-50_00601_01065 + - 2021.09.28.02.54.23_veh-50_01095_01610 + - 2021.09.28.02.54.23_veh-50_01632_01764 + - 2021.09.28.02.54.23_veh-50_01795_01890 + - 2021.09.28.02.55.36_veh-51_00011_00205 + - 2021.09.28.02.55.36_veh-51_00230_00454 + - 2021.09.28.02.55.36_veh-51_00494_00585 + - 2021.09.28.02.55.36_veh-51_00620_00794 + - 2021.09.28.02.55.36_veh-51_00818_00964 + - 2021.09.28.02.55.36_veh-51_00986_01220 + - 2021.09.28.02.55.36_veh-51_01256_01420 + - 2021.09.28.02.55.36_veh-51_01456_01811 + - 2021.09.28.02.59.21_veh-49_00020_00460 + - 2021.09.28.02.59.21_veh-49_00526_00597 + - 2021.09.28.02.59.21_veh-49_00649_00994 + - 2021.09.28.02.59.21_veh-49_01009_01101 + - 2021.09.28.02.59.21_veh-49_01168_01299 + - 2021.09.28.02.59.21_veh-49_01310_01767 + - 2021.09.28.03.16.01_veh-52_00016_00121 + - 2021.09.28.03.16.01_veh-52_00142_00235 + - 2021.09.28.03.16.01_veh-52_00252_00357 + - 2021.09.28.03.16.01_veh-52_00368_00485 + - 2021.09.28.03.16.01_veh-52_00500_00614 + - 2021.09.28.03.16.01_veh-52_00633_00787 + - 2021.09.28.03.16.01_veh-52_00847_00960 + - 2021.09.28.03.16.01_veh-52_01024_01442 + - 2021.09.28.03.16.01_veh-52_01482_01707 + - 2021.09.28.03.16.01_veh-52_01732_01920 + - 2021.09.28.03.23.36_veh-53_00016_00157 + - 2021.09.28.03.23.36_veh-53_00236_00454 + - 2021.09.28.03.23.36_veh-53_00478_01209 + - 2021.09.28.03.23.36_veh-53_01265_01328 + - 2021.09.28.03.23.36_veh-53_01486_01573 + - 2021.09.28.03.23.36_veh-53_01625_01747 + - 2021.09.28.03.32.32_veh-49_00060_00183 + - 2021.09.28.03.32.32_veh-49_00232_00423 + - 2021.09.28.03.32.32_veh-49_00463_01123 + - 2021.09.28.03.32.32_veh-49_01188_01528 + - 2021.09.28.03.51.00_veh-52_00038_00118 + - 2021.09.28.03.51.00_veh-52_00149_00360 + - 2021.09.28.03.51.00_veh-52_00382_00594 + - 2021.09.28.03.51.00_veh-52_00614_00714 + - 2021.09.28.03.51.00_veh-52_00753_01045 + - 2021.09.28.03.51.00_veh-52_01079_01152 + - 2021.09.28.03.51.00_veh-52_01165_01522 + - 2021.09.28.03.51.00_veh-52_01586_01785 + - 2021.09.28.03.58.38_veh-53_00016_00107 + - 2021.09.28.03.58.38_veh-53_00120_00265 + - 2021.09.28.03.58.38_veh-53_00299_00415 + - 2021.09.28.03.58.38_veh-53_00463_00588 + - 2021.09.28.03.58.38_veh-53_00600_00918 + - 2021.09.28.03.58.38_veh-53_00929_01084 + - 2021.09.28.03.58.38_veh-53_01221_01546 + - 2021.09.28.03.58.38_veh-53_01571_01854 + - 2021.09.28.04.07.40_veh-50_00016_00081 + - 2021.09.28.04.07.40_veh-50_00107_00716 + - 2021.09.28.04.07.40_veh-50_00772_00966 + - 2021.09.28.04.07.40_veh-50_00982_01064 + - 2021.09.28.04.07.40_veh-50_01075_01137 + - 2021.09.28.04.07.40_veh-50_01197_01310 + - 2021.09.28.04.07.40_veh-50_01499_01855 + - 2021.09.28.05.46.14_veh-50_00016_00529 + - 2021.09.28.05.46.14_veh-50_00569_00734 + - 2021.09.28.05.46.14_veh-50_00770_00907 + - 2021.09.28.05.46.14_veh-50_01010_01501 + - 2021.09.28.05.46.14_veh-50_01538_01818 + - 2021.09.28.05.46.14_veh-50_01829_01929 + - 2021.09.28.05.47.15_veh-52_00016_00140 + - 2021.09.28.05.47.15_veh-52_00167_00330 + - 2021.09.28.05.47.15_veh-52_00450_00532 + - 2021.09.28.05.47.15_veh-52_00575_00806 + - 2021.09.28.05.47.15_veh-52_00832_01001 + - 2021.09.28.05.47.15_veh-52_01044_01122 + - 2021.09.28.05.47.15_veh-52_01188_01512 + - 2021.09.28.05.47.15_veh-52_01614_01692 + - 2021.09.28.05.47.15_veh-52_01784_01953 + - 2021.09.28.06.03.19_veh-49_00016_00474 + - 2021.09.28.06.03.19_veh-49_00509_00658 + - 2021.09.28.06.03.19_veh-49_00713_00804 + - 2021.09.28.06.03.19_veh-49_00832_00924 + - 2021.09.28.06.03.19_veh-49_00956_01430 + - 2021.09.28.06.03.19_veh-49_01445_01634 + - 2021.09.28.06.03.45_veh-53_00016_00321 + - 2021.09.28.06.03.45_veh-53_00354_00672 + - 2021.09.28.06.03.45_veh-53_00720_00801 + - 2021.09.28.06.03.45_veh-53_00864_00987 + - 2021.09.28.06.03.45_veh-53_00998_01236 + - 2021.09.28.06.03.45_veh-53_01325_01773 + - 2021.09.28.06.03.45_veh-53_01822_02219 + - 2021.09.28.06.03.45_veh-53_02365_02506 + - 2021.09.28.06.03.45_veh-53_02529_02659 + - 2021.09.28.06.03.45_veh-53_02714_02783 + - 2021.09.28.06.24.06_veh-50_00016_00280 + - 2021.09.28.06.24.06_veh-50_00291_00582 + - 2021.09.28.06.24.06_veh-50_00625_00808 + - 2021.09.28.06.24.06_veh-50_01023_01123 + - 2021.09.28.06.24.06_veh-50_01246_01829 + - 2021.09.28.06.25.45_veh-52_00016_00383 + - 2021.09.28.06.25.45_veh-52_00410_00933 + - 2021.09.28.06.25.45_veh-52_00977_01624 + - 2021.09.28.06.41.34_veh-49_00015_00335 + - 2021.09.28.06.41.34_veh-49_00355_00621 + - 2021.09.28.06.41.34_veh-49_00649_00837 + - 2021.09.28.06.41.34_veh-49_00879_00954 + - 2021.09.28.06.41.34_veh-49_00966_01160 + - 2021.09.28.06.41.34_veh-49_01186_01248 + - 2021.09.28.06.41.34_veh-49_01307_01377 + - 2021.09.28.06.41.34_veh-49_01467_01687 + - 2021.09.28.06.53.26_veh-53_00066_00412 + - 2021.09.28.06.53.26_veh-53_00520_00586 + - 2021.09.28.06.53.26_veh-53_00630_01268 + - 2021.09.28.06.53.26_veh-53_01285_01404 + - 2021.09.28.06.53.26_veh-53_01502_01562 + - 2021.09.28.06.53.26_veh-53_01573_01658 + - 2021.09.28.06.53.26_veh-53_01760_01851 + - 2021.09.28.06.53.26_veh-53_01908_02329 + - 2021.09.28.06.53.26_veh-53_02387_02469 + - 2021.09.28.06.53.26_veh-53_02534_02669 + - 2021.09.28.06.59.11_veh-50_00016_00262 + - 2021.09.28.06.59.11_veh-50_00348_00478 + - 2021.09.28.06.59.11_veh-50_00524_01038 + - 2021.09.28.06.59.11_veh-50_01183_01262 + - 2021.09.28.06.59.11_veh-50_01295_01421 + - 2021.09.28.06.59.11_veh-50_01445_01792 + - 2021.09.28.07.07.41_veh-52_00016_00158 + - 2021.09.28.07.07.41_veh-52_00192_00317 + - 2021.09.28.07.07.41_veh-52_00331_00449 + - 2021.09.28.07.07.41_veh-52_00495_00717 + - 2021.09.28.07.07.41_veh-52_00756_00821 + - 2021.09.28.07.07.41_veh-52_00870_01007 + - 2021.09.28.07.07.41_veh-52_01048_01135 + - 2021.09.28.07.07.41_veh-52_01162_01241 + - 2021.09.28.07.07.41_veh-52_01265_01383 + - 2021.09.28.07.07.41_veh-52_01435_01646 + - 2021.09.28.07.07.41_veh-52_01660_01760 + - 2021.09.28.07.50.17_veh-50_00016_00251 + - 2021.09.28.07.50.17_veh-50_00269_00387 + - 2021.09.28.07.50.17_veh-50_00406_00513 + - 2021.09.28.07.50.17_veh-50_00654_00796 + - 2021.09.28.07.50.17_veh-50_00807_00918 + - 2021.09.28.07.50.17_veh-50_00978_01190 + - 2021.09.28.07.50.17_veh-50_01351_01442 + - 2021.09.28.07.50.17_veh-50_01592_01798 + - 2021.09.28.07.52.25_veh-52_00016_00285 + - 2021.09.28.07.52.25_veh-52_00361_00623 + - 2021.09.28.07.52.25_veh-52_00720_00820 + - 2021.09.28.07.52.25_veh-52_00862_00962 + - 2021.09.28.07.52.25_veh-52_01054_01165 + - 2021.09.28.07.52.25_veh-52_01246_01839 + - 2021.09.28.08.00.58_veh-49_00016_00322 + - 2021.09.28.08.00.58_veh-49_00398_00992 + - 2021.09.28.08.00.58_veh-49_01037_01136 + - 2021.09.28.08.00.58_veh-49_01219_01385 + - 2021.09.28.08.00.58_veh-49_01405_01504 + - 2021.09.28.08.00.58_veh-49_01567_01635 + - 2021.09.28.08.05.03_veh-53_00016_00639 + - 2021.09.28.08.05.03_veh-53_00689_00777 + - 2021.09.28.08.05.03_veh-53_00837_00980 + - 2021.09.28.08.05.03_veh-53_01005_01169 + - 2021.09.28.08.05.03_veh-53_01193_01331 + - 2021.09.28.08.05.03_veh-53_01342_01573 + - 2021.09.28.08.05.03_veh-53_01671_01911 + - 2021.09.28.08.05.03_veh-53_01952_02298 + - 2021.09.28.08.05.03_veh-53_02361_02484 + - 2021.09.28.08.05.03_veh-53_02512_02636 + - 2021.09.28.08.23.59_veh-50_00115_00298 + - 2021.09.28.08.23.59_veh-50_00323_00626 + - 2021.09.28.08.23.59_veh-50_00696_00814 + - 2021.09.28.08.23.59_veh-50_00887_01013 + - 2021.09.28.08.23.59_veh-50_01037_01201 + - 2021.09.28.08.23.59_veh-50_01291_01390 + - 2021.09.28.08.23.59_veh-50_01429_01722 + - 2021.09.28.08.27.17_veh-52_00016_00427 + - 2021.09.28.08.27.17_veh-52_00472_00664 + - 2021.09.28.08.27.17_veh-52_00683_00838 + - 2021.09.28.08.27.17_veh-52_00850_01094 + - 2021.09.28.08.27.17_veh-52_01114_01301 + - 2021.09.28.08.27.17_veh-52_01327_01841 + - 2021.09.28.08.53.05_veh-53_00141_00347 + - 2021.09.28.08.53.05_veh-53_00375_00543 + - 2021.09.28.08.53.05_veh-53_00582_00678 + - 2021.09.28.08.53.05_veh-53_00701_00880 + - 2021.09.28.08.53.05_veh-53_00910_00991 + - 2021.09.28.08.53.05_veh-53_01054_01191 + - 2021.09.28.08.53.05_veh-53_01234_01321 + - 2021.09.28.08.53.05_veh-53_01332_01430 + - 2021.09.28.08.53.05_veh-53_01617_01978 + - 2021.09.28.09.08.39_veh-52_00079_00197 + - 2021.09.28.09.08.39_veh-52_00221_00404 + - 2021.09.28.09.08.39_veh-52_00468_00606 + - 2021.09.28.09.08.39_veh-52_00723_00820 + - 2021.09.28.09.08.39_veh-52_00878_00947 + - 2021.09.28.09.08.39_veh-52_01041_01373 + - 2021.09.28.09.08.39_veh-52_01397_01823 + - 2021.09.28.13.06.14_veh-28_00242_00327 + - 2021.09.28.13.06.14_veh-28_00350_00564 + - 2021.09.28.13.06.14_veh-28_00636_01181 + - 2021.09.28.13.06.14_veh-28_01192_01316 + - 2021.09.28.13.06.14_veh-28_01329_01405 + - 2021.09.28.13.06.14_veh-28_01579_01781 + - 2021.09.28.13.24.06_veh-44_00043_00707 + - 2021.09.28.13.24.06_veh-44_00726_01083 + - 2021.09.28.13.24.06_veh-44_01102_01289 + - 2021.09.28.13.24.06_veh-44_01300_01737 + - 2021.09.28.13.24.06_veh-44_01757_01977 + - 2021.09.28.13.24.06_veh-44_01995_02739 + - 2021.09.28.13.24.06_veh-44_02759_02879 + - 2021.09.28.13.24.06_veh-44_02970_03103 + - 2021.09.28.13.45.15_veh-28_00016_00086 + - 2021.09.28.13.45.15_veh-28_00132_00310 + - 2021.09.28.13.45.15_veh-28_00321_00421 + - 2021.09.28.13.45.15_veh-28_00433_00504 + - 2021.09.28.13.45.15_veh-28_00527_00616 + - 2021.09.28.13.45.15_veh-28_00628_00707 + - 2021.09.28.13.45.15_veh-28_00756_00838 + - 2021.09.28.14.23.32_veh-44_00047_00194 + - 2021.09.28.14.23.32_veh-44_00248_00309 + - 2021.09.28.14.23.32_veh-44_00337_00413 + - 2021.09.28.14.23.32_veh-44_00437_00870 + - 2021.09.28.14.23.32_veh-44_00888_01058 + - 2021.09.28.14.23.32_veh-44_01090_01406 + - 2021.09.28.14.23.32_veh-44_01423_01838 + - 2021.09.28.14.23.32_veh-44_01850_03029 + - 2021.09.28.15.17.00_veh-44_00016_00401 + - 2021.09.28.15.17.00_veh-44_00421_00660 + - 2021.09.28.15.17.00_veh-44_00682_00778 + - 2021.09.28.15.17.00_veh-44_00795_01892 + - 2021.09.28.15.17.00_veh-44_01916_02112 + - 2021.09.28.15.17.00_veh-44_02130_02201 + - 2021.09.28.15.17.00_veh-44_02215_02366 + - 2021.09.28.16.09.49_veh-44_00016_00099 + - 2021.09.28.16.09.49_veh-44_00255_00316 + - 2021.09.28.16.09.49_veh-44_00389_00715 + - 2021.09.28.16.09.49_veh-44_00738_00987 + - 2021.09.28.16.09.49_veh-44_01006_01236 + - 2021.09.28.16.09.49_veh-44_01347_01439 + - 2021.09.28.16.09.49_veh-44_01769_02126 + - 2021.09.28.16.09.49_veh-44_02149_02256 + - 2021.09.28.16.50.03_veh-44_00016_00283 + - 2021.09.28.16.50.03_veh-44_00421_00483 + - 2021.09.28.16.50.03_veh-44_00633_00758 + - 2021.09.28.16.50.03_veh-44_00782_01293 + - 2021.09.28.16.50.03_veh-44_01322_01746 + - 2021.09.28.16.50.03_veh-44_01850_01922 + - 2021.09.28.17.23.06_veh-28_00015_00086 + - 2021.09.28.17.23.06_veh-28_00098_00344 + - 2021.09.28.17.23.06_veh-28_00426_00581 + - 2021.09.28.17.23.06_veh-28_00606_00823 + - 2021.09.28.17.23.06_veh-28_00847_00940 + - 2021.09.28.17.23.06_veh-28_00962_01047 + - 2021.09.28.17.23.06_veh-28_01058_01128 + - 2021.09.28.17.43.06_veh-44_00019_00154 + - 2021.09.28.17.43.06_veh-44_00419_00492 + - 2021.09.28.17.43.06_veh-44_00563_01082 + - 2021.09.28.17.43.06_veh-44_01106_01852 + - 2021.09.28.18.22.59_veh-44_00016_00126 + - 2021.09.28.18.22.59_veh-44_00236_00685 + - 2021.09.28.18.22.59_veh-44_00696_00971 + - 2021.09.28.18.22.59_veh-44_00997_01880 + - 2021.09.28.18.57.35_veh-44_00016_00158 + - 2021.09.28.18.57.35_veh-44_00183_00356 + - 2021.09.28.18.57.35_veh-44_00427_00494 + - 2021.09.28.18.57.35_veh-44_00881_00994 + - 2021.09.28.18.57.35_veh-44_01064_01998 + - 2021.09.28.18.57.35_veh-44_02010_02187 + - 2021.09.28.18.57.35_veh-44_02305_02462 + - 2021.09.28.19.55.30_veh-44_00018_00120 + - 2021.09.28.19.55.30_veh-44_00395_01217 + - 2021.09.28.19.55.30_veh-44_01239_01384 + - 2021.09.28.19.55.30_veh-44_01613_01679 + - 2021.09.28.19.55.30_veh-44_01744_01819 + - 2021.09.28.19.55.30_veh-44_01885_01952 + - 2021.09.28.19.55.30_veh-44_01975_02507 + - 2021.09.28.19.55.30_veh-44_02530_03148 + - 2021.09.28.19.55.30_veh-44_03166_03330 + - 2021.09.28.19.55.30_veh-44_03364_03461 + - 2021.09.28.19.55.30_veh-44_03475_03538 + - 2021.09.29.00.19.12_veh-50_00016_00225 + - 2021.09.29.00.19.12_veh-50_00256_00543 + - 2021.09.29.00.19.12_veh-50_00567_00664 + - 2021.09.29.00.19.12_veh-50_00746_01345 + - 2021.09.29.00.19.12_veh-50_01385_01630 + - 2021.09.29.00.19.12_veh-50_01655_01818 + - 2021.09.29.00.31.17_veh-49_00016_00152 + - 2021.09.29.00.31.17_veh-49_00173_00456 + - 2021.09.29.00.31.17_veh-49_00579_01005 + - 2021.09.29.00.31.17_veh-49_01018_01591 + - 2021.09.29.00.50.02_veh-53_00005_00432 + - 2021.09.29.00.50.02_veh-53_00476_00605 + - 2021.09.29.00.50.02_veh-53_00655_01465 + - 2021.09.29.00.50.02_veh-53_01517_01873 + - 2021.09.29.00.56.05_veh-50_00016_00179 + - 2021.09.29.00.56.05_veh-50_00210_00451 + - 2021.09.29.00.56.05_veh-50_00468_00567 + - 2021.09.29.00.56.05_veh-50_00593_00825 + - 2021.09.29.00.56.05_veh-50_00867_00972 + - 2021.09.29.00.56.05_veh-50_01004_01641 + - 2021.09.29.00.56.05_veh-50_01665_01825 + - 2021.09.29.01.04.10_veh-49_00016_00642 + - 2021.09.29.01.04.10_veh-49_00669_00796 + - 2021.09.29.01.04.10_veh-49_00808_00872 + - 2021.09.29.01.04.10_veh-49_00883_01228 + - 2021.09.29.01.04.10_veh-49_01260_01759 + - 2021.09.29.01.25.56_veh-53_00052_00427 + - 2021.09.29.01.25.56_veh-53_00438_00513 + - 2021.09.29.01.25.56_veh-53_00695_00862 + - 2021.09.29.01.25.56_veh-53_00873_01066 + - 2021.09.29.01.25.56_veh-53_01092_01265 + - 2021.09.29.01.25.56_veh-53_01276_01576 + - 2021.09.29.01.25.56_veh-53_01587_01882 + - 2021.09.29.01.43.53_veh-50_00016_00384 + - 2021.09.29.01.43.53_veh-50_00398_00526 + - 2021.09.29.01.43.53_veh-50_00645_00944 + - 2021.09.29.01.43.53_veh-50_01047_01338 + - 2021.09.29.01.43.53_veh-50_01352_01506 + - 2021.09.29.01.43.53_veh-50_01617_01789 + - 2021.09.29.01.46.47_veh-49_00231_00912 + - 2021.09.29.01.46.47_veh-49_00923_01100 + - 2021.09.29.01.46.47_veh-49_01178_01669 + - 2021.09.29.02.20.31_veh-49_00016_00187 + - 2021.09.29.02.20.31_veh-49_00273_00433 + - 2021.09.29.02.20.31_veh-49_00487_00578 + - 2021.09.29.02.20.31_veh-49_00618_00694 + - 2021.09.29.02.20.31_veh-49_00705_00849 + - 2021.09.29.02.20.31_veh-49_00890_01332 + - 2021.09.29.02.20.31_veh-49_01361_01497 + - 2021.09.29.02.20.31_veh-49_01512_01595 + - 2021.09.29.02.20.31_veh-49_01631_01706 + - 2021.09.29.02.21.43_veh-50_00016_00092 + - 2021.09.29.02.21.43_veh-50_00127_00209 + - 2021.09.29.02.21.43_veh-50_00261_00369 + - 2021.09.29.02.21.43_veh-50_00383_00574 + - 2021.09.29.02.21.43_veh-50_00599_00726 + - 2021.09.29.02.21.43_veh-50_00750_00843 + - 2021.09.29.02.21.43_veh-50_00854_00948 + - 2021.09.29.02.21.43_veh-50_00959_01217 + - 2021.09.29.02.21.43_veh-50_01246_01757 + - 2021.09.29.02.47.23_veh-53_00016_00435 + - 2021.09.29.02.47.23_veh-53_00478_00603 + - 2021.09.29.02.47.23_veh-53_00681_00764 + - 2021.09.29.02.47.23_veh-53_00775_00945 + - 2021.09.29.02.47.23_veh-53_00991_01325 + - 2021.09.29.02.47.23_veh-53_01349_01639 + - 2021.09.29.02.47.23_veh-53_01651_01795 + - 2021.09.29.03.01.05_veh-50_00016_00288 + - 2021.09.29.03.01.05_veh-50_00299_00445 + - 2021.09.29.03.01.05_veh-50_00531_00606 + - 2021.09.29.03.01.05_veh-50_00797_01149 + - 2021.09.29.03.01.05_veh-50_01183_01251 + - 2021.09.29.03.01.05_veh-50_01289_01407 + - 2021.09.29.03.01.05_veh-50_01490_01596 + - 2021.09.29.03.01.05_veh-50_01607_01726 + - 2021.09.29.03.22.12_veh-53_00032_00117 + - 2021.09.29.03.22.12_veh-53_00154_00253 + - 2021.09.29.03.22.12_veh-53_00274_00367 + - 2021.09.29.03.22.12_veh-53_00425_00583 + - 2021.09.29.03.22.12_veh-53_00624_00754 + - 2021.09.29.03.22.12_veh-53_00804_00932 + - 2021.09.29.03.22.12_veh-53_00945_01009 + - 2021.09.29.03.22.12_veh-53_01033_01378 + - 2021.09.29.03.22.12_veh-53_01395_01621 + - 2021.09.29.03.22.12_veh-53_01663_01828 + - 2021.09.29.03.28.59_veh-52_00016_00228 + - 2021.09.29.03.28.59_veh-52_00239_00584 + - 2021.09.29.03.28.59_veh-52_00610_00919 + - 2021.09.29.03.28.59_veh-52_00931_01318 + - 2021.09.29.03.28.59_veh-52_01357_01535 + - 2021.09.29.03.28.59_veh-52_01563_01674 + - 2021.09.29.03.28.59_veh-52_01718_01859 + - 2021.09.29.03.28.59_veh-52_01872_01971 + - 2021.09.29.03.28.59_veh-52_01987_02075 + - 2021.09.29.03.28.59_veh-52_02108_02669 + - 2021.09.29.03.28.59_veh-52_02691_02915 + - 2021.09.29.03.36.01_veh-51_00016_00475 + - 2021.09.29.03.36.01_veh-51_00603_00675 + - 2021.09.29.03.36.01_veh-51_00761_00860 + - 2021.09.29.03.36.01_veh-51_00990_01229 + - 2021.09.29.03.36.01_veh-51_01254_01547 + - 2021.09.29.03.36.01_veh-51_01742_01822 + - 2021.09.29.03.38.25_veh-50_00005_00305 + - 2021.09.29.03.38.25_veh-50_00479_00577 + - 2021.09.29.03.38.25_veh-50_00720_00817 + - 2021.09.29.03.38.25_veh-50_00828_00910 + - 2021.09.29.03.38.25_veh-50_00947_01264 + - 2021.09.29.03.38.25_veh-50_01334_01557 + - 2021.09.29.03.38.25_veh-50_01581_01935 + - 2021.09.29.03.38.25_veh-50_01946_02131 + - 2021.09.29.03.43.06_veh-49_00010_00486 + - 2021.09.29.03.43.06_veh-49_00524_00684 + - 2021.09.29.03.43.06_veh-49_00736_01132 + - 2021.09.29.03.43.06_veh-49_01162_01239 + - 2021.09.29.03.43.06_veh-49_01250_01700 + - 2021.09.29.04.12.31_veh-51_00051_00287 + - 2021.09.29.04.12.31_veh-51_00375_00514 + - 2021.09.29.04.12.31_veh-51_00538_00625 + - 2021.09.29.04.12.31_veh-51_00670_00966 + - 2021.09.29.04.12.31_veh-51_00986_01121 + - 2021.09.29.04.12.31_veh-51_01147_01634 + - 2021.09.29.04.12.31_veh-51_01780_02172 + - 2021.09.29.04.15.18_veh-49_00061_00719 + - 2021.09.29.04.15.18_veh-49_00737_00917 + - 2021.09.29.04.15.18_veh-49_00945_01134 + - 2021.09.29.04.15.18_veh-49_01173_01248 + - 2021.09.29.04.15.18_veh-49_01303_01810 + - 2021.09.29.05.35.05_veh-50_00080_00450 + - 2021.09.29.05.35.05_veh-50_00570_01123 + - 2021.09.29.05.35.05_veh-50_01138_01227 + - 2021.09.29.05.35.05_veh-50_01250_01492 + - 2021.09.29.05.35.05_veh-50_01533_01718 + - 2021.09.29.05.49.59_veh-49_00016_00122 + - 2021.09.29.05.49.59_veh-49_00144_00317 + - 2021.09.29.05.49.59_veh-49_00432_00643 + - 2021.09.29.05.49.59_veh-49_00688_00840 + - 2021.09.29.05.49.59_veh-49_00946_01547 + - 2021.09.29.05.49.59_veh-49_01599_01780 + - 2021.09.29.05.52.19_veh-51_00153_00236 + - 2021.09.29.05.52.19_veh-51_00247_00341 + - 2021.09.29.05.52.19_veh-51_00432_00554 + - 2021.09.29.05.52.19_veh-51_00591_00722 + - 2021.09.29.05.52.19_veh-51_00757_01377 + - 2021.09.29.05.52.19_veh-51_01549_01857 + - 2021.09.29.06.10.17_veh-53_00011_00647 + - 2021.09.29.06.10.17_veh-53_00729_01036 + - 2021.09.29.06.10.17_veh-53_01062_01290 + - 2021.09.29.06.10.17_veh-53_01368_01560 + - 2021.09.29.06.10.17_veh-53_01606_01713 + - 2021.09.29.06.10.17_veh-53_01845_01911 + - 2021.09.29.06.23.05_veh-49_00016_00132 + - 2021.09.29.06.23.05_veh-49_00190_00627 + - 2021.09.29.06.23.05_veh-49_00677_00913 + - 2021.09.29.06.23.05_veh-49_00991_01116 + - 2021.09.29.06.23.05_veh-49_01127_01336 + - 2021.09.29.06.23.05_veh-49_01417_01520 + - 2021.09.29.06.23.05_veh-49_01553_01781 + - 2021.09.29.06.29.24_veh-51_00016_00507 + - 2021.09.29.06.29.24_veh-51_00550_00628 + - 2021.09.29.06.29.24_veh-51_00639_00892 + - 2021.09.29.06.29.24_veh-51_00934_01289 + - 2021.09.29.06.29.24_veh-51_01300_01440 + - 2021.09.29.06.29.24_veh-51_01496_01644 + - 2021.09.29.06.29.24_veh-51_01667_01954 + - 2021.09.29.06.46.09_veh-53_00007_00417 + - 2021.09.29.06.46.09_veh-53_00456_00739 + - 2021.09.29.06.46.09_veh-53_00763_00893 + - 2021.09.29.06.46.09_veh-53_01054_01274 + - 2021.09.29.06.46.09_veh-53_01289_01863 + - 2021.09.29.06.46.25_veh-50_00048_00151 + - 2021.09.29.06.46.25_veh-50_00233_00306 + - 2021.09.29.06.46.25_veh-50_00416_00480 + - 2021.09.29.06.46.25_veh-50_00613_00809 + - 2021.09.29.06.46.25_veh-50_00854_01028 + - 2021.09.29.06.46.25_veh-50_01068_01176 + - 2021.09.29.06.46.25_veh-50_01198_01261 + - 2021.09.29.06.46.25_veh-50_01320_01740 + - 2021.09.29.07.12.47_veh-49_00016_00096 + - 2021.09.29.07.12.47_veh-49_00196_00430 + - 2021.09.29.07.12.47_veh-49_00455_00848 + - 2021.09.29.07.12.47_veh-49_00920_00992 + - 2021.09.29.07.12.47_veh-49_01082_01328 + - 2021.09.29.07.12.47_veh-49_01476_01563 + - 2021.09.29.07.12.47_veh-49_01660_01731 + - 2021.09.29.07.34.11_veh-50_00016_00338 + - 2021.09.29.07.34.11_veh-50_00477_00579 + - 2021.09.29.07.34.11_veh-50_00688_00822 + - 2021.09.29.07.34.11_veh-50_00869_00939 + - 2021.09.29.07.34.11_veh-50_00982_01449 + - 2021.09.29.07.34.11_veh-50_01500_01709 + - 2021.09.29.07.38.10_veh-53_00015_00207 + - 2021.09.29.07.38.10_veh-53_00254_00576 + - 2021.09.29.07.38.10_veh-53_00681_00953 + - 2021.09.29.07.38.10_veh-53_00964_01839 + - 2021.09.29.07.45.59_veh-49_00016_00815 + - 2021.09.29.07.45.59_veh-49_00850_01005 + - 2021.09.29.07.45.59_veh-49_01048_01144 + - 2021.09.29.07.45.59_veh-49_01179_01239 + - 2021.09.29.07.45.59_veh-49_01427_01489 + - 2021.09.29.07.45.59_veh-49_01500_01654 + - 2021.09.29.08.07.57_veh-50_00136_00368 + - 2021.09.29.08.07.57_veh-50_00393_00718 + - 2021.09.29.08.07.57_veh-50_00801_00969 + - 2021.09.29.08.07.57_veh-50_00981_01233 + - 2021.09.29.08.07.57_veh-50_01246_01423 + - 2021.09.29.08.07.57_veh-50_01436_01568 + - 2021.09.29.08.14.53_veh-53_00016_00554 + - 2021.09.29.08.14.53_veh-53_00590_00717 + - 2021.09.29.08.14.53_veh-53_00790_00910 + - 2021.09.29.08.14.53_veh-53_00953_01015 + - 2021.09.29.08.14.53_veh-53_01040_01173 + - 2021.09.29.08.14.53_veh-53_01363_01437 + - 2021.09.29.08.14.53_veh-53_01516_01702 + - 2021.09.29.08.14.53_veh-53_01799_01874 + - 2021.09.29.08.24.44_veh-49_00076_00152 + - 2021.09.29.08.24.44_veh-49_00176_00414 + - 2021.09.29.08.24.44_veh-49_00452_00533 + - 2021.09.29.08.24.44_veh-49_00701_00774 + - 2021.09.29.08.24.44_veh-49_00886_00980 + - 2021.09.29.08.24.44_veh-49_01004_01271 + - 2021.09.29.08.24.44_veh-49_01282_01350 + - 2021.09.29.08.24.44_veh-49_01392_01495 + - 2021.09.29.08.40.49_veh-50_00016_00325 + - 2021.09.29.08.40.49_veh-50_00336_00547 + - 2021.09.29.08.40.49_veh-50_00592_00717 + - 2021.09.29.08.40.49_veh-50_00768_00912 + - 2021.09.29.08.40.49_veh-50_00933_01050 + - 2021.09.29.08.40.49_veh-50_01089_01329 + - 2021.09.29.08.40.49_veh-50_01344_01443 + - 2021.09.29.08.50.06_veh-53_00037_00127 + - 2021.09.29.08.50.06_veh-53_00138_00352 + - 2021.09.29.08.50.06_veh-53_00414_00496 + - 2021.09.29.08.50.06_veh-53_00541_00642 + - 2021.09.29.08.50.06_veh-53_00669_00900 + - 2021.09.29.08.50.06_veh-53_01017_01155 + - 2021.09.29.08.50.06_veh-53_01188_01372 + - 2021.09.29.08.50.06_veh-53_01459_01542 + - 2021.09.29.08.50.06_veh-53_01565_01832 + - 2021.09.29.08.57.11_veh-49_00016_00192 + - 2021.09.29.08.57.11_veh-49_00203_00268 + - 2021.09.29.08.57.11_veh-49_00307_00407 + - 2021.09.29.08.57.11_veh-49_00492_00588 + - 2021.09.29.08.57.11_veh-49_00624_00706 + - 2021.09.29.08.57.11_veh-49_00822_00896 + - 2021.09.29.08.57.11_veh-49_00981_01123 + - 2021.09.29.08.57.11_veh-49_01134_01320 + - 2021.09.29.08.57.11_veh-49_01331_01432 + - 2021.09.29.08.57.11_veh-49_01443_01815 + - 2021.09.29.09.10.14_veh-50_00106_00376 + - 2021.09.29.09.10.14_veh-50_00403_00471 + - 2021.09.29.09.10.14_veh-50_00504_00767 + - 2021.09.29.09.10.14_veh-50_00804_01082 + - 2021.09.30.02.45.10_veh-50_00016_00176 + - 2021.09.30.02.45.10_veh-50_00200_00424 + - 2021.09.30.02.45.10_veh-50_00443_00635 + - 2021.09.30.02.45.10_veh-50_00666_00754 + - 2021.09.30.02.45.10_veh-50_00817_01169 + - 2021.09.30.02.45.10_veh-50_01204_01547 + - 2021.09.30.02.45.10_veh-50_01587_01847 + - 2021.09.30.02.48.13_veh-52_00005_00237 + - 2021.09.30.02.48.13_veh-52_00290_00372 + - 2021.09.30.02.48.13_veh-52_00409_00480 + - 2021.09.30.02.48.13_veh-52_00525_00700 + - 2021.09.30.02.48.13_veh-52_00875_00994 + - 2021.09.30.02.48.13_veh-52_01011_01222 + - 2021.09.30.02.48.13_veh-52_01263_01675 + - 2021.09.30.02.48.13_veh-52_01691_01810 + - 2021.09.30.02.52.58_veh-53_00016_00413 + - 2021.09.30.02.52.58_veh-53_00629_00741 + - 2021.09.30.02.52.58_veh-53_00783_00878 + - 2021.09.30.02.52.58_veh-53_00926_01084 + - 2021.09.30.02.52.58_veh-53_01106_01281 + - 2021.09.30.02.52.58_veh-53_01387_01485 + - 2021.09.30.02.52.58_veh-53_01506_01734 + - 2021.09.30.03.21.02_veh-50_00016_00130 + - 2021.09.30.03.21.02_veh-50_00370_00444 + - 2021.09.30.03.21.02_veh-50_00483_00726 + - 2021.09.30.03.21.02_veh-50_00826_01043 + - 2021.09.30.03.21.02_veh-50_01098_01553 + - 2021.09.30.03.21.02_veh-50_01645_01788 + - 2021.09.30.03.21.25_veh-52_00016_00491 + - 2021.09.30.03.21.25_veh-52_00539_00659 + - 2021.09.30.03.21.25_veh-52_00673_01011 + - 2021.09.30.03.21.25_veh-52_01039_01210 + - 2021.09.30.03.21.25_veh-52_01232_01418 + - 2021.09.30.03.21.25_veh-52_01429_01556 + - 2021.09.30.03.21.25_veh-52_01577_01760 + - 2021.09.30.03.33.11_veh-53_00045_00231 + - 2021.09.30.03.33.11_veh-53_00263_00384 + - 2021.09.30.03.33.11_veh-53_00412_00525 + - 2021.09.30.03.33.11_veh-53_00536_00891 + - 2021.09.30.03.33.11_veh-53_00912_01333 + - 2021.09.30.03.33.11_veh-53_01416_01478 + - 2021.09.30.03.33.11_veh-53_01503_01837 + - 2021.09.30.03.37.54_veh-51_00017_00273 + - 2021.09.30.03.37.54_veh-51_00311_00409 + - 2021.09.30.03.37.54_veh-51_00463_00603 + - 2021.09.30.03.37.54_veh-51_00662_00794 + - 2021.09.30.03.37.54_veh-51_00805_01011 + - 2021.09.30.03.37.54_veh-51_01022_01614 + - 2021.09.30.03.37.54_veh-51_01668_01790 + - 2021.09.30.03.37.54_veh-51_01801_01931 + - 2021.09.30.03.55.10_veh-50_00016_00319 + - 2021.09.30.03.55.10_veh-50_00349_00811 + - 2021.09.30.03.55.10_veh-50_00946_01373 + - 2021.09.30.03.55.10_veh-50_01517_01767 + - 2021.09.30.03.55.28_veh-52_00039_00117 + - 2021.09.30.03.55.28_veh-52_00236_00431 + - 2021.09.30.03.55.28_veh-52_00450_00572 + - 2021.09.30.03.55.28_veh-52_00706_01035 + - 2021.09.30.03.55.28_veh-52_01048_01316 + - 2021.09.30.03.55.28_veh-52_01367_01791 + - 2021.09.30.04.07.10_veh-53_00035_00485 + - 2021.09.30.04.07.10_veh-53_00509_00571 + - 2021.09.30.04.07.10_veh-53_00593_00672 + - 2021.09.30.04.07.10_veh-53_00683_00805 + - 2021.09.30.04.07.10_veh-53_00831_00941 + - 2021.09.30.04.07.10_veh-53_00968_01137 + - 2021.09.30.04.07.10_veh-53_01226_01365 + - 2021.09.30.04.07.10_veh-53_01388_01505 + - 2021.09.30.04.07.10_veh-53_01531_01750 + - 2021.09.30.04.15.20_veh-51_00015_00140 + - 2021.09.30.04.15.20_veh-51_00168_00250 + - 2021.09.30.04.15.20_veh-51_00313_00399 + - 2021.09.30.04.15.20_veh-51_00447_00771 + - 2021.09.30.04.15.20_veh-51_00824_00909 + - 2021.09.30.04.15.20_veh-51_00927_01203 + - 2021.09.30.04.15.20_veh-51_01216_01420 + - 2021.09.30.04.15.20_veh-51_01488_01609 + - 2021.09.30.04.15.20_veh-51_01650_01851 + - 2021.09.30.05.37.44_veh-53_00026_00285 + - 2021.09.30.05.37.44_veh-53_00314_00513 + - 2021.09.30.05.37.44_veh-53_00576_00709 + - 2021.09.30.05.37.44_veh-53_00720_01005 + - 2021.09.30.05.37.44_veh-53_01059_01137 + - 2021.09.30.05.37.44_veh-53_01153_01333 + - 2021.09.30.05.37.44_veh-53_01621_01713 + - 2021.09.30.05.52.32_veh-50_00206_00283 + - 2021.09.30.05.52.32_veh-50_00295_00360 + - 2021.09.30.05.52.32_veh-50_00441_00568 + - 2021.09.30.05.52.32_veh-50_00590_00712 + - 2021.09.30.05.52.32_veh-50_00734_00833 + - 2021.09.30.05.52.32_veh-50_00864_01332 + - 2021.09.30.05.52.32_veh-50_01384_01546 + - 2021.09.30.05.52.32_veh-50_01644_01758 + - 2021.09.30.06.13.47_veh-53_00068_00283 + - 2021.09.30.06.13.47_veh-53_00307_00770 + - 2021.09.30.06.13.47_veh-53_00781_01057 + - 2021.09.30.06.13.47_veh-53_01138_01428 + - 2021.09.30.06.13.47_veh-53_01477_01820 + - 2021.09.30.06.30.37_veh-50_00031_00191 + - 2021.09.30.06.30.37_veh-50_00215_00517 + - 2021.09.30.06.30.37_veh-50_00561_00669 + - 2021.09.30.06.30.37_veh-50_00856_01020 + - 2021.09.30.06.30.37_veh-50_01041_01161 + - 2021.09.30.06.30.37_veh-50_01188_01277 + - 2021.09.30.06.30.37_veh-50_01290_01400 + - 2021.09.30.06.30.37_veh-50_01657_01773 + - 2021.09.30.07.13.28_veh-50_00016_00208 + - 2021.09.30.07.13.28_veh-50_00255_00746 + - 2021.09.30.07.13.28_veh-50_00813_00920 + - 2021.09.30.07.13.28_veh-50_00960_01056 + - 2021.09.30.07.13.28_veh-50_01069_01198 + - 2021.09.30.07.13.28_veh-50_01231_01517 + - 2021.09.30.07.13.28_veh-50_01528_01608 + - 2021.09.30.07.54.03_veh-50_00013_00106 + - 2021.09.30.07.54.03_veh-50_00137_00795 + - 2021.09.30.13.04.47_veh-28_00015_00080 + - 2021.09.30.13.04.47_veh-28_00091_00286 + - 2021.09.30.13.04.47_veh-28_00301_00467 + - 2021.09.30.13.04.47_veh-28_00478_00572 + - 2021.09.30.13.04.47_veh-28_00723_00934 + - 2021.09.30.13.04.47_veh-28_01175_01476 + - 2021.09.30.13.04.47_veh-28_01533_01680 + - 2021.09.30.13.08.26_veh-44_00130_00262 + - 2021.09.30.13.08.26_veh-44_00316_00379 + - 2021.09.30.13.08.26_veh-44_00402_00779 + - 2021.09.30.13.08.26_veh-44_00797_01137 + - 2021.09.30.13.08.26_veh-44_01217_01372 + - 2021.09.30.13.08.26_veh-44_01399_01702 + - 2021.09.30.13.08.26_veh-44_01745_01853 + - 2021.09.30.13.08.26_veh-44_01871_01950 + - 2021.09.30.13.08.26_veh-44_02000_02075 + - 2021.09.30.13.08.26_veh-44_02155_02239 + - 2021.09.30.13.38.22_veh-28_00061_00623 + - 2021.09.30.13.38.22_veh-28_00689_00880 + - 2021.09.30.13.38.22_veh-28_01036_01238 + - 2021.09.30.13.38.22_veh-28_01332_01405 + - 2021.09.30.13.38.22_veh-28_01476_01573 + - 2021.09.30.13.38.22_veh-28_01584_01679 + - 2021.09.30.13.54.09_veh-44_00104_01877 + - 2021.09.30.13.54.09_veh-44_01902_02192 + - 2021.09.30.13.54.09_veh-44_02213_02452 + - 2021.09.30.13.54.09_veh-44_02474_02788 + - 2021.09.30.14.12.46_veh-28_00016_00157 + - 2021.09.30.14.12.46_veh-28_00169_00613 + - 2021.09.30.14.12.46_veh-28_00748_00840 + - 2021.09.30.14.12.46_veh-28_00857_00999 + - 2021.09.30.14.12.46_veh-28_01029_01111 + - 2021.09.30.14.12.46_veh-28_01140_01224 + - 2021.09.30.14.12.46_veh-28_01271_01594 + - 2021.09.30.14.12.46_veh-28_01626_01693 + - 2021.09.30.14.47.42_veh-28_00075_00232 + - 2021.09.30.14.47.42_veh-28_00245_00532 + - 2021.09.30.14.47.42_veh-28_00656_00825 + - 2021.09.30.14.47.42_veh-28_01142_01210 + - 2021.09.30.14.47.42_veh-28_01233_01528 + - 2021.09.30.14.47.42_veh-28_01557_01685 + - 2021.09.30.15.05.51_veh-44_00016_00731 + - 2021.09.30.15.05.51_veh-44_00753_01199 + - 2021.09.30.15.05.51_veh-44_01219_01632 + - 2021.09.30.15.05.51_veh-44_01655_02241 + - 2021.09.30.15.05.51_veh-44_02323_02423 + - 2021.09.30.17.20.14_veh-44_00033_00131 + - 2021.09.30.17.20.14_veh-44_00217_00287 + - 2021.09.30.17.20.14_veh-44_00422_00647 + - 2021.09.30.17.20.14_veh-44_00665_01476 + - 2021.09.30.17.20.14_veh-44_01504_01617 + - 2021.09.30.17.20.14_veh-44_01775_02229 + - 2021.09.30.18.01.05_veh-44_00016_00976 + - 2021.09.30.18.01.05_veh-44_01000_01443 + - 2021.09.30.18.01.05_veh-44_01594_01685 + - 2021.09.30.18.01.05_veh-44_01878_01985 + - 2021.09.30.18.01.05_veh-44_02289_02421 + - 2021.09.30.18.01.05_veh-44_02533_02663 + - 2021.09.30.18.30.00_veh-28_00016_00089 + - 2021.09.30.18.30.00_veh-28_00212_00302 + - 2021.09.30.18.30.00_veh-28_00365_00736 + - 2021.09.30.18.30.00_veh-28_00865_00982 + - 2021.09.30.18.30.00_veh-28_01175_01445 + - 2021.09.30.18.30.00_veh-28_01467_01702 + - 2021.09.30.19.04.00_veh-28_00025_00106 + - 2021.09.30.19.04.00_veh-28_00117_00539 + - 2021.09.30.19.04.00_veh-28_00561_00769 + - 2021.09.30.19.04.00_veh-28_00874_01009 + - 2021.09.30.19.04.00_veh-28_01047_01116 + - 2021.09.30.19.04.00_veh-28_01140_01210 + - 2021.09.30.19.04.00_veh-28_01311_01451 + - 2021.09.30.19.04.00_veh-28_01462_01673 + - 2021.09.30.19.04.00_veh-28_01686_01767 + - 2021.09.30.19.11.40_veh-44_00580_02260 + - 2021.09.30.19.58.06_veh-44_00551_00619 + - 2021.09.30.19.58.06_veh-44_00873_01492 + - 2021.09.30.19.58.06_veh-44_01514_01842 + - 2021.09.30.19.58.06_veh-44_02010_02076 + - 2021.09.30.19.58.06_veh-44_02197_02279 + - 2021.09.30.20.55.20_veh-44_00029_00093 + - 2021.09.30.20.55.20_veh-44_00299_00460 + - 2021.09.30.20.55.20_veh-44_00861_00936 + - 2021.10.01.12.54.53_veh-44_00332_00665 + - 2021.10.01.12.54.53_veh-44_00684_00799 + - 2021.10.01.12.54.53_veh-44_00858_01311 + - 2021.10.01.12.54.53_veh-44_01397_01470 + - 2021.10.01.12.54.53_veh-44_01642_01719 + - 2021.10.01.12.54.53_veh-44_02019_02101 + - 2021.10.01.12.54.53_veh-44_02307_02375 + - 2021.10.01.12.54.53_veh-44_02552_02639 + - 2021.10.01.12.54.53_veh-44_02651_03095 + - 2021.10.01.13.28.54_veh-28_00094_00181 + - 2021.10.01.13.28.54_veh-28_00405_00547 + - 2021.10.01.13.28.54_veh-28_00607_00973 + - 2021.10.01.13.28.54_veh-28_00995_01087 + - 2021.10.01.13.28.54_veh-28_01098_01337 + - 2021.10.01.13.28.54_veh-28_01421_01615 + - 2021.10.01.13.28.54_veh-28_01767_01883 + - 2021.10.01.14.16.29_veh-44_00112_00513 + - 2021.10.01.14.16.29_veh-44_00532_00631 + - 2021.10.01.14.16.29_veh-44_00675_00866 + - 2021.10.01.14.16.29_veh-44_00885_01146 + - 2021.10.01.14.16.29_veh-44_01169_01773 + - 2021.10.01.14.20.36_veh-28_00038_00128 + - 2021.10.01.14.20.36_veh-28_00243_00388 + - 2021.10.01.14.20.36_veh-28_00475_00646 + - 2021.10.01.14.20.36_veh-28_00825_00919 + - 2021.10.01.14.20.36_veh-28_00931_01128 + - 2021.10.01.14.20.36_veh-28_01151_01286 + - 2021.10.01.14.20.36_veh-28_01415_01480 + - 2021.10.01.14.20.36_veh-28_01491_01630 + - 2021.10.01.14.49.24_veh-44_00005_00686 + - 2021.10.01.14.49.24_veh-44_00772_01428 + - 2021.10.01.14.49.24_veh-44_01453_01551 + - 2021.10.01.15.32.11_veh-28_00025_00097 + - 2021.10.01.15.32.11_veh-28_00120_00248 + - 2021.10.01.15.32.11_veh-28_00291_00464 + - 2021.10.01.15.32.11_veh-28_00475_00930 + - 2021.10.01.15.32.11_veh-28_01000_01136 + - 2021.10.01.15.32.11_veh-28_01178_01392 + - 2021.10.01.16.53.37_veh-44_00056_00324 + - 2021.10.01.16.53.37_veh-44_00347_00964 + - 2021.10.01.16.53.37_veh-44_00989_01087 + - 2021.10.01.16.53.37_veh-44_01126_01602 + - 2021.10.01.16.53.37_veh-44_01654_01884 + - 2021.10.01.17.28.18_veh-44_00053_00188 + - 2021.10.01.17.28.18_veh-44_00212_00444 + - 2021.10.01.17.28.18_veh-44_00496_00584 + - 2021.10.01.17.28.18_veh-44_00609_01551 + - 2021.10.01.17.28.18_veh-44_01567_01717 + - 2021.10.01.17.52.06_veh-28_00098_00211 + - 2021.10.01.17.52.06_veh-28_00327_00427 + - 2021.10.01.17.52.06_veh-28_00450_00599 + - 2021.10.01.17.52.06_veh-28_00675_00737 + - 2021.10.01.17.52.06_veh-28_00748_00952 + - 2021.10.01.17.52.06_veh-28_01034_01107 + - 2021.10.01.17.52.06_veh-28_01141_01264 + - 2021.10.01.17.52.06_veh-28_01289_01353 + - 2021.10.01.17.52.06_veh-28_01364_01428 + - 2021.10.01.17.52.06_veh-28_01441_01573 + - 2021.10.01.17.52.06_veh-28_01622_01687 + - 2021.10.01.18.24.31_veh-44_00344_00756 + - 2021.10.01.18.24.31_veh-44_00776_00895 + - 2021.10.01.18.24.31_veh-44_00925_01112 + - 2021.10.01.18.24.31_veh-44_01137_01493 + - 2021.10.01.18.26.05_veh-28_00005_00413 + - 2021.10.01.18.26.05_veh-28_00481_00656 + - 2021.10.01.18.26.05_veh-28_00949_01041 + - 2021.10.01.18.26.05_veh-28_01081_01159 + - 2021.10.01.18.26.05_veh-28_01211_01323 + - 2021.10.01.18.26.05_veh-28_01689_01890 + - 2021.10.01.18.57.27_veh-44_00078_00205 + - 2021.10.01.18.57.27_veh-44_00240_00661 + - 2021.10.01.18.57.27_veh-44_00684_00779 + - 2021.10.01.18.57.27_veh-44_00790_01658 + - 2021.10.01.19.16.42_veh-28_00094_00216 + - 2021.10.01.19.16.42_veh-28_00274_00380 + - 2021.10.01.19.16.42_veh-28_00392_00906 + - 2021.10.01.19.16.42_veh-28_00917_01499 + - 2021.10.01.19.16.42_veh-28_01511_01624 + - 2021.10.01.19.16.42_veh-28_01731_01935 + - 2021.10.01.19.16.42_veh-28_02011_02410 + - 2021.10.01.19.16.42_veh-28_02447_02517 + - 2021.10.01.19.16.42_veh-28_02568_02833 + - 2021.10.01.19.16.42_veh-28_02903_03140 + - 2021.10.01.19.16.42_veh-28_03215_03296 + - 2021.10.01.19.16.42_veh-28_03307_03808 + - 2021.10.01.19.16.42_veh-28_03887_04040 + - 2021.10.04.02.54.04_veh-49_00050_00277 + - 2021.10.04.02.54.04_veh-49_00323_00455 + - 2021.10.04.02.54.04_veh-49_00502_00676 + - 2021.10.04.02.54.04_veh-49_00706_01636 + - 2021.10.04.02.54.04_veh-49_01647_01726 + - 2021.10.04.02.54.04_veh-49_01737_02002 + - 2021.10.04.03.30.52_veh-49_00020_00700 + - 2021.10.04.03.30.52_veh-49_00717_00848 + - 2021.10.04.03.30.52_veh-49_00874_01107 + - 2021.10.04.03.30.52_veh-49_01153_01214 + - 2021.10.04.03.30.52_veh-49_01229_01512 + - 2021.10.04.03.30.52_veh-49_01525_01846 + - 2021.10.04.03.30.52_veh-49_01859_01960 + - 2021.10.04.04.10.37_veh-49_00016_00083 + - 2021.10.04.04.10.37_veh-49_00122_00358 + - 2021.10.04.04.10.37_veh-49_00465_00553 + - 2021.10.04.04.10.37_veh-49_00564_01023 + - 2021.10.04.04.10.37_veh-49_01077_01310 + - 2021.10.04.04.10.37_veh-49_01405_01725 + - 2021.10.04.04.10.37_veh-49_01736_01882 + - 2021.10.04.05.45.21_veh-49_00016_00152 + - 2021.10.04.05.45.21_veh-49_00200_00626 + - 2021.10.04.05.45.21_veh-49_00673_00748 + - 2021.10.04.05.45.21_veh-49_00759_00911 + - 2021.10.04.05.45.21_veh-49_00970_01245 + - 2021.10.04.05.45.21_veh-49_01286_01477 + - 2021.10.04.05.45.21_veh-49_01492_01702 + - 2021.10.04.05.45.21_veh-49_01724_01803 + - 2021.10.04.06.22.37_veh-49_00013_00175 + - 2021.10.04.06.22.37_veh-49_00214_00649 + - 2021.10.04.06.22.37_veh-49_00666_00841 + - 2021.10.04.06.22.37_veh-49_00852_01069 + - 2021.10.04.06.22.37_veh-49_01080_01344 + - 2021.10.04.06.22.37_veh-49_01355_01572 + - 2021.10.04.06.22.37_veh-49_01583_01646 + - 2021.10.04.06.22.37_veh-49_01664_01887 + - 2021.10.04.06.58.24_veh-49_00005_00700 + - 2021.10.04.06.58.24_veh-49_00810_00920 + - 2021.10.04.06.58.24_veh-49_01094_01166 + - 2021.10.04.06.58.24_veh-49_01197_01287 + - 2021.10.04.06.58.24_veh-49_01299_01426 + - 2021.10.04.06.58.24_veh-49_01481_01558 + - 2021.10.04.06.58.24_veh-49_01711_01785 + - 2021.10.04.07.09.42_veh-50_00016_00382 + - 2021.10.04.07.09.42_veh-50_00420_00781 + - 2021.10.04.07.09.42_veh-50_00825_00917 + - 2021.10.04.07.09.42_veh-50_00929_00996 + - 2021.10.04.07.09.42_veh-50_01072_01167 + - 2021.10.04.07.09.42_veh-50_01245_01340 + - 2021.10.04.07.09.42_veh-50_01384_01554 + - 2021.10.04.07.09.42_veh-50_01647_01723 + - 2021.10.04.07.09.42_veh-50_01741_01846 + - 2021.10.04.07.37.18_veh-49_00016_00392 + - 2021.10.04.07.37.18_veh-49_00428_00536 + - 2021.10.04.07.37.18_veh-49_00548_00962 + - 2021.10.04.07.37.18_veh-49_00980_01044 + - 2021.10.04.07.37.18_veh-49_01065_01249 + - 2021.10.04.07.37.18_veh-49_01301_01471 + - 2021.10.04.07.37.18_veh-49_01512_01847 + - 2021.10.04.07.49.45_veh-50_00016_00182 + - 2021.10.04.07.49.45_veh-50_00249_00356 + - 2021.10.04.07.49.45_veh-50_00382_00782 + - 2021.10.04.07.49.45_veh-50_00793_01090 + - 2021.10.04.07.49.45_veh-50_01131_01197 + - 2021.10.04.07.49.45_veh-50_01242_01385 + - 2021.10.04.07.49.45_veh-50_01484_01582 + - 2021.10.04.07.49.45_veh-50_01718_01838 + - 2021.10.04.08.19.31_veh-49_00019_00152 + - 2021.10.04.08.19.31_veh-49_00202_00345 + - 2021.10.04.08.19.31_veh-49_00360_00500 + - 2021.10.04.08.19.31_veh-49_00547_00679 + - 2021.10.04.08.19.31_veh-49_00722_01134 + - 2021.10.04.08.19.31_veh-49_01152_01611 + - 2021.10.04.08.19.31_veh-49_01737_01834 + - 2021.10.04.08.19.31_veh-49_01886_01965 + - 2021.10.04.08.37.50_veh-50_00030_00223 + - 2021.10.04.08.37.50_veh-50_00359_00563 + - 2021.10.04.08.37.50_veh-50_00578_00658 + - 2021.10.04.08.37.50_veh-50_00782_00867 + - 2021.10.04.08.37.50_veh-50_00928_01032 + - 2021.10.04.08.37.50_veh-50_01084_01636 + - 2021.10.04.08.37.50_veh-50_01661_01727 + - 2021.10.04.08.37.50_veh-50_01792_01855 + - 2021.10.04.08.37.50_veh-50_01953_02374 + - 2021.10.04.14.24.12_veh-28_00017_00184 + - 2021.10.04.14.24.12_veh-28_00233_00485 + - 2021.10.04.14.24.12_veh-28_00496_00599 + - 2021.10.04.14.24.12_veh-28_00687_01039 + - 2021.10.04.14.24.12_veh-28_01186_01250 + - 2021.10.04.14.24.12_veh-28_01369_01453 + - 2021.10.04.14.24.12_veh-28_01464_01619 + - 2021.10.04.14.24.12_veh-28_01657_01751 + - 2021.10.04.15.05.57_veh-28_00016_00133 + - 2021.10.04.15.05.57_veh-28_00268_00346 + - 2021.10.04.15.05.57_veh-28_00446_00617 + - 2021.10.04.15.05.57_veh-28_00628_01009 + - 2021.10.04.15.05.57_veh-28_01181_01587 + - 2021.10.04.15.05.57_veh-28_01616_01703 + - 2021.10.04.15.05.57_veh-28_01776_01851 + - 2021.10.04.15.44.57_veh-28_00078_00210 + - 2021.10.04.15.44.57_veh-28_00404_00597 + - 2021.10.04.15.44.57_veh-28_00620_00686 + - 2021.10.04.15.44.57_veh-28_00698_00909 + - 2021.10.04.15.44.57_veh-28_01085_01272 + - 2021.10.04.15.44.57_veh-28_01326_01474 + - 2021.10.04.15.44.57_veh-28_01552_01712 + - 2021.10.04.15.44.57_veh-28_01736_01799 + - 2021.10.04.18.25.22_veh-28_00109_00331 + - 2021.10.04.18.25.22_veh-28_00352_00441 + - 2021.10.04.18.25.22_veh-28_00478_00683 + - 2021.10.04.18.25.22_veh-28_01224_01320 + - 2021.10.04.18.25.22_veh-28_01331_01545 + - 2021.10.04.18.25.22_veh-28_01597_01679 + - 2021.10.04.18.25.22_veh-28_02027_02105 + - 2021.10.04.19.10.20_veh-28_00019_00133 + - 2021.10.04.19.10.20_veh-28_00145_00239 + - 2021.10.04.19.10.20_veh-28_00378_00588 + - 2021.10.04.19.10.20_veh-28_00620_00771 + - 2021.10.04.19.10.20_veh-28_00826_00925 + - 2021.10.04.19.10.20_veh-28_01003_01126 + - 2021.10.04.19.10.20_veh-28_01191_01449 + - 2021.10.05.13.12.43_veh-28_00089_00178 + - 2021.10.05.13.12.43_veh-28_00489_00605 + - 2021.10.05.13.12.43_veh-28_00618_00916 + - 2021.10.05.13.12.43_veh-28_01151_01274 + - 2021.10.05.13.12.43_veh-28_01316_01487 + - 2021.10.05.13.12.43_veh-28_01575_01642 + - 2021.10.05.13.12.43_veh-28_01679_01770 + - 2021.10.05.13.49.59_veh-28_00016_00149 + - 2021.10.05.13.49.59_veh-28_00204_00403 + - 2021.10.05.13.49.59_veh-28_00463_00543 + - 2021.10.05.13.49.59_veh-28_00620_00892 + - 2021.10.05.13.49.59_veh-28_00903_01046 + - 2021.10.05.13.49.59_veh-28_01057_01123 + - 2021.10.05.13.49.59_veh-28_01218_01414 + - 2021.10.05.13.49.59_veh-28_01695_01906 + - 2021.10.05.13.49.59_veh-28_02160_02292 + - 2021.10.05.13.49.59_veh-28_02446_02533 + - 2021.10.05.17.48.44_veh-28_00016_00115 + - 2021.10.05.17.48.44_veh-28_00443_00975 + - 2021.10.05.17.48.44_veh-28_01119_01224 + - 2021.10.05.17.48.44_veh-28_01304_01652 + - 2021.10.05.18.36.26_veh-28_00222_00337 + - 2021.10.05.18.36.26_veh-28_00348_00462 + - 2021.10.05.18.36.26_veh-28_00525_00671 + - 2021.10.05.18.36.26_veh-28_00696_01123 + - 2021.10.05.18.36.26_veh-28_01145_01432 + - 2021.10.05.18.36.26_veh-28_01627_01717 + - 2021.10.05.19.11.47_veh-28_00032_00126 + - 2021.10.05.19.11.47_veh-28_00256_00497 + - 2021.10.05.19.11.47_veh-28_00509_00697 + - 2021.10.05.19.11.47_veh-28_00908_01256 + - 2021.10.05.19.11.47_veh-28_01422_01650 + - 2021.10.06.13.21.47_veh-28_00016_00086 + - 2021.10.06.13.21.47_veh-28_00139_00216 + - 2021.10.06.13.21.47_veh-28_00262_00334 + - 2021.10.06.13.21.47_veh-28_00441_00515 + - 2021.10.06.13.21.47_veh-28_00692_00815 + - 2021.10.06.13.21.47_veh-28_01002_01116 + - 2021.10.06.13.21.47_veh-28_01127_01187 + - 2021.10.06.13.21.47_veh-28_01198_01616 + - 2021.10.06.13.21.47_veh-28_01648_01722 + - 2021.10.06.13.21.47_veh-28_01755_01829 + - 2021.10.06.14.31.13_veh-28_00014_00079 + - 2021.10.06.14.31.13_veh-28_00223_00350 + - 2021.10.06.14.31.13_veh-28_00362_00475 + - 2021.10.06.14.31.13_veh-28_00589_00665 + - 2021.10.06.14.31.13_veh-28_00738_00908 + - 2021.10.06.14.31.13_veh-28_00981_01226 + - 2021.10.06.14.31.13_veh-28_01277_01377 + - 2021.10.06.14.31.13_veh-28_01388_01849 + - 2021.10.06.17.08.46_veh-28_00016_00116 + - 2021.10.06.17.08.46_veh-28_00127_00428 + - 2021.10.06.17.08.46_veh-28_00498_00621 + - 2021.10.06.17.08.46_veh-28_00651_01030 + - 2021.10.06.17.08.46_veh-28_01127_01287 + - 2021.10.06.17.08.46_veh-28_01298_01548 + - 2021.10.06.17.08.46_veh-28_01626_01702 + - 2021.10.06.17.43.07_veh-28_00016_00291 + - 2021.10.06.17.43.07_veh-28_00302_00486 + - 2021.10.06.17.43.07_veh-28_00508_00877 + - 2021.10.06.17.43.07_veh-28_00933_01014 + - 2021.10.06.17.43.07_veh-28_01118_01302 + - 2021.10.06.17.43.07_veh-28_01354_01536 + - 2021.10.06.17.43.07_veh-28_01587_01694 + - 2021.10.06.18.52.07_veh-28_00123_00431 + - 2021.10.06.18.52.07_veh-28_00442_00578 + - 2021.10.06.18.52.07_veh-28_00592_00655 + - 2021.10.06.18.52.07_veh-28_00839_00968 + - 2021.10.06.18.52.07_veh-28_01072_01157 + - 2021.10.06.18.52.07_veh-28_01297_01462 + - 2021.10.06.18.52.07_veh-28_01474_01908 + - 2021.10.06.19.27.33_veh-28_00016_00079 + - 2021.10.06.19.27.33_veh-28_00121_00289 + - 2021.10.06.19.27.33_veh-28_00302_00794 + - 2021.10.06.19.27.33_veh-28_00805_01736 + - 2021.10.07.06.17.01_veh-51_00005_00196 + - 2021.10.07.06.17.01_veh-51_00229_00356 + - 2021.10.07.06.17.01_veh-51_00380_00751 + - 2021.10.07.06.17.01_veh-51_00794_00929 + - 2021.10.07.06.17.01_veh-51_00977_01139 + - 2021.10.07.06.17.01_veh-51_01151_02051 + - 2021.10.07.06.17.01_veh-51_02075_02504 + - 2021.10.07.06.17.01_veh-51_02554_02629 + - 2021.10.07.06.17.01_veh-51_02674_02757 + - 2021.10.07.07.07.19_veh-51_00016_00238 + - 2021.10.07.07.07.19_veh-51_00298_00401 + - 2021.10.07.07.07.19_veh-51_00448_00646 + - 2021.10.07.07.07.19_veh-51_00865_00988 + - 2021.10.07.07.07.19_veh-51_01042_01123 + - 2021.10.07.07.07.19_veh-51_01168_01610 + - 2021.10.07.07.07.19_veh-51_01637_01752 + - 2021.10.07.07.07.19_veh-51_01766_01841 + - 2021.10.07.07.07.19_veh-51_01913_02043 + - 2021.10.07.07.07.19_veh-51_02144_02381 + - 2021.10.07.07.07.19_veh-51_02410_02522 + - 2021.10.07.07.18.59_veh-52_00007_00459 + - 2021.10.07.07.18.59_veh-52_00509_00654 + - 2021.10.07.07.18.59_veh-52_00698_00828 + - 2021.10.07.07.18.59_veh-52_00963_01412 + - 2021.10.07.07.18.59_veh-52_01492_02358 + - 2021.10.07.07.18.59_veh-52_02398_02514 + - 2021.10.07.07.18.59_veh-52_02546_02618 + - 2021.10.07.08.07.44_veh-51_00016_00094 + - 2021.10.07.08.07.44_veh-51_00125_00204 + - 2021.10.07.08.07.44_veh-51_00260_00560 + - 2021.10.07.08.07.44_veh-51_00593_00974 + - 2021.10.07.08.07.44_veh-51_00992_01109 + - 2021.10.07.08.07.44_veh-51_01123_01639 + - 2021.10.07.08.07.44_veh-51_01708_01819 + - 2021.10.07.08.07.44_veh-51_01831_01948 + - 2021.10.07.08.07.44_veh-51_01988_02379 + - 2021.10.07.08.07.44_veh-51_02520_02683 + - 2021.10.07.08.12.29_veh-52_00016_00369 + - 2021.10.07.08.12.29_veh-52_00402_00816 + - 2021.10.07.08.12.29_veh-52_00867_01478 + - 2021.10.07.08.12.29_veh-52_01638_01948 + - 2021.10.07.08.12.29_veh-52_01973_02152 + - 2021.10.07.08.12.29_veh-52_02171_02317 + - 2021.10.07.08.12.29_veh-52_02331_02481 + - 2021.10.07.08.12.29_veh-52_02502_02627 + - 2021.10.07.08.56.31_veh-51_00018_00099 + - 2021.10.07.08.56.31_veh-51_00242_00313 + - 2021.10.07.08.56.31_veh-51_00324_00890 + - 2021.10.07.08.56.31_veh-51_00968_01067 + - 2021.10.07.08.56.31_veh-51_01123_01228 + - 2021.10.07.08.56.31_veh-51_01304_01429 + - 2021.10.07.08.56.31_veh-51_01451_01833 + - 2021.10.07.09.00.00_veh-52_00019_00255 + - 2021.10.07.09.00.00_veh-52_00281_00427 + - 2021.10.07.09.00.00_veh-52_00450_00738 + - 2021.10.07.09.00.00_veh-52_00760_00948 + - 2021.10.07.09.00.00_veh-52_00992_01094 + - 2021.10.07.09.00.00_veh-52_01151_01315 + - 2021.10.07.09.00.00_veh-52_01326_01732 + - 2021.10.08.02.05.47_veh-51_00016_00192 + - 2021.10.08.02.05.47_veh-51_00416_00580 + - 2021.10.08.02.05.47_veh-51_00703_00797 + - 2021.10.08.02.05.47_veh-51_00842_01291 + - 2021.10.08.02.05.47_veh-51_01342_01510 + - 2021.10.08.02.05.47_veh-51_01533_01690 + - 2021.10.08.02.05.47_veh-51_01850_02200 + - 2021.10.08.02.05.47_veh-51_02319_02437 + - 2021.10.08.02.05.47_veh-51_02448_02541 + - 2021.10.08.02.06.16_veh-50_00016_00402 + - 2021.10.08.02.06.16_veh-50_00446_00543 + - 2021.10.08.02.06.16_veh-50_00591_00677 + - 2021.10.08.02.06.16_veh-50_00688_00758 + - 2021.10.08.02.06.16_veh-50_00815_00994 + - 2021.10.08.02.06.16_veh-50_01016_01713 + - 2021.10.08.02.09.20_veh-53_00050_00121 + - 2021.10.08.02.09.20_veh-53_00198_00991 + - 2021.10.08.02.09.20_veh-53_01002_01390 + - 2021.10.08.02.09.20_veh-53_01439_01526 + - 2021.10.08.02.09.20_veh-53_01608_01846 + - 2021.10.08.02.10.14_veh-49_00016_00795 + - 2021.10.08.02.10.14_veh-49_00808_00950 + - 2021.10.08.02.10.14_veh-49_00963_01234 + - 2021.10.08.02.10.14_veh-49_01245_01376 + - 2021.10.08.02.10.14_veh-49_01388_01726 + - 2021.10.08.02.10.14_veh-49_01747_01822 + - 2021.10.08.02.10.14_veh-49_01857_02173 + - 2021.10.08.02.10.14_veh-49_02195_02272 + - 2021.10.08.02.10.14_veh-49_02341_02456 + - 2021.10.08.02.10.14_veh-49_02490_02669 + - 2021.10.08.02.40.29_veh-50_00016_00323 + - 2021.10.08.02.40.29_veh-50_00341_00517 + - 2021.10.08.02.40.29_veh-50_00589_01182 + - 2021.10.08.02.40.29_veh-50_01237_01405 + - 2021.10.08.02.40.29_veh-50_01541_01804 + - 2021.10.08.02.59.38_veh-51_00016_01190 + - 2021.10.08.02.59.38_veh-51_01243_01350 + - 2021.10.08.02.59.38_veh-51_01374_01566 + - 2021.10.08.02.59.38_veh-51_01649_01789 + - 2021.10.08.02.59.51_veh-53_00016_00338 + - 2021.10.08.02.59.51_veh-53_00367_00787 + - 2021.10.08.02.59.51_veh-53_00849_01267 + - 2021.10.08.02.59.51_veh-53_01392_01633 + - 2021.10.08.02.59.51_veh-53_01651_01854 + - 2021.10.08.03.04.30_veh-49_00016_00204 + - 2021.10.08.03.04.30_veh-49_00246_00397 + - 2021.10.08.03.04.30_veh-49_00414_00543 + - 2021.10.08.03.04.30_veh-49_00591_00975 + - 2021.10.08.03.04.30_veh-49_00999_01132 + - 2021.10.08.03.04.30_veh-49_01189_01288 + - 2021.10.08.03.04.30_veh-49_01314_01562 + - 2021.10.08.03.22.59_veh-50_00005_00160 + - 2021.10.08.03.22.59_veh-50_00238_00455 + - 2021.10.08.03.22.59_veh-50_00494_00778 + - 2021.10.08.03.22.59_veh-50_00821_01171 + - 2021.10.08.03.22.59_veh-50_01219_01320 + - 2021.10.08.03.22.59_veh-50_01378_01466 + - 2021.10.08.03.22.59_veh-50_01498_01791 + - 2021.10.08.03.32.58_veh-51_00029_00315 + - 2021.10.08.03.32.58_veh-51_00814_00933 + - 2021.10.08.03.32.58_veh-51_00969_01347 + - 2021.10.08.03.32.58_veh-51_01388_01456 + - 2021.10.08.03.32.58_veh-51_01570_01784 + - 2021.10.08.03.32.58_veh-51_01811_02203 + - 2021.10.08.03.32.58_veh-51_02259_02674 + - 2021.10.08.03.34.47_veh-53_00016_00753 + - 2021.10.08.03.34.47_veh-53_00798_01046 + - 2021.10.08.03.34.47_veh-53_01252_01403 + - 2021.10.08.03.34.47_veh-53_01425_01671 + - 2021.10.08.03.34.47_veh-53_01682_02050 + - 2021.10.08.03.34.47_veh-53_02073_02143 + - 2021.10.08.03.34.47_veh-53_02154_02278 + - 2021.10.08.03.43.30_veh-49_00016_00122 + - 2021.10.08.03.43.30_veh-49_00163_00504 + - 2021.10.08.03.43.30_veh-49_00559_00623 + - 2021.10.08.03.43.30_veh-49_00779_00953 + - 2021.10.08.03.43.30_veh-49_01016_01264 + - 2021.10.08.03.43.30_veh-49_01426_01520 + - 2021.10.08.03.43.30_veh-49_01543_01921 + - 2021.10.08.03.56.25_veh-50_00100_00243 + - 2021.10.08.03.56.25_veh-50_00340_00688 + - 2021.10.08.03.56.25_veh-50_00742_00992 + - 2021.10.08.03.56.25_veh-50_01065_01150 + - 2021.10.08.03.56.25_veh-50_01162_01264 + - 2021.10.08.03.56.25_veh-50_01278_01844 + - 2021.10.08.05.41.56_veh-50_00016_00456 + - 2021.10.08.05.41.56_veh-50_00503_00613 + - 2021.10.08.05.41.56_veh-50_00668_00905 + - 2021.10.08.05.41.56_veh-50_00935_01518 + - 2021.10.08.05.41.56_veh-50_01548_02164 + - 2021.10.08.05.41.56_veh-50_02189_02327 + - 2021.10.08.05.41.56_veh-50_02341_02407 + - 2021.10.08.05.41.56_veh-50_02429_02659 + - 2021.10.08.06.38.01_veh-50_00016_00128 + - 2021.10.08.06.38.01_veh-50_00141_00399 + - 2021.10.08.06.38.01_veh-50_00477_00644 + - 2021.10.08.06.38.01_veh-50_00655_01017 + - 2021.10.08.06.38.01_veh-50_01170_01339 + - 2021.10.08.06.38.01_veh-50_01362_01701 + - 2021.10.08.06.38.01_veh-50_01739_01939 + - 2021.10.08.06.38.01_veh-50_01983_02198 + - 2021.10.08.06.38.01_veh-50_02274_02441 + - 2021.10.08.07.31.13_veh-50_00178_00292 + - 2021.10.08.07.31.13_veh-50_00353_00589 + - 2021.10.08.07.31.13_veh-50_00759_01099 + - 2021.10.08.07.31.13_veh-50_01129_01476 + - 2021.10.08.07.31.13_veh-50_01561_01680 + - 2021.10.08.07.31.13_veh-50_01719_01866 + - 2021.10.08.07.31.13_veh-50_01884_02329 + - 2021.10.08.07.31.13_veh-50_02421_02513 + - 2021.10.08.08.24.52_veh-50_00023_00381 + - 2021.10.08.08.24.52_veh-50_00421_00560 + - 2021.10.08.08.24.52_veh-50_00604_00708 + - 2021.10.08.08.24.52_veh-50_00915_01855 + - 2021.10.08.08.58.44_veh-50_00008_00122 + - 2021.10.08.08.58.44_veh-50_00146_00382 + - 2021.10.08.08.58.44_veh-50_00576_00736 + - 2021.10.08.08.58.44_veh-50_00784_00947 + - 2021.10.08.08.58.44_veh-50_00999_01157 + - 2021.10.08.08.58.44_veh-50_01187_01498 + - 2021.10.08.08.58.44_veh-50_01523_01805 + - 2021.10.08.13.10.02_veh-28_00016_00134 + - 2021.10.08.13.10.02_veh-28_00272_00404 + - 2021.10.08.13.10.02_veh-28_00539_01001 + - 2021.10.08.13.10.02_veh-28_01022_01222 + - 2021.10.08.13.10.02_veh-28_01245_01372 + - 2021.10.08.13.10.02_veh-28_01510_01622 + - 2021.10.08.13.10.02_veh-28_01636_01818 + - 2021.10.08.13.47.38_veh-28_00089_00172 + - 2021.10.08.13.47.38_veh-28_00242_00358 + - 2021.10.08.13.47.38_veh-28_00429_00638 + - 2021.10.08.13.47.38_veh-28_00841_00951 + - 2021.10.08.13.47.38_veh-28_01025_01129 + - 2021.10.08.13.47.38_veh-28_01184_01385 + - 2021.10.08.13.47.38_veh-28_01522_01935 + - 2021.10.08.14.24.31_veh-28_00005_00090 + - 2021.10.08.14.24.31_veh-28_00114_00265 + - 2021.10.08.14.24.31_veh-28_00294_00410 + - 2021.10.08.14.24.31_veh-28_00515_00766 + - 2021.10.08.14.24.31_veh-28_00798_00986 + - 2021.10.08.14.24.31_veh-28_01201_01414 + - 2021.10.08.14.24.31_veh-28_01587_01780 + - 2021.10.08.15.06.38_veh-28_00016_00148 + - 2021.10.08.15.06.38_veh-28_00159_00238 + - 2021.10.08.15.06.38_veh-28_00249_00338 + - 2021.10.08.15.06.38_veh-28_00447_00541 + - 2021.10.08.15.06.38_veh-28_00590_00674 + - 2021.10.08.15.06.38_veh-28_00752_00843 + - 2021.10.08.15.06.38_veh-28_00854_01095 + - 2021.10.08.15.06.38_veh-28_01228_01310 + - 2021.10.08.15.06.38_veh-28_01414_01495 + - 2021.10.08.15.06.38_veh-28_01529_01634 + - 2021.10.08.15.06.38_veh-28_01680_01810 + - 2021.10.08.17.19.32_veh-28_00028_00261 + - 2021.10.08.17.19.32_veh-28_00411_00513 + - 2021.10.08.17.19.32_veh-28_00626_00712 + - 2021.10.08.17.19.32_veh-28_00773_00841 + - 2021.10.08.17.19.32_veh-28_00853_01328 + - 2021.10.08.17.19.32_veh-28_01389_01525 + - 2021.10.08.17.19.32_veh-28_01548_01703 + - 2021.10.08.18.26.18_veh-28_00052_00152 + - 2021.10.08.18.26.18_veh-28_00178_00266 + - 2021.10.08.18.26.18_veh-28_00370_00856 + - 2021.10.08.18.26.18_veh-28_00942_01132 + - 2021.10.08.18.26.18_veh-28_01200_01286 + - 2021.10.08.18.26.18_veh-28_01297_01424 + - 2021.10.08.18.26.18_veh-28_01435_01519 + - 2021.10.08.18.57.48_veh-28_00015_00104 + - 2021.10.08.18.57.48_veh-28_00116_00282 + - 2021.10.08.18.57.48_veh-28_00620_01042 + - 2021.10.08.18.57.48_veh-28_01057_01171 + - 2021.10.08.18.57.48_veh-28_01284_01463 + - 2021.10.11.02.48.26_veh-51_00012_00249 + - 2021.10.11.02.48.26_veh-51_00342_00441 + - 2021.10.11.02.48.26_veh-51_00484_00581 + - 2021.10.11.02.48.26_veh-51_00592_00658 + - 2021.10.11.02.48.26_veh-51_00708_01089 + - 2021.10.11.02.48.26_veh-51_01130_01407 + - 2021.10.11.02.48.26_veh-51_01475_01547 + - 2021.10.11.02.48.26_veh-51_01571_01695 + - 2021.10.11.02.48.26_veh-51_01736_02077 + - 2021.10.11.02.48.26_veh-51_02213_02333 + - 2021.10.11.02.57.41_veh-50_00029_00134 + - 2021.10.11.02.57.41_veh-50_00145_00308 + - 2021.10.11.02.57.41_veh-50_00352_00535 + - 2021.10.11.02.57.41_veh-50_00704_00776 + - 2021.10.11.02.57.41_veh-50_00838_01005 + - 2021.10.11.02.57.41_veh-50_01028_01289 + - 2021.10.11.02.57.41_veh-50_01343_01501 + - 2021.10.11.02.57.41_veh-50_01522_02088 + - 2021.10.11.02.57.41_veh-50_02155_02265 + - 2021.10.11.02.57.41_veh-50_02318_02417 + - 2021.10.11.02.57.41_veh-50_02428_02548 + - 2021.10.11.03.42.46_veh-51_00139_00287 + - 2021.10.11.03.42.46_veh-51_00378_00537 + - 2021.10.11.03.42.46_veh-51_00577_00694 + - 2021.10.11.03.42.46_veh-51_00708_01122 + - 2021.10.11.03.42.46_veh-51_01144_01264 + - 2021.10.11.03.42.46_veh-51_01332_01506 + - 2021.10.11.03.42.46_veh-51_01564_01666 + - 2021.10.11.03.42.46_veh-51_01692_02035 + - 2021.10.11.03.42.46_veh-51_02046_02408 + - 2021.10.11.05.34.05_veh-50_00020_00149 + - 2021.10.11.05.34.05_veh-50_00189_00398 + - 2021.10.11.05.34.05_veh-50_00442_00556 + - 2021.10.11.05.34.05_veh-50_00568_00631 + - 2021.10.11.05.34.05_veh-50_00697_00766 + - 2021.10.11.05.34.05_veh-50_00838_00947 + - 2021.10.11.05.34.05_veh-50_00971_01251 + - 2021.10.11.05.34.05_veh-50_01281_01692 + - 2021.10.11.05.34.05_veh-50_01718_02261 + - 2021.10.11.05.34.05_veh-50_02309_02677 + - 2021.10.11.07.12.18_veh-50_00211_00304 + - 2021.10.11.07.12.18_veh-50_00345_00498 + - 2021.10.11.07.12.18_veh-50_00541_00832 + - 2021.10.11.07.12.18_veh-50_00866_01534 + - 2021.10.11.07.12.18_veh-50_01571_01823 + - 2021.10.11.07.47.13_veh-50_00080_00159 + - 2021.10.11.07.47.13_veh-50_00202_00310 + - 2021.10.11.07.47.13_veh-50_00326_00708 + - 2021.10.11.07.47.13_veh-50_00736_00843 + - 2021.10.11.07.47.13_veh-50_00886_00952 + - 2021.10.11.07.47.13_veh-50_01020_01123 + - 2021.10.11.07.47.13_veh-50_01190_01452 + - 2021.10.11.07.47.13_veh-50_01513_02138 + - 2021.10.11.08.31.07_veh-50_00005_00242 + - 2021.10.11.08.31.07_veh-50_00282_00680 + - 2021.10.11.08.31.07_veh-50_00791_00954 + - 2021.10.11.08.31.07_veh-50_01001_01076 + - 2021.10.11.08.31.07_veh-50_01184_01318 + - 2021.10.11.08.31.07_veh-50_01365_01539 + - 2021.10.11.08.31.07_veh-50_01576_01734 + - 2021.10.11.08.31.07_veh-50_01750_01948 + - 2021.10.11.08.31.07_veh-50_01972_02057 + - 2021.10.11.08.31.07_veh-50_02146_02283 + - 2021.10.11.08.31.07_veh-50_02360_02684 + - 2021.10.11.09.08.18_veh-51_00005_00427 + - 2021.10.11.09.08.18_veh-51_00438_00519 + - 2021.10.11.09.08.18_veh-51_00591_00703 + - 2021.10.11.09.08.18_veh-51_00715_00829 + - 2021.10.11.09.08.18_veh-51_00885_01000 + - 2021.10.11.09.08.18_veh-51_01195_01847 + - 2021.10.11.09.08.18_veh-51_01860_02195 + - 2021.10.11.13.27.07_veh-28_00098_00424 + - 2021.10.11.13.27.07_veh-28_00455_00671 + - 2021.10.11.13.27.07_veh-28_00699_00824 + - 2021.10.11.13.27.07_veh-28_00898_01058 + - 2021.10.11.13.27.07_veh-28_01218_01542 + - 2021.10.11.13.27.07_veh-28_01555_01678 + - 2021.10.11.14.02.47_veh-28_00126_00262 + - 2021.10.11.14.02.47_veh-28_00296_00438 + - 2021.10.11.14.02.47_veh-28_00451_00559 + - 2021.10.11.14.02.47_veh-28_00748_00841 + - 2021.10.11.14.02.47_veh-28_00926_01030 + - 2021.10.11.14.02.47_veh-28_01043_01833 + - 2021.10.11.14.48.58_veh-28_00045_00124 + - 2021.10.11.14.48.58_veh-28_00414_00642 + - 2021.10.11.14.48.58_veh-28_00654_00727 + - 2021.10.11.14.48.58_veh-28_00900_01009 + - 2021.10.11.14.48.58_veh-28_01021_01307 + - 2021.10.11.14.48.58_veh-28_01327_01457 + - 2021.10.11.14.48.58_veh-28_01521_01589 + - 2021.10.11.14.48.58_veh-28_01600_01803 + - 2021.10.11.15.23.17_veh-28_00052_00123 + - 2021.10.11.15.23.17_veh-28_00141_00298 + - 2021.10.11.15.23.17_veh-28_00387_00516 + - 2021.10.11.15.23.17_veh-28_00559_00791 + - 2021.10.11.15.23.17_veh-28_00819_00881 + - 2021.10.11.15.23.17_veh-28_01138_01222 + - 2021.10.11.17.07.38_veh-28_00088_00161 + - 2021.10.11.17.07.38_veh-28_00220_00305 + - 2021.10.11.17.07.38_veh-28_00437_00523 + - 2021.10.11.17.07.38_veh-28_00696_01222 + - 2021.10.11.17.07.38_veh-28_01247_01515 + - 2021.10.11.17.07.38_veh-28_01583_01741 + - 2021.10.11.17.07.38_veh-28_01822_01900 + - 2021.10.11.17.07.38_veh-28_01937_02042 + - 2021.10.11.17.48.54_veh-28_00021_00147 + - 2021.10.11.17.48.54_veh-28_00324_01100 + - 2021.10.11.17.48.54_veh-28_01165_01359 + - 2021.10.11.17.48.54_veh-28_01429_01505 + - 2021.10.11.17.48.54_veh-28_01516_01602 + - 2021.10.11.17.48.54_veh-28_01660_01724 + - 2021.10.11.18.33.55_veh-28_00016_00123 + - 2021.10.11.18.33.55_veh-28_00137_00243 + - 2021.10.11.18.33.55_veh-28_00255_00341 + - 2021.10.11.18.33.55_veh-28_00369_00443 + - 2021.10.11.18.33.55_veh-28_00563_00641 + - 2021.10.11.18.33.55_veh-28_00821_00938 + - 2021.10.11.18.33.55_veh-28_00950_01245 + - 2021.10.11.18.33.55_veh-28_01303_01448 + - 2021.10.11.18.33.55_veh-28_01718_01793 + - 2021.10.11.19.09.48_veh-28_00016_00122 + - 2021.10.11.19.09.48_veh-28_00257_00439 + - 2021.10.11.19.09.48_veh-28_00465_00786 + - 2021.10.11.19.09.48_veh-28_00797_01414 + - 2021.10.11.19.09.48_veh-28_01429_01504 + - 2021.10.11.19.09.48_veh-28_01515_01644 + - 2021.10.11.19.09.48_veh-28_01664_01744 + - 2021.10.11.19.09.48_veh-28_01879_01965 + - 2021.10.12.06.20.27_veh-49_00005_00350 + - 2021.10.12.06.20.27_veh-49_00385_00554 + - 2021.10.12.06.20.27_veh-49_00600_01008 + - 2021.10.12.06.20.27_veh-49_01030_01324 + - 2021.10.12.06.20.27_veh-49_01392_01846 + - 2021.10.12.06.54.55_veh-49_00043_00262 + - 2021.10.12.06.54.55_veh-49_00273_00536 + - 2021.10.12.06.54.55_veh-49_00548_00626 + - 2021.10.12.06.54.55_veh-49_00682_01341 + - 2021.10.12.08.16.50_veh-49_00009_00390 + - 2021.10.12.08.16.50_veh-49_00597_00767 + - 2021.10.12.08.16.50_veh-49_00831_01118 + - 2021.10.12.08.16.50_veh-49_01173_01304 + - 2021.10.12.08.16.50_veh-49_01315_01383 + - 2021.10.12.08.16.50_veh-49_01405_01515 + - 2021.10.12.08.16.50_veh-49_01566_01633 + - 2021.10.12.08.16.50_veh-49_01648_02088 + - 2021.10.12.08.16.50_veh-49_02104_02188 + - 2021.10.12.13.17.59_veh-28_00016_00077 + - 2021.10.12.13.17.59_veh-28_00088_00159 + - 2021.10.12.13.17.59_veh-28_00367_00618 + - 2021.10.12.13.17.59_veh-28_00629_00974 + - 2021.10.12.13.17.59_veh-28_01060_01131 + - 2021.10.12.13.17.59_veh-28_01226_01438 + - 2021.10.12.13.49.33_veh-28_00153_00251 + - 2021.10.12.13.49.33_veh-28_00332_00414 + - 2021.10.12.13.49.33_veh-28_00471_00630 + - 2021.10.12.13.49.33_veh-28_00668_00775 + - 2021.10.12.13.49.33_veh-28_00935_01078 + - 2021.10.12.13.49.33_veh-28_01171_01252 + - 2021.10.12.13.49.33_veh-28_01340_01835 + - 2021.10.12.13.49.33_veh-28_02007_02129 + - 2021.10.12.13.49.33_veh-28_02178_02303 + - 2021.10.12.14.34.49_veh-28_00016_00129 + - 2021.10.12.14.34.49_veh-28_00154_00354 + - 2021.10.12.14.34.49_veh-28_00549_00637 + - 2021.10.12.14.34.49_veh-28_00904_01101 + - 2021.10.12.14.34.49_veh-28_01140_01245 + - 2021.10.12.14.34.49_veh-28_01283_01532 + - 2021.10.12.14.34.49_veh-28_01565_01629 + - 2021.10.12.14.34.49_veh-28_01641_01728 + - 2021.10.12.14.34.49_veh-28_01851_01914 + - 2021.10.12.14.34.49_veh-28_01973_02310 + - 2021.10.12.14.34.49_veh-28_02404_02554 + - 2021.10.12.17.43.00_veh-28_00015_00119 + - 2021.10.12.17.43.00_veh-28_00188_00257 + - 2021.10.12.17.43.00_veh-28_00280_00416 + - 2021.10.12.17.43.00_veh-28_00428_01006 + - 2021.10.12.17.43.00_veh-28_01091_01256 + - 2021.10.12.17.43.00_veh-28_01617_01712 + - 2021.10.12.18.48.46_veh-28_00081_00268 + - 2021.10.12.18.48.46_veh-28_00279_00503 + - 2021.10.12.18.48.46_veh-28_00592_00940 + - 2021.10.12.18.48.46_veh-28_01118_01360 + - 2021.10.12.19.20.46_veh-28_00048_00124 + - 2021.10.12.19.20.46_veh-28_00288_00433 + - 2021.10.12.19.20.46_veh-28_00503_00633 + - 2021.10.12.19.20.46_veh-28_00644_00868 + - 2021.10.12.19.20.46_veh-28_00895_01031 + - 2021.10.12.19.20.46_veh-28_01054_01142 + - 2021.10.12.19.20.46_veh-28_01242_01408 + - 2021.10.12.19.20.46_veh-28_01419_01511 + - 2021.10.12.19.52.52_veh-28_00439_00637 + - 2021.10.12.19.52.52_veh-28_00648_00799 + - 2021.10.12.19.52.52_veh-28_00952_01204 + - 2021.10.12.19.52.52_veh-28_01281_01375 + - 2021.10.12.19.52.52_veh-28_01387_01502 + - 2021.10.13.02.51.30_veh-49_00016_00508 + - 2021.10.13.02.51.30_veh-49_00585_00696 + - 2021.10.13.02.51.30_veh-49_00760_00836 + - 2021.10.13.02.51.30_veh-49_00849_00923 + - 2021.10.13.02.51.30_veh-49_00944_01138 + - 2021.10.13.02.51.30_veh-49_01151_01393 + - 2021.10.13.02.51.30_veh-49_01404_01865 + - 2021.10.13.02.51.30_veh-49_01922_02402 + - 2021.10.13.02.51.30_veh-49_02464_02592 + - 2021.10.13.03.58.55_veh-49_00025_00373 + - 2021.10.13.03.58.55_veh-49_00385_00524 + - 2021.10.13.03.58.55_veh-49_00635_00775 + - 2021.10.13.03.58.55_veh-49_00788_01184 + - 2021.10.13.03.58.55_veh-49_01221_01789 + - 2021.10.13.03.58.55_veh-49_01879_02084 + - 2021.10.13.03.58.55_veh-49_02101_02268 + - 2021.10.13.03.58.55_veh-49_02322_02637 + - 2021.10.13.06.37.09_veh-49_00049_00189 + - 2021.10.13.06.37.09_veh-49_00203_00409 + - 2021.10.13.06.37.09_veh-49_00429_00553 + - 2021.10.13.06.37.09_veh-49_00571_01208 + - 2021.10.13.06.37.09_veh-49_01248_01422 + - 2021.10.13.06.37.09_veh-49_01548_02424 + - 2021.10.13.06.37.09_veh-49_02440_02523 + - 2021.10.13.07.28.44_veh-49_00016_00211 + - 2021.10.13.07.28.44_veh-49_00293_00447 + - 2021.10.13.07.28.44_veh-49_00543_00805 + - 2021.10.13.07.28.44_veh-49_00969_01267 + - 2021.10.13.07.28.44_veh-49_01311_01561 + - 2021.10.13.07.28.44_veh-49_01605_01677 + - 2021.10.13.07.28.44_veh-49_01705_01933 + - 2021.10.13.07.28.44_veh-49_01960_02125 + - 2021.10.13.07.28.44_veh-49_02138_02745 + - 2021.10.13.14.40.14_veh-28_00131_00430 + - 2021.10.13.14.40.14_veh-28_00528_00610 + - 2021.10.13.14.40.14_veh-28_00665_00761 + - 2021.10.13.14.40.14_veh-28_00773_01033 + - 2021.10.13.14.40.14_veh-28_01119_01246 + - 2021.10.13.14.40.14_veh-28_01257_01470 + - 2021.10.13.14.40.14_veh-28_01626_01689 + - 2021.10.13.14.40.14_veh-28_01884_01950 + - 2021.10.13.14.40.14_veh-28_01961_02068 + - 2021.10.13.14.40.14_veh-28_02223_02309 + - 2021.10.13.17.10.30_veh-28_00022_00114 + - 2021.10.13.17.10.30_veh-28_00339_00534 + - 2021.10.13.17.10.30_veh-28_00553_01312 + - 2021.10.13.17.10.30_veh-28_01433_01565 + - 2021.10.13.17.10.30_veh-28_01597_01720 + - 2021.10.13.17.44.34_veh-28_00191_00347 + - 2021.10.13.17.44.34_veh-28_00436_00735 + - 2021.10.13.17.44.34_veh-28_00806_01075 + - 2021.10.13.17.44.34_veh-28_01087_01430 + - 2021.10.13.17.44.34_veh-28_01564_01755 + - 2021.10.13.17.44.34_veh-28_01908_02007 + - 2021.10.13.18.27.19_veh-28_00076_00237 + - 2021.10.13.18.27.19_veh-28_00252_00402 + - 2021.10.13.18.27.19_veh-28_00413_00637 + - 2021.10.13.18.27.19_veh-28_00720_01088 + - 2021.10.13.18.27.19_veh-28_01129_01233 + - 2021.10.13.18.27.19_veh-28_01428_01578 + - 2021.10.13.18.27.19_veh-28_01592_01824 + - 2021.10.13.19.04.40_veh-28_00041_00175 + - 2021.10.13.19.04.40_veh-28_00330_00399 + - 2021.10.13.19.04.40_veh-28_00431_00499 + - 2021.10.13.19.04.40_veh-28_00588_00681 + - 2021.10.13.19.04.40_veh-28_00805_01264 + - 2021.10.13.19.04.40_veh-28_01305_01392 + - 2021.10.13.19.04.40_veh-28_01447_01519 + - 2021.10.13.19.37.51_veh-28_00100_00220 + - 2021.10.13.19.37.51_veh-28_00289_00909 + - 2021.10.13.19.37.51_veh-28_00938_01052 + - 2021.10.13.19.37.51_veh-28_01064_01125 + - 2021.10.14.12.21.43_veh-28_00016_00141 + - 2021.10.14.12.21.43_veh-28_00264_00436 + - 2021.10.14.12.21.43_veh-28_00449_01135 + - 2021.10.14.12.21.43_veh-28_01158_01252 + - 2021.10.14.12.21.43_veh-28_01276_01356 + - 2021.10.14.12.21.43_veh-28_01411_01521 + - 2021.10.14.12.57.37_veh-28_00098_00162 + - 2021.10.14.12.57.37_veh-28_00346_00576 + - 2021.10.14.12.57.37_veh-28_00640_00700 + - 2021.10.14.12.57.37_veh-28_00746_00948 + - 2021.10.14.12.57.37_veh-28_00972_01133 + - 2021.10.14.12.57.37_veh-28_01146_01248 + - 2021.10.14.12.57.37_veh-28_01307_01487 + - 2021.10.14.14.14.08_veh-28_00069_00321 + - 2021.10.14.14.14.08_veh-28_00382_00686 + - 2021.10.14.14.14.08_veh-28_00748_00831 + - 2021.10.14.14.14.08_veh-28_00883_00968 + - 2021.10.14.14.14.08_veh-28_01089_01616 + - 2021.10.14.14.50.40_veh-28_00022_00129 + - 2021.10.14.14.50.40_veh-28_00269_00376 + - 2021.10.14.14.50.40_veh-28_00420_00732 + - 2021.10.14.14.50.40_veh-28_00743_01037 + - 2021.10.14.14.50.40_veh-28_01059_01137 + - 2021.10.14.14.50.40_veh-28_01183_01338 + - 2021.10.14.14.50.40_veh-28_01444_01589 + - 2021.10.14.17.47.55_veh-28_00016_00169 + - 2021.10.14.17.47.55_veh-28_00336_00469 + - 2021.10.14.17.47.55_veh-28_00484_01094 + - 2021.10.14.17.47.55_veh-28_01129_01210 + - 2021.10.14.17.47.55_veh-28_01221_01385 + - 2021.10.14.17.47.55_veh-28_01716_01796 + - 2021.10.14.18.43.44_veh-28_00096_00191 + - 2021.10.14.18.43.44_veh-28_00359_00588 + - 2021.10.14.18.43.44_veh-28_00638_00712 + - 2021.10.14.18.43.44_veh-28_00724_00948 + - 2021.10.14.18.43.44_veh-28_01091_01369 + - 2021.10.14.18.43.44_veh-28_01392_01670 + - 2021.10.14.18.43.44_veh-28_01758_01833 + - 2021.10.14.19.26.26_veh-28_00028_00161 + - 2021.10.14.19.26.26_veh-28_00189_00319 + - 2021.10.14.19.26.26_veh-28_00379_00473 + - 2021.10.14.19.26.26_veh-28_00621_00693 + - 2021.10.14.19.26.26_veh-28_00776_00975 + - 2021.10.14.19.26.26_veh-28_01000_01229 + - 2021.10.14.19.26.26_veh-28_01274_01600 + - 2021.10.14.19.26.26_veh-28_01638_01790 + - 2021.10.14.19.26.26_veh-28_02040_02128 + - 2021.10.15.02.00.24_veh-53_00039_00411 + - 2021.10.15.02.00.24_veh-53_00457_00630 + - 2021.10.15.02.00.24_veh-53_00666_00786 + - 2021.10.15.02.00.24_veh-53_00805_00920 + - 2021.10.15.02.00.24_veh-53_00931_01325 + - 2021.10.15.02.00.24_veh-53_01345_01789 + - 2021.10.15.02.00.24_veh-53_01819_01972 + - 2021.10.15.02.36.56_veh-53_00142_00270 + - 2021.10.15.02.36.56_veh-53_00350_00432 + - 2021.10.15.02.36.56_veh-53_00468_00629 + - 2021.10.15.02.36.56_veh-53_00683_00753 + - 2021.10.15.02.36.56_veh-53_00782_01463 + - 2021.10.15.02.36.56_veh-53_01531_01624 + - 2021.10.15.02.36.56_veh-53_01635_02009 + - 2021.10.15.02.36.56_veh-53_02020_02442 + - 2021.10.15.12.13.23_veh-28_00021_00100 + - 2021.10.15.12.13.23_veh-28_00273_00402 + - 2021.10.15.12.13.23_veh-28_00433_00606 + - 2021.10.15.12.13.23_veh-28_00627_01090 + - 2021.10.15.12.13.23_veh-28_01187_01315 + - 2021.10.15.12.13.23_veh-28_01474_01632 + - 2021.10.15.12.46.33_veh-28_00015_00135 + - 2021.10.15.12.46.33_veh-28_00242_00430 + - 2021.10.15.12.46.33_veh-28_00441_00579 + - 2021.10.15.12.46.33_veh-28_00841_01004 + - 2021.10.15.12.46.33_veh-28_01032_01093 + - 2021.10.15.12.46.33_veh-28_01240_01413 + - 2021.10.15.12.46.33_veh-28_01469_01576 + - 2021.10.15.12.46.33_veh-28_01588_01661 + - 2021.10.15.12.46.33_veh-28_01672_01782 + - 2021.10.15.12.46.33_veh-28_01807_01889 + - 2021.10.15.13.23.06_veh-28_00103_00181 + - 2021.10.15.13.23.06_veh-28_00347_00419 + - 2021.10.15.13.23.06_veh-28_00521_00746 + - 2021.10.15.13.23.06_veh-28_00757_01003 + - 2021.10.15.13.23.06_veh-28_01090_01198 + - 2021.10.15.13.23.06_veh-28_01260_01743 + - 2021.10.15.13.23.06_veh-28_01865_01932 + - 2021.10.15.18.45.04_veh-28_00038_00126 + - 2021.10.15.18.45.04_veh-28_00140_00223 + - 2021.10.15.18.45.04_veh-28_00265_00425 + - 2021.10.15.18.45.04_veh-28_00454_01105 + - 2021.10.15.18.45.04_veh-28_01155_01318 + - 2021.10.15.18.45.04_veh-28_01501_01618 + - 2021.10.15.18.45.04_veh-28_01665_01746 + - 2021.10.15.18.45.04_veh-28_01770_01849 + - 2021.10.15.19.44.30_veh-28_00039_00211 + - 2021.10.15.19.44.30_veh-28_00294_00426 + - 2021.10.15.19.44.30_veh-28_00521_00891 + - 2021.10.15.19.44.30_veh-28_00904_01057 + - 2021.10.15.19.44.30_veh-28_01071_01198 + - 2021.10.15.19.44.30_veh-28_01361_01462 + - 2021.10.15.19.44.30_veh-28_01507_01635 + - 2021.10.15.19.44.30_veh-28_01662_01746 + - 2021.10.18.12.56.18_veh-28_00016_00097 + - 2021.10.18.12.56.18_veh-28_00109_00275 + - 2021.10.18.12.56.18_veh-28_00286_00397 + - 2021.10.18.12.56.18_veh-28_00426_00535 + - 2021.10.18.12.56.18_veh-28_00546_01154 + - 2021.10.18.12.56.18_veh-28_01183_01288 + - 2021.10.18.12.56.18_veh-28_01515_01587 + - 2021.10.18.12.56.18_veh-28_01609_01744 + - 2021.10.18.12.56.18_veh-28_01756_01845 + - 2021.10.18.12.56.18_veh-28_01856_01989 + - 2021.10.18.12.56.18_veh-28_02055_02204 + - 2021.10.18.12.56.18_veh-28_02215_02283 + - 2021.10.18.13.41.04_veh-28_00042_00226 + - 2021.10.18.13.41.04_veh-28_00255_00488 + - 2021.10.18.13.41.04_veh-28_00499_01010 + - 2021.10.18.13.41.04_veh-28_01045_01137 + - 2021.10.18.13.41.04_veh-28_01401_01476 + - 2021.10.18.13.41.04_veh-28_01565_02090 + - 2021.10.18.13.41.04_veh-28_02114_02222 + - 2021.10.18.14.24.40_veh-28_00038_00420 + - 2021.10.18.14.24.40_veh-28_00613_00808 + - 2021.10.18.14.24.40_veh-28_00908_01114 + - 2021.10.18.14.24.40_veh-28_01167_01603 + - 2021.10.18.14.57.04_veh-28_00150_00226 + - 2021.10.18.14.57.04_veh-28_00332_00477 + - 2021.10.18.14.57.04_veh-28_00884_00945 + - 2021.10.18.14.57.04_veh-28_00957_01033 + - 2021.10.18.14.57.04_veh-28_01121_01396 + - 2021.10.18.14.57.04_veh-28_01408_01796 + - 2021.10.18.14.57.04_veh-28_01807_02056 + - 2021.10.18.15.36.48_veh-28_00027_00262 + - 2021.10.18.15.36.48_veh-28_00273_00361 + - 2021.10.18.15.36.48_veh-28_00417_00497 + - 2021.10.18.15.36.48_veh-28_00653_00727 + - 2021.10.18.15.36.48_veh-28_00819_00940 + - 2021.10.18.15.36.48_veh-28_00951_01329 + - 2021.10.18.15.36.48_veh-28_01359_01448 + - 2021.10.18.15.36.48_veh-28_01461_01619 + - 2021.10.18.17.49.44_veh-28_00033_00139 + - 2021.10.18.17.49.44_veh-28_00338_00892 + - 2021.10.18.17.49.44_veh-28_00948_01081 + - 2021.10.18.17.49.44_veh-28_01112_01331 + - 2021.10.18.17.49.44_veh-28_01440_01582 + - 2021.10.18.18.22.08_veh-28_00035_00205 + - 2021.10.18.18.22.08_veh-28_00366_00498 + - 2021.10.18.18.22.08_veh-28_00622_00752 + - 2021.10.18.18.22.08_veh-28_00765_00907 + - 2021.10.18.18.22.08_veh-28_00918_00981 + - 2021.10.18.18.22.08_veh-28_01036_01121 + - 2021.10.18.18.22.08_veh-28_01133_01201 + - 2021.10.18.18.22.08_veh-28_01248_01396 + - 2021.10.18.18.22.08_veh-28_01420_01652 + - 2021.10.18.18.22.08_veh-28_01703_01775 + - 2021.10.18.18.54.22_veh-28_00360_00469 + - 2021.10.18.18.54.22_veh-28_00701_00797 + - 2021.10.18.18.54.22_veh-28_00860_01106 + - 2021.10.18.18.54.22_veh-28_01159_01427 + - 2021.10.18.18.54.22_veh-28_01499_01585 + - 2021.10.18.19.25.53_veh-28_00015_00419 + - 2021.10.18.19.25.53_veh-28_00456_00590 + - 2021.10.18.19.25.53_veh-28_00613_00695 + - 2021.10.18.19.25.53_veh-28_00821_00933 + - 2021.10.18.19.25.53_veh-28_00971_01231 + - 2021.10.18.19.25.53_veh-28_01306_01525 + - 2021.10.18.19.25.53_veh-28_01665_01875 + - 2021.10.18.19.25.53_veh-28_02063_02134 + - 2021.10.18.19.25.53_veh-28_02306_02401 + - 2021.10.18.19.25.53_veh-28_02472_02578 + - 2021.10.19.12.30.06_veh-28_00036_00128 + - 2021.10.19.12.30.06_veh-28_00274_00381 + - 2021.10.19.12.30.06_veh-28_00409_00714 + - 2021.10.19.12.30.06_veh-28_00736_00962 + - 2021.10.19.12.30.06_veh-28_00976_01199 + - 2021.10.19.12.30.06_veh-28_01419_01628 + - 2021.10.19.13.03.24_veh-28_00005_00119 + - 2021.10.19.13.03.24_veh-28_00217_00373 + - 2021.10.19.13.03.24_veh-28_00384_00590 + - 2021.10.19.13.03.24_veh-28_00899_01135 + - 2021.10.19.13.03.24_veh-28_01202_01361 + - 2021.10.19.13.03.24_veh-28_01385_01568 + - 2021.10.19.13.03.24_veh-28_01607_01671 + - 2021.10.19.13.40.14_veh-28_00009_00127 + - 2021.10.19.13.40.14_veh-28_00139_00241 + - 2021.10.19.13.40.14_veh-28_00252_00367 + - 2021.10.19.13.40.14_veh-28_00488_00577 + - 2021.10.19.13.40.14_veh-28_00605_00791 + - 2021.10.19.13.40.14_veh-28_00802_00863 + - 2021.10.19.13.40.14_veh-28_00901_00970 + - 2021.10.19.13.40.14_veh-28_00986_01207 + - 2021.10.19.13.40.14_veh-28_01304_01396 + - 2021.10.19.13.40.14_veh-28_01437_01588 + - 2021.10.19.13.40.14_veh-28_01630_01714 + - 2021.10.19.13.40.14_veh-28_01765_01831 + - 2021.10.19.14.15.34_veh-28_00279_00364 + - 2021.10.19.14.15.34_veh-28_00507_00747 + - 2021.10.19.14.15.34_veh-28_00768_00944 + - 2021.10.19.14.15.34_veh-28_00969_01043 + - 2021.10.19.14.15.34_veh-28_01098_01398 + - 2021.10.19.14.15.34_veh-28_01463_01708 + - 2021.10.19.14.48.58_veh-28_00023_00105 + - 2021.10.19.14.48.58_veh-28_00263_00343 + - 2021.10.19.14.48.58_veh-28_00368_00481 + - 2021.10.19.14.48.58_veh-28_00494_00570 + - 2021.10.19.14.48.58_veh-28_00581_00698 + - 2021.10.19.14.48.58_veh-28_00709_00977 + - 2021.10.19.14.48.58_veh-28_01102_01235 + - 2021.10.19.14.48.58_veh-28_01276_01360 + - 2021.10.19.18.09.44_veh-28_00116_00213 + - 2021.10.19.18.09.44_veh-28_00493_01040 + - 2021.10.19.18.09.44_veh-28_01064_01238 + - 2021.10.19.18.09.44_veh-28_01561_01659 + - 2021.10.19.18.09.44_veh-28_01671_01793 + - 2021.10.19.18.48.46_veh-28_00020_00123 + - 2021.10.19.18.48.46_veh-28_00295_00409 + - 2021.10.19.18.48.46_veh-28_00435_00624 + - 2021.10.19.18.48.46_veh-28_00657_00869 + - 2021.10.19.18.48.46_veh-28_00882_01031 + - 2021.10.19.18.48.46_veh-28_01081_01347 + - 2021.10.19.18.48.46_veh-28_01373_01458 + - 2021.10.19.18.48.46_veh-28_01495_01641 + - 2021.10.19.19.24.01_veh-28_00016_00131 + - 2021.10.19.19.24.01_veh-28_00144_00252 + - 2021.10.19.19.24.01_veh-28_00352_00466 + - 2021.10.19.19.24.01_veh-28_00585_01045 + - 2021.10.19.19.24.01_veh-28_01109_01342 + - 2021.10.20.13.30.37_veh-28_00028_00122 + - 2021.10.20.13.30.37_veh-28_00325_00396 + - 2021.10.20.13.30.37_veh-28_00566_00845 + - 2021.10.20.13.30.37_veh-28_00875_00947 + - 2021.10.20.13.30.37_veh-28_00981_01845 + - 2021.10.20.13.30.37_veh-28_01869_02031 + - 2021.10.20.13.30.37_veh-28_02166_02262 + - 2021.10.20.14.15.35_veh-28_00099_00294 + - 2021.10.20.14.15.35_veh-28_00345_00448 + - 2021.10.20.14.15.35_veh-28_00528_00731 + - 2021.10.20.14.15.35_veh-28_00846_01058 + - 2021.10.20.14.15.35_veh-28_01087_01272 + - 2021.10.20.14.15.35_veh-28_01301_01540 + - 2021.10.20.14.15.35_veh-28_01625_01731 + - 2021.10.20.14.15.35_veh-28_01768_01857 + - 2021.10.20.14.15.35_veh-28_01896_02052 + - 2021.10.20.17.01.17_veh-28_00016_00103 + - 2021.10.20.17.01.17_veh-28_00115_00497 + - 2021.10.20.17.01.17_veh-28_00508_00599 + - 2021.10.20.17.01.17_veh-28_00610_00743 + - 2021.10.20.17.01.17_veh-28_00812_01053 + - 2021.10.20.17.01.17_veh-28_01123_01209 + - 2021.10.20.17.01.17_veh-28_01220_01312 + - 2021.10.20.17.01.17_veh-28_01324_01584 + - 2021.10.20.17.36.18_veh-28_00016_00086 + - 2021.10.20.17.36.18_veh-28_00097_00224 + - 2021.10.20.17.36.18_veh-28_00267_00482 + - 2021.10.20.17.36.18_veh-28_00511_00903 + - 2021.10.20.17.36.18_veh-28_00990_01100 + - 2021.10.20.17.36.18_veh-28_01343_01458 + - 2021.10.20.17.36.18_veh-28_01516_01619 + - 2021.10.20.18.10.22_veh-28_00170_00286 + - 2021.10.20.18.10.22_veh-28_00297_00524 + - 2021.10.20.18.10.22_veh-28_00622_00730 + - 2021.10.20.18.10.22_veh-28_00806_00927 + - 2021.10.20.18.10.22_veh-28_00938_01026 + - 2021.10.20.18.10.22_veh-28_01037_01321 + - 2021.10.20.18.10.22_veh-28_01369_01477 + - 2021.10.20.18.10.22_veh-28_01488_01597 + - 2021.10.20.18.47.18_veh-28_00054_00262 + - 2021.10.20.18.47.18_veh-28_00317_00403 + - 2021.10.20.18.47.18_veh-28_00487_01210 + - 2021.10.20.18.47.18_veh-28_01221_01318 + - 2021.10.20.18.47.18_veh-28_01347_01475 + - 2021.10.20.18.47.18_veh-28_01502_01654 + - 2021.10.20.19.25.14_veh-28_00032_00095 + - 2021.10.20.19.25.14_veh-28_00147_00271 + - 2021.10.20.19.25.14_veh-28_00450_00992 + - 2021.10.20.19.25.14_veh-28_01065_01406 + - 2021.10.20.19.25.14_veh-28_01438_01646 + - 2021.10.20.19.25.14_veh-28_01666_01736 + - 2021.10.20.19.25.14_veh-28_01747_01951 + - 2021.10.21.13.54.43_veh-28_00167_00247 + - 2021.10.21.13.54.43_veh-28_00288_00400 + - 2021.10.21.13.54.43_veh-28_00411_00645 + - 2021.10.21.13.54.43_veh-28_00715_00864 + - 2021.10.21.13.54.43_veh-28_01213_01362 + - 2021.10.21.13.54.43_veh-28_01525_01615 + - 2021.10.21.13.54.43_veh-28_01702_01792 + - 2021.10.21.13.54.43_veh-28_01874_01958 + - 2021.10.21.13.54.43_veh-28_01991_02108 + - 2021.10.21.13.54.43_veh-28_02119_02489 + - 2021.10.21.14.43.30_veh-28_00005_00459 + - 2021.10.21.14.43.30_veh-28_00540_00633 + - 2021.10.21.14.43.30_veh-28_00712_01070 + - 2021.10.21.14.43.30_veh-28_01244_01519 + - 2021.10.21.14.43.30_veh-28_02125_02200 + - 2021.10.21.14.43.30_veh-28_02285_02372 + - 2021.10.21.14.43.30_veh-28_02383_02657 + - 2021.10.21.17.08.25_veh-28_00016_00119 + - 2021.10.21.17.08.25_veh-28_00145_00278 + - 2021.10.21.17.08.25_veh-28_00289_00495 + - 2021.10.21.17.08.25_veh-28_00521_00992 + - 2021.10.21.17.08.25_veh-28_01003_01103 + - 2021.10.21.17.08.25_veh-28_01126_01314 + - 2021.10.21.17.08.25_veh-28_01389_01613 + - 2021.10.21.17.08.25_veh-28_01635_01741 + - 2021.10.21.17.58.39_veh-28_00028_00099 + - 2021.10.21.17.58.39_veh-28_00181_00244 + - 2021.10.21.17.58.39_veh-28_00285_00368 + - 2021.10.21.17.58.39_veh-28_00737_01054 + - 2021.10.21.17.58.39_veh-28_01065_01202 + - 2021.10.21.17.58.39_veh-28_01255_01421 + - 2021.10.21.19.07.24_veh-28_00017_00178 + - 2021.10.21.19.07.24_veh-28_00256_00470 + - 2021.10.21.19.07.24_veh-28_00489_00551 + - 2021.10.21.19.07.24_veh-28_00571_01295 + - 2021.10.21.19.07.24_veh-28_01348_01685 + - 2021.10.21.19.40.48_veh-28_00097_00310 + - 2021.10.21.19.40.48_veh-28_00375_00823 + - 2021.10.21.19.40.48_veh-28_00834_01565 + - 2021.10.21.19.40.48_veh-28_01605_01695 + - 2021.10.22.13.52.39_veh-28_00104_00178 + - 2021.10.22.13.52.39_veh-28_00189_00286 + - 2021.10.22.13.52.39_veh-28_00297_00438 + - 2021.10.22.13.52.39_veh-28_00538_00614 + - 2021.10.22.13.52.39_veh-28_00858_01245 + - 2021.10.22.13.52.39_veh-28_01390_01584 + - 2021.10.22.14.58.40_veh-28_00011_00111 + - 2021.10.22.14.58.40_veh-28_00499_00630 + - 2021.10.22.14.58.40_veh-28_00727_01359 + - 2021.10.22.14.58.40_veh-28_01433_01589 + - 2021.10.22.18.02.31_veh-28_00036_00129 + - 2021.10.22.18.02.31_veh-28_00160_00315 + - 2021.10.22.18.02.31_veh-28_00326_00685 + - 2021.10.22.18.02.31_veh-28_00717_00811 + - 2021.10.22.18.02.31_veh-28_00865_00983 + - 2021.10.22.18.02.31_veh-28_01300_01380 + - 2021.10.22.18.02.31_veh-28_01391_01637 + - 2021.10.22.18.02.31_veh-28_01717_02099 + - 2021.10.22.18.45.52_veh-28_00008_00079 + - 2021.10.22.18.45.52_veh-28_00168_00302 + - 2021.10.22.18.45.52_veh-28_00313_00628 + - 2021.10.22.18.45.52_veh-28_00651_00768 + - 2021.10.22.18.45.52_veh-28_00780_00896 + - 2021.10.22.18.45.52_veh-28_00907_00973 + - 2021.10.22.18.45.52_veh-28_01093_01164 + - 2021.10.22.18.45.52_veh-28_01175_01298 + + val: + - 2021.06.07.11.59.52_veh-35_00008_00083 + - 2021.06.07.11.59.52_veh-35_00095_00555 + - 2021.06.07.11.59.52_veh-35_00566_00754 + - 2021.06.07.11.59.52_veh-35_00765_01072 + - 2021.06.07.11.59.52_veh-35_01102_01213 + - 2021.06.07.11.59.52_veh-35_01224_01328 + - 2021.06.07.11.59.52_veh-35_01412_01652 + - 2021.06.07.11.59.52_veh-35_01710_01858 + - 2021.06.07.11.59.52_veh-35_01884_01991 + - 2021.06.07.11.59.52_veh-35_02002_02116 + - 2021.06.07.11.59.52_veh-35_02127_02272 + - 2021.06.07.11.59.52_veh-35_02283_02464 + - 2021.06.07.12.01.13_veh-47_00093_00572 + - 2021.06.07.12.01.13_veh-47_00624_00689 + - 2021.06.07.12.01.13_veh-47_00730_00915 + - 2021.06.07.12.01.13_veh-47_00926_01372 + - 2021.06.07.12.01.13_veh-47_01384_01490 + - 2021.06.07.12.01.13_veh-47_01501_01579 + - 2021.06.07.12.01.13_veh-47_01590_01865 + - 2021.06.07.12.01.13_veh-47_01914_02049 + - 2021.06.07.12.01.13_veh-47_02060_02498 + - 2021.06.07.12.01.13_veh-47_02509_02927 + - 2021.06.07.12.01.13_veh-47_02938_03198 + - 2021.06.07.12.01.13_veh-47_03284_03358 + - 2021.06.07.12.01.13_veh-47_03389_03511 + - 2021.06.07.12.01.13_veh-47_03522_03611 + - 2021.06.07.12.01.13_veh-47_03622_03844 + - 2021.06.07.12.01.13_veh-47_03954_04098 + - 2021.06.07.12.01.13_veh-47_04124_04196 + - 2021.06.07.12.01.13_veh-47_04212_04281 + - 2021.06.07.12.01.13_veh-47_04396_04476 + - 2021.06.07.12.01.13_veh-47_04492_05024 + - 2021.06.07.12.01.13_veh-47_05035_05142 + - 2021.06.07.12.01.13_veh-47_05251_05336 + - 2021.06.07.12.01.13_veh-47_05423_05497 + - 2021.06.07.12.01.13_veh-47_05509_05665 + - 2021.06.07.12.01.13_veh-47_05676_05776 + - 2021.06.07.12.42.11_veh-38_00008_00092 + - 2021.06.07.12.42.11_veh-38_00103_00274 + - 2021.06.07.12.42.11_veh-38_00285_00469 + - 2021.06.07.12.42.11_veh-38_00480_00695 + - 2021.06.07.12.42.11_veh-38_00741_01497 + - 2021.06.07.12.42.11_veh-38_01508_01766 + - 2021.06.07.12.42.11_veh-38_01777_02078 + - 2021.06.07.12.42.11_veh-38_02089_02283 + - 2021.06.07.12.42.11_veh-38_02294_02427 + - 2021.06.07.12.42.11_veh-38_02445_02843 + - 2021.06.07.12.42.11_veh-38_02952_03124 + - 2021.06.07.12.42.11_veh-38_03254_03455 + - 2021.06.07.12.42.11_veh-38_03466_03608 + - 2021.06.07.12.42.11_veh-38_03639_04063 + - 2021.06.07.12.42.11_veh-38_04074_04563 + - 2021.06.07.12.42.11_veh-38_04577_04768 + - 2021.06.07.12.42.11_veh-38_04779_06284 + - 2021.06.07.12.54.00_veh-35_00010_00107 + - 2021.06.07.12.54.00_veh-35_00118_00247 + - 2021.06.07.12.54.00_veh-35_00267_00880 + - 2021.06.07.12.54.00_veh-35_00891_01175 + - 2021.06.07.12.54.00_veh-35_01186_01276 + - 2021.06.07.12.54.00_veh-35_01287_01372 + - 2021.06.07.12.54.00_veh-35_01388_01525 + - 2021.06.07.12.54.00_veh-35_01536_01742 + - 2021.06.07.12.54.00_veh-35_01843_02314 + - 2021.06.07.12.54.00_veh-35_02325_02439 + - 2021.06.07.12.54.00_veh-35_02450_02582 + - 2021.06.07.13.42.27_veh-47_00077_00282 + - 2021.06.07.13.42.27_veh-47_00299_00588 + - 2021.06.07.13.42.27_veh-47_00647_00716 + - 2021.06.07.13.42.27_veh-47_00836_00969 + - 2021.06.07.13.42.27_veh-47_01096_01251 + - 2021.06.07.13.42.27_veh-47_01262_01363 + - 2021.06.07.13.42.27_veh-47_01374_01563 + - 2021.06.07.13.42.27_veh-47_01574_01665 + - 2021.06.07.13.42.27_veh-47_01679_01792 + - 2021.06.07.13.42.27_veh-47_01803_01874 + - 2021.06.07.13.42.27_veh-47_01885_02063 + - 2021.06.07.13.42.27_veh-47_02074_02151 + - 2021.06.07.13.42.27_veh-47_02186_02256 + - 2021.06.07.13.42.27_veh-47_02373_02467 + - 2021.06.07.13.42.27_veh-47_02517_02617 + - 2021.06.07.13.42.27_veh-47_02725_02941 + - 2021.06.07.13.42.27_veh-47_03052_03124 + - 2021.06.07.13.42.27_veh-47_03212_03281 + - 2021.06.07.13.42.27_veh-47_03352_03437 + - 2021.06.07.13.42.27_veh-47_03448_03552 + - 2021.06.07.13.42.27_veh-47_03563_03623 + - 2021.06.07.13.42.27_veh-47_03634_03697 + - 2021.06.07.13.42.27_veh-47_03769_03851 + - 2021.06.07.13.42.27_veh-47_03907_03999 + - 2021.06.07.13.42.27_veh-47_04010_04151 + - 2021.06.07.13.42.27_veh-47_04177_04249 + - 2021.06.07.13.42.27_veh-47_04260_04520 + - 2021.06.07.13.53.57_veh-35_00032_00417 + - 2021.06.07.13.53.57_veh-35_00428_00678 + - 2021.06.07.13.53.57_veh-35_00689_00802 + - 2021.06.07.13.53.57_veh-35_00835_00945 + - 2021.06.07.13.53.57_veh-35_01034_01146 + - 2021.06.07.13.53.57_veh-35_01195_01572 + - 2021.06.07.13.53.57_veh-35_01583_01761 + - 2021.06.07.13.53.57_veh-35_01772_02032 + - 2021.06.07.13.53.57_veh-35_02065_02184 + - 2021.06.07.13.53.57_veh-35_02195_02298 + - 2021.06.07.13.53.57_veh-35_02309_02468 + - 2021.06.07.13.53.57_veh-35_02489_03145 + - 2021.06.07.13.53.57_veh-35_03196_03321 + - 2021.06.07.13.53.57_veh-35_03332_03909 + - 2021.06.07.17.46.49_veh-35_00005_00785 + - 2021.06.07.17.46.49_veh-35_00796_00870 + - 2021.06.07.17.46.49_veh-35_00923_01536 + - 2021.06.07.17.46.49_veh-35_01547_01716 + - 2021.06.07.17.46.49_veh-35_01772_02337 + - 2021.06.07.17.46.49_veh-35_02426_02551 + - 2021.06.07.17.46.49_veh-35_02607_03120 + - 2021.06.07.17.46.49_veh-35_03131_03401 + - 2021.06.07.17.46.49_veh-35_03412_03549 + - 2021.06.07.17.46.49_veh-35_03560_03630 + - 2021.06.07.17.46.49_veh-35_03682_03892 + - 2021.06.07.17.46.49_veh-35_03903_03972 + - 2021.06.07.17.46.49_veh-35_03983_04073 + - 2021.06.07.17.46.49_veh-35_04084_04828 + - 2021.06.07.17.46.49_veh-35_04839_05184 + - 2021.06.07.17.46.49_veh-35_05278_05385 + - 2021.06.07.17.46.49_veh-35_05396_05482 + - 2021.06.07.17.48.02_veh-38_00005_00275 + - 2021.06.07.17.48.02_veh-38_00286_00403 + - 2021.06.07.17.48.02_veh-38_00414_00524 + - 2021.06.07.17.48.02_veh-38_00535_00740 + - 2021.06.07.17.48.02_veh-38_00751_00890 + - 2021.06.07.17.48.02_veh-38_00901_01274 + - 2021.06.07.17.48.02_veh-38_01285_01447 + - 2021.06.07.17.48.02_veh-38_01460_01648 + - 2021.06.07.17.48.02_veh-38_01706_01815 + - 2021.06.07.17.48.02_veh-38_01826_01898 + - 2021.06.07.17.48.02_veh-38_01949_02085 + - 2021.06.07.17.48.02_veh-38_02170_02260 + - 2021.06.07.17.48.02_veh-38_02271_02339 + - 2021.06.07.17.48.02_veh-38_02350_02698 + - 2021.06.07.17.48.02_veh-38_02750_02878 + - 2021.06.07.17.48.02_veh-38_02937_03152 + - 2021.06.07.17.48.02_veh-38_03184_03381 + - 2021.06.07.17.48.02_veh-38_03392_03579 + - 2021.06.07.17.48.02_veh-38_03590_03715 + - 2021.06.07.17.48.02_veh-38_03747_03859 + - 2021.06.07.17.48.02_veh-38_03870_04096 + - 2021.06.07.17.48.02_veh-38_04107_04300 + - 2021.06.07.17.48.02_veh-38_04330_04517 + - 2021.06.07.17.48.02_veh-38_04528_04694 + - 2021.06.07.17.48.02_veh-38_04705_04782 + - 2021.06.07.17.48.02_veh-38_04793_05022 + - 2021.06.07.17.49.04_veh-47_00016_00530 + - 2021.06.07.17.49.04_veh-47_00561_01239 + - 2021.06.07.17.49.04_veh-47_01289_01354 + - 2021.06.07.17.49.04_veh-47_01430_01514 + - 2021.06.07.17.49.04_veh-47_01711_01779 + - 2021.06.07.17.49.04_veh-47_01842_01923 + - 2021.06.07.17.49.04_veh-47_01934_02036 + - 2021.06.07.17.49.04_veh-47_02047_02161 + - 2021.06.07.17.49.04_veh-47_02172_02270 + - 2021.06.07.17.49.04_veh-47_02350_02426 + - 2021.06.07.17.49.04_veh-47_02526_02700 + - 2021.06.07.17.49.04_veh-47_02780_02926 + - 2021.06.07.17.49.04_veh-47_02937_03014 + - 2021.06.07.17.49.04_veh-47_03025_03119 + - 2021.06.07.17.49.04_veh-47_03180_03245 + - 2021.06.07.17.49.04_veh-47_03256_03403 + - 2021.06.07.17.49.04_veh-47_03415_03520 + - 2021.06.07.17.49.04_veh-47_03585_03786 + - 2021.06.07.17.49.04_veh-47_03797_03875 + - 2021.06.07.17.49.04_veh-47_03886_03999 + - 2021.06.07.17.49.04_veh-47_04093_04260 + - 2021.06.07.17.49.04_veh-47_04271_04356 + - 2021.06.07.17.49.04_veh-47_04367_04514 + - 2021.06.07.17.49.04_veh-47_04546_04650 + - 2021.06.07.17.49.04_veh-47_04681_04751 + - 2021.06.07.17.49.04_veh-47_04868_04968 + - 2021.06.07.17.49.04_veh-47_04979_05124 + - 2021.06.07.17.49.04_veh-47_05171_05262 + - 2021.06.07.17.49.04_veh-47_05273_05367 + - 2021.06.07.18.29.03_veh-16_00049_00824 + - 2021.06.07.18.29.03_veh-16_00835_01058 + - 2021.06.07.18.29.03_veh-16_01069_01662 + - 2021.06.07.18.29.03_veh-16_01732_01797 + - 2021.06.07.18.29.03_veh-16_01808_01873 + - 2021.06.07.18.29.03_veh-16_01901_01969 + - 2021.06.07.18.29.03_veh-16_01980_02157 + - 2021.06.07.18.29.03_veh-16_02224_02440 + - 2021.06.07.18.29.03_veh-16_02451_02640 + - 2021.06.07.18.29.03_veh-16_02679_03723 + - 2021.06.07.18.29.03_veh-16_03780_04226 + - 2021.06.07.18.29.03_veh-16_04252_04622 + - 2021.06.07.18.29.03_veh-16_04707_04786 + - 2021.06.07.18.29.03_veh-16_04807_04969 + - 2021.06.07.18.29.03_veh-16_04987_05220 + - 2021.06.07.18.29.03_veh-16_05231_05546 + - 2021.06.07.18.29.03_veh-16_05571_05797 + - 2021.06.07.18.53.26_veh-26_00005_00427 + - 2021.06.07.18.53.26_veh-26_00438_00615 + - 2021.06.07.18.53.26_veh-26_00692_00845 + - 2021.06.07.18.53.26_veh-26_00894_01148 + - 2021.06.07.18.53.26_veh-26_01208_01412 + - 2021.06.07.18.53.26_veh-26_01423_01516 + - 2021.06.07.19.29.59_veh-38_00016_00463 + - 2021.06.07.19.29.59_veh-38_00474_00922 + - 2021.06.07.19.29.59_veh-38_00933_01014 + - 2021.06.07.19.29.59_veh-38_01025_01274 + - 2021.06.07.19.29.59_veh-38_01315_01489 + - 2021.06.07.19.29.59_veh-38_01500_01575 + - 2021.06.07.19.29.59_veh-38_01586_01704 + - 2021.06.07.19.29.59_veh-38_01715_01871 + - 2021.06.07.19.29.59_veh-38_01949_02349 + - 2021.06.07.19.29.59_veh-38_02418_02564 + - 2021.06.07.19.29.59_veh-38_02615_02779 + - 2021.06.07.19.29.59_veh-38_02790_02994 + - 2021.06.07.19.29.59_veh-38_03005_03160 + - 2021.06.07.19.43.00_veh-35_00005_00222 + - 2021.06.07.19.43.00_veh-35_00342_00587 + - 2021.06.07.19.43.00_veh-35_00621_00710 + - 2021.06.07.19.43.00_veh-35_00721_00818 + - 2021.06.07.19.43.00_veh-35_00829_00910 + - 2021.06.07.19.43.00_veh-35_00922_01351 + - 2021.06.07.19.43.00_veh-35_01364_01535 + - 2021.06.07.19.43.00_veh-35_01546_01713 + - 2021.06.07.19.43.00_veh-35_01782_01986 + - 2021.06.07.19.43.00_veh-35_01997_02072 + - 2021.06.07.19.43.00_veh-35_02298_02525 + - 2021.06.07.19.43.00_veh-35_02625_03000 + - 2021.06.07.19.43.00_veh-35_03011_03079 + - 2021.06.07.19.43.00_veh-35_03090_03191 + - 2021.06.07.19.51.52_veh-47_00176_00264 + - 2021.06.07.19.51.52_veh-47_00275_00338 + - 2021.06.07.19.51.52_veh-47_00417_00628 + - 2021.06.07.19.51.52_veh-47_00677_01057 + - 2021.06.07.19.51.52_veh-47_01084_01145 + - 2021.06.07.19.51.52_veh-47_01156_01416 + - 2021.06.07.19.51.52_veh-47_01500_01663 + - 2021.06.07.19.51.52_veh-47_01700_01785 + - 2021.06.07.19.51.52_veh-47_01796_01893 + - 2021.06.07.19.51.52_veh-47_01904_02086 + - 2021.06.08.12.00.19_veh-35_00034_00245 + - 2021.06.08.12.00.19_veh-35_00256_00323 + - 2021.06.08.12.00.19_veh-35_00378_00748 + - 2021.06.08.12.00.19_veh-35_00759_00954 + - 2021.06.08.12.00.19_veh-35_00965_01253 + - 2021.06.08.12.00.19_veh-35_01264_01345 + - 2021.06.08.12.00.19_veh-35_01356_01711 + - 2021.06.08.12.00.19_veh-35_01722_02119 + - 2021.06.08.12.00.19_veh-35_02135_02369 + - 2021.06.08.12.00.19_veh-35_02399_02545 + - 2021.06.08.12.00.19_veh-35_02556_02689 + - 2021.06.08.12.00.19_veh-35_02700_02977 + - 2021.06.08.12.00.19_veh-35_02988_03160 + - 2021.06.08.12.00.19_veh-35_03171_03396 + - 2021.06.08.12.00.19_veh-35_03451_03644 + - 2021.06.08.12.00.19_veh-35_03655_03792 + - 2021.06.08.12.00.19_veh-35_03803_03919 + - 2021.06.08.12.00.19_veh-35_03930_04099 + - 2021.06.08.12.00.19_veh-35_04110_04230 + - 2021.06.08.12.00.19_veh-35_04241_04354 + - 2021.06.08.12.00.19_veh-35_04422_04725 + - 2021.06.08.12.00.19_veh-35_04736_05224 + - 2021.06.08.12.00.19_veh-35_05235_05578 + - 2021.06.08.12.00.19_veh-35_05593_05747 + - 2021.06.08.12.10.22_veh-38_00005_00238 + - 2021.06.08.12.10.22_veh-38_00361_00494 + - 2021.06.08.12.10.22_veh-38_00505_00600 + - 2021.06.08.12.10.22_veh-38_00613_00804 + - 2021.06.08.12.10.22_veh-38_00919_01140 + - 2021.06.08.12.10.22_veh-38_01668_01735 + - 2021.06.08.12.10.22_veh-38_01746_01901 + - 2021.06.08.12.10.22_veh-38_01912_02498 + - 2021.06.08.12.10.22_veh-38_02527_02601 + - 2021.06.08.12.10.22_veh-38_02612_02960 + - 2021.06.08.12.10.22_veh-38_02971_03238 + - 2021.06.08.12.10.22_veh-38_03249_03335 + - 2021.06.08.12.10.22_veh-38_03346_03499 + - 2021.06.08.12.10.22_veh-38_03514_03617 + - 2021.06.08.12.10.22_veh-38_03628_04043 + - 2021.06.08.12.10.22_veh-38_04161_04226 + - 2021.06.08.12.10.22_veh-38_04339_04879 + - 2021.06.08.12.10.22_veh-38_04953_05015 + - 2021.06.08.12.10.22_veh-38_05026_05405 + - 2021.06.08.12.10.22_veh-38_05416_05501 + - 2021.06.08.12.10.22_veh-38_05512_05652 + - 2021.06.08.12.10.22_veh-38_05685_05761 + - 2021.06.08.12.10.22_veh-38_05772_05856 + - 2021.06.08.12.10.22_veh-38_05867_05937 + - 2021.06.08.12.10.22_veh-38_05967_06080 + - 2021.06.08.12.10.22_veh-38_06091_06210 + - 2021.06.08.12.10.22_veh-38_06221_06282 + - 2021.06.08.12.10.22_veh-38_06293_06407 + - 2021.06.08.12.10.22_veh-38_06455_06590 + - 2021.06.08.12.10.22_veh-38_06601_06682 + - 2021.06.08.12.10.22_veh-38_06693_06773 + - 2021.06.08.12.10.22_veh-38_06854_07183 + - 2021.06.08.12.10.22_veh-38_07194_07425 + - 2021.06.08.12.10.22_veh-38_07436_07783 + - 2021.06.08.12.11.33_veh-16_00055_00232 + - 2021.06.08.12.11.33_veh-16_00243_00774 + - 2021.06.08.12.11.33_veh-16_00785_00891 + - 2021.06.08.12.54.54_veh-26_00015_00507 + - 2021.06.08.12.54.54_veh-26_00518_00582 + - 2021.06.08.12.54.54_veh-26_00594_00722 + - 2021.06.08.12.54.54_veh-26_00733_00983 + - 2021.06.08.12.54.54_veh-26_00994_01185 + - 2021.06.08.12.54.54_veh-26_01196_01278 + - 2021.06.08.12.54.54_veh-26_01289_01417 + - 2021.06.08.12.54.54_veh-26_01428_01522 + - 2021.06.08.12.54.54_veh-26_01614_02077 + - 2021.06.08.12.54.54_veh-26_02088_02219 + - 2021.06.08.12.54.54_veh-26_02232_02312 + - 2021.06.08.12.54.54_veh-26_02323_02479 + - 2021.06.08.12.54.54_veh-26_02490_02657 + - 2021.06.08.12.54.54_veh-26_02668_02983 + - 2021.06.08.12.54.54_veh-26_02994_03970 + - 2021.06.08.12.54.54_veh-26_03981_04251 + - 2021.06.08.12.54.54_veh-26_04262_04732 + - 2021.06.08.12.54.54_veh-26_04829_05317 + - 2021.06.08.13.14.49_veh-47_00041_00263 + - 2021.06.08.13.14.49_veh-47_00344_00674 + - 2021.06.08.13.14.49_veh-47_00718_00834 + - 2021.06.08.13.14.49_veh-47_00927_01074 + - 2021.06.08.13.14.49_veh-47_01085_01163 + - 2021.06.08.13.14.49_veh-47_01184_01245 + - 2021.06.08.13.14.49_veh-47_01256_01461 + - 2021.06.08.13.14.49_veh-47_01497_01659 + - 2021.06.08.13.14.49_veh-47_01670_01844 + - 2021.06.08.13.14.49_veh-47_01855_01957 + - 2021.06.08.13.14.49_veh-47_01968_02204 + - 2021.06.08.13.14.49_veh-47_02235_02393 + - 2021.06.08.13.14.49_veh-47_02404_02876 + - 2021.06.08.13.14.49_veh-47_03037_03294 + - 2021.06.08.13.14.49_veh-47_03316_03545 + - 2021.06.08.13.14.49_veh-47_03592_03682 + - 2021.06.08.13.14.49_veh-47_03693_03811 + - 2021.06.08.13.14.49_veh-47_03822_04167 + - 2021.06.08.13.14.49_veh-47_04202_04373 + - 2021.06.08.13.14.49_veh-47_04385_04598 + - 2021.06.08.13.14.49_veh-47_04660_04834 + - 2021.06.08.13.14.49_veh-47_04906_05194 + - 2021.06.08.13.14.49_veh-47_05306_05380 + - 2021.06.08.13.23.30_veh-16_00030_00386 + - 2021.06.08.13.23.30_veh-16_00440_00515 + - 2021.06.08.13.23.30_veh-16_00538_00655 + - 2021.06.08.13.23.30_veh-16_00666_01034 + - 2021.06.08.13.23.30_veh-16_01045_01275 + - 2021.06.08.13.23.30_veh-16_01286_01467 + - 2021.06.08.13.23.30_veh-16_01489_01621 + - 2021.06.08.13.23.30_veh-16_01683_01753 + - 2021.06.08.13.23.30_veh-16_01953_02059 + - 2021.06.08.13.23.30_veh-16_02070_02336 + - 2021.06.08.13.23.30_veh-16_02347_02567 + - 2021.06.08.13.23.30_veh-16_02656_02754 + - 2021.06.08.13.23.30_veh-16_02766_02967 + - 2021.06.08.13.23.30_veh-16_02978_03089 + - 2021.06.08.13.23.30_veh-16_03110_03173 + - 2021.06.08.13.23.30_veh-16_03184_03355 + - 2021.06.08.13.23.30_veh-16_03366_03536 + - 2021.06.08.13.23.30_veh-16_03547_03686 + - 2021.06.08.13.23.30_veh-16_03697_04211 + - 2021.06.08.13.23.30_veh-16_04245_04347 + - 2021.06.08.13.23.30_veh-16_04358_04444 + - 2021.06.08.13.23.30_veh-16_04469_04582 + - 2021.06.08.13.23.30_veh-16_04593_05174 + - 2021.06.08.13.23.30_veh-16_05185_05254 + - 2021.06.08.14.14.51_veh-35_00012_00082 + - 2021.06.08.14.14.51_veh-35_00093_00320 + - 2021.06.08.14.14.51_veh-35_00331_00850 + - 2021.06.08.14.14.51_veh-35_00893_01188 + - 2021.06.08.14.14.51_veh-35_01238_01400 + - 2021.06.08.14.14.51_veh-35_01411_01497 + - 2021.06.08.14.14.51_veh-35_01508_01763 + - 2021.06.08.14.14.51_veh-35_01815_02289 + - 2021.06.08.14.14.51_veh-35_02338_02444 + - 2021.06.08.14.14.51_veh-35_02455_02589 + - 2021.06.08.14.14.51_veh-35_02600_02918 + - 2021.06.08.14.14.51_veh-35_02930_03199 + - 2021.06.08.14.14.51_veh-35_03232_03473 + - 2021.06.08.14.14.51_veh-35_03484_03574 + - 2021.06.08.14.14.51_veh-35_03585_03662 + - 2021.06.08.14.14.51_veh-35_03673_03761 + - 2021.06.08.14.14.51_veh-35_03805_04010 + - 2021.06.08.14.14.51_veh-35_04048_04164 + - 2021.06.08.14.14.51_veh-35_04291_04586 + - 2021.06.08.14.14.51_veh-35_04597_05038 + - 2021.06.08.14.14.51_veh-35_05049_05320 + - 2021.06.08.14.14.51_veh-35_05331_05531 + - 2021.06.08.14.35.24_veh-26_00016_00102 + - 2021.06.08.14.35.24_veh-26_00113_00204 + - 2021.06.08.14.35.24_veh-26_00237_00583 + - 2021.06.08.14.35.24_veh-26_00594_00813 + - 2021.06.08.14.35.24_veh-26_00824_01072 + - 2021.06.08.14.35.24_veh-26_01105_01317 + - 2021.06.08.14.35.24_veh-26_01356_01914 + - 2021.06.08.14.35.24_veh-26_01989_02235 + - 2021.06.08.14.35.24_veh-26_02246_02541 + - 2021.06.08.14.35.24_veh-26_02555_03004 + - 2021.06.08.14.35.24_veh-26_03015_03130 + - 2021.06.08.14.35.24_veh-26_03141_03324 + - 2021.06.08.14.35.24_veh-26_03335_03464 + - 2021.06.08.14.35.24_veh-26_03475_03577 + - 2021.06.08.14.35.24_veh-26_03588_04332 + - 2021.06.08.14.35.24_veh-26_04343_04575 + - 2021.06.08.14.35.24_veh-26_04642_04727 + - 2021.06.08.14.35.24_veh-26_04792_04857 + - 2021.06.08.14.35.24_veh-26_04868_04984 + - 2021.06.08.14.35.24_veh-26_04995_05088 + - 2021.06.08.14.35.24_veh-26_05099_05185 + - 2021.06.08.14.35.24_veh-26_05202_05297 + - 2021.06.08.14.36.49_veh-38_00005_00079 + - 2021.06.08.14.36.49_veh-38_00107_00301 + - 2021.06.08.14.36.49_veh-38_00312_00694 + - 2021.06.08.14.36.49_veh-38_00705_01463 + - 2021.06.08.14.36.49_veh-38_01474_01537 + - 2021.06.08.14.36.49_veh-38_01567_02014 + - 2021.06.08.14.57.07_veh-47_00016_00174 + - 2021.06.08.14.57.07_veh-47_00214_00426 + - 2021.06.08.14.57.07_veh-47_00437_00553 + - 2021.06.08.14.57.07_veh-47_00667_00795 + - 2021.06.08.14.57.07_veh-47_00806_00878 + - 2021.06.08.14.57.07_veh-47_00890_01000 + - 2021.06.08.14.57.07_veh-47_01012_01121 + - 2021.06.08.14.57.07_veh-47_01154_01309 + - 2021.06.08.14.57.07_veh-47_01416_01545 + - 2021.06.08.14.57.07_veh-47_01556_01964 + - 2021.06.08.14.57.07_veh-47_02038_02281 + - 2021.06.08.14.57.07_veh-47_02315_02456 + - 2021.06.08.14.57.07_veh-47_02472_02661 + - 2021.06.08.14.57.07_veh-47_02672_02816 + - 2021.06.08.14.57.07_veh-47_02847_03011 + - 2021.06.08.14.57.07_veh-47_03130_03229 + - 2021.06.08.14.57.07_veh-47_03240_03389 + - 2021.06.08.14.57.07_veh-47_03427_03768 + - 2021.06.08.14.57.07_veh-47_03795_04016 + - 2021.06.08.14.57.07_veh-47_04027_04122 + - 2021.06.08.14.57.07_veh-47_04133_04206 + - 2021.06.08.14.57.07_veh-47_04217_04401 + - 2021.06.08.14.57.07_veh-47_04412_04567 + - 2021.06.08.14.57.07_veh-47_04617_04728 + - 2021.06.08.14.57.07_veh-47_04739_04947 + - 2021.06.08.14.57.07_veh-47_04967_05099 + - 2021.06.08.14.57.07_veh-47_05110_05325 + - 2021.06.08.16.31.33_veh-38_00015_00262 + - 2021.06.08.16.31.33_veh-38_00273_00386 + - 2021.06.08.16.31.33_veh-38_00397_00532 + - 2021.06.08.16.31.33_veh-38_00553_00703 + - 2021.06.08.16.31.33_veh-38_00748_01069 + - 2021.06.08.16.31.33_veh-38_01080_01257 + - 2021.06.08.16.31.33_veh-38_01268_01578 + - 2021.06.08.16.31.33_veh-38_01589_02072 + - 2021.06.08.16.31.33_veh-38_02181_02243 + - 2021.06.08.16.31.33_veh-38_02254_02317 + - 2021.06.08.16.31.33_veh-38_02424_02513 + - 2021.06.08.16.31.33_veh-38_02524_02854 + - 2021.06.08.16.31.33_veh-38_03021_03210 + - 2021.06.08.16.31.33_veh-38_03221_03330 + - 2021.06.08.16.31.33_veh-38_03406_03605 + - 2021.06.08.16.31.33_veh-38_03787_03930 + - 2021.06.08.16.31.33_veh-38_03941_04118 + - 2021.06.08.16.31.33_veh-38_04129_04253 + - 2021.06.08.16.31.33_veh-38_04275_04425 + - 2021.06.08.16.31.33_veh-38_04459_04601 + - 2021.06.08.16.31.33_veh-38_04617_04880 + - 2021.06.08.16.31.33_veh-38_05137_05204 + - 2021.06.08.17.25.03_veh-35_00008_00154 + - 2021.06.08.17.25.03_veh-35_00165_00277 + - 2021.06.08.17.25.03_veh-35_00359_00894 + - 2021.06.08.17.25.03_veh-35_00905_01326 + - 2021.06.08.17.25.03_veh-35_01375_01666 + - 2021.06.08.17.25.03_veh-35_01721_01942 + - 2021.06.08.17.25.03_veh-35_01953_02306 + - 2021.06.08.17.25.03_veh-35_02351_02436 + - 2021.06.08.17.25.03_veh-35_02448_02655 + - 2021.06.08.17.25.03_veh-35_02666_02731 + - 2021.06.08.17.25.03_veh-35_02809_02920 + - 2021.06.08.17.25.03_veh-35_02931_03019 + - 2021.06.08.17.25.03_veh-35_03075_03265 + - 2021.06.08.17.25.03_veh-35_03342_03422 + - 2021.06.08.17.25.03_veh-35_03433_03510 + - 2021.06.08.17.25.03_veh-35_03522_03716 + - 2021.06.08.17.25.03_veh-35_03727_03939 + - 2021.06.08.17.25.03_veh-35_04015_04087 + - 2021.06.08.17.25.03_veh-35_04125_04235 + - 2021.06.08.17.25.03_veh-35_04246_04416 + - 2021.06.08.17.25.03_veh-35_04428_04569 + - 2021.06.08.17.25.03_veh-35_04632_05000 + - 2021.06.08.17.25.03_veh-35_05031_05225 + - 2021.06.08.17.25.03_veh-35_05236_05328 + - 2021.06.08.17.29.54_veh-16_00005_00083 + - 2021.06.08.17.29.54_veh-16_00094_00205 + - 2021.06.08.17.29.54_veh-16_00251_00460 + - 2021.06.08.17.29.54_veh-16_00471_00914 + - 2021.06.08.17.29.54_veh-16_01034_01609 + - 2021.06.08.17.29.54_veh-16_01672_01764 + - 2021.06.08.17.29.54_veh-16_01776_02013 + - 2021.06.08.17.29.54_veh-16_02024_02117 + - 2021.06.08.17.29.54_veh-16_02128_02701 + - 2021.06.08.17.29.54_veh-16_02760_03069 + - 2021.06.08.17.29.54_veh-16_03080_03206 + - 2021.06.08.17.29.54_veh-16_03285_03364 + - 2021.06.08.17.29.54_veh-16_03403_03518 + - 2021.06.08.17.29.54_veh-16_03696_03865 + - 2021.06.08.17.29.54_veh-16_03876_03957 + - 2021.06.08.17.29.54_veh-16_03968_04033 + - 2021.06.08.17.29.54_veh-16_04050_04156 + - 2021.06.08.17.29.54_veh-16_04167_04322 + - 2021.06.08.17.29.54_veh-16_04333_04409 + - 2021.06.08.17.29.54_veh-16_04460_04547 + - 2021.06.08.17.29.54_veh-16_04558_04629 + - 2021.06.08.17.29.54_veh-16_04640_04720 + - 2021.06.08.17.36.50_veh-26_00016_00413 + - 2021.06.08.17.36.50_veh-26_00424_00487 + - 2021.06.08.17.36.50_veh-26_00533_00628 + - 2021.06.08.17.36.50_veh-26_00639_01479 + - 2021.06.08.17.36.50_veh-26_01490_01603 + - 2021.06.08.17.36.50_veh-26_01617_01796 + - 2021.06.08.17.36.50_veh-26_01807_02223 + - 2021.06.08.17.36.50_veh-26_02261_02604 + - 2021.06.08.17.36.50_veh-26_02683_03186 + - 2021.06.08.17.36.50_veh-26_03249_03543 + - 2021.06.08.17.36.50_veh-26_03554_03731 + - 2021.06.08.17.36.50_veh-26_03742_03862 + - 2021.06.08.17.36.50_veh-26_03873_04225 + - 2021.06.08.17.36.50_veh-26_04236_04319 + - 2021.06.08.17.36.50_veh-26_04330_04911 + - 2021.06.08.17.36.50_veh-26_04980_05123 + - 2021.06.08.17.36.50_veh-26_05134_05378 + - 2021.06.08.18.18.30_veh-38_00005_00421 + - 2021.06.08.18.18.30_veh-38_00488_00795 + - 2021.06.08.18.18.30_veh-38_00806_01230 + - 2021.06.08.18.18.30_veh-38_01241_01417 + - 2021.06.08.18.18.30_veh-38_01428_01644 + - 2021.06.08.18.18.30_veh-38_01679_02102 + - 2021.06.08.18.18.30_veh-38_02113_02380 + - 2021.06.08.18.18.30_veh-38_02448_02646 + - 2021.06.08.18.18.30_veh-38_02657_02782 + - 2021.06.08.18.18.30_veh-38_02816_03242 + - 2021.06.08.18.18.30_veh-38_03253_03384 + - 2021.06.08.18.18.30_veh-38_03395_03530 + - 2021.06.08.18.18.30_veh-38_03541_03640 + - 2021.06.08.18.18.30_veh-38_03651_03780 + - 2021.06.08.18.18.30_veh-38_03792_03951 + - 2021.06.08.18.18.30_veh-38_03962_04250 + - 2021.06.08.18.18.30_veh-38_04304_05029 + - 2021.06.08.18.18.30_veh-38_05085_05165 + - 2021.06.08.18.18.30_veh-38_05239_05451 + - 2021.06.08.18.18.30_veh-38_05462_05566 + - 2021.06.08.18.18.30_veh-38_05578_05988 + - 2021.06.08.18.18.30_veh-38_06017_06142 + - 2021.06.08.18.19.18_veh-47_00005_00097 + - 2021.06.08.18.19.18_veh-47_00132_00406 + - 2021.06.08.18.19.18_veh-47_00417_00521 + - 2021.06.08.18.19.18_veh-47_00544_00624 + - 2021.06.08.18.19.18_veh-47_00635_01096 + - 2021.06.08.18.19.18_veh-47_01107_01215 + - 2021.06.08.18.19.18_veh-47_01226_01742 + - 2021.06.08.18.19.18_veh-47_01790_01951 + - 2021.06.08.18.19.18_veh-47_02027_02332 + - 2021.06.08.18.19.18_veh-47_02431_02526 + - 2021.06.08.18.19.18_veh-47_02602_02751 + - 2021.06.08.18.19.18_veh-47_02797_02938 + - 2021.06.08.18.19.18_veh-47_02982_03113 + - 2021.06.08.18.19.18_veh-47_03172_03366 + - 2021.06.08.18.19.18_veh-47_03429_03494 + - 2021.06.08.18.19.18_veh-47_03702_03931 + - 2021.06.08.18.19.18_veh-47_03984_04405 + - 2021.06.08.18.19.18_veh-47_04510_04651 + - 2021.06.08.18.19.18_veh-47_04862_05042 + - 2021.06.08.18.19.18_veh-47_05080_05192 + - 2021.06.08.18.19.18_veh-47_05378_05490 + - 2021.06.08.18.19.18_veh-47_05590_05712 + - 2021.06.08.18.19.18_veh-47_05728_05983 + - 2021.06.08.18.19.18_veh-47_05994_06094 + - 2021.06.08.18.19.18_veh-47_06298_06467 + - 2021.06.08.18.59.48_veh-12_00161_00545 + - 2021.06.08.18.59.48_veh-12_00556_00715 + - 2021.06.08.18.59.48_veh-12_00738_00907 + - 2021.06.08.18.59.48_veh-12_00946_01203 + - 2021.06.08.18.59.48_veh-12_01276_01459 + - 2021.06.08.18.59.48_veh-12_01470_01550 + - 2021.06.08.18.59.48_veh-12_01582_02015 + - 2021.06.08.18.59.48_veh-12_02028_02105 + - 2021.06.08.18.59.48_veh-12_02116_02247 + - 2021.06.08.18.59.48_veh-12_02306_02500 + - 2021.06.08.18.59.48_veh-12_02546_02646 + - 2021.06.08.18.59.48_veh-12_02657_02865 + - 2021.06.08.18.59.48_veh-12_02896_03111 + - 2021.06.08.18.59.48_veh-12_03122_03677 + - 2021.06.08.18.59.48_veh-12_03688_03755 + - 2021.06.08.18.59.48_veh-12_03766_03974 + - 2021.06.08.18.59.48_veh-12_04090_04528 + - 2021.06.08.18.59.48_veh-12_04539_04666 + - 2021.06.08.18.59.48_veh-12_04678_04805 + - 2021.06.08.18.59.48_veh-12_04816_05011 + - 2021.06.08.18.59.48_veh-12_05022_05117 + - 2021.06.08.19.16.23_veh-26_00016_00107 + - 2021.06.08.19.16.23_veh-26_00118_00182 + - 2021.06.08.19.16.23_veh-26_00193_00322 + - 2021.06.08.19.16.23_veh-26_00333_00529 + - 2021.06.08.19.16.23_veh-26_00540_00697 + - 2021.06.08.19.16.23_veh-26_00780_00960 + - 2021.06.08.19.16.23_veh-26_00973_01139 + - 2021.06.08.19.16.23_veh-26_01150_01236 + - 2021.06.08.19.16.23_veh-26_01247_01620 + - 2021.06.08.19.16.23_veh-26_01664_01735 + - 2021.06.08.19.16.23_veh-26_01782_01967 + - 2021.06.08.19.16.23_veh-26_01998_02267 + - 2021.07.24.00.12.51_veh-37_00016_00490 + - 2021.07.24.00.12.51_veh-37_00501_01420 + - 2021.07.24.00.12.51_veh-37_01445_01578 + - 2021.07.24.00.12.51_veh-37_01589_02406 + - 2021.07.24.00.12.51_veh-37_02427_02605 + - 2021.07.24.00.12.51_veh-37_02616_03464 + - 2021.07.24.00.12.51_veh-37_03485_04947 + - 2021.07.24.00.36.59_veh-47_00016_00417 + - 2021.07.24.00.36.59_veh-47_00439_02454 + - 2021.07.24.00.36.59_veh-47_02465_04054 + - 2021.07.24.00.36.59_veh-47_04103_04349 + - 2021.07.24.00.36.59_veh-47_04360_05497 + - 2021.07.24.00.36.59_veh-47_05518_05589 + - 2021.07.24.00.36.59_veh-47_05600_06769 + - 2021.07.24.00.36.59_veh-47_06810_07310 + - 2021.07.24.00.58.02_veh-12_00016_00623 + - 2021.07.24.00.58.02_veh-12_00646_01056 + - 2021.07.24.00.58.02_veh-12_01105_01810 + - 2021.07.24.00.58.02_veh-12_01831_03390 + - 2021.07.24.00.58.02_veh-12_03411_03932 + - 2021.07.24.00.58.02_veh-12_03954_04144 + - 2021.07.24.00.58.02_veh-12_04155_04723 + - 2021.07.24.00.58.02_veh-12_04734_05270 + - 2021.07.24.00.58.02_veh-12_05281_05518 + - 2021.07.24.00.58.02_veh-12_05542_06266 + - 2021.07.24.02.32.57_veh-37_00016_00362 + - 2021.07.24.02.32.57_veh-37_00411_00959 + - 2021.07.24.03.01.39_veh-47_00005_00893 + - 2021.07.24.03.01.39_veh-47_00930_01568 + - 2021.07.24.15.54.20_veh-47_00135_00397 + - 2021.07.24.15.54.20_veh-47_00418_01528 + - 2021.07.24.15.54.20_veh-47_01539_02066 + - 2021.07.24.15.54.20_veh-47_02088_03551 + - 2021.07.24.15.54.20_veh-47_03573_05252 + - 2021.07.24.15.54.20_veh-47_05274_05475 + - 2021.07.24.16.07.03_veh-35_00016_00223 + - 2021.07.24.16.07.03_veh-35_00244_01628 + - 2021.07.24.16.07.03_veh-35_01649_01813 + - 2021.07.24.16.07.03_veh-35_01834_03011 + - 2021.07.24.16.07.03_veh-35_03033_05899 + - 2021.07.24.16.41.10_veh-12_00037_00110 + - 2021.07.24.16.41.10_veh-12_00134_00220 + - 2021.07.24.16.41.10_veh-12_00231_01246 + - 2021.07.24.16.48.51_veh-17_00016_00166 + - 2021.07.24.16.48.51_veh-17_00177_02552 + - 2021.07.24.16.48.51_veh-17_02573_03272 + - 2021.07.24.16.48.51_veh-17_03292_03530 + - 2021.07.24.16.48.51_veh-17_03553_04284 + - 2021.07.24.16.48.51_veh-17_04308_04567 + - 2021.07.24.16.48.51_veh-17_04593_05398 + - 2021.07.24.16.51.13_veh-26_00015_00393 + - 2021.07.24.16.51.13_veh-26_00404_00941 + - 2021.07.24.16.51.13_veh-26_01004_01138 + - 2021.07.24.16.51.13_veh-26_01241_01864 + - 2021.07.24.16.51.13_veh-26_01887_04395 + - 2021.07.24.18.06.35_veh-35_00016_03642 + - 2021.07.24.18.06.35_veh-35_03664_03799 + - 2021.07.24.19.10.14_veh-37_00015_01108 + - 2021.07.24.19.10.14_veh-37_01119_02358 + - 2021.07.24.19.10.14_veh-37_02381_02666 + - 2021.07.24.19.10.14_veh-37_02677_02916 + - 2021.07.24.19.10.14_veh-37_02937_03698 + - 2021.07.24.19.24.15_veh-26_00629_00698 + - 2021.07.24.19.24.15_veh-26_00858_00964 + - 2021.07.24.19.24.15_veh-26_01393_01556 + - 2021.07.24.19.24.15_veh-26_01642_01716 + - 2021.07.24.19.24.15_veh-26_01805_01869 + - 2021.07.24.19.24.15_veh-26_02081_02147 + - 2021.07.24.19.24.15_veh-26_02672_02772 + - 2021.07.24.19.24.15_veh-26_02850_02936 + - 2021.07.24.19.24.15_veh-26_03060_03133 + - 2021.07.24.19.24.15_veh-26_04143_04216 + - 2021.07.24.20.02.23_veh-47_00005_00767 + - 2021.07.24.20.02.23_veh-47_00819_00890 + - 2021.07.24.20.02.23_veh-47_00901_01641 + - 2021.07.24.20.02.23_veh-47_01668_02060 + - 2021.07.24.20.02.23_veh-47_02071_02432 + - 2021.07.24.20.37.45_veh-17_00015_00375 + - 2021.07.24.20.37.45_veh-17_00386_01357 + - 2021.07.24.20.58.00_veh-35_00016_00776 + - 2021.07.24.20.58.00_veh-35_00798_01211 + - 2021.07.24.22.45.30_veh-26_01130_01214 + - 2021.07.24.22.45.30_veh-26_02607_02921 + - 2021.07.24.22.45.30_veh-26_03125_03207 + - 2021.07.24.22.45.30_veh-26_03518_03604 + - 2021.07.24.22.45.30_veh-26_04457_04542 + - 2021.07.24.22.45.30_veh-26_04651_04745 + - 2021.07.24.22.45.30_veh-26_05051_05138 + - 2021.07.24.22.45.30_veh-26_05283_05406 + - 2021.07.24.22.45.30_veh-26_05738_05823 + - 2021.07.24.22.45.30_veh-26_06157_06243 + - 2021.07.24.22.45.30_veh-26_06349_06470 + - 2021.07.24.22.52.16_veh-35_00016_00289 + - 2021.07.24.22.52.16_veh-35_00310_00504 + - 2021.07.24.22.52.16_veh-35_00515_00709 + - 2021.07.24.22.52.16_veh-35_00720_00813 + - 2021.07.24.22.52.16_veh-35_00834_00947 + - 2021.07.24.22.52.16_veh-35_00958_01308 + - 2021.07.24.22.52.16_veh-35_01319_01644 + - 2021.07.24.22.52.16_veh-35_01694_02316 + - 2021.07.24.22.52.16_veh-35_02350_03214 + - 2021.07.24.22.52.16_veh-35_03236_04096 + - 2021.07.24.22.52.16_veh-35_04118_04231 + - 2021.07.24.22.52.16_veh-35_04252_04896 + - 2021.07.24.22.52.16_veh-35_04956_06521 + - 2021.07.24.22.53.21_veh-47_00045_00901 + - 2021.07.24.22.53.21_veh-47_00976_01155 + - 2021.07.24.22.53.21_veh-47_01177_01407 + - 2021.07.24.22.53.21_veh-47_01429_03205 + - 2021.07.24.22.53.21_veh-47_03216_03375 + - 2021.07.24.22.53.21_veh-47_03396_04635 + - 2021.07.24.22.53.21_veh-47_04646_05066 + - 2021.07.24.22.53.21_veh-47_05087_05365 + - 2021.07.24.22.53.21_veh-47_05389_05705 + - 2021.07.24.22.58.17_veh-37_00015_00186 + - 2021.07.24.22.58.17_veh-37_00207_03083 + - 2021.07.24.22.58.17_veh-37_03094_05238 + - 2021.07.24.23.50.16_veh-17_00010_00554 + - 2021.07.24.23.50.16_veh-17_00565_00857 + - 2021.07.24.23.50.16_veh-17_00884_01040 + - 2021.07.24.23.50.16_veh-17_01051_01332 + - 2021.07.24.23.50.16_veh-17_01343_01674 + - 2021.07.24.23.50.16_veh-17_01696_02071 + - 2021.07.24.23.50.16_veh-17_02093_02478 + - 2021.07.24.23.50.16_veh-17_02546_02823 + - 2021.07.24.23.50.16_veh-17_02844_03442 + - 2021.07.24.23.50.16_veh-17_03463_03542 + - 2021.07.24.23.50.16_veh-17_03553_03670 + - 2021.07.24.23.50.16_veh-17_03681_04569 + - 2021.07.24.23.50.16_veh-17_04580_05245 + - 2021.07.24.23.50.16_veh-17_05256_05504 + - 2021.07.24.23.50.16_veh-17_05516_05665 + - 2021.07.24.23.50.16_veh-17_05707_05989 + - 2021.07.24.23.50.16_veh-17_06000_06210 + - 2021.07.24.23.50.16_veh-17_06285_06528 + - 2021.07.24.23.50.16_veh-17_06539_06969 + - 2021.07.24.23.50.16_veh-17_06980_07096 + - 2021.07.24.23.50.16_veh-17_07107_07231 + - 2021.07.24.23.59.52_veh-12_00016_00481 + - 2021.07.24.23.59.52_veh-12_00503_00715 + - 2021.07.24.23.59.52_veh-12_00736_01004 + - 2021.07.24.23.59.52_veh-12_01025_01526 + - 2021.07.24.23.59.52_veh-12_01548_02862 + - 2021.07.24.23.59.52_veh-12_02884_03403 + - 2021.07.24.23.59.52_veh-12_03414_04602 + - 2021.07.24.23.59.52_veh-12_04623_04745 + - 2021.07.24.23.59.52_veh-12_04767_05924 + - 2021.07.24.23.59.52_veh-12_05945_06022 + - 2021.07.24.23.59.52_veh-12_06043_06238 + - 2021.07.24.23.59.52_veh-12_06259_07141 + - 2021.07.24.23.59.52_veh-12_07152_07341 + - 2021.07.24.23.59.52_veh-12_07425_07576 + - 2021.07.24.23.59.52_veh-12_07598_08663 + - 2021.07.24.23.59.52_veh-12_08685_09191 + - 2021.08.09.17.55.59_veh-28_00021_00307 + - 2021.08.09.17.55.59_veh-28_00320_00544 + - 2021.08.09.17.55.59_veh-28_00558_00680 + - 2021.08.09.17.55.59_veh-28_00691_00876 + - 2021.08.09.17.55.59_veh-28_00960_01031 + - 2021.08.09.17.55.59_veh-28_01065_01167 + - 2021.08.09.18.37.41_veh-28_00053_00548 + - 2021.08.09.18.37.41_veh-28_00648_00730 + - 2021.08.24.12.39.05_veh-42_00268_00336 + - 2021.08.24.12.39.05_veh-42_00373_00482 + - 2021.08.24.12.39.05_veh-42_00519_00589 + - 2021.08.24.12.39.05_veh-42_00649_00718 + - 2021.08.24.12.39.05_veh-42_00948_01039 + - 2021.08.24.12.39.05_veh-42_01232_01375 + - 2021.08.24.12.39.05_veh-42_01445_01585 + - 2021.08.24.12.39.05_veh-42_01860_01929 + - 2021.08.24.12.39.05_veh-42_02417_02512 + - 2021.08.24.12.40.19_veh-45_00016_00082 + - 2021.08.24.12.40.19_veh-45_00201_00315 + - 2021.08.24.12.40.19_veh-45_00351_00429 + - 2021.08.24.12.40.19_veh-45_00451_00768 + - 2021.08.24.12.40.19_veh-45_00785_00969 + - 2021.08.24.12.40.19_veh-45_01028_01182 + - 2021.08.24.12.40.19_veh-45_01246_01454 + - 2021.08.24.12.40.19_veh-45_01472_01612 + - 2021.08.24.13.12.55_veh-45_00156_00249 + - 2021.08.24.13.12.55_veh-45_00386_00472 + - 2021.08.24.13.12.55_veh-45_00507_00867 + - 2021.08.24.13.12.55_veh-45_00990_01081 + - 2021.08.24.13.12.55_veh-45_01209_01317 + - 2021.08.24.13.12.55_veh-45_01770_01846 + - 2021.08.24.13.20.17_veh-08_00016_00738 + - 2021.08.24.13.20.17_veh-08_01147_01322 + - 2021.08.24.13.20.17_veh-08_01350_01547 + - 2021.08.24.13.20.17_veh-08_01577_01746 + - 2021.08.24.13.20.17_veh-08_01777_01861 + - 2021.08.24.14.25.28_veh-42_00333_00472 + - 2021.08.24.14.25.28_veh-42_00534_00649 + - 2021.08.24.14.25.28_veh-42_00660_00753 + - 2021.08.24.14.25.28_veh-42_00765_00831 + - 2021.08.24.14.25.28_veh-42_00921_00983 + - 2021.08.24.14.25.28_veh-42_01301_01371 + - 2021.08.24.14.25.28_veh-42_01409_01477 + - 2021.08.24.14.25.28_veh-42_01872_01959 + - 2021.08.24.14.25.28_veh-42_01996_02110 + - 2021.08.24.14.25.28_veh-42_02147_02215 + - 2021.08.24.14.25.28_veh-42_02351_02572 + - 2021.08.24.14.25.28_veh-42_02635_02779 + - 2021.08.24.14.25.28_veh-42_02815_02880 + - 2021.08.24.14.35.46_veh-45_00011_00162 + - 2021.08.24.14.35.46_veh-45_00244_00418 + - 2021.08.24.14.35.46_veh-45_00440_00501 + - 2021.08.24.14.35.46_veh-45_00549_00693 + - 2021.08.24.14.35.46_veh-45_00715_01404 + - 2021.08.24.14.35.46_veh-45_01568_01663 + - 2021.08.24.15.09.18_veh-45_00216_00862 + - 2021.08.24.15.09.18_veh-45_00956_01148 + - 2021.08.24.15.09.18_veh-45_01233_01318 + - 2021.08.24.15.09.18_veh-45_01376_01439 + - 2021.08.24.15.09.18_veh-45_01464_01626 + - 2021.08.24.17.01.06_veh-45_00053_00154 + - 2021.08.24.17.01.06_veh-45_00228_00689 + - 2021.08.24.17.01.06_veh-45_00708_00770 + - 2021.08.24.17.01.06_veh-45_00823_01085 + - 2021.08.24.17.01.06_veh-45_01269_01407 + - 2021.08.24.17.01.06_veh-45_01557_01681 + - 2021.08.24.17.34.27_veh-45_00374_00501 + - 2021.08.24.17.34.27_veh-45_00696_00786 + - 2021.08.24.17.34.27_veh-45_00808_00993 + - 2021.08.24.17.34.27_veh-45_01118_01346 + - 2021.08.24.17.34.27_veh-45_01478_01553 + - 2021.08.24.17.37.11_veh-08_00186_00303 + - 2021.08.24.17.37.11_veh-08_00314_00494 + - 2021.08.24.17.37.11_veh-08_00510_00673 + - 2021.08.24.17.37.11_veh-08_00770_01101 + - 2021.08.24.17.37.11_veh-08_01117_01293 + - 2021.08.24.17.37.11_veh-08_01304_01759 + - 2021.08.24.17.37.11_veh-08_01919_02040 + - 2021.08.24.17.37.11_veh-08_02359_02623 + - 2021.08.24.17.45.37_veh-42_01515_01611 + - 2021.08.24.17.45.37_veh-42_01776_01900 + - 2021.08.24.17.45.37_veh-42_02035_02167 + - 2021.08.24.17.45.37_veh-42_02178_02285 + - 2021.08.24.17.45.37_veh-42_02371_02441 + - 2021.08.24.17.45.37_veh-42_02638_02702 + - 2021.08.24.18.07.48_veh-45_00203_00300 + - 2021.08.24.18.07.48_veh-45_00325_00550 + - 2021.08.24.18.07.48_veh-45_00590_00850 + - 2021.08.24.18.07.48_veh-45_00873_01142 + - 2021.08.24.18.07.48_veh-45_01164_01482 + - 2021.08.24.18.07.48_veh-45_01504_01722 + - 2021.08.24.18.30.46_veh-08_00035_01650 + - 2021.08.24.18.30.46_veh-08_01674_01850 + - 2021.08.24.18.30.46_veh-08_01985_02093 + - 2021.08.24.18.30.46_veh-08_02327_02583 + - 2021.08.24.18.30.46_veh-08_02605_02732 + - 2021.08.24.18.56.54_veh-45_00399_00499 + - 2021.08.24.18.56.54_veh-45_00522_00779 + - 2021.08.24.18.56.54_veh-45_00801_01587 + - 2021.08.24.18.56.54_veh-45_01661_01768 + - 2021.08.24.19.26.32_veh-08_00067_00143 + - 2021.08.24.19.26.32_veh-08_00154_00225 + - 2021.08.24.19.26.32_veh-08_00249_00710 + - 2021.08.24.19.26.32_veh-08_00733_00794 + - 2021.08.24.19.26.32_veh-08_00809_00880 + - 2021.08.24.19.26.32_veh-08_00903_01021 + - 2021.08.24.19.26.32_veh-08_01043_01341 + - 2021.08.24.19.26.32_veh-08_01800_01935 + - 2021.08.24.19.26.32_veh-08_01958_02519 + - 2021.08.24.19.26.32_veh-08_02537_02633 + - 2021.08.24.19.30.33_veh-45_00172_00260 + - 2021.08.24.19.30.33_veh-45_00290_00484 + - 2021.08.24.19.30.33_veh-45_00532_00604 + - 2021.08.24.19.30.33_veh-45_00676_00755 + - 2021.08.24.19.30.33_veh-45_00820_01077 + - 2021.08.24.19.30.33_veh-45_01096_01251 + - 2021.08.24.19.30.33_veh-45_01391_01523 + - 2021.08.24.19.30.33_veh-45_01549_01695 + - 2021.08.24.20.03.01_veh-45_00021_00143 + - 2021.08.24.20.03.01_veh-45_00171_00238 + - 2021.08.24.20.03.01_veh-45_00269_00428 + - 2021.08.24.20.03.01_veh-45_00463_00588 + - 2021.08.24.20.03.01_veh-45_00687_00787 + - 2021.08.24.20.03.01_veh-45_00824_00888 + - 2021.08.24.20.03.01_veh-45_01091_01622 + - 2021.08.31.11.47.30_veh-40_00016_00141 + - 2021.08.31.11.47.30_veh-40_00248_00376 + - 2021.08.31.11.47.30_veh-40_00393_00847 + - 2021.08.31.11.47.30_veh-40_00919_01000 + - 2021.08.31.11.47.30_veh-40_01146_01347 + - 2021.08.31.11.47.30_veh-40_01362_01737 + - 2021.08.31.12.21.30_veh-40_00056_00155 + - 2021.08.31.12.21.30_veh-40_00248_00367 + - 2021.08.31.12.21.30_veh-40_00378_00527 + - 2021.08.31.12.21.30_veh-40_00538_00638 + - 2021.08.31.12.21.30_veh-40_00661_00762 + - 2021.08.31.12.21.30_veh-40_01141_01207 + - 2021.08.31.12.21.30_veh-40_01485_01676 + - 2021.08.31.12.54.56_veh-40_00024_00106 + - 2021.08.31.12.54.56_veh-40_00305_00667 + - 2021.08.31.12.54.56_veh-40_00725_00909 + - 2021.08.31.12.54.56_veh-40_00921_01014 + - 2021.08.31.12.54.56_veh-40_01056_01183 + - 2021.08.31.12.54.56_veh-40_01249_01397 + - 2021.08.31.12.54.56_veh-40_01536_01758 + - 2021.08.31.13.27.52_veh-40_00058_00145 + - 2021.08.31.13.27.52_veh-40_00186_00414 + - 2021.08.31.13.27.52_veh-40_00486_00634 + - 2021.08.31.13.27.52_veh-40_00688_00750 + - 2021.08.31.13.27.52_veh-40_00869_01319 + - 2021.08.31.13.27.52_veh-40_01330_01491 + - 2021.08.31.13.27.52_veh-40_01615_01687 + - 2021.08.31.14.01.15_veh-40_00304_00384 + - 2021.08.31.14.01.15_veh-40_00407_00497 + - 2021.08.31.14.01.15_veh-40_00573_00681 + - 2021.08.31.14.01.15_veh-40_00692_00977 + - 2021.08.31.14.01.15_veh-40_01109_01272 + - 2021.08.31.14.01.15_veh-40_01284_01345 + - 2021.08.31.14.01.15_veh-40_01449_01552 + - 2021.08.31.14.01.15_veh-40_01576_01714 + - 2021.08.31.14.40.58_veh-40_00016_00084 + - 2021.08.31.14.40.58_veh-40_00125_00269 + - 2021.08.31.14.40.58_veh-40_00285_00456 + - 2021.08.31.14.40.58_veh-40_00467_00668 + - 2021.08.31.14.40.58_veh-40_00679_00892 + - 2021.08.31.14.40.58_veh-40_01022_01255 + - 2021.08.31.14.40.58_veh-40_01268_01618 + - 2021.08.31.14.40.58_veh-40_01630_01721 + - 2021.08.31.16.37.21_veh-40_00016_00099 + - 2021.08.31.16.37.21_veh-40_00110_00187 + - 2021.08.31.16.37.21_veh-40_00198_00265 + - 2021.08.31.16.37.21_veh-40_00277_00417 + - 2021.08.31.16.37.21_veh-40_00429_00541 + - 2021.08.31.16.37.21_veh-40_00554_00733 + - 2021.08.31.16.37.21_veh-40_00798_00955 + - 2021.08.31.16.37.21_veh-40_01101_01177 + - 2021.08.31.16.37.21_veh-40_01247_01379 + - 2021.08.31.16.37.21_veh-40_01405_01642 + - 2021.08.31.16.37.21_veh-40_01655_01736 + - 2021.08.31.17.42.52_veh-40_00389_00526 + - 2021.08.31.17.42.52_veh-40_00551_00680 + - 2021.08.31.17.42.52_veh-40_00833_00953 + - 2021.08.31.17.42.52_veh-40_01033_01313 + - 2021.08.31.17.42.52_veh-40_01331_01444 + - 2021.08.31.17.42.52_veh-40_01551_01684 + - 2021.08.31.18.15.54_veh-40_00038_00199 + - 2021.08.31.18.15.54_veh-40_00227_00324 + - 2021.08.31.18.15.54_veh-40_00335_00568 + - 2021.08.31.18.15.54_veh-40_00579_00980 + - 2021.08.31.18.15.54_veh-40_01010_01094 + - 2021.08.31.18.15.54_veh-40_01143_01496 + - 2021.09.13.13.03.21_veh-28_00015_00087 + - 2021.09.13.13.03.21_veh-28_00110_00334 + - 2021.09.13.13.03.21_veh-28_00356_00576 + - 2021.09.13.13.03.21_veh-28_00983_01070 + - 2021.09.13.13.03.21_veh-28_01082_01561 + - 2021.09.13.13.03.21_veh-28_01614_01733 + - 2021.09.13.13.21.28_veh-39_00015_00153 + - 2021.09.13.13.21.28_veh-39_00352_00540 + - 2021.09.13.13.21.28_veh-39_00563_00690 + - 2021.09.13.13.21.28_veh-39_00782_00880 + - 2021.09.13.13.21.28_veh-39_00945_01414 + - 2021.09.13.13.21.28_veh-39_01541_01700 + - 2021.09.13.13.21.28_veh-39_01713_01950 + - 2021.09.13.13.38.29_veh-28_00015_00088 + - 2021.09.13.13.38.29_veh-28_00283_00398 + - 2021.09.13.13.38.29_veh-28_00457_00656 + - 2021.09.13.13.38.29_veh-28_00667_01228 + - 2021.09.13.13.38.29_veh-28_01358_01647 + - 2021.09.13.13.38.29_veh-28_01703_01794 + - 2021.09.13.14.00.42_veh-39_00005_00066 + - 2021.09.13.14.00.42_veh-39_00175_00267 + - 2021.09.13.14.00.42_veh-39_00455_00624 + - 2021.09.13.14.00.42_veh-39_00650_00842 + - 2021.09.13.14.00.42_veh-39_00941_01003 + - 2021.09.13.14.00.42_veh-39_01154_01352 + - 2021.09.13.14.00.42_veh-39_01377_01498 + - 2021.09.13.14.00.42_veh-39_01559_01620 + - 2021.09.13.14.00.42_veh-39_01631_01778 + - 2021.09.13.14.16.34_veh-28_00143_00352 + - 2021.09.13.14.16.34_veh-28_00363_00529 + - 2021.09.13.14.16.34_veh-28_00559_00623 + - 2021.09.13.14.16.34_veh-28_00634_00778 + - 2021.09.13.14.16.34_veh-28_00820_00997 + - 2021.09.13.14.16.34_veh-28_01082_01169 + - 2021.09.13.14.16.34_veh-28_01212_01283 + - 2021.09.13.14.16.34_veh-28_01329_01427 + - 2021.09.13.14.16.34_veh-28_01645_01724 + - 2021.09.13.14.42.29_veh-39_00070_00192 + - 2021.09.13.14.42.29_veh-39_00261_00402 + - 2021.09.13.14.42.29_veh-39_00415_00647 + - 2021.09.13.14.42.29_veh-39_00658_00935 + - 2021.09.13.14.42.29_veh-39_00959_01048 + - 2021.09.13.14.42.29_veh-39_01255_01556 + - 2021.09.13.14.42.29_veh-39_01694_01867 + - 2021.09.13.14.55.48_veh-28_00025_00154 + - 2021.09.13.14.55.48_veh-28_00296_00457 + - 2021.09.13.14.55.48_veh-28_00468_00627 + - 2021.09.13.14.55.48_veh-28_00638_01212 + - 2021.09.13.14.55.48_veh-28_01268_01391 + - 2021.09.13.14.55.48_veh-28_01513_01671 + - 2021.09.13.14.55.48_veh-28_01728_01820 + - 2021.09.13.17.14.37_veh-28_00016_00107 + - 2021.09.13.17.14.37_veh-28_00286_00383 + - 2021.09.13.17.14.37_veh-28_00449_00655 + - 2021.09.13.17.14.37_veh-28_00666_00930 + - 2021.09.13.17.14.37_veh-28_01004_01116 + - 2021.09.13.17.14.37_veh-28_01127_01355 + - 2021.09.13.17.14.37_veh-28_01380_01521 + - 2021.09.13.17.14.37_veh-28_01558_01691 + - 2021.09.13.17.32.06_veh-39_00016_00147 + - 2021.09.13.17.32.06_veh-39_00321_00411 + - 2021.09.13.17.32.06_veh-39_00423_00506 + - 2021.09.13.17.32.06_veh-39_00533_00750 + - 2021.09.13.17.32.06_veh-39_00776_01213 + - 2021.09.13.17.32.06_veh-39_01315_01527 + - 2021.09.13.17.32.06_veh-39_01706_01777 + - 2021.09.13.17.46.46_veh-28_00091_00209 + - 2021.09.13.17.46.46_veh-28_00307_00399 + - 2021.09.13.17.46.46_veh-28_00666_00982 + - 2021.09.13.17.46.46_veh-28_01028_01139 + - 2021.09.13.17.46.46_veh-28_01192_01517 + - 2021.09.13.17.46.46_veh-28_01532_01690 + - 2021.09.13.18.06.11_veh-39_00080_00234 + - 2021.09.13.18.06.11_veh-39_00309_00384 + - 2021.09.13.18.06.11_veh-39_00588_00748 + - 2021.09.13.18.06.11_veh-39_00811_00892 + - 2021.09.13.18.06.11_veh-39_00904_01089 + - 2021.09.13.18.06.11_veh-39_01100_01173 + - 2021.09.13.18.06.11_veh-39_01395_01681 + - 2021.09.13.18.06.11_veh-39_01692_01775 + - 2021.09.13.18.23.05_veh-28_00016_00130 + - 2021.09.13.18.23.05_veh-28_00313_00449 + - 2021.09.13.18.23.05_veh-28_00465_00664 + - 2021.09.13.18.23.05_veh-28_00751_00831 + - 2021.09.13.18.23.05_veh-28_00994_01168 + - 2021.09.13.18.23.05_veh-28_01370_01549 + - 2021.09.13.18.23.05_veh-28_01560_01642 + - 2021.09.13.18.39.41_veh-39_00068_00224 + - 2021.09.13.18.39.41_veh-39_00273_00761 + - 2021.09.13.18.39.41_veh-39_01032_01117 + - 2021.09.13.18.39.41_veh-39_01160_01235 + - 2021.09.13.18.39.41_veh-39_01348_01467 + - 2021.09.13.18.39.41_veh-39_01538_01635 + - 2021.09.13.18.39.41_veh-39_01646_01767 + - 2021.09.13.18.55.39_veh-28_00039_00130 + - 2021.09.13.18.55.39_veh-28_00171_00289 + - 2021.09.13.18.55.39_veh-28_00334_00475 + - 2021.09.13.18.55.39_veh-28_00487_00688 + - 2021.09.13.18.55.39_veh-28_00769_00841 + - 2021.09.13.18.55.39_veh-28_00960_01090 + - 2021.09.13.18.55.39_veh-28_01101_01350 + - 2021.09.13.18.55.39_veh-28_01375_01450 + - 2021.09.13.18.55.39_veh-28_01461_01578 + - 2021.09.13.18.55.39_veh-28_01613_01711 + - 2021.09.13.19.12.44_veh-39_00294_00509 + - 2021.09.13.19.12.44_veh-39_00556_00720 + - 2021.09.13.19.12.44_veh-39_00742_00837 + - 2021.09.13.19.12.44_veh-39_01004_01095 + - 2021.09.13.19.12.44_veh-39_01171_01264 + - 2021.09.13.19.12.44_veh-39_01399_01786 + - 2021.09.13.19.54.33_veh-39_00005_00106 + - 2021.09.13.19.54.33_veh-39_00267_00431 + - 2021.09.13.19.54.33_veh-39_00444_00620 + - 2021.09.13.19.54.33_veh-39_00631_01093 + - 2021.09.13.19.54.33_veh-39_01271_01376 + - 2021.09.13.19.54.33_veh-39_01398_01606 + - 2021.09.13.19.54.33_veh-39_01634_01760 + - 2021.09.13.19.54.33_veh-39_01817_01895 + - 2021.09.14.14.17.04_veh-45_00039_00161 + - 2021.09.14.14.17.04_veh-45_00240_00506 + - 2021.09.14.14.17.04_veh-45_00545_00633 + - 2021.09.14.14.17.04_veh-45_00654_00766 + - 2021.09.14.14.17.04_veh-45_00872_01944 + - 2021.09.14.14.17.04_veh-45_01964_02145 + - 2021.09.14.15.03.51_veh-45_00035_00154 + - 2021.09.14.15.03.51_veh-45_00178_00336 + - 2021.09.14.15.03.51_veh-45_00390_00585 + - 2021.09.14.15.03.51_veh-45_00609_00779 + - 2021.09.14.15.03.51_veh-45_00803_01139 + - 2021.09.14.15.03.51_veh-45_01205_01789 + - 2021.09.14.16.46.51_veh-45_00149_00900 + - 2021.09.14.16.46.51_veh-45_00946_01175 + - 2021.09.14.16.46.51_veh-45_01206_01475 + - 2021.09.14.16.46.51_veh-45_01498_01768 + - 2021.09.14.16.46.51_veh-45_01845_02175 + - 2021.09.14.16.46.51_veh-45_02201_02302 + - 2021.09.14.16.46.51_veh-45_02322_02510 + - 2021.09.14.16.46.51_veh-45_02564_02650 + - 2021.09.14.17.35.14_veh-45_00016_00212 + - 2021.09.14.17.35.14_veh-45_00286_00470 + - 2021.09.14.17.35.14_veh-45_00520_01008 + - 2021.09.14.17.35.14_veh-45_01030_01328 + - 2021.09.14.17.35.14_veh-45_01351_01661 + - 2021.09.14.17.35.14_veh-45_01680_01781 + - 2021.09.14.17.35.14_veh-45_01816_01995 + - 2021.09.14.17.35.14_veh-45_02006_02248 + - 2021.09.14.17.35.14_veh-45_02293_02481 + - 2021.09.14.17.35.14_veh-45_02511_02663 + - 2021.09.14.17.35.14_veh-45_02723_02954 + - 2021.09.14.17.35.14_veh-45_02966_03047 + - 2021.09.14.17.35.14_veh-45_03216_03308 + - 2021.09.14.18.43.41_veh-45_00196_00578 + - 2021.09.14.18.43.41_veh-45_00602_00856 + - 2021.09.14.18.43.41_veh-45_00885_00952 + - 2021.09.14.18.43.41_veh-45_00965_01195 + - 2021.09.14.18.43.41_veh-45_01245_01529 + - 2021.09.14.18.43.41_veh-45_01555_02218 + - 2021.09.14.18.43.41_veh-45_02296_02477 + - 2021.09.14.18.43.41_veh-45_02503_03013 + - 2021.09.14.19.46.05_veh-45_00086_00843 + - 2021.09.14.19.46.05_veh-45_00867_00996 + - 2021.09.14.19.46.05_veh-45_01029_01458 + - 2021.09.14.19.46.05_veh-45_01508_01878 + - 2021.09.14.19.46.05_veh-45_01937_02119 + - 2021.09.14.19.46.05_veh-45_02130_02483 + - 2021.09.14.19.46.05_veh-45_02574_02889 + - 2021.09.14.19.46.05_veh-45_02912_03071 + - 2021.09.14.20.42.30_veh-45_00041_00210 + - 2021.09.14.20.42.30_veh-45_00221_00440 + - 2021.09.14.20.42.30_veh-45_00464_00579 + - 2021.09.14.20.42.30_veh-45_00624_00714 + - 2021.09.14.20.42.30_veh-45_00805_01078 + - 2021.09.14.20.42.30_veh-45_01097_01242 + - 2021.09.14.20.42.30_veh-45_01265_01584 + - 2021.09.14.20.42.30_veh-45_01603_01670 + - 2021.09.23.01.37.15_veh-53_00016_00424 + - 2021.09.23.01.37.15_veh-53_00462_00586 + - 2021.09.23.01.37.15_veh-53_00633_00752 + - 2021.09.23.01.37.15_veh-53_00864_01648 + - 2021.09.23.01.37.15_veh-53_01715_01799 + - 2021.09.23.01.44.00_veh-49_00031_00661 + - 2021.09.23.01.44.00_veh-49_00692_00829 + - 2021.09.23.01.44.00_veh-49_00853_01182 + - 2021.09.23.01.44.00_veh-49_01207_01408 + - 2021.09.23.01.44.00_veh-49_01420_01599 + - 2021.09.23.01.44.00_veh-49_01645_01766 + - 2021.09.23.01.59.54_veh-51_00029_00499 + - 2021.09.23.01.59.54_veh-51_00538_00627 + - 2021.09.23.01.59.54_veh-51_00674_00881 + - 2021.09.23.01.59.54_veh-51_00940_01482 + - 2021.09.23.01.59.54_veh-51_01513_01892 + - 2021.09.23.01.59.54_veh-51_01942_02037 + - 2021.09.23.02.12.02_veh-53_00116_00495 + - 2021.09.23.02.12.02_veh-53_00506_00595 + - 2021.09.23.02.12.02_veh-53_00675_00872 + - 2021.09.23.02.12.02_veh-53_00897_01171 + - 2021.09.23.02.12.02_veh-53_01314_01582 + - 2021.09.23.02.12.02_veh-53_01618_01759 + - 2021.09.23.02.17.18_veh-49_00071_00204 + - 2021.09.23.02.17.18_veh-49_00230_00345 + - 2021.09.23.02.17.18_veh-49_00447_00590 + - 2021.09.23.02.17.18_veh-49_00663_01081 + - 2021.09.23.02.17.18_veh-49_01180_01384 + - 2021.09.23.02.17.18_veh-49_01396_01472 + - 2021.09.23.02.17.18_veh-49_01483_01543 + - 2021.09.23.02.17.18_veh-49_01556_01818 + - 2021.09.23.02.37.41_veh-51_00039_00529 + - 2021.09.23.02.37.41_veh-51_00578_00683 + - 2021.09.23.02.37.41_veh-51_00697_01086 + - 2021.09.23.02.37.41_veh-51_01147_01635 + - 2021.09.23.02.37.41_veh-51_01757_01965 + - 2021.09.23.02.58.49_veh-53_00045_00193 + - 2021.09.23.02.58.49_veh-53_00275_00362 + - 2021.09.23.02.58.49_veh-53_00373_00477 + - 2021.09.23.02.58.49_veh-53_00489_00758 + - 2021.09.23.02.58.49_veh-53_00780_00895 + - 2021.09.23.02.58.49_veh-53_00913_01591 + - 2021.09.23.02.58.49_veh-53_01634_01848 + - 2021.09.23.03.06.36_veh-49_00005_00146 + - 2021.09.23.03.06.36_veh-49_00159_00283 + - 2021.09.23.03.06.36_veh-49_00309_00469 + - 2021.09.23.03.06.36_veh-49_00505_00612 + - 2021.09.23.03.06.36_veh-49_00732_00981 + - 2021.09.23.03.06.36_veh-49_00997_01126 + - 2021.09.23.03.06.36_veh-49_01138_01332 + - 2021.09.23.03.06.36_veh-49_01456_01840 + - 2021.09.23.03.29.13_veh-51_00016_00267 + - 2021.09.23.03.29.13_veh-51_00279_00368 + - 2021.09.23.03.29.13_veh-51_00408_00483 + - 2021.09.23.03.29.13_veh-51_00677_00838 + - 2021.09.23.03.29.13_veh-51_00864_01005 + - 2021.09.23.03.29.13_veh-51_01162_01775 + - 2021.09.23.03.33.49_veh-53_00010_00520 + - 2021.09.23.03.33.49_veh-53_00577_00850 + - 2021.09.23.03.33.49_veh-53_00901_00990 + - 2021.09.23.03.33.49_veh-53_01016_01422 + - 2021.09.23.03.33.49_veh-53_01443_01566 + - 2021.09.23.03.33.49_veh-53_01590_01877 + - 2021.09.23.03.40.18_veh-49_00005_00350 + - 2021.09.23.03.40.18_veh-49_00388_00524 + - 2021.09.23.03.40.18_veh-49_00535_00746 + - 2021.09.23.03.40.18_veh-49_00757_01172 + - 2021.09.23.03.40.18_veh-49_01258_01414 + - 2021.09.23.03.40.18_veh-49_01496_01585 + - 2021.09.23.03.40.18_veh-49_01618_01830 + - 2021.09.23.04.02.57_veh-51_00043_00153 + - 2021.09.23.04.02.57_veh-51_00313_00422 + - 2021.09.23.04.02.57_veh-51_00433_00863 + - 2021.09.23.04.02.57_veh-51_00897_01050 + - 2021.09.23.04.02.57_veh-51_01061_01186 + - 2021.09.23.04.02.57_veh-51_01198_01410 + - 2021.09.23.04.02.57_veh-51_01434_01622 + - 2021.09.23.04.02.57_veh-51_01648_01860 + - 2021.09.23.05.28.59_veh-53_00016_00447 + - 2021.09.23.05.28.59_veh-53_00483_00657 + - 2021.09.23.05.28.59_veh-53_00707_00791 + - 2021.09.23.05.28.59_veh-53_01001_01415 + - 2021.09.23.05.28.59_veh-53_01463_01778 + - 2021.09.23.05.33.01_veh-51_00016_00386 + - 2021.09.23.05.33.01_veh-51_00455_00528 + - 2021.09.23.05.33.01_veh-51_00592_00693 + - 2021.09.23.05.33.01_veh-51_00809_00944 + - 2021.09.23.05.33.01_veh-51_00993_01143 + - 2021.09.23.05.33.01_veh-51_01202_01325 + - 2021.09.23.05.33.01_veh-51_01336_01464 + - 2021.09.23.05.33.01_veh-51_01475_01580 + - 2021.09.23.05.33.01_veh-51_01624_01766 + - 2021.09.23.06.04.24_veh-53_00016_00192 + - 2021.09.23.06.04.24_veh-53_00258_00380 + - 2021.09.23.06.04.24_veh-53_00419_00614 + - 2021.09.23.06.04.24_veh-53_00629_00779 + - 2021.09.23.06.04.24_veh-53_00792_00932 + - 2021.09.23.06.04.24_veh-53_00945_01126 + - 2021.09.23.06.04.24_veh-53_01161_01287 + - 2021.09.23.06.04.24_veh-53_01323_01432 + - 2021.09.23.06.04.24_veh-53_01499_01778 + - 2021.09.23.06.06.47_veh-51_00016_00255 + - 2021.09.23.06.06.47_veh-51_00269_00441 + - 2021.09.23.06.06.47_veh-51_00452_01411 + - 2021.09.23.06.06.47_veh-51_01483_01949 + - 2021.09.23.06.10.51_veh-50_00016_00241 + - 2021.09.23.06.10.51_veh-50_00276_00363 + - 2021.09.23.06.10.51_veh-50_00441_00540 + - 2021.09.23.06.10.51_veh-50_00572_00663 + - 2021.09.23.06.10.51_veh-50_00685_00841 + - 2021.09.23.06.10.51_veh-50_00857_00948 + - 2021.09.23.06.10.51_veh-50_00981_01113 + - 2021.09.23.06.10.51_veh-50_01170_01291 + - 2021.09.23.06.10.51_veh-50_01327_01700 + - 2021.09.23.06.10.51_veh-50_01725_01885 + - 2021.09.23.06.45.26_veh-50_00037_00232 + - 2021.09.23.06.45.26_veh-50_00300_00398 + - 2021.09.23.06.45.26_veh-50_00413_00572 + - 2021.09.23.06.45.26_veh-50_00630_00752 + - 2021.09.23.06.45.26_veh-50_00787_00854 + - 2021.09.23.06.45.26_veh-50_00865_01080 + - 2021.09.23.06.45.26_veh-50_01105_01216 + - 2021.09.23.06.45.26_veh-50_01252_01476 + - 2021.09.23.06.45.26_veh-50_01532_01789 + - 2021.09.23.06.47.56_veh-53_00016_00621 + - 2021.09.23.06.47.56_veh-53_00669_01005 + - 2021.09.23.06.47.56_veh-53_01016_01108 + - 2021.09.23.06.47.56_veh-53_01160_01435 + - 2021.09.23.06.47.56_veh-53_01463_01592 + - 2021.09.23.06.51.14_veh-51_00016_00093 + - 2021.09.23.06.51.14_veh-51_00127_00187 + - 2021.09.23.06.51.14_veh-51_00302_00389 + - 2021.09.23.06.51.14_veh-51_00434_00663 + - 2021.09.23.06.51.14_veh-51_00674_00842 + - 2021.09.23.06.51.14_veh-51_01045_01233 + - 2021.09.23.06.51.14_veh-51_01382_01988 + - 2021.09.23.07.22.32_veh-53_00016_00116 + - 2021.09.23.07.22.32_veh-53_00127_00342 + - 2021.09.23.07.22.32_veh-53_00374_00468 + - 2021.09.23.07.22.32_veh-53_00522_00930 + - 2021.09.23.07.22.32_veh-53_00971_01821 + - 2021.09.23.07.27.52_veh-50_00016_00106 + - 2021.09.23.07.27.52_veh-50_00118_00631 + - 2021.09.23.07.27.52_veh-50_00669_00806 + - 2021.09.23.07.27.52_veh-50_00818_00915 + - 2021.09.23.07.27.52_veh-50_00928_01055 + - 2021.09.23.07.27.52_veh-50_01115_01196 + - 2021.09.23.07.27.52_veh-50_01213_01372 + - 2021.09.23.07.27.52_veh-50_01388_01486 + - 2021.09.23.07.27.52_veh-50_01553_01671 + - 2021.09.23.07.27.52_veh-50_01706_01806 + - 2021.09.23.07.55.03_veh-51_00016_00231 + - 2021.09.23.07.55.03_veh-51_00255_00376 + - 2021.09.23.07.55.03_veh-51_00444_00777 + - 2021.09.23.07.55.03_veh-51_00840_01100 + - 2021.09.23.07.55.03_veh-51_01251_01329 + - 2021.09.23.07.55.03_veh-51_01340_01436 + - 2021.09.23.07.55.03_veh-51_01536_01605 + - 2021.09.23.07.55.03_veh-51_01677_01828 + - 2021.09.23.07.55.03_veh-51_01864_01931 + - 2021.09.23.08.19.28_veh-53_00017_00336 + - 2021.09.23.08.19.28_veh-53_00353_00501 + - 2021.09.23.08.19.28_veh-53_00513_00579 + - 2021.09.23.08.19.28_veh-53_00692_00801 + - 2021.09.23.08.19.28_veh-53_00857_00922 + - 2021.09.23.08.19.28_veh-53_00933_01402 + - 2021.09.23.08.19.28_veh-53_01414_01683 + - 2021.09.23.08.31.59_veh-51_00016_00117 + - 2021.09.23.08.31.59_veh-51_00133_00360 + - 2021.09.23.08.31.59_veh-51_00384_00606 + - 2021.09.23.08.31.59_veh-51_00633_00723 + - 2021.09.23.08.31.59_veh-51_00756_01140 + - 2021.09.23.08.31.59_veh-51_01224_01557 + - 2021.09.23.08.31.59_veh-51_01579_01752 + - 2021.10.05.04.03.05_veh-50_00058_00321 + - 2021.10.05.04.03.05_veh-50_00365_00493 + - 2021.10.05.04.03.05_veh-50_00536_00637 + - 2021.10.05.04.03.05_veh-50_00648_00744 + - 2021.10.05.04.03.05_veh-50_00770_00979 + - 2021.10.05.04.03.05_veh-50_01003_01426 + - 2021.10.05.04.03.05_veh-50_01466_01790 + - 2021.10.05.04.38.41_veh-50_00014_00429 + - 2021.10.05.04.38.41_veh-50_00441_00515 + - 2021.10.05.04.38.41_veh-50_00576_00721 + - 2021.10.05.04.38.41_veh-50_00753_00956 + - 2021.10.05.04.38.41_veh-50_00996_01109 + - 2021.10.05.04.38.41_veh-50_01202_01296 + - 2021.10.05.04.38.41_veh-50_01312_01643 + - 2021.10.05.06.24.06_veh-50_00021_00383 + - 2021.10.05.06.24.06_veh-50_00431_00527 + - 2021.10.05.06.24.06_veh-50_00563_00688 + - 2021.10.05.06.24.06_veh-50_00717_01300 + - 2021.10.05.06.24.06_veh-50_01311_01409 + - 2021.10.05.06.24.06_veh-50_01420_01553 + - 2021.10.05.06.24.06_veh-50_01566_01672 + - 2021.10.05.06.31.40_veh-52_00005_00342 + - 2021.10.05.06.31.40_veh-52_00355_00454 + - 2021.10.05.06.31.40_veh-52_00465_00713 + - 2021.10.05.06.31.40_veh-52_00734_01305 + - 2021.10.05.06.31.40_veh-52_01316_01565 + - 2021.10.05.06.31.40_veh-52_01598_02013 + - 2021.10.05.06.57.40_veh-50_00025_00261 + - 2021.10.05.06.57.40_veh-50_00485_00624 + - 2021.10.05.06.57.40_veh-50_00665_00857 + - 2021.10.05.06.57.40_veh-50_00940_01105 + - 2021.10.05.06.57.40_veh-50_01131_01452 + - 2021.10.05.06.57.40_veh-50_01493_01624 + - 2021.10.05.06.57.40_veh-50_01658_01796 + - 2021.10.05.07.10.04_veh-52_00016_00206 + - 2021.10.05.07.10.04_veh-52_00252_00406 + - 2021.10.05.07.10.04_veh-52_00418_00563 + - 2021.10.05.07.10.04_veh-52_00596_00663 + - 2021.10.05.07.10.04_veh-52_00689_01322 + - 2021.10.05.07.10.04_veh-52_01442_01802 + - 2021.10.05.07.31.14_veh-53_00093_00366 + - 2021.10.05.07.31.14_veh-53_00403_00623 + - 2021.10.05.07.31.14_veh-53_00655_00761 + - 2021.10.05.07.31.14_veh-53_00922_01526 + - 2021.10.05.07.31.14_veh-53_01593_01673 + - 2021.10.05.07.31.14_veh-53_01704_01807 + - 2021.10.05.07.38.12_veh-50_00132_00234 + - 2021.10.05.07.38.12_veh-50_00245_00433 + - 2021.10.05.07.38.12_veh-50_00602_00663 + - 2021.10.05.07.38.12_veh-50_00805_00887 + - 2021.10.05.07.38.12_veh-50_00898_01058 + - 2021.10.05.07.38.12_veh-50_01085_01463 + - 2021.10.05.07.38.12_veh-50_01477_01565 + - 2021.10.05.07.49.39_veh-52_00034_00111 + - 2021.10.05.07.49.39_veh-52_00152_00281 + - 2021.10.05.07.49.39_veh-52_00328_00550 + - 2021.10.05.07.49.39_veh-52_00563_00680 + - 2021.10.05.07.49.39_veh-52_00770_00905 + - 2021.10.05.07.49.39_veh-52_00934_01406 + - 2021.10.05.07.49.39_veh-52_01417_01574 + - 2021.10.05.07.49.39_veh-52_01719_01839 + - 2021.10.05.07.49.39_veh-52_01883_02148 + - 2021.10.05.08.05.31_veh-53_00016_00171 + - 2021.10.05.08.05.31_veh-53_00196_00414 + - 2021.10.05.08.05.31_veh-53_00489_00583 + - 2021.10.05.08.05.31_veh-53_00594_00858 + - 2021.10.05.08.05.31_veh-53_00895_01091 + - 2021.10.05.08.05.31_veh-53_01111_01584 + - 2021.10.05.08.05.31_veh-53_01609_01697 + - 2021.10.05.08.11.15_veh-50_00059_00151 + - 2021.10.05.08.11.15_veh-50_00163_00321 + - 2021.10.05.08.11.15_veh-50_00360_00426 + - 2021.10.05.08.11.15_veh-50_00437_00585 + - 2021.10.05.08.11.15_veh-50_00710_00903 + - 2021.10.05.08.11.15_veh-50_00970_01211 + - 2021.10.05.08.11.15_veh-50_01222_01462 + - 2021.10.05.08.11.15_veh-50_01478_01545 + - 2021.10.05.08.11.15_veh-50_01566_01801 + - 2021.10.05.08.44.14_veh-53_00010_00964 + - 2021.10.05.08.44.14_veh-53_00994_01575 + - 2021.10.05.08.44.14_veh-53_01598_01795 + + test: + - 2021.05.25.12.30.39_veh-25_00005_00215 + - 2021.05.25.12.30.39_veh-25_00226_00299 + - 2021.05.25.12.30.39_veh-25_00321_01196 + - 2021.05.25.12.30.39_veh-25_01207_01368 + - 2021.05.25.12.30.39_veh-25_01405_01622 + - 2021.05.25.12.30.39_veh-25_01717_01901 + - 2021.05.25.12.30.39_veh-25_01912_02176 + - 2021.05.25.12.30.39_veh-25_02271_02371 + - 2021.05.25.12.30.39_veh-25_02402_02596 + - 2021.05.25.12.30.39_veh-25_02608_02701 + - 2021.05.25.12.30.39_veh-25_02778_02998 + - 2021.05.25.12.30.39_veh-25_03009_03121 + - 2021.05.25.12.30.39_veh-25_03132_03236 + - 2021.05.25.12.30.39_veh-25_03247_03327 + - 2021.05.25.12.30.39_veh-25_03349_03418 + - 2021.05.25.12.30.39_veh-25_03533_03763 + - 2021.05.25.12.30.39_veh-25_03774_03886 + - 2021.05.25.12.30.39_veh-25_03897_04053 + - 2021.05.25.12.30.39_veh-25_04064_04256 + - 2021.05.25.12.30.39_veh-25_04267_04848 + - 2021.05.25.12.30.39_veh-25_04859_04970 + - 2021.05.25.12.30.39_veh-25_04981_05073 + - 2021.05.25.12.30.39_veh-25_05084_05152 + - 2021.05.25.12.30.39_veh-25_05164_05268 + - 2021.05.25.12.30.39_veh-25_05279_05340 + - 2021.05.25.12.40.06_veh-47_00008_00086 + - 2021.05.25.12.40.06_veh-47_00097_00173 + - 2021.05.25.12.40.06_veh-47_00185_00368 + - 2021.05.25.12.40.06_veh-47_00493_00811 + - 2021.05.25.12.40.06_veh-47_00822_00984 + - 2021.05.25.12.40.06_veh-47_00995_01090 + - 2021.05.25.12.40.06_veh-47_01110_01596 + - 2021.05.25.12.40.06_veh-47_01607_01783 + - 2021.05.25.12.40.06_veh-47_01794_02027 + - 2021.05.25.12.40.06_veh-47_02038_02256 + - 2021.05.25.12.40.06_veh-47_02270_02397 + - 2021.05.25.12.40.06_veh-47_02408_02753 + - 2021.05.25.12.40.06_veh-47_02797_03040 + - 2021.05.25.12.40.06_veh-47_03051_03306 + - 2021.05.25.12.40.06_veh-47_03323_03544 + - 2021.05.25.12.40.06_veh-47_03644_03729 + - 2021.05.25.12.40.06_veh-47_03740_04119 + - 2021.05.25.12.40.06_veh-47_04130_04253 + - 2021.05.25.12.40.06_veh-47_04315_04464 + - 2021.05.25.12.40.06_veh-47_04475_04610 + - 2021.05.25.12.40.06_veh-47_04682_04934 + - 2021.05.25.12.40.06_veh-47_05000_05150 + - 2021.05.25.12.40.06_veh-47_05213_05515 + - 2021.05.25.14.16.10_veh-35_00011_00072 + - 2021.05.25.14.16.10_veh-35_00083_00485 + - 2021.05.25.14.16.10_veh-35_00496_00697 + - 2021.05.25.14.16.10_veh-35_00745_00843 + - 2021.05.25.14.16.10_veh-35_00854_01089 + - 2021.05.25.14.16.10_veh-35_01100_01664 + - 2021.05.25.14.16.10_veh-35_01690_02183 + - 2021.05.25.14.16.10_veh-35_02194_02267 + - 2021.05.25.14.16.10_veh-35_02278_02356 + - 2021.05.25.14.16.10_veh-35_02367_02471 + - 2021.05.25.14.16.10_veh-35_02482_02649 + - 2021.05.25.14.16.10_veh-35_02660_02766 + - 2021.05.25.14.16.10_veh-35_02777_02981 + - 2021.05.25.14.16.10_veh-35_02992_03074 + - 2021.05.25.14.16.10_veh-35_03085_03362 + - 2021.05.25.14.16.10_veh-35_03373_03550 + - 2021.05.25.14.16.10_veh-35_03561_04009 + - 2021.05.25.14.16.10_veh-35_04020_04086 + - 2021.05.25.14.16.10_veh-35_04097_04328 + - 2021.05.25.14.16.10_veh-35_04339_04524 + - 2021.05.25.14.16.10_veh-35_04561_05104 + - 2021.05.25.14.16.10_veh-35_05115_05378 + - 2021.05.25.14.24.08_veh-25_00005_00246 + - 2021.05.25.14.24.08_veh-25_00257_00747 + - 2021.05.25.14.24.08_veh-25_00801_00887 + - 2021.05.25.14.24.08_veh-25_00934_01067 + - 2021.05.25.14.24.08_veh-25_01129_01494 + - 2021.05.25.14.24.08_veh-25_01505_01632 + - 2021.05.25.14.24.08_veh-25_01644_01745 + - 2021.05.25.14.24.08_veh-25_01818_01924 + - 2021.05.25.14.24.08_veh-25_01935_02297 + - 2021.05.25.14.24.08_veh-25_02308_02421 + - 2021.05.25.14.24.08_veh-25_02432_02562 + - 2021.05.25.14.24.08_veh-25_02573_02691 + - 2021.05.25.14.24.08_veh-25_02702_02812 + - 2021.05.25.14.24.08_veh-25_02823_03091 + - 2021.05.25.14.24.08_veh-25_03253_03419 + - 2021.05.25.14.24.08_veh-25_03430_03514 + - 2021.05.25.14.24.08_veh-25_03525_03753 + - 2021.05.25.14.24.08_veh-25_03764_04034 + - 2021.05.25.14.24.08_veh-25_04059_04203 + - 2021.05.25.14.24.08_veh-25_04214_04512 + - 2021.05.25.14.24.08_veh-25_04523_04700 + - 2021.05.25.14.24.08_veh-25_04711_04979 + - 2021.05.25.14.24.08_veh-25_04990_05072 + - 2021.05.25.14.24.08_veh-25_05083_05249 + - 2021.05.25.14.26.37_veh-27_00136_00242 + - 2021.05.25.14.26.37_veh-27_00253_00691 + - 2021.05.25.14.26.37_veh-27_00753_01258 + - 2021.05.25.14.26.37_veh-27_01289_01376 + - 2021.05.25.14.26.37_veh-27_01387_01451 + - 2021.05.25.14.26.37_veh-27_01462_01646 + - 2021.05.25.14.26.37_veh-27_01661_01763 + - 2021.05.25.14.26.37_veh-27_01774_01960 + - 2021.05.25.14.26.37_veh-27_01971_02622 + - 2021.05.25.14.26.37_veh-27_02633_02725 + - 2021.05.25.14.26.37_veh-27_02736_03564 + - 2021.05.25.14.26.37_veh-27_03603_04010 + - 2021.05.25.14.26.37_veh-27_04021_04088 + - 2021.05.25.14.26.37_veh-27_04122_04279 + - 2021.05.25.14.26.37_veh-27_04290_04783 + - 2021.05.25.14.26.37_veh-27_04808_05021 + - 2021.05.25.14.26.37_veh-27_05049_05175 + - 2021.05.25.15.14.31_veh-47_00016_00107 + - 2021.05.25.15.14.31_veh-47_00118_00905 + - 2021.05.25.15.14.31_veh-47_00916_01062 + - 2021.05.25.15.14.31_veh-47_01073_01429 + - 2021.05.25.15.14.31_veh-47_01482_01793 + - 2021.05.25.15.14.31_veh-47_01863_02344 + - 2021.05.25.15.14.31_veh-47_02387_02692 + - 2021.05.25.15.14.31_veh-47_02703_02902 + - 2021.05.25.15.14.31_veh-47_02913_02998 + - 2021.05.25.15.14.31_veh-47_03009_03227 + - 2021.05.25.15.14.31_veh-47_03238_03528 + - 2021.05.25.15.14.31_veh-47_03539_03850 + - 2021.05.25.15.14.31_veh-47_03861_04051 + - 2021.05.25.15.14.31_veh-47_04062_04128 + - 2021.05.25.15.14.31_veh-47_04153_04287 + - 2021.05.25.15.14.31_veh-47_04298_04443 + - 2021.05.25.15.14.31_veh-47_04454_04721 + - 2021.05.25.15.14.31_veh-47_04732_04838 + - 2021.05.25.15.14.31_veh-47_04859_05064 + - 2021.05.25.15.14.31_veh-47_05075_05162 + - 2021.05.25.15.14.31_veh-47_05173_05303 + - 2021.05.25.15.14.31_veh-47_05314_05563 + - 2021.05.25.15.59.03_veh-30_00005_00111 + - 2021.05.25.15.59.03_veh-30_00122_00614 + - 2021.05.25.15.59.03_veh-30_00625_00855 + - 2021.05.25.15.59.03_veh-30_00885_01251 + - 2021.05.25.15.59.03_veh-30_01262_01453 + - 2021.05.25.15.59.03_veh-30_01478_01643 + - 2021.05.25.15.59.03_veh-30_01654_01772 + - 2021.05.25.15.59.03_veh-30_01783_02022 + - 2021.05.25.15.59.03_veh-30_02101_02234 + - 2021.05.25.15.59.03_veh-30_02245_02415 + - 2021.05.25.15.59.03_veh-30_02426_02564 + - 2021.05.25.15.59.03_veh-30_02575_02688 + - 2021.05.25.15.59.03_veh-30_02776_03017 + - 2021.05.25.15.59.03_veh-30_03028_03116 + - 2021.05.25.15.59.03_veh-30_03159_03488 + - 2021.05.25.15.59.03_veh-30_03499_03671 + - 2021.05.25.15.59.03_veh-30_03815_04016 + - 2021.05.25.15.59.03_veh-30_04027_04200 + - 2021.05.25.15.59.03_veh-30_04211_04303 + - 2021.05.25.15.59.03_veh-30_04314_04439 + - 2021.05.25.15.59.03_veh-30_04463_04606 + - 2021.05.25.15.59.03_veh-30_04621_04715 + - 2021.05.25.15.59.03_veh-30_04726_04798 + - 2021.05.25.15.59.03_veh-30_04809_05034 + - 2021.05.25.15.59.03_veh-30_05045_05234 + - 2021.05.25.15.59.03_veh-30_05245_05413 + - 2021.05.25.16.37.23_veh-25_00005_00217 + - 2021.05.25.16.37.23_veh-25_00291_00387 + - 2021.05.25.16.37.23_veh-25_00408_00628 + - 2021.05.25.16.37.23_veh-25_00718_01019 + - 2021.05.25.16.37.23_veh-25_01099_01453 + - 2021.05.25.16.37.23_veh-25_01464_01608 + - 2021.05.25.16.37.23_veh-25_01619_01699 + - 2021.05.25.16.37.23_veh-25_01827_02053 + - 2021.05.25.16.37.23_veh-25_02064_02275 + - 2021.05.25.16.37.23_veh-25_02286_02397 + - 2021.05.25.16.37.23_veh-25_02443_02853 + - 2021.05.25.16.37.23_veh-25_02929_03039 + - 2021.05.25.16.37.23_veh-25_03050_03252 + - 2021.05.25.16.37.23_veh-25_03311_03550 + - 2021.05.25.16.37.23_veh-25_03561_03933 + - 2021.05.25.16.37.23_veh-25_04067_04175 + - 2021.05.25.16.37.23_veh-25_04272_04344 + - 2021.05.25.16.37.23_veh-25_04355_04458 + - 2021.05.25.16.37.23_veh-25_04469_04758 + - 2021.05.25.16.37.23_veh-25_05040_05187 + - 2021.05.25.16.37.23_veh-25_05198_05415 + - 2021.05.25.16.54.14_veh-47_00016_00247 + - 2021.05.25.16.54.14_veh-47_00258_00390 + - 2021.05.25.16.54.14_veh-47_00459_00527 + - 2021.05.25.16.54.14_veh-47_00598_00786 + - 2021.05.25.16.54.14_veh-47_00797_00968 + - 2021.05.25.16.54.14_veh-47_00979_01163 + - 2021.05.25.16.54.14_veh-47_01279_01522 + - 2021.05.25.16.54.14_veh-47_01559_01733 + - 2021.05.25.16.54.14_veh-47_01744_01907 + - 2021.05.25.16.54.14_veh-47_01944_02100 + - 2021.05.25.16.54.14_veh-47_02114_02287 + - 2021.05.25.16.54.14_veh-47_02307_02418 + - 2021.05.25.16.54.14_veh-47_02429_02693 + - 2021.05.25.16.54.14_veh-47_02737_02863 + - 2021.05.25.16.54.14_veh-47_02874_03052 + - 2021.05.25.16.54.14_veh-47_03064_03243 + - 2021.05.25.16.54.14_veh-47_03317_03698 + - 2021.05.25.16.54.14_veh-47_03709_03839 + - 2021.05.25.16.54.14_veh-47_03850_04140 + - 2021.05.25.16.54.14_veh-47_04179_04255 + - 2021.05.25.16.54.14_veh-47_04266_04844 + - 2021.05.25.16.54.14_veh-47_04855_04946 + - 2021.05.25.16.54.14_veh-47_04957_05118 + - 2021.05.25.16.54.14_veh-47_05169_05524 + - 2021.05.25.17.38.43_veh-27_00048_00406 + - 2021.05.25.17.38.43_veh-27_00417_00512 + - 2021.05.25.17.38.43_veh-27_00523_00866 + - 2021.05.25.17.38.43_veh-27_00877_01366 + - 2021.05.25.17.38.43_veh-27_01377_01515 + - 2021.05.25.17.38.43_veh-27_01526_01626 + - 2021.05.25.17.54.41_veh-35_00020_00122 + - 2021.05.25.17.54.41_veh-35_00133_00222 + - 2021.05.25.17.54.41_veh-35_00287_00437 + - 2021.05.25.17.54.41_veh-35_00461_00671 + - 2021.05.25.17.54.41_veh-35_00682_00894 + - 2021.05.25.17.54.41_veh-35_01042_01145 + - 2021.05.25.17.54.41_veh-35_01330_01594 + - 2021.05.25.17.54.41_veh-35_01654_01850 + - 2021.05.25.17.54.41_veh-35_01905_02121 + - 2021.05.25.17.54.41_veh-35_02169_02608 + - 2021.05.25.17.54.41_veh-35_02647_02712 + - 2021.05.25.17.54.41_veh-35_02723_02902 + - 2021.05.25.17.54.41_veh-35_02978_03237 + - 2021.05.25.17.54.41_veh-35_03248_03401 + - 2021.05.25.17.54.41_veh-35_03412_03627 + - 2021.05.25.17.54.41_veh-35_03671_04070 + - 2021.05.25.17.54.41_veh-35_04111_04288 + - 2021.05.25.17.54.41_veh-35_04299_04847 + - 2021.05.25.17.54.41_veh-35_04858_04956 + - 2021.05.25.17.54.41_veh-35_04967_05098 + - 2021.05.25.17.54.41_veh-35_05109_05347 + - 2021.05.25.18.38.25_veh-25_00008_00181 + - 2021.05.25.18.38.25_veh-25_00192_00275 + - 2021.05.25.18.38.25_veh-25_00286_00518 + - 2021.05.25.18.38.25_veh-25_00529_00625 + - 2021.05.25.18.38.25_veh-25_00647_00777 + - 2021.05.25.18.38.25_veh-25_00788_00848 + - 2021.05.25.18.38.25_veh-25_00859_01445 + - 2021.05.25.18.38.25_veh-25_01457_01693 + - 2021.05.25.18.38.25_veh-25_01776_01967 + - 2021.05.25.18.38.25_veh-25_01978_02298 + - 2021.05.25.18.38.25_veh-25_02309_03344 + - 2021.05.25.18.38.25_veh-25_03355_04047 + - 2021.05.25.18.38.25_veh-25_04058_04186 + - 2021.05.25.18.38.25_veh-25_04197_04324 + - 2021.05.25.18.38.25_veh-25_04335_04452 + - 2021.05.25.18.38.25_veh-25_04463_04538 + - 2021.05.25.18.38.25_veh-25_04549_04754 + - 2021.05.25.18.38.25_veh-25_04765_05304 + - 2021.05.25.20.02.28_veh-35_00005_00103 + - 2021.05.25.20.02.28_veh-35_00159_00426 + - 2021.05.25.20.02.28_veh-35_00751_00878 + - 2021.05.25.20.02.28_veh-35_00942_01021 + - 2021.05.25.20.02.28_veh-35_01105_01244 + - 2021.05.25.20.02.28_veh-35_01353_01454 + - 2021.05.25.20.02.28_veh-35_01655_01732 + - 2021.05.25.20.02.28_veh-35_01803_01942 + - 2021.05.25.20.02.28_veh-35_02047_02144 + - 2021.05.25.20.02.28_veh-35_02167_02254 + - 2021.05.25.20.02.28_veh-35_02296_02491 + - 2021.05.25.20.02.28_veh-35_02614_02674 + - 2021.05.25.20.02.28_veh-35_02712_02945 + - 2021.05.25.20.02.28_veh-35_02956_03268 + - 2021.05.25.20.02.28_veh-35_03300_03399 + - 2021.06.03.12.02.06_veh-35_00038_00222 + - 2021.06.03.12.02.06_veh-35_00233_00609 + - 2021.06.03.12.02.06_veh-35_00621_00735 + - 2021.06.03.12.02.06_veh-35_00804_00940 + - 2021.06.03.12.02.06_veh-35_00952_01089 + - 2021.06.03.12.02.06_veh-35_01100_01227 + - 2021.06.03.12.02.06_veh-35_01276_01356 + - 2021.06.03.12.02.06_veh-35_01367_01475 + - 2021.06.03.12.02.06_veh-35_01614_01794 + - 2021.06.03.12.02.06_veh-35_01805_02034 + - 2021.06.03.12.02.06_veh-35_02092_02307 + - 2021.06.03.12.02.06_veh-35_02318_02380 + - 2021.06.03.12.02.06_veh-35_02422_02490 + - 2021.06.03.12.02.06_veh-35_02501_02582 + - 2021.06.03.12.02.06_veh-35_02593_03002 + - 2021.06.03.12.02.06_veh-35_03060_03188 + - 2021.06.03.12.02.06_veh-35_03233_03397 + - 2021.06.03.12.02.06_veh-35_03526_03712 + - 2021.06.03.12.02.06_veh-35_03726_03949 + - 2021.06.03.12.02.06_veh-35_03971_04092 + - 2021.06.03.12.02.06_veh-35_04135_04230 + - 2021.06.03.12.02.06_veh-35_04242_04305 + - 2021.06.03.12.02.06_veh-35_04422_04491 + - 2021.06.03.12.02.06_veh-35_04692_04763 + - 2021.06.03.12.02.06_veh-35_04774_04978 + - 2021.06.03.12.02.06_veh-35_04989_05115 + - 2021.06.03.12.02.06_veh-35_05127_05302 + - 2021.06.03.12.06.21_veh-47_00015_00390 + - 2021.06.03.12.06.21_veh-47_00401_00660 + - 2021.06.03.12.06.21_veh-47_00673_00800 + - 2021.06.03.12.06.21_veh-47_00811_00995 + - 2021.06.03.12.06.21_veh-47_01006_01109 + - 2021.06.03.12.06.21_veh-47_01120_01372 + - 2021.06.03.12.06.21_veh-47_01383_01649 + - 2021.06.03.12.06.21_veh-47_01660_01789 + - 2021.06.03.12.06.21_veh-47_01800_01895 + - 2021.06.03.12.06.21_veh-47_01987_02088 + - 2021.06.03.12.06.21_veh-47_02099_02188 + - 2021.06.03.12.06.21_veh-47_02226_02307 + - 2021.06.03.12.06.21_veh-47_02318_02415 + - 2021.06.03.12.06.21_veh-47_02426_02656 + - 2021.06.03.12.06.21_veh-47_02690_02814 + - 2021.06.03.12.06.21_veh-47_02825_02931 + - 2021.06.03.12.06.21_veh-47_02991_03272 + - 2021.06.03.12.06.21_veh-47_03283_03355 + - 2021.06.03.12.06.21_veh-47_03366_03599 + - 2021.06.03.12.06.21_veh-47_03634_03811 + - 2021.06.03.12.06.21_veh-47_03822_04409 + - 2021.06.03.12.06.21_veh-47_04420_04542 + - 2021.06.03.12.06.21_veh-47_04553_04938 + - 2021.06.03.12.06.21_veh-47_05056_05311 + - 2021.06.03.12.36.43_veh-38_00016_00188 + - 2021.06.03.12.36.43_veh-38_00216_00402 + - 2021.06.03.12.36.43_veh-38_00462_00623 + - 2021.06.03.12.36.43_veh-38_00667_00774 + - 2021.06.03.12.36.43_veh-38_00843_00925 + - 2021.06.03.12.36.43_veh-38_01074_01365 + - 2021.06.03.12.36.43_veh-38_01436_01576 + - 2021.06.03.12.36.43_veh-38_01626_01694 + - 2021.06.03.12.36.43_veh-38_01750_01829 + - 2021.06.03.12.36.43_veh-38_01840_02081 + - 2021.06.03.12.36.43_veh-38_02093_02215 + - 2021.06.03.12.36.43_veh-38_02267_02731 + - 2021.06.03.12.36.43_veh-38_02747_02832 + - 2021.06.03.12.36.43_veh-38_02843_02955 + - 2021.06.03.12.36.43_veh-38_02986_03129 + - 2021.06.03.12.36.43_veh-38_03170_03330 + - 2021.06.03.12.36.43_veh-38_03341_03406 + - 2021.06.03.12.36.43_veh-38_03417_03547 + - 2021.06.03.12.36.43_veh-38_03591_03673 + - 2021.06.03.12.36.43_veh-38_03716_03847 + - 2021.06.03.12.36.43_veh-38_03953_04248 + - 2021.06.03.12.36.43_veh-38_04259_04515 + - 2021.06.03.12.36.43_veh-38_04526_04653 + - 2021.06.03.12.36.43_veh-38_04699_04936 + - 2021.06.03.12.36.43_veh-38_05008_05131 + - 2021.06.03.12.36.43_veh-38_05142_05279 + - 2021.06.03.12.36.43_veh-38_05290_05371 + - 2021.06.03.12.36.43_veh-38_05382_05488 + - 2021.06.03.12.36.43_veh-38_05525_05735 + - 2021.06.03.12.36.43_veh-38_05786_05910 + - 2021.06.03.13.55.17_veh-35_00073_00426 + - 2021.06.03.13.55.17_veh-35_00452_00523 + - 2021.06.03.13.55.17_veh-35_00580_00764 + - 2021.06.03.13.55.17_veh-35_00789_00999 + - 2021.06.03.13.55.17_veh-35_01027_01104 + - 2021.06.03.13.55.17_veh-35_01160_01299 + - 2021.06.03.13.55.17_veh-35_01310_01496 + - 2021.06.03.13.55.17_veh-35_01597_01741 + - 2021.06.03.13.55.17_veh-35_01752_01888 + - 2021.06.03.13.55.17_veh-35_01910_01989 + - 2021.06.03.13.55.17_veh-35_02000_02154 + - 2021.06.03.13.55.17_veh-35_02249_02408 + - 2021.06.03.13.55.17_veh-35_02419_02561 + - 2021.06.03.13.55.17_veh-35_02572_02855 + - 2021.06.03.13.55.17_veh-35_02866_03582 + - 2021.06.03.13.55.17_veh-35_03712_04098 + - 2021.06.03.13.55.17_veh-35_04225_04326 + - 2021.06.03.13.55.17_veh-35_04392_04472 + - 2021.06.03.13.55.17_veh-35_04505_04580 + - 2021.06.03.13.55.17_veh-35_04591_04722 + - 2021.06.03.13.55.17_veh-35_04830_04923 + - 2021.06.03.13.55.17_veh-35_04934_05009 + - 2021.06.03.13.55.17_veh-35_05020_05119 + - 2021.06.03.13.55.17_veh-35_05130_05366 + - 2021.06.03.14.16.46_veh-47_00053_00230 + - 2021.06.03.14.16.46_veh-47_00241_00323 + - 2021.06.03.14.16.46_veh-47_00362_00430 + - 2021.06.03.14.16.46_veh-47_00468_00957 + - 2021.06.03.14.16.46_veh-47_01047_01550 + - 2021.06.03.14.16.46_veh-47_01561_01715 + - 2021.06.03.14.16.46_veh-47_01726_01866 + - 2021.06.03.14.16.46_veh-47_01877_02158 + - 2021.06.03.14.16.46_veh-47_02169_02331 + - 2021.06.03.14.16.46_veh-47_02342_02465 + - 2021.06.03.14.16.46_veh-47_02476_02610 + - 2021.06.03.14.16.46_veh-47_02621_02987 + - 2021.06.03.14.16.46_veh-47_03046_03520 + - 2021.06.03.14.16.46_veh-47_03531_03595 + - 2021.06.03.14.16.46_veh-47_03606_03790 + - 2021.06.03.14.16.46_veh-47_03865_04001 + - 2021.06.03.14.16.46_veh-47_04012_04263 + - 2021.06.03.14.16.46_veh-47_04274_04539 + - 2021.06.03.14.16.46_veh-47_04550_04771 + - 2021.06.03.14.16.46_veh-47_04782_04935 + - 2021.06.03.14.16.46_veh-47_04946_05142 + - 2021.06.03.14.16.46_veh-47_05153_05305 + - 2021.06.03.14.29.58_veh-16_00016_00142 + - 2021.06.03.14.29.58_veh-16_00225_00315 + - 2021.06.03.14.29.58_veh-16_00326_00413 + - 2021.06.03.14.29.58_veh-16_00541_00745 + - 2021.06.03.14.29.58_veh-16_00756_00849 + - 2021.06.03.14.29.58_veh-16_00860_00930 + - 2021.06.03.14.29.58_veh-16_00957_01152 + - 2021.06.03.14.29.58_veh-16_01163_01937 + - 2021.06.03.14.29.58_veh-16_01948_02055 + - 2021.06.03.14.29.58_veh-16_02066_02206 + - 2021.06.03.14.29.58_veh-16_02266_02389 + - 2021.06.03.14.29.58_veh-16_02400_02655 + - 2021.06.03.14.29.58_veh-16_02667_02938 + - 2021.06.03.14.29.58_veh-16_02949_03146 + - 2021.06.03.14.29.58_veh-16_03183_03273 + - 2021.06.03.14.29.58_veh-16_03284_03566 + - 2021.06.03.14.29.58_veh-16_03582_03646 + - 2021.06.03.14.29.58_veh-16_03657_03825 + - 2021.06.03.14.29.58_veh-16_03836_04336 + - 2021.06.03.14.29.58_veh-16_04347_04596 + - 2021.06.03.14.29.58_veh-16_04607_05159 + - 2021.06.03.14.29.58_veh-16_05199_05347 + - 2021.06.03.14.29.58_veh-16_05358_05456 + - 2021.06.03.14.29.58_veh-16_05573_06071 + - 2021.06.03.14.29.58_veh-16_06082_06160 + - 2021.06.03.14.29.58_veh-16_06171_06386 + - 2021.06.03.14.37.17_veh-38_00160_00290 + - 2021.06.03.14.37.17_veh-38_00313_00412 + - 2021.06.03.14.37.17_veh-38_00423_00934 + - 2021.06.03.14.37.17_veh-38_00997_01131 + - 2021.06.03.14.37.17_veh-38_01142_01597 + - 2021.06.03.14.37.17_veh-38_01613_01773 + - 2021.06.03.14.37.17_veh-38_01799_01936 + - 2021.06.03.14.37.17_veh-38_01947_02053 + - 2021.06.03.14.37.17_veh-38_02064_02191 + - 2021.06.03.14.37.17_veh-38_02269_02606 + - 2021.06.03.14.37.17_veh-38_02669_02730 + - 2021.06.03.14.37.17_veh-38_02767_02991 + - 2021.06.03.14.37.17_veh-38_03002_03226 + - 2021.06.03.14.37.17_veh-38_03245_03443 + - 2021.06.03.14.37.17_veh-38_03454_03575 + - 2021.06.03.14.37.17_veh-38_03586_03656 + - 2021.06.03.14.37.17_veh-38_03667_03736 + - 2021.06.03.14.37.17_veh-38_03747_03954 + - 2021.06.03.14.37.17_veh-38_03965_04081 + - 2021.06.03.14.37.17_veh-38_04093_04292 + - 2021.06.03.14.37.17_veh-38_04303_04406 + - 2021.06.03.14.37.17_veh-38_04417_04517 + - 2021.06.03.14.37.17_veh-38_04650_04843 + - 2021.06.03.14.37.17_veh-38_04855_04932 + - 2021.06.03.14.37.17_veh-38_05036_05156 + - 2021.06.03.14.37.17_veh-38_05167_05352 + - 2021.06.03.14.37.17_veh-38_05363_05567 + - 2021.06.03.14.37.17_veh-38_05578_05743 + - 2021.06.03.17.06.58_veh-35_00016_00450 + - 2021.06.03.17.06.58_veh-35_00461_00655 + - 2021.06.03.17.06.58_veh-35_00712_00855 + - 2021.06.03.17.06.58_veh-35_00871_00946 + - 2021.06.03.17.06.58_veh-35_00957_01604 + - 2021.06.03.17.06.58_veh-35_01615_02220 + - 2021.06.03.17.06.58_veh-35_02231_02410 + - 2021.06.03.17.06.58_veh-35_02441_02560 + - 2021.06.03.17.06.58_veh-35_02571_02742 + - 2021.06.03.17.06.58_veh-35_02755_02901 + - 2021.06.03.17.06.58_veh-35_02943_03220 + - 2021.06.03.17.06.58_veh-35_03231_03685 + - 2021.06.03.17.06.58_veh-35_03696_03849 + - 2021.06.03.17.06.58_veh-35_03860_03992 + - 2021.06.03.17.06.58_veh-35_04062_04123 + - 2021.06.03.17.06.58_veh-35_04134_04313 + - 2021.06.03.17.06.58_veh-35_04324_04406 + - 2021.06.03.17.06.58_veh-35_04417_04760 + - 2021.06.03.17.06.58_veh-35_04771_04921 + - 2021.06.03.17.06.58_veh-35_04942_05066 + - 2021.06.03.17.06.58_veh-35_05160_05331 + - 2021.06.03.17.55.42_veh-38_00064_00230 + - 2021.06.03.17.55.42_veh-38_00271_00402 + - 2021.06.03.17.55.42_veh-38_00413_00629 + - 2021.06.03.17.55.42_veh-38_00640_00902 + - 2021.06.03.17.55.42_veh-38_00913_01152 + - 2021.06.03.17.55.42_veh-38_01172_01279 + - 2021.06.03.17.55.42_veh-38_01290_01473 + - 2021.06.03.17.55.42_veh-38_01484_01672 + - 2021.06.03.17.55.42_veh-38_01713_01887 + - 2021.06.03.17.55.42_veh-38_02024_02150 + - 2021.06.03.17.55.42_veh-38_02220_02365 + - 2021.06.03.17.55.42_veh-38_02376_02535 + - 2021.06.03.17.55.42_veh-38_02617_02837 + - 2021.06.03.17.55.42_veh-38_02848_03110 + - 2021.06.03.17.55.42_veh-38_03171_03252 + - 2021.06.03.17.55.42_veh-38_03372_03458 + - 2021.06.03.17.55.42_veh-38_03469_03798 + - 2021.06.03.17.55.42_veh-38_03810_04001 + - 2021.06.03.17.55.42_veh-38_04045_04223 + - 2021.06.03.17.55.42_veh-38_04234_04336 + - 2021.06.03.17.55.42_veh-38_04347_04521 + - 2021.06.03.17.55.42_veh-38_04591_04776 + - 2021.06.03.17.55.42_veh-38_04800_05150 + - 2021.06.03.17.55.42_veh-38_05161_05786 + - 2021.06.03.17.55.42_veh-38_05828_05897 + - 2021.06.03.18.08.45_veh-16_00130_00257 + - 2021.06.03.18.08.45_veh-16_00345_00461 + - 2021.06.03.18.08.45_veh-16_00647_00713 + - 2021.06.03.18.08.45_veh-16_00724_00820 + - 2021.06.03.18.08.45_veh-16_00831_01343 + - 2021.06.03.18.08.45_veh-16_01449_01636 + - 2021.06.03.18.08.45_veh-16_01707_01943 + - 2021.06.03.18.08.45_veh-16_02018_02095 + - 2021.06.03.18.08.45_veh-16_02106_02194 + - 2021.06.03.18.08.45_veh-16_02223_02286 + - 2021.06.03.18.08.45_veh-16_02302_02510 + - 2021.06.03.18.08.45_veh-16_02683_03034 + - 2021.06.03.18.08.45_veh-16_03045_03192 + - 2021.06.03.18.08.45_veh-16_03203_03283 + - 2021.06.03.18.08.45_veh-16_03407_03698 + - 2021.06.03.18.08.45_veh-16_03775_03929 + - 2021.06.03.18.08.45_veh-16_03988_04096 + - 2021.06.03.18.08.45_veh-16_04107_04242 + - 2021.06.03.18.08.45_veh-16_04254_04640 + - 2021.06.03.18.08.45_veh-16_04651_04749 + - 2021.06.03.18.08.45_veh-16_04778_04901 + - 2021.06.03.18.08.45_veh-16_04912_05038 + - 2021.06.03.18.08.45_veh-16_05049_05217 + - 2021.06.03.18.08.45_veh-16_05228_05297 + - 2021.06.03.18.08.45_veh-16_05308_05423 + - 2021.06.03.18.47.39_veh-35_00016_00112 + - 2021.06.03.18.47.39_veh-35_00123_00246 + - 2021.06.03.18.47.39_veh-35_00257_00492 + - 2021.06.03.18.47.39_veh-35_00503_00777 + - 2021.06.03.18.47.39_veh-35_00788_00870 + - 2021.06.03.18.47.39_veh-35_00881_02426 + - 2021.06.03.18.47.39_veh-35_02458_02535 + - 2021.06.03.18.47.39_veh-35_02546_02662 + - 2021.06.03.18.47.39_veh-35_02673_03602 + - 2021.06.03.18.47.39_veh-35_03613_04352 + - 2021.06.03.18.47.39_veh-35_04363_04426 + - 2021.06.03.18.47.39_veh-35_04437_04567 + - 2021.06.03.18.47.39_veh-35_04649_04887 + - 2021.06.03.18.47.39_veh-35_04898_04997 + - 2021.06.03.18.47.39_veh-35_05008_05212 + - 2021.06.03.18.57.27_veh-47_00005_00178 + - 2021.06.03.18.57.27_veh-47_00257_00366 + - 2021.06.03.18.57.27_veh-47_00423_00497 + - 2021.06.03.18.57.27_veh-47_00581_00647 + - 2021.06.03.18.57.27_veh-47_00658_00792 + - 2021.06.03.18.57.27_veh-47_00843_00921 + - 2021.06.03.18.57.27_veh-47_00932_01793 + - 2021.06.03.18.57.27_veh-47_01827_01955 + - 2021.06.03.18.57.27_veh-47_01977_02366 + - 2021.06.03.18.57.27_veh-47_02377_02546 + - 2021.06.03.18.57.27_veh-47_02625_02716 + - 2021.06.03.18.57.27_veh-47_02727_03031 + - 2021.06.03.18.57.27_veh-47_03042_03456 + - 2021.06.03.18.57.27_veh-47_03477_03601 + - 2021.06.03.18.57.27_veh-47_03613_03777 + - 2021.06.03.18.57.27_veh-47_03788_04197 + - 2021.06.03.18.57.27_veh-47_04208_04291 + - 2021.06.03.18.57.27_veh-47_04312_04382 + - 2021.06.03.18.57.27_veh-47_04393_04762 + - 2021.06.03.18.57.27_veh-47_04773_05036 + - 2021.06.03.18.57.27_veh-47_05047_05368 + - 2021.06.28.13.47.12_veh-12_00019_00118 + - 2021.06.28.13.47.12_veh-12_00139_00402 + - 2021.06.28.13.47.12_veh-12_00424_00934 + - 2021.06.28.13.47.12_veh-12_00956_02040 + - 2021.06.28.13.47.12_veh-12_02139_02676 + - 2021.06.28.13.47.12_veh-12_02697_02964 + - 2021.06.28.13.53.26_veh-26_00016_00266 + - 2021.06.28.13.53.26_veh-26_00277_00481 + - 2021.06.28.13.53.26_veh-26_00492_00696 + - 2021.06.28.13.53.26_veh-26_00707_03205 + - 2021.06.28.13.57.58_veh-35_00016_00291 + - 2021.06.28.13.57.58_veh-35_00312_02552 + - 2021.06.28.13.59.32_veh-38_00015_00936 + - 2021.06.28.13.59.32_veh-38_00957_01441 + - 2021.06.28.13.59.32_veh-38_01505_01922 + - 2021.06.28.13.59.32_veh-38_01933_03338 + - 2021.06.28.14.51.28_veh-26_00016_00110 + - 2021.06.28.14.51.28_veh-26_00135_02642 + - 2021.06.28.14.51.28_veh-26_02653_05399 + - 2021.06.28.14.55.14_veh-12_00016_00166 + - 2021.06.28.14.55.14_veh-12_00177_00362 + - 2021.06.28.14.55.14_veh-12_00384_00671 + - 2021.06.28.14.55.14_veh-12_00682_01451 + - 2021.06.28.14.55.14_veh-12_01462_01562 + - 2021.06.28.14.55.14_veh-12_01602_04021 + - 2021.06.28.14.55.14_veh-12_04032_04916 + - 2021.06.28.15.02.02_veh-38_00071_00236 + - 2021.06.28.15.02.02_veh-38_00247_00550 + - 2021.06.28.15.02.02_veh-38_00571_01201 + - 2021.06.28.15.02.02_veh-38_01222_01779 + - 2021.06.28.15.02.02_veh-38_01800_01945 + - 2021.06.28.15.02.02_veh-38_01966_02377 + - 2021.06.28.15.02.02_veh-38_02398_02848 + - 2021.06.28.15.02.02_veh-38_02869_03012 + - 2021.06.28.15.02.02_veh-38_03034_03116 + - 2021.06.28.15.07.02_veh-35_00016_00239 + - 2021.06.28.15.07.02_veh-35_00260_05954 + - 2021.06.28.15.10.57_veh-16_00016_00553 + - 2021.06.28.15.10.57_veh-16_00574_00728 + - 2021.06.28.15.10.57_veh-16_00749_00980 + - 2021.06.28.15.10.57_veh-16_01001_02195 + - 2021.06.28.15.10.57_veh-16_02206_02427 + - 2021.06.28.15.10.57_veh-16_02438_02580 + - 2021.06.28.15.10.57_veh-16_02591_02675 + - 2021.06.28.15.10.57_veh-16_02686_03731 + - 2021.06.28.15.10.57_veh-16_03742_04746 + - 2021.06.28.15.10.57_veh-16_04768_04892 + - 2021.06.28.15.10.57_veh-16_04903_06361 + - 2021.06.28.15.59.39_veh-47_00016_01074 + - 2021.06.28.15.59.39_veh-47_01085_01534 + - 2021.06.28.15.59.39_veh-47_01555_03368 + - 2021.06.28.15.59.39_veh-47_03379_04184 + - 2021.06.28.15.59.39_veh-47_04195_04516 + - 2021.06.28.15.59.39_veh-47_04537_05600 + - 2021.06.28.16.29.11_veh-38_00022_00368 + - 2021.06.28.16.29.11_veh-38_00389_00726 + - 2021.06.28.16.29.11_veh-38_00750_01393 + - 2021.06.28.16.29.11_veh-38_01415_01821 + - 2021.06.28.16.29.11_veh-38_01894_02598 + - 2021.06.28.16.29.11_veh-38_02620_02861 + - 2021.06.28.16.29.11_veh-38_02872_02985 + - 2021.06.28.16.29.11_veh-38_03006_03242 + - 2021.06.28.16.29.11_veh-38_03263_03766 + - 2021.06.28.16.29.11_veh-38_03855_04287 + - 2021.06.28.16.29.11_veh-38_04308_04457 + - 2021.06.28.16.29.11_veh-38_04478_04596 + - 2021.06.28.16.29.11_veh-38_04607_06901 + - 2021.06.28.16.29.11_veh-38_06912_07220 + - 2021.06.28.16.35.45_veh-12_00029_00514 + - 2021.06.28.16.35.45_veh-12_00525_02226 + - 2021.06.28.16.35.45_veh-12_02247_03143 + - 2021.06.28.16.35.45_veh-12_03154_03715 + - 2021.06.28.16.35.45_veh-12_03736_03952 + - 2021.06.28.16.35.45_veh-12_03975_04056 + - 2021.06.28.16.35.45_veh-12_04067_04216 + - 2021.06.28.16.35.45_veh-12_04331_04784 + - 2021.06.28.16.35.45_veh-12_04795_04969 + - 2021.06.28.16.57.59_veh-26_00016_00484 + - 2021.06.28.16.57.59_veh-26_00505_00895 + - 2021.06.28.16.57.59_veh-26_00920_01691 + - 2021.06.28.16.57.59_veh-26_01702_02475 + - 2021.06.28.16.57.59_veh-26_02496_04017 + - 2021.06.28.16.57.59_veh-26_04038_04724 + - 2021.06.28.16.57.59_veh-26_04745_06261 + - 2021.06.28.17.13.34_veh-16_00015_01780 + - 2021.06.28.17.13.34_veh-16_01791_04035 + - 2021.06.28.17.13.34_veh-16_04046_04493 + - 2021.06.28.17.13.34_veh-16_04504_06163 + - 2021.06.28.17.56.29_veh-47_00016_01367 + - 2021.06.28.17.56.29_veh-47_01378_02853 + - 2021.06.28.17.56.29_veh-47_02864_03023 + - 2021.06.28.17.56.29_veh-47_03034_04012 + - 2021.06.28.17.56.29_veh-47_04034_05100 + - 2021.06.28.18.03.27_veh-14_00620_01581 + - 2021.06.28.18.03.27_veh-14_01603_02530 + - 2021.06.28.18.03.27_veh-14_02688_03115 + - 2021.06.28.18.03.27_veh-14_03140_03856 + - 2021.06.28.18.30.41_veh-12_00016_00535 + - 2021.06.28.18.30.41_veh-12_00572_01613 + - 2021.06.28.18.30.41_veh-12_01624_02831 + - 2021.06.28.18.30.41_veh-12_02870_04378 + - 2021.06.28.18.30.41_veh-12_04405_04500 + - 2021.06.28.18.30.41_veh-12_04521_05146 + - 2021.06.28.18.44.16_veh-35_00022_00346 + - 2021.06.28.18.44.16_veh-35_00367_00660 + - 2021.06.28.18.44.16_veh-35_00682_02674 + - 2021.06.28.18.44.16_veh-35_02695_04087 + - 2021.06.28.18.44.16_veh-35_04143_04347 + - 2021.06.28.18.44.16_veh-35_04358_04600 + - 2021.06.28.20.24.43_veh-38_00017_00139 + - 2021.06.28.20.24.43_veh-38_00164_00355 + - 2021.06.28.20.24.43_veh-38_00369_00601 + - 2021.06.28.20.24.43_veh-38_00616_00744 + - 2021.06.28.20.24.43_veh-38_00816_01345 + - 2021.06.28.20.24.43_veh-38_01368_01571 + - 2021.06.28.20.24.43_veh-38_01668_02298 + - 2021.06.28.20.24.43_veh-38_02323_03371 + - 2021.06.28.20.24.43_veh-38_03385_04952 + - 2021.06.28.20.24.43_veh-38_04976_05979 + - 2021.06.28.20.47.13_veh-26_00060_00131 + - 2021.06.28.20.47.13_veh-26_00142_00228 + - 2021.06.28.20.47.13_veh-26_00303_00389 + - 2021.06.28.20.47.13_veh-26_00400_00461 + - 2021.06.28.20.47.13_veh-26_00549_00633 + - 2021.06.28.20.47.13_veh-26_00644_00789 + - 2021.06.28.20.47.13_veh-26_00800_01033 + - 2021.06.28.20.47.13_veh-26_01367_01478 + - 2021.06.28.20.47.13_veh-26_01525_01596 + - 2021.06.28.20.47.13_veh-26_01607_01796 + - 2021.06.28.20.47.13_veh-26_02105_02213 + - 2021.06.28.20.47.13_veh-26_02224_02289 + - 2021.06.28.20.47.13_veh-26_02593_02660 + - 2021.06.28.20.47.13_veh-26_02671_02747 + - 2021.06.28.20.47.13_veh-26_02928_03035 + - 2021.06.28.20.47.13_veh-26_03084_03151 + - 2021.06.28.20.47.13_veh-26_03162_03331 + - 2021.06.28.20.47.13_veh-26_03416_03479 + - 2021.06.28.20.47.13_veh-26_03490_03560 + - 2021.06.28.20.47.13_veh-26_03606_03740 + - 2021.06.28.20.47.13_veh-26_03917_04028 + - 2021.06.28.20.47.13_veh-26_04076_04152 + - 2021.06.28.20.47.13_veh-26_04194_04304 + - 2021.06.28.20.47.13_veh-26_04397_04470 + - 2021.06.28.20.47.13_veh-26_04882_04948 + - 2021.06.28.20.47.13_veh-26_04998_05112 + - 2021.06.28.20.47.13_veh-26_05166_05272 + - 2021.06.28.20.47.13_veh-26_05319_05390 + - 2021.06.28.20.47.13_veh-26_05487_05618 + - 2021.06.28.20.47.13_veh-26_05629_05728 + - 2021.06.28.20.47.13_veh-26_05816_05924 + - 2021.06.28.21.16.05_veh-14_00016_00935 + - 2021.06.28.21.16.05_veh-14_00957_01198 + - 2021.06.28.21.16.05_veh-14_01209_01317 + - 2021.06.28.21.16.05_veh-14_01338_02740 + - 2021.06.28.21.16.05_veh-14_02762_03194 + - 2021.06.28.21.16.05_veh-14_03216_03725 + - 2021.06.28.21.16.05_veh-14_03736_04256 + - 2021.06.28.21.23.50_veh-47_00016_00313 + - 2021.06.28.21.23.50_veh-47_00334_01865 + - 2021.06.28.21.23.50_veh-47_01886_04690 + - 2021.06.28.21.23.50_veh-47_04712_05316 + - 2021.06.28.21.29.28_veh-16_00034_00843 + - 2021.06.28.21.29.28_veh-16_00854_01891 + - 2021.06.28.21.29.28_veh-16_01912_03183 + - 2021.06.28.21.29.39_veh-12_00016_00150 + - 2021.06.28.21.29.39_veh-12_00270_00482 + - 2021.06.28.21.29.39_veh-12_00585_00789 + - 2021.06.28.21.29.39_veh-12_00811_01199 + - 2021.06.28.21.29.39_veh-12_01221_01834 + - 2021.06.28.21.29.39_veh-12_01856_02160 + - 2021.06.28.21.29.39_veh-12_02171_02725 + - 2021.06.28.21.29.39_veh-12_02746_03179 + - 2021.06.28.21.29.39_veh-12_03200_03870 + - 2021.06.28.21.29.39_veh-12_03881_03942 + - 2021.06.28.21.29.39_veh-12_03964_04149 + - 2021.06.28.21.29.39_veh-12_04170_04759 + - 2021.06.28.21.47.53_veh-35_00016_00269 + - 2021.06.28.21.47.53_veh-35_00280_00424 + - 2021.06.28.21.47.53_veh-35_00495_00926 + - 2021.06.28.21.47.53_veh-35_00972_02652 + - 2021.06.28.21.47.53_veh-35_02673_03342 + - 2021.06.28.22.48.36_veh-14_00005_00762 + - 2021.06.28.22.48.36_veh-14_00785_01142 + - 2021.06.28.22.48.36_veh-14_01175_02603 + - 2021.06.28.22.48.36_veh-14_02625_03479 + - 2021.06.28.23.51.43_veh-14_00005_00127 + - 2021.06.28.23.51.43_veh-14_00169_01681 + - 2021.06.28.23.51.43_veh-14_01692_02372 + - 2021.08.16.14.23.37_veh-45_00015_00132 + - 2021.08.16.14.23.37_veh-45_00181_00679 + - 2021.08.16.14.23.37_veh-45_00713_00971 + - 2021.08.16.14.23.37_veh-45_00993_01483 + - 2021.08.16.14.23.37_veh-45_01623_01808 + - 2021.08.16.17.03.12_veh-08_00016_00093 + - 2021.08.16.17.03.12_veh-08_00172_00582 + - 2021.08.16.17.03.12_veh-08_00641_01035 + - 2021.08.16.17.03.12_veh-08_01060_01243 + - 2021.08.16.17.03.12_veh-08_01354_01490 + - 2021.08.16.17.03.12_veh-08_01571_01733 + - 2021.08.16.17.03.12_veh-08_01806_02134 + - 2021.08.16.17.03.12_veh-08_02167_02236 + - 2021.08.16.17.03.12_veh-08_02329_02601 + - 2021.08.30.11.18.32_veh-40_00019_00275 + - 2021.08.30.13.45.25_veh-40_00288_00363 + - 2021.08.30.13.45.25_veh-40_00375_00441 + - 2021.08.30.13.45.25_veh-40_00520_00595 + - 2021.08.30.13.45.25_veh-40_00610_00771 + - 2021.08.30.13.45.25_veh-40_00784_00867 + - 2021.08.30.13.45.25_veh-40_00878_01104 + - 2021.08.30.13.45.25_veh-40_01116_01336 + - 2021.08.30.13.45.25_veh-40_01483_01578 + - 2021.08.30.13.45.25_veh-40_01645_01800 + - 2021.08.30.14.54.34_veh-40_00334_00419 + - 2021.08.30.14.54.34_veh-40_00439_00835 + - 2021.08.30.14.54.34_veh-40_00885_00986 + - 2021.08.30.14.54.34_veh-40_01103_01179 + - 2021.08.30.14.54.34_veh-40_01201_01320 + - 2021.08.30.14.54.34_veh-40_01506_01586 + - 2021.08.30.16.16.44_veh-40_00005_00074 + - 2021.08.30.16.16.44_veh-40_00256_00716 + - 2021.08.30.16.16.44_veh-40_00779_01088 + - 2021.08.30.16.16.44_veh-40_01099_01351 + - 2021.08.30.16.16.44_veh-40_01537_01649 + - 2021.08.30.16.54.42_veh-40_00005_00208 + - 2021.08.30.16.54.42_veh-40_00301_00371 + - 2021.08.30.16.54.42_veh-40_00512_00655 + - 2021.08.30.16.54.42_veh-40_00763_00911 + - 2021.08.30.16.54.42_veh-40_00925_01221 + - 2021.08.30.16.54.42_veh-40_01270_01453 + - 2021.08.30.16.54.42_veh-40_01469_01572 + - 2021.08.30.16.54.42_veh-40_01846_01948 + - 2021.08.30.16.54.42_veh-40_01977_02075 + - 2021.08.30.17.34.35_veh-40_00005_00112 + - 2021.08.30.17.34.35_veh-40_00123_00224 + - 2021.08.30.17.34.35_veh-40_00408_00528 + - 2021.08.30.17.34.35_veh-40_00541_00606 + - 2021.08.30.17.34.35_veh-40_00636_01192 + - 2021.08.30.17.34.35_veh-40_01222_01337 + - 2021.08.30.17.34.35_veh-40_01447_01512 + - 2021.08.30.17.34.35_veh-40_01546_01786 + - 2021.08.30.17.34.35_veh-40_01870_01951 + - 2021.08.30.17.34.35_veh-40_02134_02374 + - 2021.08.30.18.36.39_veh-40_00005_00129 + - 2021.08.30.18.36.39_veh-40_00142_00239 + - 2021.08.30.18.49.17_veh-40_00112_00176 + - 2021.08.30.18.49.17_veh-40_00560_00688 + - 2021.08.30.18.49.17_veh-40_00699_01061 + - 2021.08.30.18.49.17_veh-40_01151_01466 + - 2021.08.30.18.49.17_veh-40_01508_01569 + - 2021.08.30.18.49.17_veh-40_01696_01805 + - 2021.08.30.18.49.17_veh-40_01955_02163 + - 2021.09.09.14.18.22_veh-48_00045_00191 + - 2021.09.09.14.18.22_veh-48_00221_00299 + - 2021.09.09.14.18.22_veh-48_00322_00895 + - 2021.09.09.14.18.22_veh-48_00960_01115 + - 2021.09.09.14.18.22_veh-48_01298_01492 + - 2021.09.09.14.18.22_veh-48_01503_01761 + - 2021.09.09.14.18.22_veh-48_01775_01866 + - 2021.09.09.14.18.22_veh-48_01878_02136 + - 2021.09.09.14.18.22_veh-48_02267_02394 + - 2021.09.09.14.44.40_veh-40_00015_00081 + - 2021.09.09.14.44.40_veh-40_00092_00291 + - 2021.09.09.14.44.40_veh-40_00475_00620 + - 2021.09.09.14.44.40_veh-40_00686_00749 + - 2021.09.09.14.44.40_veh-40_00786_00952 + - 2021.09.09.14.44.40_veh-40_00975_01042 + - 2021.09.09.14.44.40_veh-40_01147_01210 + - 2021.09.09.14.44.40_veh-40_01291_01373 + - 2021.09.09.14.44.40_veh-40_01463_01573 + - 2021.09.09.14.44.40_veh-40_01595_01714 + - 2021.09.09.17.18.51_veh-48_00098_00328 + - 2021.09.09.17.18.51_veh-48_00343_00560 + - 2021.09.09.17.18.51_veh-48_00574_00646 + - 2021.09.09.17.18.51_veh-48_00657_00876 + - 2021.09.09.17.18.51_veh-48_00889_01147 + - 2021.09.09.17.18.51_veh-48_01173_01237 + - 2021.09.09.17.18.51_veh-48_01248_01450 + - 2021.09.09.17.18.51_veh-48_01462_01552 + - 2021.09.09.17.18.51_veh-48_01899_02007 + - 2021.09.09.17.18.51_veh-48_02055_02269 + - 2021.09.09.18.04.06_veh-40_00031_00501 + - 2021.09.09.18.04.06_veh-40_00555_00731 + - 2021.09.09.18.04.06_veh-40_00743_01071 + - 2021.09.09.18.04.06_veh-40_01093_01252 + - 2021.09.09.18.04.06_veh-40_01340_01425 + - 2021.09.09.18.29.25_veh-39_00022_00198 + - 2021.09.09.18.29.25_veh-39_00427_00556 + - 2021.09.09.18.29.25_veh-39_00569_00903 + - 2021.09.09.18.29.25_veh-39_00969_01184 + - 2021.09.09.18.29.25_veh-39_01258_01337 + - 2021.09.09.18.29.25_veh-39_01367_01557 + - 2021.09.09.18.29.25_veh-39_01622_01766 + - 2021.09.09.18.38.12_veh-40_00015_00156 + - 2021.09.09.18.38.12_veh-40_00184_00247 + - 2021.09.09.18.38.12_veh-40_00362_00426 + - 2021.09.09.18.38.12_veh-40_00472_00555 + - 2021.09.09.18.38.12_veh-40_00627_00712 + - 2021.09.09.18.38.12_veh-40_00737_00799 + - 2021.09.09.18.38.12_veh-40_00820_01236 + - 2021.09.09.18.38.12_veh-40_01247_01425 + - 2021.09.09.18.38.12_veh-40_01437_01622 + - 2021.09.09.18.38.12_veh-40_01635_01734 + - 2021.09.09.18.38.12_veh-40_01748_01879 + - 2021.09.09.18.38.12_veh-40_01895_02696 + - 2021.09.09.19.10.24_veh-39_00015_00135 + - 2021.09.09.19.10.24_veh-39_00148_00372 + - 2021.09.09.19.10.24_veh-39_00489_00629 + - 2021.09.09.19.10.24_veh-39_00664_01059 + - 2021.09.09.19.10.24_veh-39_01125_01324 + - 2021.09.09.19.10.24_veh-39_01406_01487 + - 2021.09.09.19.10.24_veh-39_01746_01868 + - 2021.09.09.19.49.25_veh-39_00005_00110 + - 2021.09.09.19.49.25_veh-39_00321_00426 + - 2021.09.09.19.49.25_veh-39_00453_00713 + - 2021.09.09.19.49.25_veh-39_00733_00885 + - 2021.09.09.19.49.25_veh-39_00925_01218 + - 2021.09.09.19.49.25_veh-39_01275_01510 + - 2021.09.09.19.49.25_veh-39_01524_01665 + - 2021.09.16.13.05.51_veh-42_00016_00101 + - 2021.09.16.13.05.51_veh-42_00126_00264 + - 2021.09.16.13.05.51_veh-42_00302_00394 + - 2021.09.16.13.05.51_veh-42_00428_00700 + - 2021.09.16.13.05.51_veh-42_00755_00842 + - 2021.09.16.13.05.51_veh-42_00866_01027 + - 2021.09.16.13.05.51_veh-42_01038_01100 + - 2021.09.16.13.05.51_veh-42_01215_01280 + - 2021.09.16.13.05.51_veh-42_01410_01571 + - 2021.09.16.13.05.51_veh-42_01597_01965 + - 2021.09.16.13.05.51_veh-42_01976_02197 + - 2021.09.16.13.05.51_veh-42_02215_02389 + - 2021.09.16.13.05.51_veh-42_02501_02575 + - 2021.09.16.13.53.10_veh-42_00077_00153 + - 2021.09.16.13.53.10_veh-42_00180_00342 + - 2021.09.16.13.53.10_veh-42_00388_00597 + - 2021.09.16.13.53.10_veh-42_00630_00818 + - 2021.09.16.13.53.10_veh-42_00860_01069 + - 2021.09.16.13.53.10_veh-42_01177_01418 + - 2021.09.16.13.53.10_veh-42_01510_01591 + - 2021.09.16.14.14.03_veh-45_00005_00305 + - 2021.09.16.14.14.03_veh-45_00332_00418 + - 2021.09.16.14.14.03_veh-45_00441_00502 + - 2021.09.16.14.14.03_veh-45_00526_00861 + - 2021.09.16.14.14.03_veh-45_00884_01030 + - 2021.09.16.14.14.03_veh-45_01071_01180 + - 2021.09.16.14.14.03_veh-45_01289_01356 + - 2021.09.16.14.14.03_veh-45_01371_01792 + - 2021.09.16.14.14.03_veh-45_01818_02132 + - 2021.09.16.14.14.03_veh-45_02154_02434 + - 2021.09.16.14.14.03_veh-45_02452_02551 + - 2021.09.16.14.39.34_veh-42_00032_00186 + - 2021.09.16.14.39.34_veh-42_00297_00935 + - 2021.09.16.14.39.34_veh-42_00953_01043 + - 2021.09.16.14.39.34_veh-42_01111_01448 + - 2021.09.16.14.39.34_veh-42_01506_01567 + - 2021.09.16.14.39.34_veh-42_01609_01687 + - 2021.09.16.15.00.21_veh-45_00172_00236 + - 2021.09.16.15.00.21_veh-45_00359_00751 + - 2021.09.16.15.00.21_veh-45_00806_01354 + - 2021.09.16.15.00.21_veh-45_01380_01959 + - 2021.09.16.15.00.21_veh-45_01988_02182 + - 2021.09.16.15.12.03_veh-42_00016_00111 + - 2021.09.16.15.12.03_veh-42_00275_00620 + - 2021.09.16.15.12.03_veh-42_00639_00804 + - 2021.09.16.15.12.03_veh-42_00885_01014 + - 2021.09.16.15.12.03_veh-42_01037_01434 + - 2021.09.16.15.12.03_veh-42_01575_01701 + - 2021.09.16.15.47.30_veh-45_00016_00093 + - 2021.09.16.15.47.30_veh-45_00236_00304 + - 2021.09.16.15.47.30_veh-45_00370_00612 + - 2021.09.16.15.47.30_veh-45_00623_00891 + - 2021.09.16.15.47.30_veh-45_00925_01177 + - 2021.09.16.15.47.30_veh-45_01199_01391 + - 2021.09.16.15.47.30_veh-45_01574_01662 + - 2021.09.16.16.20.27_veh-08_00119_00399 + - 2021.09.16.16.20.27_veh-08_00410_00505 + - 2021.09.16.16.20.27_veh-08_00526_00962 + - 2021.09.16.16.20.27_veh-08_00987_01202 + - 2021.09.16.16.20.27_veh-08_01220_01539 + - 2021.09.16.16.20.27_veh-08_01562_02066 + - 2021.09.16.16.20.27_veh-08_02077_02214 + - 2021.09.16.16.20.27_veh-08_02300_02424 + - 2021.09.16.16.20.27_veh-08_02435_02525 + - 2021.09.16.16.20.27_veh-08_02675_03170 + - 2021.09.16.16.20.27_veh-08_03385_03468 + - 2021.09.16.17.40.09_veh-45_00039_00119 + - 2021.09.16.17.40.09_veh-45_00171_00269 + - 2021.09.16.17.40.09_veh-45_00374_00876 + - 2021.09.16.17.40.09_veh-45_00900_01153 + - 2021.09.16.17.40.09_veh-45_01171_01256 + - 2021.09.16.17.40.09_veh-45_01319_01456 + - 2021.09.16.17.40.09_veh-45_01480_01773 + - 2021.09.16.17.40.09_veh-45_01796_02236 + - 2021.09.16.17.40.09_veh-45_02259_02425 + - 2021.09.16.17.40.09_veh-45_02539_02745 + - 2021.09.16.17.40.35_veh-08_00032_01780 + - 2021.09.16.17.40.35_veh-08_01800_01865 + - 2021.09.16.17.40.35_veh-08_01925_02211 + - 2021.09.16.17.40.35_veh-08_02269_02956 + - 2021.09.16.17.40.35_veh-08_02978_03110 + - 2021.09.16.17.40.35_veh-08_03147_03461 + - 2021.09.16.18.31.12_veh-45_00101_00309 + - 2021.09.16.18.31.12_veh-45_00331_00414 + - 2021.09.16.18.31.12_veh-45_00480_00566 + - 2021.09.16.18.31.12_veh-45_00619_00693 + - 2021.09.16.18.31.12_veh-45_00721_00781 + - 2021.09.16.18.31.12_veh-45_00938_01128 + - 2021.09.16.18.31.12_veh-45_01186_01344 + - 2021.09.16.18.31.12_veh-45_01366_01449 + - 2021.09.16.18.31.12_veh-45_01460_01571 + - 2021.09.16.18.31.12_veh-45_01607_01779 + - 2021.09.16.18.31.12_veh-45_01812_01928 + - 2021.09.16.18.31.12_veh-45_01952_02416 + - 2021.09.16.18.31.12_veh-45_02447_02656 + - 2021.09.16.18.41.38_veh-08_00016_00493 + - 2021.09.16.18.41.38_veh-08_00515_01113 + - 2021.09.16.18.41.38_veh-08_01150_01418 + - 2021.09.16.18.41.38_veh-08_01472_01832 + - 2021.09.16.18.41.38_veh-08_01954_02201 + - 2021.09.16.18.41.38_veh-08_02231_02678 + - 2021.09.16.18.41.38_veh-08_02696_02786 + - 2021.09.16.19.12.04_veh-42_00289_00398 + - 2021.09.16.19.12.04_veh-42_00440_00717 + - 2021.09.16.19.12.04_veh-42_00742_00813 + - 2021.09.16.19.12.04_veh-42_00837_01066 + - 2021.09.16.19.12.04_veh-42_01088_01192 + - 2021.09.16.19.12.04_veh-42_01221_01380 + - 2021.09.16.19.12.04_veh-42_01438_01677 + - 2021.09.16.19.27.01_veh-45_00068_00151 + - 2021.09.16.19.27.01_veh-45_00274_00399 + - 2021.09.16.19.27.01_veh-45_00472_00711 + - 2021.09.16.19.27.01_veh-45_00734_00959 + - 2021.09.16.19.27.01_veh-45_00988_01156 + - 2021.09.16.19.27.01_veh-45_01320_01727 + - 2021.09.16.19.27.01_veh-45_01749_03230 + - 2021.09.16.19.47.47_veh-08_00104_00231 + - 2021.09.16.19.47.47_veh-08_00294_00764 + - 2021.09.16.19.47.47_veh-08_00847_01251 + - 2021.09.16.19.47.47_veh-08_01278_01633 + - 2021.09.16.19.47.47_veh-08_01739_01993 + - 2021.09.16.19.47.47_veh-08_02029_02343 + - 2021.09.16.19.47.47_veh-08_02366_03150 + - 2021.09.16.19.49.00_veh-42_00015_00113 + - 2021.09.16.19.49.00_veh-42_00369_00454 + - 2021.09.16.19.49.00_veh-42_00484_00684 + - 2021.09.16.19.49.00_veh-42_00707_00979 + - 2021.09.16.19.49.00_veh-42_00990_01609 + - 2021.09.16.19.49.00_veh-42_01631_01734 + - 2021.09.16.19.49.00_veh-42_02005_02080 + - 2021.09.16.20.23.58_veh-45_00054_00389 + - 2021.09.16.20.23.58_veh-45_00413_00497 + - 2021.09.16.20.23.58_veh-45_00508_00757 + - 2021.09.16.20.23.58_veh-45_00780_01037 + - 2021.09.16.20.23.58_veh-45_01161_01367 + - 2021.09.16.20.23.58_veh-45_01432_01493 + - 2021.09.16.20.23.58_veh-45_01549_01634 + - 2021.09.16.20.23.58_veh-45_01654_01839 + - 2021.09.16.20.23.58_veh-45_01866_02014 + - 2021.09.16.20.23.58_veh-45_02041_02547 + - 2021.09.16.20.23.58_veh-45_02583_02730 + - 2021.09.16.20.30.08_veh-42_00133_00245 + - 2021.09.16.20.30.08_veh-42_00431_00635 + - 2021.09.16.20.30.08_veh-42_00658_00910 + - 2021.09.16.20.30.08_veh-42_00995_01436 + - 2021.09.16.20.30.08_veh-42_01466_01700 + - 2021.09.16.20.30.08_veh-42_01747_02010 + - 2021.09.16.20.43.47_veh-08_00028_00487 + - 2021.09.16.20.43.47_veh-08_00510_00762 + - 2021.09.16.20.43.47_veh-08_00783_01358 + - 2021.09.16.20.43.47_veh-08_01377_01471 + - 2021.09.16.20.43.47_veh-08_01692_01814 + - 2021.09.16.21.13.20_veh-45_00016_00122 + - 2021.09.16.21.13.20_veh-45_00151_00412 + - 2021.09.16.21.13.20_veh-45_00454_00657 + - 2021.09.16.21.13.20_veh-45_00680_01017 + - 2021.09.16.21.13.20_veh-45_01044_01533 + - 2021.09.16.21.13.20_veh-45_01585_01703 + - 2021.09.16.21.13.37_veh-42_00006_00077 + - 2021.09.16.21.13.37_veh-42_00172_00347 + - 2021.09.16.21.13.37_veh-42_00358_00710 + - 2021.09.16.21.13.37_veh-42_00770_00881 + - 2021.09.22.01.45.32_veh-53_00016_00268 + - 2021.09.22.01.45.32_veh-53_00298_00432 + - 2021.09.22.01.45.32_veh-53_00470_00626 + - 2021.09.22.01.45.32_veh-53_00719_00976 + - 2021.09.22.01.45.32_veh-53_01009_01366 + - 2021.09.22.01.45.32_veh-53_01447_01564 + - 2021.09.22.01.45.32_veh-53_01576_01639 + - 2021.09.22.01.52.09_veh-51_00016_00247 + - 2021.09.22.01.52.09_veh-51_00288_00364 + - 2021.09.22.01.52.09_veh-51_00420_00523 + - 2021.09.22.01.52.09_veh-51_00535_01150 + - 2021.09.22.01.52.09_veh-51_01201_01449 + - 2021.09.22.01.52.09_veh-51_01532_01896 + - 2021.09.22.02.20.43_veh-53_00137_00395 + - 2021.09.22.02.20.43_veh-53_00466_00743 + - 2021.09.22.02.20.43_veh-53_00915_01150 + - 2021.09.22.02.20.43_veh-53_01162_01349 + - 2021.09.22.02.20.43_veh-53_01384_01607 + - 2021.09.22.02.20.43_veh-53_01644_01758 + - 2021.09.22.02.28.02_veh-51_00119_00426 + - 2021.09.22.02.28.02_veh-51_00576_00671 + - 2021.09.22.02.28.02_veh-51_00728_00798 + - 2021.09.22.02.28.02_veh-51_00902_01107 + - 2021.09.22.02.28.02_veh-51_01119_01280 + - 2021.09.22.02.28.02_veh-51_01355_01499 + - 2021.09.22.02.28.02_veh-51_01561_01904 + - 2021.09.22.02.55.42_veh-53_00052_00199 + - 2021.09.22.02.55.42_veh-53_00258_00329 + - 2021.09.22.02.55.42_veh-53_00340_00466 + - 2021.09.22.02.55.42_veh-53_00570_00662 + - 2021.09.22.02.55.42_veh-53_00820_01056 + - 2021.09.22.02.55.42_veh-53_01229_01296 + - 2021.09.22.02.55.42_veh-53_01340_01564 + - 2021.09.22.03.09.02_veh-51_00092_00370 + - 2021.09.22.03.09.02_veh-51_00387_00541 + - 2021.09.22.03.09.02_veh-51_00580_00664 + - 2021.09.22.03.09.02_veh-51_00732_01093 + - 2021.09.22.03.09.02_veh-51_01104_01194 + - 2021.09.22.03.09.02_veh-51_01216_01469 + - 2021.09.22.03.09.02_veh-51_01618_01752 + - 2021.09.22.03.09.02_veh-51_01764_02031 + - 2021.09.22.03.14.43_veh-49_00013_00448 + - 2021.09.22.03.14.43_veh-49_00493_00666 + - 2021.09.22.03.14.43_veh-49_00695_00977 + - 2021.09.22.03.14.43_veh-49_00988_01571 + - 2021.09.22.03.14.43_veh-49_01616_01839 + - 2021.09.22.03.46.15_veh-51_00016_00232 + - 2021.09.22.03.46.15_veh-51_00292_00373 + - 2021.09.22.03.46.15_veh-51_00405_00542 + - 2021.09.22.03.46.15_veh-51_00553_00813 + - 2021.09.22.03.46.15_veh-51_00871_01341 + - 2021.09.22.03.46.15_veh-51_01522_02013 + - 2021.09.22.03.50.00_veh-49_00016_00125 + - 2021.09.22.03.50.00_veh-49_00165_00413 + - 2021.09.22.03.50.00_veh-49_00426_00621 + - 2021.09.22.03.50.00_veh-49_00650_00838 + - 2021.09.22.03.50.00_veh-49_00893_01139 + - 2021.09.22.03.50.00_veh-49_01185_01328 + - 2021.09.22.03.50.00_veh-49_01356_01615 + - 2021.09.22.03.50.00_veh-49_01638_01948 + - 2021.09.22.05.32.47_veh-49_00019_00328 + - 2021.09.22.05.32.47_veh-49_00363_00524 + - 2021.09.22.05.32.47_veh-49_00570_00679 + - 2021.09.22.05.32.47_veh-49_00822_01257 + - 2021.09.22.05.32.47_veh-49_01278_01421 + - 2021.09.22.05.32.47_veh-49_01432_01561 + - 2021.09.22.05.32.47_veh-49_01586_01685 + - 2021.09.22.06.07.17_veh-49_00034_00144 + - 2021.09.22.06.07.17_veh-49_00166_00716 + - 2021.09.22.06.07.17_veh-49_00754_00859 + - 2021.09.22.06.07.17_veh-49_00870_00967 + - 2021.09.22.06.07.17_veh-49_00994_01162 + - 2021.09.22.06.07.17_veh-49_01290_01470 + - 2021.09.22.06.07.17_veh-49_01481_01774 + - 2021.09.22.06.36.13_veh-53_00017_00394 + - 2021.09.22.06.36.13_veh-53_00431_00511 + - 2021.09.22.06.36.13_veh-53_00541_00629 + - 2021.09.22.06.36.13_veh-53_00692_00775 + - 2021.09.22.06.36.13_veh-53_00787_01126 + - 2021.09.22.06.36.13_veh-53_01137_01583 + - 2021.09.22.06.36.13_veh-53_01616_01679 + - 2021.09.22.07.07.05_veh-49_00016_00132 + - 2021.09.22.07.07.05_veh-49_00157_00226 + - 2021.09.22.07.07.05_veh-49_00237_00372 + - 2021.09.22.07.07.05_veh-49_00434_00684 + - 2021.09.22.07.07.05_veh-49_00793_00943 + - 2021.09.22.07.07.05_veh-49_01048_01549 + - 2021.09.22.07.07.05_veh-49_01566_01634 + - 2021.09.22.07.07.05_veh-49_01656_01726 + - 2021.09.22.07.11.54_veh-53_00016_00084 + - 2021.09.22.07.11.54_veh-53_00133_00306 + - 2021.09.22.07.11.54_veh-53_00482_00620 + - 2021.09.22.07.11.54_veh-53_00663_00885 + - 2021.09.22.07.11.54_veh-53_00914_01150 + - 2021.09.22.07.11.54_veh-53_01209_01303 + - 2021.09.22.07.11.54_veh-53_01328_01454 + - 2021.09.22.07.11.54_veh-53_01511_01732 + - 2021.09.22.07.43.38_veh-49_00055_00130 + - 2021.09.22.07.43.38_veh-49_00166_00454 + - 2021.09.22.07.43.38_veh-49_00465_00586 + - 2021.09.22.07.43.38_veh-49_00623_00766 + - 2021.09.22.07.43.38_veh-49_00792_00865 + - 2021.09.22.07.43.38_veh-49_00908_00988 + - 2021.09.22.07.43.38_veh-49_01000_01170 + - 2021.09.22.07.43.38_veh-49_01198_01286 + - 2021.09.22.07.43.38_veh-49_01336_01478 + - 2021.09.22.07.43.38_veh-49_01489_01803 + - 2021.09.22.07.49.35_veh-53_00016_00431 + - 2021.09.22.07.49.35_veh-53_00514_00649 + - 2021.09.22.07.49.35_veh-53_00675_00761 + - 2021.09.22.07.49.35_veh-53_00846_01126 + - 2021.09.22.07.49.35_veh-53_01225_01348 + - 2021.09.22.07.49.35_veh-53_01439_01520 + - 2021.09.22.07.49.35_veh-53_01676_02076 + - 2021.09.22.08.18.52_veh-49_00060_00225 + - 2021.09.22.08.18.52_veh-49_00246_00440 + - 2021.09.22.08.18.52_veh-49_00482_00882 + - 2021.09.22.08.18.52_veh-49_00921_01027 + - 2021.09.22.08.18.52_veh-49_01219_01337 + - 2021.09.22.08.18.52_veh-49_01385_01450 + - 2021.09.22.08.18.52_veh-49_01545_01709 + - 2021.09.22.08.18.52_veh-49_01744_01809 + - 2021.09.29.13.54.31_veh-28_00016_00082 + - 2021.09.29.13.54.31_veh-28_00122_00250 + - 2021.09.29.13.54.31_veh-28_00264_00481 + - 2021.09.29.13.54.31_veh-28_00492_00847 + - 2021.09.29.13.54.31_veh-28_00973_01116 + - 2021.09.29.13.54.31_veh-28_01152_01396 + - 2021.09.29.13.54.31_veh-28_01491_01682 + - 2021.09.29.13.54.31_veh-28_01966_02106 + - 2021.09.29.13.54.31_veh-28_02216_02373 + - 2021.09.29.13.54.31_veh-28_02384_02655 + - 2021.09.29.14.44.26_veh-28_00073_00210 + - 2021.09.29.14.44.26_veh-28_00238_00320 + - 2021.09.29.14.44.26_veh-28_00337_00504 + - 2021.09.29.14.44.26_veh-28_00528_00992 + - 2021.09.29.14.44.26_veh-28_01059_01191 + - 2021.09.29.14.44.26_veh-28_01202_01296 + - 2021.09.29.14.44.26_veh-28_01331_01485 + - 2021.09.29.14.44.26_veh-28_01509_01628 + - 2021.09.29.14.44.26_veh-28_01640_01743 + - 2021.09.29.14.44.26_veh-28_01806_01912 + - 2021.09.29.15.23.04_veh-28_00057_00165 + - 2021.09.29.15.23.04_veh-28_00350_00520 + - 2021.09.29.15.23.04_veh-28_00601_00802 + - 2021.09.29.15.23.04_veh-28_00814_01101 + - 2021.09.29.15.23.04_veh-28_01349_01759 + - 2021.09.29.15.23.04_veh-28_01803_01898 + - 2021.09.29.15.23.04_veh-28_01976_02058 + - 2021.09.29.17.32.16_veh-28_00037_00145 + - 2021.09.29.17.32.16_veh-28_00278_00377 + - 2021.09.29.17.32.16_veh-28_00507_00581 + - 2021.09.29.17.32.16_veh-28_00599_00733 + - 2021.09.29.17.32.16_veh-28_00757_00872 + - 2021.09.29.17.32.16_veh-28_01026_01206 + - 2021.09.29.17.32.16_veh-28_01218_01699 + - 2021.09.29.17.32.16_veh-28_01725_01874 + - 2021.09.29.17.32.16_veh-28_02009_02207 + - 2021.09.29.18.19.40_veh-28_00005_00113 + - 2021.09.29.18.19.40_veh-28_00141_00213 + - 2021.09.29.18.19.40_veh-28_00331_00426 + - 2021.09.29.18.19.40_veh-28_00438_00833 + - 2021.09.29.18.19.40_veh-28_00844_01218 + - 2021.09.29.18.19.40_veh-28_01268_01685 + - 2021.09.29.18.19.40_veh-28_01727_01833 + - 2021.09.29.18.19.40_veh-28_01918_02050 + - 2021.09.29.19.02.14_veh-28_00015_00239 + - 2021.09.29.19.02.14_veh-28_00273_00514 + - 2021.09.29.19.02.14_veh-28_00540_00917 + - 2021.09.29.19.02.14_veh-28_00964_01689 + - 2021.09.29.19.02.14_veh-28_01717_01824 + - 2021.09.29.19.02.14_veh-28_01979_02060 + - 2021.09.29.19.02.14_veh-28_02084_02253 + - 2021.09.29.19.02.14_veh-28_02264_02371 + - 2021.09.29.19.02.14_veh-28_02451_02708 + - 2021.09.29.19.02.14_veh-28_02911_03005 + - 2021.09.29.19.02.14_veh-28_03198_03360 + - 2021.09.29.20.04.30_veh-28_00010_00142 + - 2021.09.29.20.04.30_veh-28_00342_00415 + - 2021.09.29.20.04.30_veh-28_00477_00684 + - 2021.09.29.20.04.30_veh-28_00696_00772 + - 2021.10.06.02.32.50_veh-53_00016_00205 + - 2021.10.06.02.32.50_veh-53_00295_00428 + - 2021.10.06.02.32.50_veh-53_00491_00618 + - 2021.10.06.02.32.50_veh-53_00633_00800 + - 2021.10.06.02.32.50_veh-53_00814_00963 + - 2021.10.06.02.32.50_veh-53_00984_01278 + - 2021.10.06.02.32.50_veh-53_01292_01787 + - 2021.10.06.03.07.17_veh-53_00022_00089 + - 2021.10.06.03.07.17_veh-53_00121_00293 + - 2021.10.06.03.07.17_veh-53_00363_00688 + - 2021.10.06.03.07.17_veh-53_00703_00974 + - 2021.10.06.03.07.17_veh-53_00985_01265 + - 2021.10.06.03.07.17_veh-53_01278_02139 + - 2021.10.06.03.07.17_veh-53_02162_02227 + - 2021.10.06.03.07.17_veh-53_02252_02337 + - 2021.10.06.03.07.17_veh-53_02349_02640 + - 2021.10.06.04.07.24_veh-49_00016_00124 + - 2021.10.06.04.07.24_veh-49_00145_00349 + - 2021.10.06.04.07.24_veh-49_00385_00479 + - 2021.10.06.04.07.24_veh-49_00560_00638 + - 2021.10.06.04.07.24_veh-49_00776_01719 + - 2021.10.06.04.07.24_veh-49_01831_02115 + - 2021.10.06.04.07.24_veh-49_02174_02296 + - 2021.10.06.04.07.24_veh-49_02315_02714 + - 2021.10.06.05.58.04_veh-49_00018_00134 + - 2021.10.06.05.58.04_veh-49_00185_00387 + - 2021.10.06.05.58.04_veh-49_00429_00574 + - 2021.10.06.05.58.04_veh-49_00612_01298 + - 2021.10.06.05.58.04_veh-49_01358_01437 + - 2021.10.06.05.58.04_veh-49_01458_01972 + - 2021.10.06.06.13.06_veh-51_00016_00234 + - 2021.10.06.06.13.06_veh-51_00279_00428 + - 2021.10.06.06.13.06_veh-51_00440_00559 + - 2021.10.06.06.13.06_veh-51_00570_00718 + - 2021.10.06.06.13.06_veh-51_00763_00916 + - 2021.10.06.06.13.06_veh-51_00927_01219 + - 2021.10.06.06.13.06_veh-51_01242_01348 + - 2021.10.06.06.13.06_veh-51_01367_01444 + - 2021.10.06.06.13.06_veh-51_01477_01561 + - 2021.10.06.06.13.06_veh-51_01646_01881 + - 2021.10.06.06.34.19_veh-49_00108_00241 + - 2021.10.06.06.34.19_veh-49_00271_00639 + - 2021.10.06.06.34.19_veh-49_00651_01190 + - 2021.10.06.06.34.19_veh-49_01211_01561 + - 2021.10.06.06.34.19_veh-49_01574_01751 + - 2021.10.06.06.34.19_veh-49_01799_01937 + - 2021.10.06.06.37.20_veh-53_00051_00160 + - 2021.10.06.06.37.20_veh-53_00207_00285 + - 2021.10.06.06.37.20_veh-53_00296_00468 + - 2021.10.06.06.37.20_veh-53_00535_00596 + - 2021.10.06.06.37.20_veh-53_00748_00827 + - 2021.10.06.06.37.20_veh-53_00920_01201 + - 2021.10.06.06.37.20_veh-53_01259_01406 + - 2021.10.06.06.37.20_veh-53_01420_01653 + - 2021.10.06.06.37.20_veh-53_01688_01764 + - 2021.10.06.06.50.39_veh-51_00090_00209 + - 2021.10.06.06.50.39_veh-51_00265_00509 + - 2021.10.06.06.50.39_veh-51_00628_00721 + - 2021.10.06.06.50.39_veh-51_00732_00797 + - 2021.10.06.06.50.39_veh-51_00848_00915 + - 2021.10.06.06.50.39_veh-51_00939_01158 + - 2021.10.06.06.50.39_veh-51_01181_01357 + - 2021.10.06.06.50.39_veh-51_01411_01525 + - 2021.10.06.06.50.39_veh-51_01589_01894 + - 2021.10.06.07.15.13_veh-49_00016_00116 + - 2021.10.06.07.15.13_veh-49_00144_00229 + - 2021.10.06.07.15.13_veh-49_00240_00360 + - 2021.10.06.07.15.13_veh-49_00400_00884 + - 2021.10.06.07.15.13_veh-49_00952_01059 + - 2021.10.06.07.15.13_veh-49_01094_01376 + - 2021.10.06.07.15.13_veh-49_01444_01678 + - 2021.10.06.07.15.13_veh-49_01719_01855 + - 2021.10.06.07.26.10_veh-52_00006_00398 + - 2021.10.06.07.26.10_veh-52_00422_00728 + - 2021.10.06.07.26.10_veh-52_00772_00917 + - 2021.10.06.07.26.10_veh-52_00953_01126 + - 2021.10.06.07.26.10_veh-52_01154_01234 + - 2021.10.06.07.26.10_veh-52_01245_02064 + - 2021.10.06.07.26.10_veh-52_02089_02186 + - 2021.10.06.07.26.10_veh-52_02208_02394 + - 2021.10.06.07.36.28_veh-51_00016_00090 + - 2021.10.06.07.36.28_veh-51_00115_00175 + - 2021.10.06.07.36.28_veh-51_00225_00308 + - 2021.10.06.07.36.28_veh-51_00319_00383 + - 2021.10.06.07.36.28_veh-51_00441_00537 + - 2021.10.06.07.36.28_veh-51_00660_00951 + - 2021.10.06.07.36.28_veh-51_00996_01064 + - 2021.10.06.07.36.28_veh-51_01113_01241 + - 2021.10.06.07.36.28_veh-51_01321_01406 + - 2021.10.06.07.36.28_veh-51_01446_01556 + - 2021.10.06.07.36.28_veh-51_01688_01826 + - 2021.10.06.07.36.28_veh-51_01841_01936 + - 2021.10.06.07.54.27_veh-49_00074_00207 + - 2021.10.06.07.54.27_veh-49_00391_00875 + - 2021.10.06.07.54.27_veh-49_00909_01008 + - 2021.10.06.07.54.27_veh-49_01157_01353 + - 2021.10.06.07.54.27_veh-49_01421_01503 + - 2021.10.06.07.59.57_veh-53_00016_00455 + - 2021.10.06.07.59.57_veh-53_00479_00744 + - 2021.10.06.07.59.57_veh-53_00788_00884 + - 2021.10.06.07.59.57_veh-53_00895_01083 + - 2021.10.06.07.59.57_veh-53_01146_01333 + - 2021.10.06.07.59.57_veh-53_01346_01456 + - 2021.10.06.07.59.57_veh-53_01550_01764 + - 2021.10.06.08.13.16_veh-51_00086_00147 + - 2021.10.06.08.13.16_veh-51_00171_00359 + - 2021.10.06.08.13.16_veh-51_00386_00649 + - 2021.10.06.08.13.16_veh-51_00692_01123 + - 2021.10.06.08.13.16_veh-51_01134_01603 + - 2021.10.06.08.13.16_veh-51_01679_01809 + - 2021.10.06.08.13.16_veh-51_01820_02209 + - 2021.10.06.08.13.16_veh-51_02243_02446 + - 2021.10.06.08.13.16_veh-51_02507_02745 + - 2021.10.06.08.16.17_veh-52_00032_00170 + - 2021.10.06.08.16.17_veh-52_00181_00574 + - 2021.10.06.08.16.17_veh-52_00612_00782 + - 2021.10.06.08.16.17_veh-52_00794_00895 + - 2021.10.06.08.16.17_veh-52_00922_01296 + - 2021.10.06.08.16.17_veh-52_01323_01390 + - 2021.10.06.08.16.17_veh-52_01430_01579 + - 2021.10.06.08.16.17_veh-52_01590_01725 + - 2021.10.06.08.16.17_veh-52_01758_01849 + - 2021.10.06.08.16.17_veh-52_01860_01938 + - 2021.10.06.08.16.17_veh-52_01949_02501 + - 2021.10.06.08.30.27_veh-49_00017_00080 + - 2021.10.06.08.30.27_veh-49_00095_00439 + - 2021.10.06.08.30.27_veh-49_00478_01184 + - 2021.10.06.08.30.27_veh-49_01258_01499 + - 2021.10.06.08.30.27_veh-49_01511_01781 + - 2021.10.06.08.30.27_veh-49_01793_02049 + - 2021.10.06.08.34.20_veh-53_00020_00165 + - 2021.10.06.08.34.20_veh-53_00179_00244 + - 2021.10.06.08.34.20_veh-53_00259_00711 + - 2021.10.06.08.34.20_veh-53_00723_00973 + - 2021.10.06.08.34.20_veh-53_01000_01070 + - 2021.10.06.08.34.20_veh-53_01089_01868 diff --git a/sledge/script/config/common/worker/ray_distributed.yaml b/sledge/script/config/common/worker/ray_distributed.yaml new file mode 100644 index 0000000..c51de1d --- /dev/null +++ b/sledge/script/config/common/worker/ray_distributed.yaml @@ -0,0 +1,8 @@ +_target_: nuplan.planning.utils.multithreading.worker_ray.RayDistributed +_convert_: 'all' +master_node_ip: null # Set to a master node IP if you desire to connect to cluster remotely +threads_per_node: null # Number of CPU threads to use per node, "null" means all threads available +debug_mode: false # If true all tasks will be executed serially, mainly for testing +log_to_driver: true # If true, all printouts from ray threads will be displayed in driver +logs_subdir: 'logs' # Subdirectory to store logs inside the experiment directory +use_distributed: false # Whether to use the built-in distributed mode of ray diff --git a/sledge/script/config/common/worker/sequential.yaml b/sledge/script/config/common/worker/sequential.yaml new file mode 100644 index 0000000..0b7955e --- /dev/null +++ b/sledge/script/config/common/worker/sequential.yaml @@ -0,0 +1,2 @@ +_target_: nuplan.planning.utils.multithreading.worker_sequential.Sequential +_convert_: 'all' diff --git a/sledge/script/config/common/worker/single_machine_thread_pool.yaml b/sledge/script/config/common/worker/single_machine_thread_pool.yaml new file mode 100644 index 0000000..87fe473 --- /dev/null +++ b/sledge/script/config/common/worker/single_machine_thread_pool.yaml @@ -0,0 +1,4 @@ +_target_: nuplan.planning.utils.multithreading.worker_parallel.SingleMachineParallelExecutor +_convert_: 'all' +use_process_pool: False # If true, use ProcessPoolExecutor as the backend, otherwise uses ThreadPoolExecutor +max_workers: null # Number of CPU workers (threads/processes) to use per node, "null" means all available diff --git a/sledge/script/config/diffusion/__init__.py b/sledge/script/config/diffusion/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/config/diffusion/accelerator/default_accelerator.yaml b/sledge/script/config/diffusion/accelerator/default_accelerator.yaml new file mode 100644 index 0000000..335f2a5 --- /dev/null +++ b/sledge/script/config/diffusion/accelerator/default_accelerator.yaml @@ -0,0 +1,14 @@ + +# accelerator: +_target_: accelerate.Accelerator +_convert_: all + +gradient_accumulation_steps: 1 +mixed_precision: "no" +log_with: "tensorboard" + +project_config: + _target_: accelerate.utils.ProjectConfiguration + _convert_: all + project_dir: ${output_dir} + diff --git a/sledge/script/config/diffusion/default_diffusion.yaml b/sledge/script/config/diffusion/default_diffusion.yaml new file mode 100644 index 0000000..f0aea87 --- /dev/null +++ b/sledge/script/config/diffusion/default_diffusion.yaml @@ -0,0 +1,67 @@ +hydra: + run: + dir: ${output_dir} + output_subdir: ${output_dir}/code/hydra # Store hydra's config breakdown here for debugging + searchpath: # Only in these paths are discoverable + - pkg://sledge.script.config.common + - pkg://sledge.script.experiments # Put experiments configs in script/experiments/ + +defaults: + - default_experiment + - default_common + + # hugging face accelerator config + - accelerator: default_accelerator + + # optimizer settings + - noise_scheduler: ddpm_scheduler + - optimizer: adamw + +experiment_name: ${py_func}_diffusion + +num_epochs: 100 + +debug_mode: true # runs one train batch and inference if true + +inference_epochs: 1 +inference_batch_size: 16 +num_classes: 4 + +guidance_scale: 4.0 +num_inference_timesteps: 100 + +autoencoder_checkpoint: ??? +diffusion_checkpoint: null + +data_loader: + params: + batch_size: 64 + shuffle: true + num_workers: 8 + +lr_scheduler: + name: "constant" + step_rules: null + num_warmup_steps: 1 + num_cycles: 1 + power: 1.0 + last_epoch: -1 + +ema: + use_ema: true + max_decay: 0.9999 + inv_gamma: 1.0 + power: 0.75 + + +# Cache parameters +cache: + autoencoder_cache_path: ${oc.env:SLEDGE_EXP_ROOT}/caches/autoencoder_cache + diffusion_cache_path: ${oc.env:SLEDGE_EXP_ROOT}/caches/diffusion_cache + cleanup_diffusion_cache: false + + scenario_cache_path: ${oc.env:SLEDGE_EXP_ROOT}/caches/scenario_cache + scenario_cache_size: 32 + +# # Mandatory parameters +py_func: ??? diff --git a/sledge/script/config/diffusion/noise_scheduler/ddpm_scheduler.yaml b/sledge/script/config/diffusion/noise_scheduler/ddpm_scheduler.yaml new file mode 100644 index 0000000..9d031f4 --- /dev/null +++ b/sledge/script/config/diffusion/noise_scheduler/ddpm_scheduler.yaml @@ -0,0 +1,9 @@ +_target_: diffusers.schedulers.DDPMScheduler +_convert_: 'all' + +# https://huggingface.co/docs/diffusers/api/schedulers/ddpm +num_train_timesteps: 1000 # number of diffusion steps for training +beta_start: 0.0015 # starting `beta` value of inference +beta_end: 0.015 # final `beta` value +beta_schedule: "linear" # linear, scaled_linear, or squaredcos_cap_v2 +clip_sample: False # Clip the predicted sample for numerical stability. (unsuitable for latent-space diffusion models) \ No newline at end of file diff --git a/sledge/script/config/diffusion/optimizer/adamw.yaml b/sledge/script/config/diffusion/optimizer/adamw.yaml new file mode 100644 index 0000000..8760f7f --- /dev/null +++ b/sledge/script/config/diffusion/optimizer/adamw.yaml @@ -0,0 +1,7 @@ +_target_: torch.optim.AdamW +_convert_: 'all' + +lr: 1e-4 # learning rate +betas: [0.95, 0.999] # coefficients used for computing running averages of gradient and its square +eps: 1e-8 # term added to the denominator to improve numerical stability +weight_decay: 1e-6 # weight decay coefficient diff --git a/sledge/script/config/simulation/__init__.py b/sledge/script/config/simulation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/config/simulation/callback/serialization_callback.yaml b/sledge/script/config/simulation/callback/serialization_callback.yaml new file mode 100644 index 0000000..27bbc3c --- /dev/null +++ b/sledge/script/config/simulation/callback/serialization_callback.yaml @@ -0,0 +1,8 @@ +serialization_callback: + _target_: nuplan.planning.simulation.callback.serialization_callback.SerializationCallback + _convert_: 'all' + + folder_name: simulation # Name of a folder in which the serialization will take place + serialize_into_single_file: false # If true, the output will be a single file, if False, the data will be serialized + # into one file per time step + serialization_type: "json" # A way to serialize output, options: ["json", "pickle", "msgpack"] diff --git a/sledge/script/config/simulation/callback/simulation_log_callback.yaml b/sledge/script/config/simulation/callback/simulation_log_callback.yaml new file mode 100644 index 0000000..019a9df --- /dev/null +++ b/sledge/script/config/simulation/callback/simulation_log_callback.yaml @@ -0,0 +1,7 @@ +simulation_log_callback: + _target_: nuplan.planning.simulation.callback.simulation_log_callback.SimulationLogCallback + _convert_: 'all' + + output_directory: ${output_dir} + simulation_log_dir: simulation_log # Simulation log dir + serialization_type: "msgpack" # A way to serialize output, options: ["pickle", "msgpack"] diff --git a/sledge/script/config/simulation/callback/timing_callback.yaml b/sledge/script/config/simulation/callback/timing_callback.yaml new file mode 100644 index 0000000..0a7e550 --- /dev/null +++ b/sledge/script/config/simulation/callback/timing_callback.yaml @@ -0,0 +1,3 @@ +timing_callback: + _target_: nuplan.planning.simulation.callback.timing_callback.TimingCallback + _convert_: 'all' diff --git a/sledge/script/config/simulation/default_simulation.yaml b/sledge/script/config/simulation/default_simulation.yaml new file mode 100644 index 0000000..52575bb --- /dev/null +++ b/sledge/script/config/simulation/default_simulation.yaml @@ -0,0 +1,89 @@ +hydra: + run: + dir: ${output_dir} + output_subdir: ${output_dir}/code/hydra # Store hydra's config breakdown here for debugging + searchpath: + - pkg://sledge.script.config.common + - pkg://sledge.script.experiments + # - pkg://sledge.script.config.simulation + # - pkg://nuplan.planning.script.config.common + # - pkg://nuplan.planning.script.config.simulation + # - pkg://nuplan.planning.script.experiments + +defaults: + # Add ungrouped items + - default_experiment + - default_common + + - simulation_metric: + - default_metrics + - callback: + - simulation_log_callback + - main_callback: + - time_callback + - metric_file_callback + - metric_aggregator_callback + - metric_summary_callback + - splitter: nuplan + + # Hyperparameters need to be specified + - observation: null + - ego_controller: null + - planner: null + - simulation_time_controller: step_simulation_time_controller + - metric_aggregator: + - default_weighted_average + + - override hydra/job_logging: none # Disable hydra's logging + - override hydra/hydra_logging: none # Disable hydra's logging + +experiment_name: 'simulation' +aggregated_metric_folder_name: 'aggregator_metric' # Aggregated metric folder name +aggregator_save_path: ${output_dir}/${aggregated_metric_folder_name} + + +# Progress Visualization +enable_simulation_progress_bar: False # Show for every simulation its progress + +# Simulation Setup +simulation_history_buffer_duration: 2.0 # [s] The look back duration to initialize the simulation history buffer with + +# Number (or fractional, e.g., 0.25) of GPUs available for single simulation (per scenario and planner). +# This number can also be < 1 because we allow multiple models to be loaded into a single GPU. +# In case this number is 0 or null, no GPU is used for simulation and all cpu cores are leveraged +# Note, that the user have to make sure that if a number < 1 is chosen, the model will fit 1 / num_gpus into GPU memory +number_of_gpus_allocated_per_simulation: 0 + +# This number specifies number of CPU threads that are used for simulation +# In case this is null, then each simulation will use unlimited resources. +# That will typically swamp the host computer, leading to slowdowns and failure. +number_of_cpus_allocated_per_simulation: 1 + +# Set false to disable metric computation +run_metric: true + +# Set to rerun metrics with existing simulation logs without setting run_metric to false. +simulation_log_main_path: null + +# If false, continue running the simulation even it a scenario has failed +exit_on_failure: false + +# Maximum number of workers to be used for running simulation callbacks outside the main process +max_callback_workers: 4 + +# Disable callback parallelization when using the Sequential worker. By default, when running with the sequential worker, +# on_simulation_end callbacks are not submitted to a parallel worker. +disable_callback_parallelization: true + +# Distributed processing mode. If multi-node simulation is enable, this parameter selects how the scenarios distributed +# to each node. The modes are: +# - SCENARIO_BASED: Works in two stages, first getting a list of all, scenarios to process, then breaking up that +# list and distributing across the workers +# - LOG_FILE_BASED: Works in a single stage, breaking up the scenarios based on what log file they are in and +# distributing the number of log files evenly across all workers +# - SINGLE_NODE: Does no distribution, processes all scenarios in config +distributed_mode: 'SINGLE_NODE' + +# Cache parameters +cache: + scenario_cache_path: ${oc.env:SLEDGE_EXP_ROOT}/caches/scenario_cache diff --git a/sledge/script/config/simulation/ego_controller/log_play_back_controller.yaml b/sledge/script/config/simulation/ego_controller/log_play_back_controller.yaml new file mode 100644 index 0000000..7060122 --- /dev/null +++ b/sledge/script/config/simulation/ego_controller/log_play_back_controller.yaml @@ -0,0 +1,2 @@ +_target_: nuplan.planning.simulation.controller.log_playback.LogPlaybackController +_convert_: 'all' diff --git a/sledge/script/config/simulation/ego_controller/motion_model/kinematic_bicycle_model.yaml b/sledge/script/config/simulation/ego_controller/motion_model/kinematic_bicycle_model.yaml new file mode 100644 index 0000000..ed3ca5b --- /dev/null +++ b/sledge/script/config/simulation/ego_controller/motion_model/kinematic_bicycle_model.yaml @@ -0,0 +1,4 @@ +_target_: nuplan.planning.simulation.controller.motion_model.kinematic_bicycle.KinematicBicycleModel +_convert_: 'all' + +vehicle: ${scenario_builder.vehicle_parameters} diff --git a/sledge/script/config/simulation/ego_controller/perfect_tracking_controller.yaml b/sledge/script/config/simulation/ego_controller/perfect_tracking_controller.yaml new file mode 100644 index 0000000..f41d072 --- /dev/null +++ b/sledge/script/config/simulation/ego_controller/perfect_tracking_controller.yaml @@ -0,0 +1,2 @@ +_target_: nuplan.planning.simulation.controller.perfect_tracking.PerfectTrackingController +_convert_: 'all' diff --git a/sledge/script/config/simulation/ego_controller/tracker/ilqr_tracker.yaml b/sledge/script/config/simulation/ego_controller/tracker/ilqr_tracker.yaml new file mode 100644 index 0000000..8a63729 --- /dev/null +++ b/sledge/script/config/simulation/ego_controller/tracker/ilqr_tracker.yaml @@ -0,0 +1,44 @@ +_target_: nuplan.planning.simulation.controller.tracker.ilqr_tracker.ILQRTracker +_convert_: all + +n_horizon: 40 # Maximum time horizon (number of discrete time steps) that we should plan ahead. + +ilqr_solver: + _target_: nuplan.planning.simulation.controller.tracker.ilqr.ilqr_solver.ILQRSolver + _convert_: all + + solver_params: + _target_: nuplan.planning.simulation.controller.tracker.ilqr.ilqr_solver.ILQRSolverParameters + _convert_: all + + discretization_time: 0.2 # [s] Time discretization used for integration. + + # Cost weights for state variables [x, y, heading, velocity, steering angle] + state_cost_diagonal_entries: [1.0, 1.0, 10.0, 0.0, 0.0] + + # Cost weights for input variables [acceleration, steering rate] + input_cost_diagonal_entries: [1.0, 10.0] + + # Trust region cost weights for states and inputs. + state_trust_region_entries: [1.0, 1.0, 1.0, 1.0, 1.0] + input_trust_region_entries: [1.0, 1.0] + + max_ilqr_iterations: 20 # Maximum number of iterations to run iLQR before timeout. + convergence_threshold: 1e-6 # Threshold for delta inputs below which we can terminate iLQR early. + max_solve_time: 0.05 # [s] If defined, sets a maximum time to run a solve call of iLQR before terminating. + + max_acceleration: 3.0 # [m/s^2] Absolute value threshold on acceleration input. + max_steering_angle: 1.047197 # [rad] Absolute value threshold on steering angle. + max_steering_angle_rate: 0.5 # [rad/s] Absolute value threshold on steering rate input. + min_velocity_linearization: 0.01 # [m/s] Absolute value threshold below which linearization velocity is modified. + + warm_start_params: + _target_: nuplan.planning.simulation.controller.tracker.ilqr.ilqr_solver.ILQRWarmStartParameters + _convert_: all + + k_velocity_error_feedback: 0.5 # Gain for initial velocity error for warm start acceleration. + k_steering_angle_error_feedback: 0.05 # Gain for initial steering angle error for warm start steering rate. + lookahead_distance_lateral_error: 15.0 # [m] Distance ahead for which we estimate lateral error. + k_lateral_error: 0.1 # Gain for lateral error to compute steering angle feedback. + jerk_penalty_warm_start_fit: 1e-4 # Penalty for jerk in velocity profile estimation. + curvature_rate_penalty_warm_start_fit: 1e-2 # Penalty for curvature rate in curvature profile estimation. diff --git a/sledge/script/config/simulation/ego_controller/tracker/lqr_tracker.yaml b/sledge/script/config/simulation/ego_controller/tracker/lqr_tracker.yaml new file mode 100644 index 0000000..f988420 --- /dev/null +++ b/sledge/script/config/simulation/ego_controller/tracker/lqr_tracker.yaml @@ -0,0 +1,18 @@ +_target_: nuplan.planning.simulation.controller.tracker.lqr.LQRTracker +_convert_: 'all' + +# LQR tuning +q_longitudinal: [10.0] # velocity tracking cost gain +r_longitudinal: [1.0] # acceleration tracking cost gain +q_lateral: [1.0, 10.0, 0.0] # [lateral_error, heading_error, steering_angle] tracking cost gains +r_lateral: [1.0] # steering_rate tracking cost gain +discretization_time: 0.1 # [s] The time interval used for discretizing the continuous time dynamics. +tracking_horizon: 10 # The number of time steps (at discretization_time interval) ahead we consider for LQR. + +# Parameters for velocity and curvature estimation. +jerk_penalty: 1e-4 # Penalty for jerk in velocity profile estimation. +curvature_rate_penalty: 1e-2 # Penalty for curvature rate in curvature profile estimation. + +# Stopping logic +stopping_proportional_gain: 0.5 # Proportional controller tuning for stopping controller +stopping_velocity: 0.2 # [m/s] Velocity threshold for stopping diff --git a/sledge/script/config/simulation/ego_controller/two_stage_controller.yaml b/sledge/script/config/simulation/ego_controller/two_stage_controller.yaml new file mode 100644 index 0000000..b20be5a --- /dev/null +++ b/sledge/script/config/simulation/ego_controller/two_stage_controller.yaml @@ -0,0 +1,6 @@ +_target_: nuplan.planning.simulation.controller.two_stage_controller.TwoStageController +_convert_: 'all' + +defaults: + - tracker: lqr_tracker + - motion_model: kinematic_bicycle_model diff --git a/sledge/script/config/simulation/main_callback/completion_callback.yaml b/sledge/script/config/simulation/main_callback/completion_callback.yaml new file mode 100644 index 0000000..2ab81d8 --- /dev/null +++ b/sledge/script/config/simulation/main_callback/completion_callback.yaml @@ -0,0 +1,6 @@ +completion_callback: + _target_: nuplan.planning.simulation.main_callback.completion_callback.CompletionCallback + _convert_: 'all' + + output_dir: ${output_dir} + challenge_name: ${job_name} diff --git a/sledge/script/config/simulation/main_callback/metric_aggregator_callback.yaml b/sledge/script/config/simulation/main_callback/metric_aggregator_callback.yaml new file mode 100644 index 0000000..f1c8b34 --- /dev/null +++ b/sledge/script/config/simulation/main_callback/metric_aggregator_callback.yaml @@ -0,0 +1,5 @@ +metric_aggregator_callback: + _target_: nuplan.planning.simulation.main_callback.metric_aggregator_callback.MetricAggregatorCallback + _convert_: 'all' + + metric_save_path: ${output_dir}/${metric_dir} # Path of a folder in which the metrics are saved diff --git a/sledge/script/config/simulation/main_callback/metric_file_callback.yaml b/sledge/script/config/simulation/main_callback/metric_file_callback.yaml new file mode 100644 index 0000000..6296a64 --- /dev/null +++ b/sledge/script/config/simulation/main_callback/metric_file_callback.yaml @@ -0,0 +1,8 @@ +metric_file_callback: + _target_: nuplan.planning.simulation.main_callback.metric_file_callback.MetricFileCallback + _convert_: 'all' + + metric_file_output_path: ${output_dir}/${metric_dir} # Path to save metric files + scenario_metric_paths: # A list of paths in which scenario metrics are saved + - ${output_dir}/${metric_dir} + delete_scenario_metric_files: True # Delete previous scenario metric files diff --git a/sledge/script/config/simulation/main_callback/metric_summary_callback.yaml b/sledge/script/config/simulation/main_callback/metric_summary_callback.yaml new file mode 100644 index 0000000..81942b9 --- /dev/null +++ b/sledge/script/config/simulation/main_callback/metric_summary_callback.yaml @@ -0,0 +1,9 @@ +metric_summary_callback: + _target_: nuplan.planning.simulation.main_callback.metric_summary_callback.MetricSummaryCallback + _convert_: 'all' + + metric_save_path: ${output_dir}/${metric_dir} # Path to saved metric files + metric_aggregator_save_path: ${aggregator_save_path} # Path to saved aggregated files + summary_output_path: ${output_dir}/summary + num_bins: 20 + pdf_file_name: 'summary.pdf' diff --git a/sledge/script/config/simulation/main_callback/publisher_callback.yaml b/sledge/script/config/simulation/main_callback/publisher_callback.yaml new file mode 100644 index 0000000..a2ce51c --- /dev/null +++ b/sledge/script/config/simulation/main_callback/publisher_callback.yaml @@ -0,0 +1,42 @@ +publisher_callback: + _target_: nuplan.planning.simulation.main_callback.publisher_callback.PublisherCallback + _convert_: 'all' + + s3_client: null + s3_bucket: null + remote_prefix: null + + # Path to the directories + uploads: + metrics: + upload: True + save_path: ${output_dir}/${metric_dir} # Path of a folder in which the metrics are saved + remote_path: ${job_name}/${metric_dir} # Path of a folder in which the metrics are saved + aggregator_metric: + upload: True + save_path: ${output_dir}/${aggregator_metric_dir} # Path of a folder in which the aggregated metrics are saved + remote_path: ${aggregator_metric_dir} # Path of a folder in which the aggregated metrics are saved + simulation: + upload: False + save_path: ${output_dir}/simulation_log # Path of a folder in which the simulation output is saved + remote_path: ${job_name}/simulation_log # Path of a folder in which the simulation output is saved + runner_report: + upload: True + save_path: ${output_dir}/runner_report.parquet # Path of a folder in which the simulation output is saved + remote_path: ${job_name} # Path of a folder in which the simulation output is saved + passed: + upload: True + save_path: ${output_dir}/validation-results/passed.txt # Path of a folder in which the validation result is saved + remote_path: validation-results # Path of a folder in which the simulation output is saved + failed: + upload: True + save_path: ${output_dir}/validation-results/failed.txt # Path of a folder in which the validation result is saved + remote_path: validation-results # Path of a folder in which the simulation output is saved + completed: + upload: True + save_path: ${output_dir}/simulation-results # Path of a folder in which the validation result is saved + remote_path: simulation-results # Path of a folder in which the simulation output is saved + submission_logs: + upload: True + save_path: /tmp/submission.log # Path of a folder in which the validation result is saved + remote_path: ${job_name}/submission.log # Path of a folder in which the simulation output is saved diff --git a/sledge/script/config/simulation/main_callback/time_callback.yaml b/sledge/script/config/simulation/main_callback/time_callback.yaml new file mode 100644 index 0000000..daa828a --- /dev/null +++ b/sledge/script/config/simulation/main_callback/time_callback.yaml @@ -0,0 +1,3 @@ +time_callback: + _target_: nuplan.planning.simulation.main_callback.time_callback.TimeCallback + _convert_: 'all' diff --git a/sledge/script/config/simulation/main_callback/validation_callback.yaml b/sledge/script/config/simulation/main_callback/validation_callback.yaml new file mode 100644 index 0000000..0be14d2 --- /dev/null +++ b/sledge/script/config/simulation/main_callback/validation_callback.yaml @@ -0,0 +1,6 @@ +validation_callback: + _target_: nuplan.planning.simulation.main_callback.validation_callback.ValidationCallback + _convert_: 'all' + + output_dir: ${output_dir} + validation_dir_name: 'validation-results' diff --git a/sledge/script/config/simulation/metric_aggregator/closed_loop_reactive_agents_weighted_average.yaml b/sledge/script/config/simulation/metric_aggregator/closed_loop_reactive_agents_weighted_average.yaml new file mode 100644 index 0000000..0bf971e --- /dev/null +++ b/sledge/script/config/simulation/metric_aggregator/closed_loop_reactive_agents_weighted_average.yaml @@ -0,0 +1,19 @@ +closed_loop_reactive_agents_weighted_average: + _target_: nuplan.planning.metrics.aggregator.weighted_average_metric_aggregator.WeightedAverageMetricAggregator + name: 'closed_loop_reactive_agents_weighted_average' + metric_weights: # Below we list the metrics used in the scenario scoring function and their corresponsing weights to calculate a weighted average score for each scenario, + # if not specified, the weight is set as default. + # metric name : metric weight in the weighted average function + ego_progress_along_expert_route: 5.0 # This metric has the highest weight equal to 5.0 in the weighted average function, its base score can take a value in [0,1] depending on the ratio of ego to expert progress + time_to_collision_within_bound: 5.0 # This metric has the highest weight equal to 5.0 in the weighted average function, its base score can be 0 or 1 depending on the minimum time to collision threshold + speed_limit_compliance: 4.0 # This metric has a weight equal to 4.0 in the weighted average function, its base score can take a value in [0,1] depending on the amount and duration of over-speeding + ego_is_comfortable: 2.0 # This metric has the lowest weight equal to 2.0 in the weighted average function, its base score can be 0 or 1 depending on the comfort thresholds on acceleration, jerk and yaw. + default: 1.0 + file_name: closed_loop_reactive_agents_weighted_average_metrics_${now:${date_format}} + # The scenario score is defined as the weighted average score of the metrics listed above, multiplied by the score of the multiple_metrics below. + multiple_metrics: + - no_ego_at_fault_collisions # This metric score can be 0, 0.5 or 1 depending on whether there is an at fault collision with VRUs, vehicles or objects + - drivable_area_compliance # This metric score can be 0 or 1 depending on whether ego drives outside the drivable area + - ego_is_making_progress # This metric score can be 0 or 1 depending on whether ego makes progress more than a minimum threshold compared to expert's progress + - driving_direction_compliance # This metric score can be 0 or 0.5 or 1 depending on how much ego drives in the opposite direction if any + challenge_name: ${job_name} diff --git a/sledge/script/config/simulation/metric_aggregator/default_weighted_average.yaml b/sledge/script/config/simulation/metric_aggregator/default_weighted_average.yaml new file mode 100644 index 0000000..c382960 --- /dev/null +++ b/sledge/script/config/simulation/metric_aggregator/default_weighted_average.yaml @@ -0,0 +1,11 @@ +weighted_average_metric_aggregator: + _target_: nuplan.planning.metrics.aggregator.weighted_average_metric_aggregator.WeightedAverageMetricAggregator + name: 'weighted_average_metric_aggregator' + metric_weights: # Below we list the metrics used in the scenario scoring function and their corresponsing weights to calculate a weighted average score for each scenario, + # if not specified, the weight is set as default. + # metric name : metric weight in the weighted average function + default: 1.0 + file_name: weighted_average_metrics_${now:${date_format}} + # The scenario score is defined as the weighted average score of the metrics listed above, multiplied by the score of the multiple_metrics below. + multiple_metrics: null + challenge_name: null diff --git a/sledge/script/config/simulation/observation/sledge_agents_observation.yaml b/sledge/script/config/simulation/observation/sledge_agents_observation.yaml new file mode 100644 index 0000000..1ed1f40 --- /dev/null +++ b/sledge/script/config/simulation/observation/sledge_agents_observation.yaml @@ -0,0 +1,10 @@ +_target_: sledge.simulation.observation.sledge_idm_agents.SledgeIDMAgents +_convert_: 'all' +target_velocity: 10 # Desired velocity in free traffic [m/s] +min_gap_to_lead_agent: 1.0 # Minimum relative distance to lead vehicle [m] +headway_time: 1.5 # Desired time headway. The minimum possible time to the vehicle in front [s] +accel_max: 1.0 # maximum acceleration [m/s^2] +decel_max: 2.0 # maximum deceleration (positive value) [m/s^2] +static_detections_types: ["BARRIER", "CZONE_SIGN", "TRAFFIC_CONE", "GENERIC_OBJECT"] # Open-loop detections to include +minimum_path_length: 30 # [m] The minimum path length to maintain +radius: 64 # [m] Only agents within this radius around the ego will be simulated. diff --git a/sledge/script/config/simulation/planner/pdm_closed_planner.yaml b/sledge/script/config/simulation/planner/pdm_closed_planner.yaml new file mode 100644 index 0000000..12d7828 --- /dev/null +++ b/sledge/script/config/simulation/planner/pdm_closed_planner.yaml @@ -0,0 +1,30 @@ +pdm_closed_planner: + _target_: sledge.simulation.planner.pdm_planner.pdm_closed_planner.PDMClosedPlanner + _convert_: 'all' + + # parameters for output trajectory + trajectory_sampling: + _target_: nuplan.planning.simulation.trajectory.trajectory_sampling.TrajectorySampling + _convert_: 'all' + num_poses: 80 # target future poses + interval_length: 0.1 # interval of samples [s] + + # parameters for proposals + proposal_sampling: + _target_: nuplan.planning.simulation.trajectory.trajectory_sampling.TrajectorySampling + _convert_: 'all' + num_poses: 40 # target future poses + interval_length: 0.1 # interval of samples [s] + + idm_policies: + _target_: sledge.simulation.planner.pdm_planner.proposal.batch_idm_policy.BatchIDMPolicy + _convert_: 'all' + speed_limit_fraction: [0.2,0.4,0.6,0.8,1.0] # Target velocity as fractions of current speed-limit + fallback_target_velocity: 15.0 # Desired fallback velocity in free traffic [m/s] + min_gap_to_lead_agent: 1.0 # Minimum relative distance to lead vehicle [m] + headway_time: 1.5 # Desired time headway. The minimum possible time to the vehicle in front [s] + accel_max: 1.5 # Maximum acceleration [m/s^2] + decel_max: 3.0 # Maximum deceleration (positive value) [m/s^2] + + lateral_offsets: [-1.0, 1.0] # Signed offsets from centerline (or null) [m] + map_radius: 50 # Radius to consider around ego [m] diff --git a/sledge/script/config/simulation/simulation_time_controller/step_simulation_time_controller.yaml b/sledge/script/config/simulation/simulation_time_controller/step_simulation_time_controller.yaml new file mode 100644 index 0000000..76c06e2 --- /dev/null +++ b/sledge/script/config/simulation/simulation_time_controller/step_simulation_time_controller.yaml @@ -0,0 +1,2 @@ +_target_: nuplan.planning.simulation.simulation_time_controller.step_simulation_time_controller.StepSimulationTimeController +_convert_: 'all' diff --git a/sledge/script/config/sledgeboard/__init__.py b/sledge/script/config/sledgeboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/config/sledgeboard/default_sledgeboard.yaml b/sledge/script/config/sledgeboard/default_sledgeboard.yaml new file mode 100644 index 0000000..4946708 --- /dev/null +++ b/sledge/script/config/sledgeboard/default_sledgeboard.yaml @@ -0,0 +1,27 @@ +hydra: + run: + dir: . + output_subdir: null # Store hydra's config breakdown here for debugging + searchpath: # Only in these paths are discoverable + - pkg://sledge.script.config.common + - pkg://sledge.script.experiments # Put experiments configs in script/experiments/ + + +defaults: + - default_common + - simulation_metric: null + # - default_metrics # TODO + - override hydra/job_logging: none # Disable hydra's logging + - override hydra/hydra_logging: none # Disable hydra's logging + +log_config: False # Whether to log the final config after all overrides and interpolations +port_number: 5006 +simulation_path: null +resource_prefix: null +profiler_path: null +async_scenario_rendering: True # Setting this to True will improve UX by showing the scenario canvas as early as possible and then drawing individual plots as their data become available + +# Maximum frames to render in the scenario tab per second, must be between 1-60. +# Use lower values when running SledgeBoard in the cloud to prevent frame queues due to latency. The rule of thumb +# is to match the frame rate with the expected latency, e.g 5Hz for 200ms round-trip latency: +scenario_rendering_frame_rate_cap_hz: 60 diff --git a/sledge/script/experiments/__init__.py b/sledge/script/experiments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/script/experiments/autoencoder/training_rvae_model.yaml b/sledge/script/experiments/autoencoder/training_rvae_model.yaml new file mode 100644 index 0000000..be78183 --- /dev/null +++ b/sledge/script/experiments/autoencoder/training_rvae_model.yaml @@ -0,0 +1,30 @@ +# @package _global_ +experiment_name: training_rvae_model +py_func: training +objective_aggregate_mode: sum + +defaults: + - override /data_augmentation: + - rvae_augmentation + + - override /objective: + - rvae_lines_objective + - rvae_vehicles_objective + - rvae_pedestrians_objective + - rvae_static_objects_objective + - rvae_green_lights_objective + - rvae_red_lights_objective + - rvae_ego_objective + - kl_objective + + - override /matching: + - rvae_lines_matching + - rvae_vehicles_matching + - rvae_pedestrians_matching + - rvae_static_objects_matching + - rvae_green_lights_matching + - rvae_red_lights_matching + + - override /splitter: nuplan + - override /autoencoder_model: rvae_model + - override /training_metric: \ No newline at end of file diff --git a/sledge/script/experiments/autoencoder/training_vae_model.yaml b/sledge/script/experiments/autoencoder/training_vae_model.yaml new file mode 100644 index 0000000..0fe116a --- /dev/null +++ b/sledge/script/experiments/autoencoder/training_vae_model.yaml @@ -0,0 +1,17 @@ +# @package _global_ +experiment_name: training_vae_model +py_func: training +objective_aggregate_mode: sum + +defaults: + - override /data_augmentation: + - vae_augmentation + + - override /objective: + - vae_bce_objective + - kl_objective + + - override /splitter: nuplan + - override /autoencoder_model: vae_model + - override /training_metric: + - override /matching: \ No newline at end of file diff --git a/sledge/script/experiments/diffusion/training_dit_model.yaml b/sledge/script/experiments/diffusion/training_dit_model.yaml new file mode 100644 index 0000000..91b2b11 --- /dev/null +++ b/sledge/script/experiments/diffusion/training_dit_model.yaml @@ -0,0 +1,9 @@ +# @package _global_ +experiment_name: training_dit_model +py_func: training + +defaults: + + - override /autoencoder_model: rvae_model + - override /diffusion_model: dit_b_model + - override /scenario_filter: null diff --git a/sledge/script/experiments/simulation/sledge_reactive_agents.yaml b/sledge/script/experiments/simulation/sledge_reactive_agents.yaml new file mode 100644 index 0000000..d17ef24 --- /dev/null +++ b/sledge/script/experiments/simulation/sledge_reactive_agents.yaml @@ -0,0 +1,9 @@ +# @package _global_ +job_name: sledge_reactive_agents + +defaults: + - override /observation: sledge_agents_observation + - override /ego_controller: two_stage_controller + - override /planner: pdm_closed_planner + - override /simulation_metric: simulation_closed_loop_reactive_agents + - override /metric_aggregator: closed_loop_reactive_agents_weighted_average \ No newline at end of file diff --git a/sledge/script/run_autoencoder.py b/sledge/script/run_autoencoder.py new file mode 100644 index 0000000..236774c --- /dev/null +++ b/sledge/script/run_autoencoder.py @@ -0,0 +1,90 @@ +import logging +import os +from typing import Optional + +import hydra +import pytorch_lightning as pl +from omegaconf import DictConfig + +from nuplan.planning.script.builders.folder_builder import build_training_experiment_folder +from nuplan.planning.script.builders.logging_builder import build_logger +from nuplan.planning.script.builders.worker_pool_builder import build_worker +from nuplan.planning.script.profiler_context_manager import ProfilerContextManager +from nuplan.planning.script.utils import set_default_path +from nuplan.planning.training.experiments.training import TrainingEngine + +from sledge.autoencoder.experiments.training import build_training_engine +from sledge.autoencoder.experiments.latent_caching import cache_latent +from sledge.autoencoder.experiments.feature_caching import cache_feature +from sledge.script.builders.utils.utils_config import update_config_for_autoencoder_training + +logging.getLogger("numba").setLevel(logging.WARNING) +logger = logging.getLogger(__name__) + +# If set, use the env. variable to overwrite the default dataset and experiment paths +set_default_path() + +# If set, use the env. variable to overwrite the Hydra config +CONFIG_PATH = os.getenv("NUPLAN_HYDRA_CONFIG_PATH", "config/autoencoder") + +if os.environ.get("NUPLAN_HYDRA_CONFIG_PATH") is not None: + CONFIG_PATH = os.path.join("../../../../", CONFIG_PATH) + +if os.path.basename(CONFIG_PATH) != "autoencoder": + CONFIG_PATH = os.path.join(CONFIG_PATH, "autoencoder") +CONFIG_NAME = "default_autoencoder" + + +@hydra.main(config_path=CONFIG_PATH, config_name=CONFIG_NAME) +def main(cfg: DictConfig) -> Optional[TrainingEngine]: + """ + Main entrypoint for autoencoder experiments. + :param cfg: omegaconf dictionary + """ + # Fix random seed + pl.seed_everything(cfg.seed, workers=True) + + # Configure logger + build_logger(cfg) + + # Override configs based on setup, and print config + update_config_for_autoencoder_training(cfg) + + # Create output storage folder + build_training_experiment_folder(cfg=cfg) + + # Build worker + worker = build_worker(cfg) + + if cfg.py_func == "training": + # Build training engine + with ProfilerContextManager(cfg.output_dir, cfg.enable_profiling, "build_training_engine"): + engine = build_training_engine(cfg, worker) + + # Run training + logger.info("Starting training...") + with ProfilerContextManager(cfg.output_dir, cfg.enable_profiling, "training"): + engine.trainer.fit(model=engine.model, datamodule=engine.datamodule) + return engine + elif cfg.py_func == "feature_caching": + # Precompute and cache all features + logger.info("Starting feature caching...") + if cfg.worker == "ray_distributed" and cfg.worker.use_distributed: + raise AssertionError("ray in distributed mode will not work with this job") + with ProfilerContextManager(cfg.output_dir, cfg.enable_profiling, "caching"): + cache_feature(cfg=cfg, worker=worker) + return None + elif cfg.py_func == "latent_caching": + # Precompute and cache latents of the autoencoder + logger.info("Starting latent caching...") + if cfg.worker == "ray_distributed" and cfg.worker.use_distributed: + raise AssertionError("ray in distributed mode will not work with this job") + with ProfilerContextManager(cfg.output_dir, cfg.enable_profiling, "caching"): + cache_latent(cfg=cfg, worker=worker) + return None + else: + raise NameError(f"Function {cfg.py_func} does not exist") + + +if __name__ == "__main__": + main() diff --git a/sledge/script/run_diffusion.py b/sledge/script/run_diffusion.py new file mode 100644 index 0000000..486fb44 --- /dev/null +++ b/sledge/script/run_diffusion.py @@ -0,0 +1,62 @@ +import logging +import os +from typing import Optional + +import hydra +import pytorch_lightning as pl +from omegaconf import DictConfig + +from nuplan.planning.training.experiments.training import TrainingEngine +from nuplan.planning.script.builders.folder_builder import build_training_experiment_folder +from nuplan.planning.script.builders.logging_builder import build_logger +from nuplan.planning.script.utils import set_default_path + +from sledge.diffusion.experiments.training import run_training_diffusion +from sledge.diffusion.experiments.scenario_caching import run_scenario_caching + +logging.getLogger("numba").setLevel(logging.WARNING) +logger = logging.getLogger(__name__) + +# If set, use the env. variable to overwrite the default dataset and experiment paths +set_default_path() + +# If set, use the env. variable to overwrite the Hydra config +CONFIG_PATH = os.getenv("NUPLAN_HYDRA_CONFIG_PATH", "config/diffusion") + +if os.environ.get("NUPLAN_HYDRA_CONFIG_PATH") is not None: + CONFIG_PATH = os.path.join("../../../../", CONFIG_PATH) + +if os.path.basename(CONFIG_PATH) != "diffusion": + CONFIG_PATH = os.path.join(CONFIG_PATH, "diffusion") +CONFIG_NAME = "default_diffusion" + + +@hydra.main(config_path=CONFIG_PATH, config_name=CONFIG_NAME) +def main(cfg: DictConfig) -> Optional[TrainingEngine]: + """ + Main entrypoint for diffusion experiments. + :param cfg: omegaconf dictionary + """ + # Fix random seed + pl.seed_everything(cfg.seed, workers=True) + + # Configure logger + build_logger(cfg) + + # Create output storage folder + build_training_experiment_folder(cfg=cfg) + + # Build worker + # worker = build_worker(cfg) + print(cfg.py_func) + + if cfg.py_func == "training": + run_training_diffusion(cfg) + elif cfg.py_func == "scenario_caching": + run_scenario_caching(cfg) + else: + raise NameError(f"Function {cfg.py_func} does not exist") + + +if __name__ == "__main__": + main() diff --git a/sledge/script/run_simulation.py b/sledge/script/run_simulation.py new file mode 100644 index 0000000..73cabb3 --- /dev/null +++ b/sledge/script/run_simulation.py @@ -0,0 +1,95 @@ +import logging +import os +from typing import List, Optional, Union + +import hydra +import pytorch_lightning as pl +from omegaconf import DictConfig, OmegaConf + +from nuplan.planning.script.builders.simulation_callback_builder import ( + build_callbacks_worker, + build_simulation_callbacks, +) +from nuplan.planning.script.utils import run_runners, set_up_common_builder +from nuplan.planning.simulation.planner.abstract_planner import AbstractPlanner + +from sledge.script.builders.simulation_builder import build_simulations + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +# If set, use the env. variable to overwrite the Hydra config +CONFIG_PATH = os.getenv("NUPLAN_HYDRA_CONFIG_PATH", "config/simulation") + +if os.environ.get("NUPLAN_HYDRA_CONFIG_PATH") is not None: + CONFIG_PATH = os.path.join("../../../../", CONFIG_PATH) + +if os.path.basename(CONFIG_PATH) != "simulation": + CONFIG_PATH = os.path.join(CONFIG_PATH, "simulation") +CONFIG_NAME = "default_simulation" + + +def run_simulation(cfg: DictConfig, planners: Optional[Union[AbstractPlanner, List[AbstractPlanner]]] = None) -> None: + """ + Execute all available challenges simultaneously on the same scenario. Helper function for main to allow planner to + be specified via config or directly passed as argument. + :param cfg: Configuration that is used to run the experiment. + Already contains the changes merged from the experiment's config to default config. + :param planners: Pre-built planner(s) to run in simulation. Can either be a single planner or list of planners. + """ + # Fix random seed + pl.seed_everything(cfg.seed, workers=True) + + profiler_name = "building_simulation" + common_builder = set_up_common_builder(cfg=cfg, profiler_name=profiler_name) + + # Build simulation callbacks + callbacks_worker_pool = build_callbacks_worker(cfg) + callbacks = build_simulation_callbacks(cfg=cfg, output_dir=common_builder.output_dir, worker=callbacks_worker_pool) + + # Remove planner from config to make sure run_simulation does not receive multiple planner specifications. + if planners and "planner" in cfg.keys(): + logger.info("Using pre-instantiated planner. Ignoring planner in config") + OmegaConf.set_struct(cfg, False) + cfg.pop("planner") + OmegaConf.set_struct(cfg, True) + + # Construct simulations + if isinstance(planners, AbstractPlanner): + planners = [planners] + + runners = build_simulations( + cfg=cfg, + callbacks=callbacks, + worker=common_builder.worker, + pre_built_planners=planners, + callbacks_worker=callbacks_worker_pool, + ) + + if common_builder.profiler: + # Stop simulation construction profiling + common_builder.profiler.save_profiler(profiler_name) + + logger.info("Running simulation...") + run_runners(runners=runners, common_builder=common_builder, cfg=cfg, profiler_name="running_simulation") + logger.info("Finished running simulation!") + + +@hydra.main(config_path=CONFIG_PATH, config_name=CONFIG_NAME) +def main(cfg: DictConfig) -> None: + """ + Execute all available challenges simultaneously on the same scenario. Calls run_simulation to allow planner to + be specified via config or directly passed as argument. + :param cfg: Configuration that is used to run the experiment. + Already contains the changes merged from the experiment's config to default config. + """ + assert cfg.simulation_log_main_path is None, "Simulation_log_main_path must not be set when running simulation." + + # Execute simulation with pre-configured planner(s). + run_simulation(cfg=cfg) + + +if __name__ == "__main__": + print(CONFIG_PATH) + main() diff --git a/sledge/script/run_sledgeboard.py b/sledge/script/run_sledgeboard.py new file mode 100644 index 0000000..8c25f99 --- /dev/null +++ b/sledge/script/run_sledgeboard.py @@ -0,0 +1,77 @@ +import logging +import os +from pathlib import Path + +import hydra +import nest_asyncio +from hydra.utils import instantiate +from omegaconf import DictConfig + +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters +from sledge.sledgeboard.sledgeboard import SledgeBoard +from nuplan.planning.script.builders.scenario_building_builder import build_scenario_builder +from nuplan.planning.script.builders.utils.utils_config import update_config_for_nuboard +from nuplan.planning.script.utils import set_default_path + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# If set, use the env. variable to overwrite the default dataset and experiment paths +set_default_path() + +# If set, use the env. variable to overwrite the Hydra config +CONFIG_PATH = os.getenv("NUPLAN_HYDRA_CONFIG_PATH", "config/sledgeboard") + +if os.environ.get("NUPLAN_HYDRA_CONFIG_PATH") is not None: + CONFIG_PATH = os.path.join("../../../../", CONFIG_PATH) + +if os.path.basename(CONFIG_PATH) != "sledgeboard": + CONFIG_PATH = os.path.join(CONFIG_PATH, "sledgeboard") +CONFIG_NAME = "default_sledgeboard" + +nest_asyncio.apply() + + +def initialize_sledgeboard(cfg: DictConfig) -> SledgeBoard: + """ + Sets up dependencies and instantiates a SledgeBoard object. + :param cfg: DictConfig. Configuration that is used to run the experiment. + :return: SledgeBoard object. + """ + # Update and override configs for sledge board + update_config_for_nuboard(cfg=cfg) + + scenario_builder = build_scenario_builder(cfg) + + # Build vehicle parameters + vehicle_parameters: VehicleParameters = instantiate(cfg.scenario_builder.vehicle_parameters) + profiler_path = None + if cfg.profiler_path: + profiler_path = Path(cfg.profiler_path) + + sledgeboard = SledgeBoard( + profiler_path=profiler_path, + sledgeboard_paths=cfg.simulation_path, + scenario_builder=scenario_builder, + port_number=cfg.port_number, + resource_prefix=cfg.resource_prefix, + vehicle_parameters=vehicle_parameters, + async_scenario_rendering=cfg.async_scenario_rendering, + scenario_rendering_frame_rate_cap_hz=cfg.scenario_rendering_frame_rate_cap_hz, + ) + + return sledgeboard + + +@hydra.main(config_path=CONFIG_PATH, config_name=CONFIG_NAME) +def main(cfg: DictConfig) -> None: + """ + Execute all available challenges simultaneously on the same scenario. + :param cfg: DictConfig. Configuration that is used to run the experiment. + """ + sledgeboard = initialize_sledgeboard(cfg) + sledgeboard.run() + + +if __name__ == "__main__": + main() diff --git a/sledge/simulation/__init__.py b/sledge/simulation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/maps/__init__.py b/sledge/simulation/maps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/maps/sledge_map/__init__.py b/sledge/simulation/maps/sledge_map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/maps/sledge_map/sledge_lane.py b/sledge/simulation/maps/sledge_map/sledge_lane.py new file mode 100644 index 0000000..14e4664 --- /dev/null +++ b/sledge/simulation/maps/sledge_map/sledge_lane.py @@ -0,0 +1,134 @@ +from functools import cached_property +from typing import List, Optional, Tuple + +from shapely.geometry import Polygon, LineString + +from nuplan.common.actor_state.state_representation import Point2D +from nuplan.common.maps.abstract_map_objects import ( + Lane, + LaneGraphEdgeMapObject, + PolylineMapObject, + RoadBlockGraphEdgeMapObject, +) + +from sledge.simulation.maps.sledge_map.sledge_map_graph import SledgeMapGraph +from sledge.simulation.maps.sledge_map.sledge_polyline import SledgePolylineMapObject +import sledge.simulation.maps.sledge_map.sledge_roadblock as sledge_roadblock + + +class SledgeLane(Lane): + """Lane implementation for sledge map.""" + + def __init__( + self, + lane_id: str, + sledge_map_graph: SledgeMapGraph, + speed_limit_mps: float = 15.0, + ): + """ + Initializes sledge lane + :param lane_id: unique identifies of lane + :param sledge_map_graph: map graph interface in sledge + :param speed_limit_mps: speed limit in [m/s], defaults to 15.0 + """ + super().__init__(lane_id) + self._sledge_map_graph = sledge_map_graph + self._speed_limit_mps = speed_limit_mps + + @cached_property + def incoming_edges(self) -> List[LaneGraphEdgeMapObject]: + """Inherited from superclass.""" + incoming_ids = list(self._sledge_map_graph.directed_lane_graph.predecessors(self.id)) + return [SledgeLane(incoming_id, self._sledge_map_graph) for incoming_id in incoming_ids] + + @cached_property + def outgoing_edges(self) -> List[LaneGraphEdgeMapObject]: + """Inherited from superclass.""" + outgoing_ids = list(self._sledge_map_graph.directed_lane_graph.successors(self.id)) + return [SledgeLane(outgoing_id, self._sledge_map_graph) for outgoing_id in outgoing_ids] + + @cached_property + def parallel_edges(self) -> List[LaneGraphEdgeMapObject]: + """Inherited from superclass""" + raise NotImplementedError + + @cached_property + def baseline_path(self) -> PolylineMapObject: + """Inherited from superclass.""" + linestring = LineString(self._sledge_map_graph.baseline_paths_dict[self.id][:, :2]) + return SledgePolylineMapObject(self.id, linestring) + + @property + def traffic_light_baseline_path(self) -> Optional[PolylineMapObject]: + """Property indicating the section of the baseline path with traffic light signal.""" + if self.id in self._sledge_map_graph.traffic_light_dict.keys(): + poses = self._sledge_map_graph.traffic_light_dict[self.id].poses + return SledgePolylineMapObject(self.id, LineString(poses[:, :2])) + return None + + @property + def traffic_light_polygon(self) -> Polygon: + """Property of traffic light polygon.""" + if self.id in self._sledge_map_graph.traffic_light_dict.keys(): + polygon = self._sledge_map_graph.traffic_light_dict[self.id].polygon + return polygon + return None + + @cached_property + def left_boundary(self) -> PolylineMapObject: + """Inherited from superclass.""" + raise NotImplementedError + + @cached_property + def right_boundary(self) -> PolylineMapObject: + """Inherited from superclass.""" + raise NotImplementedError + + def get_roadblock_id(self) -> str: + """Inherited from superclass.""" + return self.id + + @cached_property + def parent(self) -> RoadBlockGraphEdgeMapObject: + """Inherited from superclass""" + return sledge_roadblock.SledgeRoadBlock(self.id, self._sledge_map_graph) + + @cached_property + def speed_limit_mps(self) -> Optional[float]: + """Inherited from superclass.""" + return self._speed_limit_mps + + @cached_property + def polygon(self) -> Polygon: + """Inherited from superclass.""" + return self._sledge_map_graph.polygon_dict[self.id] + + def is_left_of(self, other: Lane) -> bool: + """Inherited from superclass.""" + raise NotImplementedError + + def is_right_of(self, other: Lane) -> bool: + """Inherited from superclass.""" + raise NotImplementedError + + @cached_property + def adjacent_edges( + self, + ) -> Tuple[Optional[LaneGraphEdgeMapObject], Optional[LaneGraphEdgeMapObject]]: + """Inherited from superclass.""" + raise NotImplementedError + + def get_width_left_right( + self, point: Point2D, include_outside: bool = False + ) -> Tuple[Optional[float], Optional[float]]: + """Inherited from superclass.""" + raise NotImplementedError + + def oriented_distance(self, point: Point2D) -> float: + """Inherited from superclass""" + raise NotImplementedError + + @cached_property + def index(self) -> int: + """Inherited from superclass""" + raise NotImplementedError diff --git a/sledge/simulation/maps/sledge_map/sledge_map.py b/sledge/simulation/maps/sledge_map/sledge_map.py new file mode 100644 index 0000000..b4be5d8 --- /dev/null +++ b/sledge/simulation/maps/sledge_map/sledge_map.py @@ -0,0 +1,302 @@ +from __future__ import annotations + +from collections import defaultdict +from typing import Any, Callable, Dict, List, Optional, Tuple, Type + +import numpy as np +import numpy.typing as npt + +import shapely.geometry as geom +from shapely.geometry import Point + +from nuplan.common.actor_state.state_representation import Point2D +from nuplan.common.maps.abstract_map import AbstractMap, MapObject +from nuplan.common.maps.abstract_map_objects import Lane, RoadBlockGraphEdgeMapObject +from nuplan.common.maps.maps_datatypes import RasterLayer, RasterMap, SemanticMapLayer, VectorLayer +from nuplan.common.maps.nuplan_map.utils import raster_layer_from_map_layer +from nuplan.database.maps_db.layer import MapLayer + +from sledge.simulation.maps.sledge_map.sledge_map_graph import construct_sledge_map_graph, SledgeMapGraph +from sledge.simulation.maps.sledge_map.sledge_lane import SledgeLane +from sledge.simulation.maps.sledge_map.sledge_roadblock import SledgeRoadBlock +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVector + + +AVAILABLE_MAP_LAYERS = [SemanticMapLayer.LANE, SemanticMapLayer.ROADBLOCK] + + +class SledgeMap(AbstractMap): + """Implementation of map api in sledge.""" + + def __init__(self, sledge_vector: SledgeVector, map_name: Optional[str] = None) -> None: + + self._sledge_vector = sledge_vector + self._sledge_map_graph: SledgeMapGraph = construct_sledge_map_graph(sledge_vector) + + self._vector_map: Dict[str, VectorLayer] = defaultdict(VectorLayer) + self._raster_map: Dict[str, RasterLayer] = defaultdict(RasterLayer) + self._map_objects: Dict[SemanticMapLayer, Dict[str, MapObject]] = defaultdict(dict) + self._map_name = map_name if map_name else "undefined" + + self._map_object_getter: Dict[SemanticMapLayer, Callable[[str], MapObject]] = { + SemanticMapLayer.LANE: self._get_lane, + SemanticMapLayer.LANE_CONNECTOR: self._get_not_available, + SemanticMapLayer.ROADBLOCK: self._get_roadblock, + SemanticMapLayer.ROADBLOCK_CONNECTOR: self._get_not_available, + SemanticMapLayer.STOP_LINE: self._get_not_available, + SemanticMapLayer.CROSSWALK: self._get_not_available, + SemanticMapLayer.INTERSECTION: self._get_not_available, + SemanticMapLayer.WALKWAYS: self._get_not_available, + SemanticMapLayer.CARPARK_AREA: self._get_not_available, + } + + self._vector_layer_mapping = { + SemanticMapLayer.LANE: "lanes_polygons", + SemanticMapLayer.ROADBLOCK: "lane_groups_polygons", + SemanticMapLayer.INTERSECTION: "intersections", + SemanticMapLayer.STOP_LINE: "stop_polygons", + SemanticMapLayer.CROSSWALK: "crosswalks", + SemanticMapLayer.DRIVABLE_AREA: "drivable_area", + SemanticMapLayer.LANE_CONNECTOR: "lane_connectors", + SemanticMapLayer.ROADBLOCK_CONNECTOR: "lane_group_connectors", + SemanticMapLayer.BASELINE_PATHS: "baseline_paths", + SemanticMapLayer.BOUNDARIES: "boundaries", + SemanticMapLayer.WALKWAYS: "walkways", + SemanticMapLayer.CARPARK_AREA: "carpark_areas", + } + self._raster_layer_mapping = { + SemanticMapLayer.DRIVABLE_AREA: "drivable_area", + } + + # Special vector layer mapping for lane connector polygons. + self._LANE_CONNECTOR_POLYGON_LAYER = "gen_lane_connectors_scaled_width_polygons" + + def __reduce__(self) -> Tuple[Type[SledgeMap], Tuple[Any, ...]]: + """ + Hints on how to reconstruct the object when pickling. + This object is reconstructed by pickle to avoid serializing potentially large state/caches. + :return: Object type and constructor arguments to be used. + """ + return self.__class__, (self._sledge_vector, self._map_name) + + @property + def map_name(self) -> str: + """Inherited, see superclass.""" + return self._map_name + + @property + def sledge_map_graph(self) -> SledgeMapGraph: + """Inherited, see superclass.""" + return self._sledge_map_graph + + def get_available_map_objects(self) -> List[SemanticMapLayer]: + """Inherited, see superclass.""" + return list(self._map_object_getter.keys()) + + def get_available_raster_layers(self) -> List[SemanticMapLayer]: + """Inherited, see superclass.""" + return list(self._raster_layer_mapping.keys()) + + def get_raster_map_layer(self, layer: SemanticMapLayer) -> RasterLayer: + """Inherited, see superclass.""" + # FIXME: Update for Sledge + layer_id = self._semantic_raster_layer_map(layer) + return self._load_raster_layer(layer_id) + + def get_raster_map(self, layers: List[SemanticMapLayer]) -> RasterMap: + """Inherited, see superclass.""" + # FIXME: Update for Sledge + raster_map = RasterMap(layers=defaultdict(RasterLayer)) + for layer in layers: + raster_map.layers[layer] = self.get_raster_map_layer(layer) + return raster_map + + def is_in_layer(self, point: Point2D, layer: SemanticMapLayer) -> bool: + """Inherited, see superclass.""" + + # TODO add functionality for other map objects + if layer in AVAILABLE_MAP_LAYERS: + return len(self._sledge_map_graph.occupancy_map.query(Point(*point))) > 0 + + return False + + def get_all_map_objects(self, point: Point2D, layer: SemanticMapLayer) -> List[MapObject]: + """Inherited, see superclass.""" + + if layer in AVAILABLE_MAP_LAYERS: + contains_idx = self._sledge_map_graph.occupancy_map.query(Point(*point)) + contains_ids = [self._sledge_map_graph.occupancy_map.tokens[idx] for idx in contains_idx] + return [self.get_map_object(object_id, layer) for object_id in contains_ids] + + return [] + + def get_one_map_object(self, point: Point2D, layer: SemanticMapLayer) -> Optional[MapObject]: + """Inherited, see superclass.""" + + # NOTE: no idea what's the purpose of this function + map_objects = self.get_all_map_objects(point, layer) + if len(map_objects) == 0: + return None + + return map_objects[0] + + def get_proximal_map_objects( + self, point: Point2D, radius: float, layers: List[SemanticMapLayer] + ) -> Dict[SemanticMapLayer, List[MapObject]]: + """Inherited, see superclass.""" + x_min, x_max = point.x - radius, point.x + radius + y_min, y_max = point.y - radius, point.y + radius + patch = geom.box(x_min, y_min, x_max, y_max) + + # TODO: might remove these extra statements + supported_layers = self.get_available_map_objects() + unsupported_layers = [layer for layer in layers if layer not in supported_layers] + assert len(unsupported_layers) == 0, f"Object representation for layer(s): {unsupported_layers} is unavailable" + + object_map: Dict[SemanticMapLayer, List[MapObject]] = defaultdict(list) + + for layer in layers: + object_map[layer] = self._get_proximity_map_object(patch, layer) + + return object_map + + def get_map_object(self, object_id: str, layer: SemanticMapLayer) -> Optional[MapObject]: + """Inherited, see superclass.""" + try: + if object_id not in self._map_objects[layer]: + map_object: MapObject = self._map_object_getter[layer](object_id) + self._map_objects[layer][object_id] = map_object + + return self._map_objects[layer][object_id] + except KeyError: + raise ValueError(f"Object representation for layer: {layer.name} object: {object_id} is unavailable") + + def get_distance_to_nearest_map_object( + self, point: Point2D, layer: SemanticMapLayer + ) -> Tuple[Optional[str], Optional[float]]: + """Inherited from superclass.""" + # TODO add functionality for other map objects + if layer in AVAILABLE_MAP_LAYERS: + ( + nearest_idcs, + nearest_distances, + ) = self._sledge_map_graph.occupancy_map._str_tree.query_nearest(Point(*point), return_distance=True) + + nearest_id = self._sledge_map_graph.occupancy_map.tokens[nearest_idcs[0]] + nearest_distance = nearest_distances[0] + return nearest_id, nearest_distance + + return None, None + + def get_distance_to_nearest_raster_layer(self, point: Point2D, layer: SemanticMapLayer) -> float: + """Inherited from superclass""" + raise NotImplementedError + + def get_distances_matrix_to_nearest_map_object( + self, points: List[Point2D], layer: SemanticMapLayer + ) -> Optional[npt.NDArray[np.float64]]: + """ + Returns the distance matrix (in meters) between a list of points and their nearest desired surface. + That distance is the L1 norm from the point to the closest location on the surface. + :param points: [m] A list of x, y coordinates in global frame. + :param layer: A semantic layer to query. + :return: An array of shortest distance from each point to the nearest desired surface. + """ + raise NotImplementedError + + def initialize_all_layers(self) -> None: + """ + Load all layers to vector map + :param: None + :return: None + """ + pass + + def _semantic_vector_layer_map(self, layer: SemanticMapLayer) -> str: + """ + Mapping from SemanticMapLayer int to MapsDB internal representation of vector layers. + :param layer: The querired semantic map layer. + :return: A internal layer name as a string. + @raise ValueError if the requested layer does not exist for MapsDBMap + """ + try: + return self._vector_layer_mapping[layer] + except KeyError: + raise ValueError("Unknown layer: {}".format(layer.name)) + + def _semantic_raster_layer_map(self, layer: SemanticMapLayer) -> str: + """ + Mapping from SemanticMapLayer int to MapsDB internal representation of raster layers. + :param layer: The queried semantic map layer. + :return: A internal layer name as a string. + @raise ValueError if the requested layer does not exist for MapsDBMap + """ + try: + return self._raster_layer_mapping[layer] + except KeyError: + raise ValueError("Unknown layer: {}".format(layer.name)) + + def _get_vector_map_layer(self, layer: SemanticMapLayer) -> VectorLayer: + """Inherited, see superclass.""" + layer_id = self._semantic_vector_layer_map(layer) + return self._load_vector_map_layer(layer_id) + + def _load_raster_layer(self, layer_name: str) -> RasterLayer: + """ + Load and cache raster layers. + :layer_name: the name of the vector layer to be loaded. + :return: the loaded RasterLayer. + """ + if layer_name not in self._raster_map: + map_layer: MapLayer = self._maps_db.load_layer(self._map_name, layer_name) + self._raster_map[layer_name] = raster_layer_from_map_layer(map_layer) + + return self._raster_map[layer_name] + + def _load_vector_map_layer(self, layer_name: str) -> VectorLayer: + """ + Load and cache vector layers. + :layer_name: the name of the vector layer to be loaded. + :return: the loaded VectorLayer. + """ + if layer_name not in self._vector_map: + if layer_name == "drivable_area": + self._initialize_drivable_area() + else: + self._vector_map[layer_name] = self._maps_db.load_vector_layer(self._map_name, layer_name) + return self._vector_map[layer_name] + + def _get_proximity_map_object(self, patch: geom.Polygon, layer: SemanticMapLayer) -> List[MapObject]: + """ + Gets nearby lanes within the given patch. + :param patch: The area to be checked. + :param layer: desired layer to check. + :return: A list of map objects. + """ + + if layer in AVAILABLE_MAP_LAYERS: + map_object_ids = self._sledge_map_graph.occupancy_map.intersects(patch) + return [self.get_map_object(map_object_id, layer) for map_object_id in map_object_ids] + return [] + + def _get_not_available(self, object_id: str) -> MapObject: + """ + Placeholder for map object getter that is not available in SledgeMap + """ + return None + + def _get_lane(self, lane_id: str) -> Lane: + """ + Gets the lane with the given lane id. + :param lane_id: Desired unique id of a lane that should be extracted. + :return: Lane object. + """ + return SledgeLane(lane_id, self._sledge_map_graph) + + def _get_roadblock(self, roadblock_id: str) -> RoadBlockGraphEdgeMapObject: + """ + Gets the roadblock with the given roadblock_id. + :param roadblock_id: Desired unique id of a roadblock that should be extracted. + :return: RoadBlock object. + """ + return SledgeRoadBlock(roadblock_id, self._sledge_map_graph) diff --git a/sledge/simulation/maps/sledge_map/sledge_map_graph.py b/sledge/simulation/maps/sledge_map/sledge_map_graph.py new file mode 100644 index 0000000..3cb594b --- /dev/null +++ b/sledge/simulation/maps/sledge_map/sledge_map_graph.py @@ -0,0 +1,210 @@ +from dataclasses import dataclass +from typing import Dict, List, Tuple + +import numpy as np +import numpy.typing as npt + +import networkx as nx + +from shapely.geometry import Polygon, LineString, Point +from shapely.geometry.base import CAP_STYLE + +from nuplan.common.maps.maps_datatypes import TrafficLightStatusType + +from sledge.simulation.planner.pdm_planner.observation.pdm_occupancy_map import PDMOccupancyMap +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle +from sledge.simulation.maps.sledge_map.sledge_path import SledgePath +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeVector, + SledgeVectorElement, + SledgeVectorElementType, +) + + +@dataclass +class SledgeTrafficLight: + """Traffic light object for sledge map graph interface.""" + + poses: npt.NDArray[np.float64] + status: TrafficLightStatusType + polygon: Polygon + + +@dataclass +class SledgeMapGraph: + """General map graph interface, used for sledge map.""" + + baseline_paths_dict: Dict[str, npt.NDArray[np.float64]] + polygon_dict: Dict[str, Polygon] + directed_lane_graph: nx.DiGraph + traffic_light_dict: Dict[str, SledgeTrafficLight] + occupancy_map: PDMOccupancyMap = None + + def __post_init__(self): + self.occupancy_map = PDMOccupancyMap(list(self.polygon_dict.keys()), list(self.polygon_dict.values())) + + +def construct_sledge_map_graph( + sledge_vector: SledgeVector, + baseline_path_interval: float = 0.5, + mask_thresh: float = 0.3, + distance_thresh: float = 1.5, + angular_thresh: float = np.deg2rad(60), + polygon_width: float = 3.0, +) -> SledgeMapGraph: + """ + Constructs sledge map graph interface. + TODO: Refactor & add parameters to config + :param sledge_vector: dataclass of vector representation in sledge + :param baseline_path_interval: interval of baseline path poses [m], defaults to 0.5 + :param mask_thresh: threshold for probability of existence, defaults to 0.3 + :param distance_thresh: distance threshold of lane node connection, defaults to 1.5 + :param angular_thresh: angular threshold of lane node connection, defaults to np.deg2rad(60) + :param polygon_width: fixed width of lane polygons, defaults to 3.0 + :return: sledge map graph dataclass + """ + + # Create a directed graph objects + lane_path_list, lane_poses_list = interpolated_lines(sledge_vector.lines, baseline_path_interval, mask_thresh) + + lane_path_dict: Dict[str, SledgePath] = {} + baseline_paths_dict: Dict[str, npt.NDArray[np.float64]] = {} + polygon_dict: Dict[str, Polygon] = {} + + for lane_id, (lane_path, lane_poses) in enumerate(zip(lane_path_list, lane_poses_list)): + lane_path_dict[str(lane_id)] = lane_path + baseline_paths_dict[str(lane_id)] = lane_poses + polygon_dict[str(lane_id)] = LineString(lane_poses[:, :2]).buffer(polygon_width, cap_style=CAP_STYLE.square) + + directed_lane_graph = get_directed_lane_graph(lane_path_dict, distance_thresh, angular_thresh) + traffic_light_dict = get_traffic_light_dict(sledge_vector, lane_path_dict, baseline_path_interval, mask_thresh) + return SledgeMapGraph(baseline_paths_dict, polygon_dict, directed_lane_graph, traffic_light_dict) + + +def get_directed_lane_graph( + lane_path_dict: Dict[str, SledgePath], distance_thresh: float = 1.5, angular_thresh: float = np.deg2rad(60) +) -> nx.DiGraph: + """ + Extract lane graph from lane path dictionary. + :param lane_path_dict: dictionary of id's and paths as keys and values + :param distance_thresh: distance threshold of lane node connection, defaults to 1.5 + :param angular_thresh: angular threshold of lane node connection, defaults to np.deg2rad(60) + :return: networkx directed lane graph + """ + + # 1. add lane id's as nodes + directed_lane_graph = nx.DiGraph() + for lane_id in lane_path_dict.keys(): + directed_lane_graph.add_node(lane_id) + + # 2. add lane connections based on thresholds + start_poses = np.array([path.states_se2_array[0] for path in lane_path_dict.values()]) + end_poses = np.array([path.states_se2_array[-1] for path in lane_path_dict.values()]) + + end_start_distance = np.linalg.norm(end_poses[:, None, :2] - start_poses[None, :, :2], axis=-1) + end_start_angular_error = np.abs(normalize_angle(end_poses[:, None, 2] - start_poses[None, :, 2])) + + lane_ids = list(lane_path_dict.keys()) + connections = np.where( + np.logical_and( + end_start_distance < distance_thresh, + end_start_angular_error < angular_thresh, + ) + ) + + for lane_i, lane_j in zip(*connections): + lane_i_id, lane_j_id = lane_ids[lane_i], lane_ids[lane_j] + if lane_path_dict[lane_i_id].length > distance_thresh or lane_path_dict[lane_j_id].length > distance_thresh: + directed_lane_graph.add_edge(lane_i_id, lane_j_id) + + return directed_lane_graph + + +def get_traffic_light_dict( + sledge_vector: SledgeVector, + lane_path_dict: Dict[str, SledgePath], + baseline_path_interval: float = 0.5, + mask_thresh: float = 0.3, + polygon_width: float = 3.0, +) -> Dict[str, SledgeTrafficLight]: + """ + Extracts dictionary of traffic light dataclasses. + :param sledge_vector: sledge vector dataclass of current scene + :param lane_path_dict: dictionary of id's and paths as keys and values + :param baseline_path_interval: interval of baseline path poses [m], defaults to 0.5 + :param mask_thresh: threshold for probability of existence, defaults to 0.3 + :param polygon_width: fixed width of lane polygons, defaults to 3.0 + :return: dictionary of lane id's and traffic light dataclass + """ + + traffic_light_dict: Dict[str, Tuple[TrafficLightStatusType, npt.NDArray[np.float64]]] = {} + + # extract green + red traffic lights + _, green_lights = interpolated_lines(sledge_vector.green_lights, baseline_path_interval, mask_thresh) + _, red_lights = interpolated_lines(sledge_vector.red_lights, baseline_path_interval, mask_thresh) + status_types = len(green_lights) * [TrafficLightStatusType.GREEN] + len(red_lights) * [TrafficLightStatusType.RED] + + for traffic_light_line, traffic_light_type in zip(green_lights + red_lights, status_types): + min_distance, min_lane_id = None, None + for lane_id, lane_path in lane_path_dict.items(): + distances = [] + for pose in traffic_light_line: + p = Point(pose[:2]) + distances.append(p.distance(lane_path.linestring)) + + average_distance = np.mean(distances) + if (min_distance is None) or (min_distance > average_distance): + min_distance = average_distance + min_lane_id = lane_id + + start_distance = lane_path_dict[min_lane_id].project(Point(traffic_light_line[0, :2])) + end_distance = lane_path_dict[min_lane_id].project(Point(traffic_light_line[-1, :2])) + + start_distance = np.clip(start_distance, 0, lane_path_dict[min_lane_id].length) + end_distance = np.clip(end_distance, 0, lane_path_dict[min_lane_id].length) + num_samples = int((end_distance - start_distance) // baseline_path_interval) + + if num_samples > 1: + distances = np.linspace(start_distance, end_distance, num=num_samples, endpoint=True) + traffic_light_poses = lane_path_dict[min_lane_id].interpolate(distances) + traffic_light_polygon = LineString(traffic_light_poses[:, :2]).buffer( + polygon_width, + cap_style=CAP_STYLE.square, + ) + traffic_light_dict[min_lane_id] = SledgeTrafficLight( + traffic_light_poses, + traffic_light_type, + traffic_light_polygon, + ) + + return traffic_light_dict + + +def interpolated_lines( + sledge_vector_element: SledgeVectorElement, + baseline_path_interval: float = 0.5, + mask_thresh: float = 0.3, +) -> Tuple[List[SledgePath], List[npt.NDArray[np.float64]]]: + """ + Extract interpolatable paths and poses from vector element dataclass + :param sledge_vector_element: vector element dataclass + :param baseline_path_interval: interval of baseline path poses [m], defaults to 0.5 + :param mask_thresh: threshold for probability of existence, defaults to 0.3 + :return: list of interpolatable paths and poses as numpy array + """ + assert sledge_vector_element.get_element_type() == SledgeVectorElementType.LINE + + path_list: List[SledgePath] = [] + poses_list: List[npt.NDArray[np.float64]] = [] + + for state, p in zip(sledge_vector_element.states, sledge_vector_element.mask): + valid = p if (type(p) is np.bool_) else p > mask_thresh + if valid: + path = SledgePath(state) + distances = np.arange(0, path.length + baseline_path_interval, step=baseline_path_interval) + poses = path.interpolate(distances) + if len(poses) > 1: + path_list.append(path) + poses_list.append(poses) + + return path_list, poses_list diff --git a/sledge/simulation/maps/sledge_map/sledge_path.py b/sledge/simulation/maps/sledge_map/sledge_path.py new file mode 100644 index 0000000..df188e2 --- /dev/null +++ b/sledge/simulation/maps/sledge_map/sledge_path.py @@ -0,0 +1,102 @@ +# TODO: remove class and merge with PDMPath in sledge.common + +from typing import Any, List, Union + +import numpy as np +import numpy.typing as npt + +from scipy.interpolate import interp1d +from shapely.creation import linestrings +from shapely.geometry import LineString + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import SE2Index +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle + + +def compute_headings(coords: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]: + """ + Compute the heading angles, based on the 2D coordinates of a line. + :param coords: numpy array of (x,y) coordinates + :return: 1D numpy arrays of heading angles + """ + assert coords.ndim == 2 + assert coords.shape[-1] == 2 + + A = coords[:-1] + B = coords[1:] + y = B[:, 1] - A[:, 1] + x = B[:, 0] - A[:, 0] + headings = np.arctan2(y, x) + + return np.append(headings, headings[-1]) + + +def calculate_progress(states_se2_array: npt.NDArray[np.float64]) -> List[float]: + """ + Calculate the cumulative progress of a given path. + :param states_se2_array: array of (x,y,θ) in last dim + :return: a cumulative array of progress + """ + x_diff = np.diff(states_se2_array[:, 0]) + y_diff = np.diff(states_se2_array[:, 1]) + points_diff: npt.NDArray[np.float64] = np.concatenate(([x_diff], [y_diff]), axis=0, dtype=np.float64) + progress_diff = np.append(0.0, np.linalg.norm(points_diff, axis=0)) + + return np.cumsum(progress_diff, dtype=np.float64) + + +class SledgePath: + """Interpolatable path from 2D coordinates.""" + + def __init__(self, coords: npt.NDArray[np.float64]): + """ + Initializes interpolatable path. + NOTE: object will be removed in future (repetitive with PDMPath). + :param coords: numpy array of (x,y) coordinates + """ + + if coords.shape[-1] == 2: + coords = np.concatenate([coords, compute_headings(coords)[..., None]], axis=-1) + + self._states_se2_array = np.copy(coords) + self._states_se2_array[:, SE2Index.HEADING] = np.unwrap(self._states_se2_array[:, SE2Index.HEADING], axis=0) + self._progress = calculate_progress(self._states_se2_array) + + self._linestring = linestrings(self._states_se2_array[:, : SE2Index.HEADING]) + self._interpolator = interp1d(self._progress, self._states_se2_array, axis=0) + + @property + def length(self): + """Getter for length of path.""" + return self._progress[-1] + + @property + def states_se2_array(self): + """Getter for state se2 array.""" + return self._states_se2_array + + @property + def linestring(self) -> LineString: + """Getter for shapely's linestring of path.""" + return self._linestring + + def project(self, points: Any) -> Any: + """Projects point on linestring, to retrieve distance along path.""" + return self._linestring.project(points) + + def interpolate( + self, + distances: Union[List[float], npt.NDArray[np.float64]], + ) -> npt.NDArray[np.float64]: + """ + Calculates (x,y,θ) for a given distance along the path. + :param distances: list of array of distance values + :param as_array: whether to return in array representation, defaults to False + :return: array of StateSE2 class or (x,y,θ) values + """ + clipped_distances = np.clip(distances, 1e-5, self.length) + interpolated_se2_array = self._interpolator(clipped_distances) + interpolated_se2_array[..., 2] = normalize_angle(interpolated_se2_array[..., 2]) + interpolated_se2_array[np.isnan(interpolated_se2_array)] = 0.0 + + return interpolated_se2_array diff --git a/sledge/simulation/maps/sledge_map/sledge_polyline.py b/sledge/simulation/maps/sledge_map/sledge_polyline.py new file mode 100644 index 0000000..91b385a --- /dev/null +++ b/sledge/simulation/maps/sledge_map/sledge_polyline.py @@ -0,0 +1,85 @@ +import math +from functools import cached_property +from typing import List, cast + +from shapely.geometry import LineString, Point + +from nuplan.common.actor_state.state_representation import Point2D, StateSE2 +from nuplan.common.maps.abstract_map_objects import PolylineMapObject +from nuplan.common.maps.nuplan_map.utils import estimate_curvature_along_path, extract_discrete_polyline + + +def _get_heading(pt1: Point, pt2: Point) -> float: + """ + Computes the angle two points makes to the x-axis. + :param pt1: origin point. + :param pt2: end point. + :return: [rad] resulting angle. + """ + x_diff = pt2.x - pt1.x + y_diff = pt2.y - pt1.y + return math.atan2(y_diff, x_diff) + + +class SledgePolylineMapObject(PolylineMapObject): + """Implementation of polyline map object in sledge.""" + + def __init__( + self, + polyline_id: str, + polyline: LineString, + distance_for_curvature_estimation: float = 2.0, + distance_for_heading_estimation: float = 0.5, + ): + """ + Constructor of polyline map layer in sledge. + :param polyline: a pandas series representing the polyline. + :param distance_for_curvature_estimation: [m] distance of the split between 3-points curvature estimation. + :param distance_for_heading_estimation: [m] distance between two points on the polyline to calculate + the relative heading. + """ + super().__init__(polyline_id) + self._polyline: LineString = polyline + assert self._polyline.length > 0.0, "The length of the polyline has to be greater than 0!" + + self._distance_for_curvature_estimation = distance_for_curvature_estimation + self._distance_for_heading_estimation = distance_for_heading_estimation + + @property + def linestring(self) -> LineString: + """Inherited from superclass.""" + return self._polyline + + @property + def length(self) -> float: + """Inherited from superclass.""" + return float(self._polyline.length) + + @cached_property + def discrete_path(self) -> List[StateSE2]: + """Inherited from superclass.""" + return cast(List[StateSE2], extract_discrete_polyline(self._polyline)) + + def get_nearest_arc_length_from_position(self, point: Point2D) -> float: + """Inherited from superclass.""" + return self._polyline.project(Point(point.x, point.y)) # type: ignore + + def get_nearest_pose_from_position(self, point: Point2D) -> StateSE2: + """Inherited from superclass.""" + arc_length = self.get_nearest_arc_length_from_position(point) + state1 = self._polyline.interpolate(arc_length) + state2 = self._polyline.interpolate(arc_length + self._distance_for_heading_estimation) + + if state1 == state2: + # Handle the case where the queried position (state1) is at the end of the baseline path + state2 = self._polyline.interpolate(arc_length - self._distance_for_heading_estimation) + heading = _get_heading(state2, state1) + else: + heading = _get_heading(state1, state2) + + return StateSE2(state1.x, state1.y, heading) + + def get_curvature_at_arc_length(self, arc_length: float) -> float: + """Inherited from superclass.""" + curvature = estimate_curvature_along_path(self._polyline, arc_length, self._distance_for_curvature_estimation) + return float(curvature) diff --git a/sledge/simulation/maps/sledge_map/sledge_roadblock.py b/sledge/simulation/maps/sledge_map/sledge_roadblock.py new file mode 100644 index 0000000..1afd8ec --- /dev/null +++ b/sledge/simulation/maps/sledge_map/sledge_roadblock.py @@ -0,0 +1,57 @@ +from functools import cached_property +from typing import List + +from shapely.geometry import Polygon + +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject, RoadBlockGraphEdgeMapObject, StopLine + +from sledge.simulation.maps.sledge_map.sledge_map_graph import SledgeMapGraph +import sledge.simulation.maps.sledge_map.sledge_lane as sledge_lane + + +class SledgeRoadBlock(RoadBlockGraphEdgeMapObject): + """Implementation of Roadblock in sledge.""" + + def __init__(self, roadblock_id: str, sledge_map_graph: SledgeMapGraph): + """ + Initialize roadblock interface of sledge. + NOTE: SledgeRoadBlock wrapper of a single lane with same id. + :param roadblock_id: unique identifier of roadblock. + :param sledge_map_graph: lane map graph interface in sledge. + """ + super().__init__(roadblock_id) + self._sledge_map_graph = sledge_map_graph + + @cached_property + def incoming_edges(self) -> List[RoadBlockGraphEdgeMapObject]: + """Inherited from superclass.""" + incoming_ids = list(self._sledge_map_graph.directed_lane_graph.predecessors(self.id)) + return [SledgeRoadBlock(incoming_id, self._sledge_map_graph) for incoming_id in incoming_ids] + + @cached_property + def outgoing_edges(self) -> List[RoadBlockGraphEdgeMapObject]: + """Inherited from superclass.""" + outgoing_ids = list(self._sledge_map_graph.directed_lane_graph.successors(self.id)) + return [SledgeRoadBlock(outgoing_id, self._sledge_map_graph) for outgoing_id in outgoing_ids] + + @cached_property + def interior_edges(self) -> List[LaneGraphEdgeMapObject]: + """Inherited from superclass.""" + # NOTE: Additional heuristic of grouping lanes in roadblock could be added. + lane_ids = [self.id] + return [sledge_lane.SledgeLane(lane_id, self._sledge_map_graph) for lane_id in lane_ids] + + @cached_property + def polygon(self) -> Polygon: + """Inherited from superclass.""" + return self._sledge_map_graph.polygon_dict[self.id] + + @cached_property + def children_stop_lines(self) -> List[StopLine]: + """Inherited from superclass.""" + raise NotImplementedError + + @cached_property + def parallel_edges(self) -> List[RoadBlockGraphEdgeMapObject]: + """Inherited from superclass.""" + raise NotImplementedError diff --git a/sledge/simulation/observation/__init__.py b/sledge/simulation/observation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/observation/sledge_idm/__init__.py b/sledge/simulation/observation/sledge_idm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/observation/sledge_idm/sledge_idm_agent.py b/sledge/simulation/observation/sledge_idm/sledge_idm_agent.py new file mode 100644 index 0000000..712fe2c --- /dev/null +++ b/sledge/simulation/observation/sledge_idm/sledge_idm_agent.py @@ -0,0 +1,391 @@ +from collections import deque +from dataclasses import dataclass +from typing import Deque, Dict, List, Optional + +import numpy as np +from shapely.geometry import Polygon +from shapely.geometry.base import CAP_STYLE +from shapely.ops import unary_union + +from nuplan.common.actor_state.agent import Agent, PredictedTrajectory +from nuplan.common.actor_state.oriented_box import OrientedBox +from nuplan.common.actor_state.scene_object import SceneObjectMetadata +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import ProgressStateSE2, StateSE2, StateVector2D +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject +from nuplan.common.maps.maps_datatypes import TrafficLightStatusType +from nuplan.common.actor_state.static_object import StaticObject +from nuplan.planning.simulation.path.interpolated_path import InterpolatedPath +from nuplan.planning.simulation.path.utils import trim_path, trim_path_up_to_progress +from nuplan.planning.simulation.observation.idm.idm_policy import IDMPolicy +from nuplan.planning.simulation.observation.idm.idm_states import IDMAgentState, IDMLeadAgentState +from nuplan.planning.simulation.observation.idm.utils import create_path_from_se2, path_to_linestring + +from sledge.simulation.scenarios.sledge_scenario.sledge_scenario_utils import project_state_se2 + + +def pose_distance(pose_a: StateSE2, pose_b: StateSE2) -> float: + """ + Helper function to calculate distance between two poses. + :return: distance + """ + return np.linalg.norm(pose_a.array - pose_b.array) + + +@dataclass(frozen=True) +class SledgeIDMInitialState: + """Initial state of SledgeIDMAgent.""" + + metadata: SceneObjectMetadata + tracked_object_type: TrackedObjectType + box: OrientedBox + velocity: StateVector2D + path_progress: float + predictions: Optional[List[PredictedTrajectory]] + + +class SledgeIDMAgent: + """Vehicle state class for IMD agents in sledge.""" + + def __init__( + self, + start_iteration: int, + initial_state: SledgeIDMInitialState, + route: List[LaneGraphEdgeMapObject], + policy: IDMPolicy, + minimum_path_length: float, + max_route_len: int = 20, + ): + """ + Constructor for SledgeIDMAgent. + :param start_iteration: scenario iteration where agent first appeared + :param initial_state: agent initial state + :param route: agent initial route plan + :param policy: policy controlling the agent behavior + :param minimum_path_length: [m] The minimum path length + :param max_route_len: The max number of route elements to store + """ + self._start_iteration = start_iteration # scenario iteration where agent first appears + self._initial_state = initial_state + self._state = IDMAgentState(initial_state.path_progress, initial_state.velocity.x) + self._route: Deque[LaneGraphEdgeMapObject] = deque(route, maxlen=max_route_len) + self._path = self._convert_route_to_path() + self._policy = policy + self._minimum_path_length = minimum_path_length + self._size = (initial_state.box.width, initial_state.box.length, initial_state.box.height) + + # This variable is used to trigger when the _full_agent_state needs to be recalculated + self._requires_state_update: bool = True + self._full_agent_state: Optional[Agent] = None + + def propagate(self, lead_agent: IDMLeadAgentState, tspan: float) -> None: + """ + Propagate agent forward according to the IDM policy. + :param lead_agent: the agent leading this agent + :param tspan: the interval of time to propagate for + """ + speed_limit = self.end_segment.speed_limit_mps + if speed_limit is not None and speed_limit > 0.0: + self._policy.target_velocity = speed_limit + + solution = self._policy.solve_forward_euler_idm_policy( + IDMAgentState(0, self._state.velocity), lead_agent, tspan + ) + self._state.progress += solution.progress + self._state.velocity = max(solution.velocity, 0) + + # A caching flag to trigger re-computation of self.agent + self._requires_state_update = True + + @property + def agent(self) -> Agent: + """:return: the agent as a Agent object""" + return self._get_agent_at_progress(self._get_bounded_progress()) + + @property + def polygon(self) -> Polygon: + """:return: the agent as a Agent object""" + return self.agent.box.geometry + + def get_route(self) -> List[LaneGraphEdgeMapObject]: + """:return: The route the IDM agent is following.""" + return list(self._route) + + @property + def projected_footprint(self) -> Polygon: + """ + Returns the agent's projected footprint along it's planned path. The extended length is proportional + to it's current velocity + :return: The agent's projected footprint as a Polygon. + """ + start_progress = self._clamp_progress(self.progress - self.length / 2) + end_progress = self._clamp_progress(self.progress + self.length / 2 + self.velocity * self._policy.headway_time) + projected_path = path_to_linestring(trim_path(self._path, start_progress, end_progress)) + return unary_union([projected_path.buffer((self.width / 2), cap_style=CAP_STYLE.flat), self.polygon]) + + @property + def width(self) -> float: + """:return: [m] agent's width""" + return float(self._initial_state.box.width) + + @property + def length(self) -> float: + """:return: [m] agent's length""" + return float(self._initial_state.box.length) + + @property + def progress(self) -> float: + """:return: [m] agent's progress""" + return self._state.progress # type: ignore + + @property + def velocity(self) -> float: + """:return: [m/s] agent's velocity along the path""" + return self._state.velocity # type: ignore + + @property + def end_segment(self) -> LaneGraphEdgeMapObject: + """ + Returns the last segment in the agent's route + :return: End segment as a LaneGraphEdgeMapObject + """ + return self._route[-1] + + def to_se2(self) -> StateSE2: + """ + :return: the agent as a StateSE2 object + """ + return self._get_agent_at_progress(self._get_bounded_progress()).box.center + + def is_active(self, ego_state: EgoState, radius: float) -> bool: + """ + Return if the agent should be active at a simulation iteration + :param iteration: the current simulation iteration + :param radius: radius around ego to simulate + :return: true if active, false otherwise + """ + return pose_distance(self.to_se2(), ego_state.center) <= radius + + def has_valid_path(self) -> bool: + """ + :return: true if agent has a valid path, false otherwise + """ + return self._path is not None + + def _get_bounded_progress(self) -> float: + """ + :return: [m] The agent's progress. The progress is clamped between the start and end progress of it's path + """ + return self._clamp_progress(self._state.progress) + + def get_path_to_go(self) -> List[ProgressStateSE2]: + """ + :return: The agent's path trimmed to start at the agent's current progress + """ + return trim_path_up_to_progress(self._path, self._get_bounded_progress()) # type: ignore + + def get_progress_to_go(self) -> float: + """ + return: [m] the progress left until the end of the path + """ + return self._path.get_end_progress() - self.progress # type: ignore + + def get_vehicle(self) -> Agent: + """ + Samples the the agent's trajectory. The velocity is assumed to be constant over the sampled trajectory + :param num_samples: number of elements to sample. + :param sampling_time: [s] time interval of sequence to sample from. + :return: the agent's trajectory as a list of Agent + """ + return self._get_agent_at_progress(self._get_bounded_progress()) + + def plan_route(self, traffic_light_status: Dict[TrafficLightStatusType, List[str]]) -> None: + """ + The planning logic for the agent. + - Prefers going straight. Selects edge with the lowest curvature. + - Looks to add a segment to the route if: + - the progress to go is less than the agent's desired velocity multiplied by the desired headway time + plus the minimum path length + - the outgoing segment is active + + :param traffic_light_status: {traffic_light_status: lane_connector_ids} A dictionary containing traffic light information + """ + + max_number_expand = 10 + number_expand = 0 + while ( + self.get_progress_to_go() + < self._minimum_path_length + self._policy.target_velocity * self._policy.headway_time + and number_expand < max_number_expand + ): + outgoing_edges = self.end_segment.outgoing_edges + + green_outgoing_edges = [] + unknown_outgoing_edges = [] + red_outgoing_edges = [] + + for edge in outgoing_edges: + # Intersection handling + if edge.id in traffic_light_status[TrafficLightStatusType.GREEN]: + green_outgoing_edges.append(edge) + elif edge.id in traffic_light_status[TrafficLightStatusType.RED]: + red_outgoing_edges.append(edge) + else: + unknown_outgoing_edges.append(edge) + + if len(green_outgoing_edges) > 0: + selected_outgoing_edges = green_outgoing_edges + elif len(unknown_outgoing_edges) > 0: + selected_outgoing_edges = unknown_outgoing_edges + elif len(red_outgoing_edges) > 0: + selected_outgoing_edges = red_outgoing_edges + else: + break + + # Select edge with the lowest curvature (prefer going straight) + curvatures = [abs(edge.baseline_path.get_curvature_at_arc_length(0.0)) for edge in selected_outgoing_edges] + idx = np.argmin(curvatures) + new_segment = selected_outgoing_edges[idx] + + self._route.append(new_segment) + self._path = create_path_from_se2(self.get_path_to_go() + new_segment.baseline_path.discrete_path) + self._state.progress = 0 + number_expand += 1 + + def _get_agent_at_progress( + self, + progress: float, + ) -> Agent: + """ + Returns the agent as a box at a given progress + :param progress: the arc length along the agent's path + :return: the agent as a Agent object at the given progress + """ + # Caching + if not self._requires_state_update: + return self._full_agent_state + + if self._path is not None: + init_pose = self._path.get_state_at_progress(progress) + box = OrientedBox.from_new_pose( + self._initial_state.box, StateSE2(init_pose.x, init_pose.y, init_pose.heading) + ) + self._full_agent_state = Agent( + metadata=self._initial_state.metadata, + oriented_box=box, + velocity=self._velocity_to_global_frame(init_pose.heading), + tracked_object_type=self._initial_state.tracked_object_type, + predictions=None, + ) + + else: + self._full_agent_state = Agent( + metadata=self._initial_state.metadata, + oriented_box=self._initial_state.box, + velocity=self._initial_state.velocity, + tracked_object_type=self._initial_state.tracked_object_type, + predictions=self._initial_state.predictions, + ) + self._requires_state_update = False + return self._full_agent_state + + def _clamp_progress(self, progress: float) -> float: + """ + Clamp the progress to be between the agent's path bounds + :param progress: [m] the progress along the agent's path + :return: [m] the progress clamped between the start and end progress of the agent's path + """ + return max(self._path.get_start_progress(), (min(progress, self._path.get_end_progress()))) # type: ignore + + def _convert_route_to_path(self) -> InterpolatedPath: + """ + Converts the route into an InterpolatedPath + :return: InterpolatedPath from the agent's route + """ + blp: List[StateSE2] = [] + for segment in self._route: + blp.extend(segment.baseline_path.discrete_path) + return create_path_from_se2(blp) + + def _velocity_to_global_frame(self, heading: float) -> StateVector2D: + """ + Transform agent's velocity along the path to global frame + :param heading: [rad] The heading defining the transform to global frame. + :return: The velocity vector in global frame. + """ + return StateVector2D(self.velocity * np.cos(heading), self.velocity * np.sin(heading)) + + +class SledgePedestrianAgent: + """Pedestrian state class for agents in sledge.""" + + def __init__(self, pedestrian: Agent): + """ + Constructor for pedestrian agent object. + :param pedestrian: generic agent interface + """ + assert pedestrian.tracked_object_type == TrackedObjectType.PEDESTRIAN + self._initial_pedestrian = pedestrian + self._current_pedestrian = pedestrian + self._active_time_s = 0.0 + + def propagate(self, delta_t: float) -> None: + """ + Propagates pedestrian agents based one constant velocity and heading. + :param delta_t: time span to project agent in seconds + """ + + self._active_time_s += delta_t + propagated_pose = project_state_se2( + self._initial_pedestrian.center, self._active_time_s, self._initial_pedestrian.velocity.magnitude() + ) + oriented_box = OrientedBox( + propagated_pose, + length=self._initial_pedestrian.box.length, + width=self._initial_pedestrian.box.width, + height=self._initial_pedestrian.box.height, + ) + self._current_pedestrian = Agent( + TrackedObjectType.PEDESTRIAN, + oriented_box, + self._initial_pedestrian.velocity, + self._initial_pedestrian.metadata, + ) + + def is_active(self, ego_state: EgoState, radius: float) -> bool: + """ + Return if the agent should be active at a simulation iteration + :param iteration: the current simulation iteration + :param radius: radius around ego to simulate + :return: true if active, false otherwise + """ + return pose_distance(self._current_pedestrian.center, ego_state.center) <= radius + + def get_pedestrian(self) -> Agent: + """ + :return: current agent instance of pedestrian + """ + return self._current_pedestrian + + +class SledgeObjectAgent: + """Static object state class for agents in sledge.""" + + def __init__(self, static_object: StaticObject): + self._initial_static_object = static_object + + def is_active(self, ego_state: EgoState, radius: float) -> bool: + """ + Return if the agent should be active at a simulation iteration + :param iteration: the current simulation iteration + :param radius: radius around ego to simulate + :return: true if active, false otherwise + """ + return pose_distance(self._initial_static_object.center, ego_state.center) <= radius + + def get_object(self) -> StaticObject: + """ + :return: current static object instance + """ + return self._initial_static_object diff --git a/sledge/simulation/observation/sledge_idm/sledge_idm_agent_manager.py b/sledge/simulation/observation/sledge_idm/sledge_idm_agent_manager.py new file mode 100644 index 0000000..8665331 --- /dev/null +++ b/sledge/simulation/observation/sledge_idm/sledge_idm_agent_manager.py @@ -0,0 +1,201 @@ +from typing import Dict, List + +import numpy as np +from shapely.geometry.base import CAP_STYLE + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateSE2 +from nuplan.common.actor_state.tracked_objects import TrackedObjects +from nuplan.common.geometry.transform import rotate_angle +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.maps.abstract_map_objects import StopLine +from nuplan.common.maps.maps_datatypes import SemanticMapLayer, TrafficLightStatusType +from nuplan.common.actor_state.static_object import StaticObject +from nuplan.planning.simulation.observation.idm.idm_states import IDMLeadAgentState +from nuplan.planning.metrics.utils.expert_comparisons import principal_value +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks +from nuplan.planning.simulation.occupancy_map.abstract_occupancy_map import OccupancyMap +from nuplan.planning.simulation.observation.idm.utils import path_to_linestring + +from sledge.simulation.observation.sledge_idm.sledge_idm_agent import ( + SledgeIDMAgent, + SledgePedestrianAgent, + SledgeObjectAgent, +) + +UniqueIDMAgents = Dict[str, SledgeIDMAgent] +UniquePedestrians = Dict[str, SledgePedestrianAgent] + + +class SledgeIDMAgentManager: + + def __init__( + self, + vehicles: Dict[str, SledgeIDMAgent], + pedestrians: Dict[str, SledgePedestrianAgent], + static_objects: Dict[str, StaticObject], + occupancy: OccupancyMap, + map_api: AbstractMap, + radius: float, + ): + """ + Constructor for SledgeIDMAgentManager. + :param vehicles: dictionary of vehicle identifier and IDM states + :param pedestrians: dictionary of pedestrian identifier and states. + :param static_objects: dictionary of static object identifier and states. + :param occupancy: occupancy map of current objects in scene (for IDM). + :param map_api: map interface of simulation + :param radius: distance around ego to propagate agents in meter. + """ + self.vehicles: Dict[str, SledgeIDMAgent] = vehicles + self.pedestrians: Dict[str, SledgePedestrianAgent] = pedestrians + self.static_objects: Dict[str, SledgeObjectAgent] = static_objects + + self.occupancy = occupancy + self._map_api = map_api + self._radius = radius + + def propagate_agents( + self, + ego_state: EgoState, + tspan: float, + traffic_light_status: Dict[TrafficLightStatusType, List[str]], + ) -> None: + """ + Propagates agents in agent manager. + :param ego_state: object of ego vehicle state in nuPlan + :param tspan: time interval to propagate agents in seconds + :param traffic_light_status: dictionary of traffic light types and lane ids. + """ + + self.occupancy.set("ego", ego_state.car_footprint.geometry) + relative_distance = None + for agent_token, agent in self.vehicles.items(): + if agent.is_active(ego_state, self._radius) and agent.has_valid_path(): + agent.plan_route(traffic_light_status) + + # Add stop lines into occupancy map if they are impacting the agent + stop_lines = self._get_relevant_stop_lines(agent, traffic_light_status) + # Keep track of the stop lines that were inserted. This is to remove them for each agent + inactive_stop_line_tokens = self._insert_stop_lines_into_occupancy_map(stop_lines) + + # Check for agents that intersects THIS agent's path + agent_path_buffer = path_to_linestring(agent.get_path_to_go()).buffer( + (agent.width / 2), + cap_style=CAP_STYLE.flat, + ) + intersecting_agents = self.occupancy.intersects(agent_path_buffer) + + assert intersecting_agents.contains(agent_token), "Agent's baseline does not intersect the agent itself" + + # Checking if there are agents intersecting THIS agent's baseline. + # Hence, we are checking for at least 2 intersecting agents. + if intersecting_agents.size > 1: + + nearest_id, nearest_agent_polygon, relative_distance = intersecting_agents.get_nearest_entry_to( + agent_token + ) + agent_heading = agent.to_se2().heading + + if "ego" in nearest_id: + ego_velocity = ego_state.dynamic_car_state.rear_axle_velocity_2d + longitudinal_velocity = np.hypot(ego_velocity.x, ego_velocity.y) + relative_heading = ego_state.rear_axle.heading - agent_heading + elif "stop_line" in nearest_id: + longitudinal_velocity = 0.0 + relative_heading = 0.0 + elif nearest_id in self.vehicles: + nearest_agent = self.vehicles[nearest_id] + longitudinal_velocity = nearest_agent.velocity + relative_heading = nearest_agent.to_se2().heading - agent_heading + else: + longitudinal_velocity = 0.0 + relative_heading = 0.0 + + # Wrap angle to [-pi, pi] + relative_heading = principal_value(relative_heading) + # take the longitudinal component of the projected velocity + projected_velocity = rotate_angle(StateSE2(longitudinal_velocity, 0, 0), relative_heading).x + + # relative_distance already takes the vehicle dimension into account. + # Therefore there is no need to pass in the length_rear. + length_rear = 0 + else: + # Free road case: no leading vehicle + projected_velocity = 0.0 + relative_distance = agent.get_progress_to_go() + length_rear = agent.length / 2 + + if relative_distance is None: + relative_distance = 1.0 + agent.propagate( + IDMLeadAgentState( + progress=relative_distance, + velocity=projected_velocity, + length_rear=length_rear, + ), + tspan, + ) + self.occupancy.set(agent_token, agent.projected_footprint) + self.occupancy.remove(inactive_stop_line_tokens) + + for pedestrian_token, pedestrian in self.pedestrians.items(): + if pedestrian.is_active(ego_state, 15): + pedestrian.propagate(tspan) + self.occupancy.set(pedestrian_token, pedestrian.get_pedestrian().box.geometry) + + def get_active_agents(self, ego_state: EgoState) -> DetectionsTracks: + """ + Collect all active agents and return in detection dataclass. + :param ego_state: object of ego vehicle state in nuPlan + :return: detected objects in current scene. + """ + vehicles = [ + vehicle.get_vehicle() for vehicle in self.vehicles.values() if vehicle.is_active(ego_state, self._radius) + ] + + pedestrians = [ + pedestrian.get_pedestrian() + for pedestrian in self.pedestrians.values() + if pedestrian.is_active(ego_state, self._radius) + ] + + static_objects = [ + static_object.get_object() + for static_object in self.static_objects.values() + if static_object.is_active(ego_state, self._radius) + ] + + return DetectionsTracks(TrackedObjects(vehicles + pedestrians + static_objects)) + + def _get_relevant_stop_lines( + self, agent: SledgeIDMAgent, traffic_light_status: Dict[TrafficLightStatusType, List[str]] + ) -> List[StopLine]: + """ + Retrieve the stop lines that are affecting the given agent. + :param agent: The vehicle IDM agent of interest. + :param traffic_light_status: {traffic_light_status: lane_connector_ids} A dictionary containing traffic light information. + :return: A list of stop lines associated with the given traffic light status. + """ + relevant_lane_connectors = list( + {segment.id for segment in agent.get_route()} & set(traffic_light_status[TrafficLightStatusType.RED]) + ) + lane_connectors = [ + self._map_api.get_map_object(lc_id, SemanticMapLayer.LANE_CONNECTOR) for lc_id in relevant_lane_connectors + ] + return [stop_line for lc in lane_connectors if lc for stop_line in lc.stop_lines] + + def _insert_stop_lines_into_occupancy_map(self, stop_lines: List[StopLine]) -> List[str]: + """ + Insert stop lines into the occupancy map. + :param stop_lines: A list of stop lines to be inserted. + :return: A list of token corresponding to the inserted stop lines. + """ + stop_line_tokens: List[str] = [] + for stop_line in stop_lines: + stop_line_token = f"stop_line_{stop_line.id}" + if not self.occupancy.contains(stop_line_token): + self.occupancy.set(stop_line_token, stop_line.polygon) + stop_line_tokens.append(stop_line_token) + + return stop_line_tokens diff --git a/sledge/simulation/observation/sledge_idm/sledge_idm_agents_builder.py b/sledge/simulation/observation/sledge_idm/sledge_idm_agents_builder.py new file mode 100644 index 0000000..c6f4232 --- /dev/null +++ b/sledge/simulation/observation/sledge_idm/sledge_idm_agents_builder.py @@ -0,0 +1,155 @@ +import logging +from typing import Dict, List, Optional, Tuple + +import numpy as np + +from nuplan.common.actor_state.agent import Agent +from nuplan.common.actor_state.oriented_box import OrientedBox +from nuplan.common.actor_state.state_representation import StateSE2, StateVector2D +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType +from nuplan.common.maps.abstract_map import AbstractMap, SemanticMapLayer +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.occupancy_map.abstract_occupancy_map import OccupancyMap +from nuplan.planning.simulation.occupancy_map.strtree_occupancy_map import STRTreeOccupancyMapFactory +from nuplan.planning.simulation.observation.idm.idm_policy import IDMPolicy + +from sledge.simulation.observation.sledge_idm.sledge_idm_agent_manager import UniqueIDMAgents +from sledge.simulation.observation.sledge_idm.sledge_idm_agent import ( + SledgeIDMAgent, + SledgeIDMInitialState, + SledgePedestrianAgent, + SledgeObjectAgent, +) + +logger = logging.getLogger(__name__) + + +def get_starting_segment( + agent: Agent, map_api: AbstractMap +) -> Tuple[Optional[LaneGraphEdgeMapObject], Optional[float]]: + """ + Gets the map object that the agent is on and the progress along the segment. + :param agent: The agent of interested. + :param map_api: An AbstractMap instance. + :return: GraphEdgeMapObject and progress along the segment. If no map object is found then None. + """ + if map_api.is_in_layer(agent.center, SemanticMapLayer.LANE): + layer = SemanticMapLayer.LANE + elif map_api.is_in_layer(agent.center, SemanticMapLayer.INTERSECTION): + layer = SemanticMapLayer.LANE_CONNECTOR + else: + return None, None + + segments: List[LaneGraphEdgeMapObject] = map_api.get_all_map_objects(agent.center, layer) + if not segments: + return None, None + + # Get segment with the closest heading to the agent + heading_diff = [ + segment.baseline_path.get_nearest_pose_from_position(agent.center).heading - agent.center.heading + for segment in segments + ] + closest_segment = segments[np.argmin(np.abs(heading_diff))] + + progress = closest_segment.baseline_path.get_nearest_arc_length_from_position(agent.center) + return closest_segment, progress + + +def build_idm_agents_on_map_rails( + target_velocity: float, + min_gap_to_lead_agent: float, + headway_time: float, + accel_max: float, + decel_max: float, + minimum_path_length: float, + scenario: AbstractScenario, + static_detections_types: List[TrackedObjectType], +) -> Tuple[UniqueIDMAgents, OccupancyMap]: + """ + Build unique agents from a scenario. InterpolatedPaths are created for each agent according to their driven path + TODO: Refactor + :param target_velocity: Desired velocity in free traffic [m/s] + :param min_gap_to_lead_agent: Minimum relative distance to lead vehicle [m] + :param headway_time: Desired time headway. The minimum possible time to the vehicle in front [s] + :param accel_max: maximum acceleration [m/s^2] + :param decel_max: maximum deceleration (positive value) [m/s^2] + :param minimum_path_length: [m] The minimum path length + :param scenario: scenario + :param static_detections_types: The static objects to include during simulation + :return: a tuple of unique vehicles to simulation and a scene occupancy map + """ + unique_vehicles: Dict[str, SledgeIDMAgent] = {} + unique_pedestrians: Dict[str, SledgePedestrianAgent] = {} + unique_static_objects: Dict[str, SledgeObjectAgent] = {} + + detections = scenario.initial_tracked_objects + + map_api = scenario.map_api + ego_agent = scenario.get_ego_state_at_iteration(0).agent + + static_detections = detections.tracked_objects.get_tracked_objects_of_types(static_detections_types) + occupancy_map = STRTreeOccupancyMapFactory.get_from_boxes(static_detections) + occupancy_map.insert(ego_agent.token, ego_agent.box.geometry) + for static_detection in static_detections: + unique_static_objects[static_detection.track_token] = SledgeObjectAgent(static_detection) + + for pedestrian in detections.tracked_objects.get_tracked_objects_of_type(TrackedObjectType.PEDESTRIAN): + occupancy_map.insert(pedestrian.track_token, pedestrian.box.geometry) + unique_pedestrians[pedestrian.track_token] = SledgePedestrianAgent(pedestrian) + + agent: Agent + for agent in detections.tracked_objects.get_tracked_objects_of_type(TrackedObjectType.VEHICLE): + # filter for only vehicles + if agent.track_token not in unique_vehicles: + + route, progress = get_starting_segment(agent, map_api) + + # Ignore agents that a baseline path cannot be built for + if route is None: + continue + + # Snap agent to baseline path + state_on_path = route.baseline_path.get_nearest_pose_from_position(agent.center.point) + box_on_baseline = OrientedBox.from_new_pose( + agent.box, StateSE2(state_on_path.x, state_on_path.y, state_on_path.heading) + ) + + # Check for collision + if not occupancy_map.intersects(box_on_baseline.geometry).is_empty(): + continue + + # Add to init_agent_occupancy for collision checking + occupancy_map.insert(agent.track_token, box_on_baseline.geometry) + + # Project velocity into local frame + if np.isnan(agent.velocity.array).any(): + ego_state = scenario.get_ego_state_at_iteration(0) + logger.debug( + f"Agents has nan velocity. Setting velocity to ego's velocity of " + f"{ego_state.dynamic_car_state.speed}" + ) + velocity = StateVector2D(ego_state.dynamic_car_state.speed, 0.0) + else: + velocity = StateVector2D(np.hypot(agent.velocity.x, agent.velocity.y), 0) + + initial_state = SledgeIDMInitialState( + metadata=agent.metadata, + tracked_object_type=agent.tracked_object_type, + box=box_on_baseline, + velocity=velocity, + path_progress=progress, + predictions=agent.predictions, + ) + target_velocity = route.speed_limit_mps or target_velocity + unique_vehicles[agent.track_token] = SledgeIDMAgent( + start_iteration=0, + initial_state=initial_state, + route=[route], + policy=IDMPolicy(target_velocity, min_gap_to_lead_agent, headway_time, accel_max, decel_max), + minimum_path_length=minimum_path_length, + ) + + occupancy_map.remove([ego_agent.token]) + + return unique_vehicles, unique_pedestrians, unique_static_objects, occupancy_map diff --git a/sledge/simulation/observation/sledge_idm/utils.py b/sledge/simulation/observation/sledge_idm/utils.py new file mode 100644 index 0000000..1436a08 --- /dev/null +++ b/sledge/simulation/observation/sledge_idm/utils.py @@ -0,0 +1,222 @@ +# from functools import lru_cache +# from typing import Callable, List, Optional, Set, Tuple, cast + +# import numpy as np +# import numpy.typing as npt +# from scipy.spatial.transform import Rotation as R +# from shapely.geometry import LineString + +# from nuplan.common.actor_state.agent import Agent +# from nuplan.common.actor_state.ego_state import EgoState +# from nuplan.common.actor_state.state_representation import ProgressStateSE2, StateSE2 +# from nuplan.common.actor_state.tracked_objects import TrackedObject +# from nuplan.common.geometry.compute import signed_lateral_distance +# from nuplan.planning.simulation.observation.observation_type import DetectionsTracks +# from nuplan.planning.simulation.path.interpolated_path import InterpolatedPath +# from nuplan.planning.simulation.path.utils import calculate_progress + + +# @lru_cache(maxsize=256) +# def get_agent_relative_angle(ego_state: StateSE2, agent_state: StateSE2) -> float: +# """ +# Get the the relative angle of an agent position to the ego +# :param ego_state: pose of ego +# :param agent_state: pose of an agent +# :return: relative angle in radians. +# """ +# agent_vector: npt.NDArray[np.float32] = np.array( +# [agent_state.x - ego_state.x, agent_state.y - ego_state.y] +# ) +# ego_vector: npt.NDArray[np.float32] = np.array( +# [np.cos(ego_state.heading), np.sin(ego_state.heading)] +# ) +# dot_product = np.dot(ego_vector, agent_vector / np.linalg.norm(agent_vector)) +# return float(np.arccos(dot_product)) + + +# def is_agent_ahead(ego_state: StateSE2, agent_state: StateSE2, angle_tolerance: float = 30) -> bool: +# """ +# Determines if an agent is ahead of the ego +# :param ego_state: ego's pose +# :param agent_state: agent's pose +# :param angle_tolerance: tolerance to consider if agent is ahead, where zero is the heading of the ego [deg] +# :return: true if agent is ahead, false otherwise. +# """ +# return bool(get_agent_relative_angle(ego_state, agent_state) < np.deg2rad(angle_tolerance)) + + +# def is_agent_behind( +# ego_state: StateSE2, agent_state: StateSE2, angle_tolerance: float = 150 +# ) -> bool: +# """ +# Determines if an agent is behind of the ego +# :param ego_state: ego's pose +# :param agent_state: agent's pose +# :param angle_tolerance: tolerance to consider if agent is behind, where zero is the heading of the ego [deg] +# :return: true if agent is behind, false otherwise +# """ +# return bool(get_agent_relative_angle(ego_state, agent_state) > np.deg2rad(angle_tolerance)) + + +# def get_closest_agent_in_position( +# ego_state: EgoState, +# observations: DetectionsTracks, +# is_in_position: Callable[[StateSE2, StateSE2], bool], +# collided_track_ids: Set[str] = set(), +# lateral_distance_threshold: float = 0.5, +# ) -> Tuple[Optional[Agent], float]: +# """ +# Searches for the closest agent in a specified position +# :param ego_state: ego's state +# :param observations: agents as DetectionTracks +# :param is_in_position: a function to determine the positional relationship to the ego +# :param collided_track_ids: Set of collided track tokens, default {} +# :param lateral_distance_threshold: Agents laterally further away than this threshold are not considered, default 0.5 meters +# :return: the closest agent in the position and the corresponding shortest distance. +# """ +# closest_distance = np.inf +# closest_agent = None + +# for agent in observations.tracked_objects.get_agents(): +# if ( +# is_in_position(ego_state.rear_axle, agent.center) +# and agent.track_token not in collided_track_ids +# and abs(signed_lateral_distance(ego_state.rear_axle, agent.box.geometry)) +# < lateral_distance_threshold +# ): +# distance = abs( +# ego_state.car_footprint.oriented_box.geometry.distance(agent.box.geometry) +# ) +# if distance < closest_distance: +# closest_distance = distance +# closest_agent = agent + +# return closest_agent, float(closest_distance) + + +# def ego_path_to_se2(path: List[EgoState]) -> List[StateSE2]: +# """ +# Convert a list of EgoState into a list of StateSE2. +# :param path: The path to be converted. +# :return: A list of StateSE2. +# """ +# return [state.center for state in path] + + +# def create_path_from_se2(states: List[StateSE2]) -> InterpolatedPath: +# """ +# Constructs an InterpolatedPath from a list of StateSE2. +# :param states: Waypoints to construct an InterpolatedPath. +# :return: InterpolatedPath. +# """ +# progress_list = calculate_progress(states) + +# # Find indices where the progress states are repeated and to be filtered out. +# progress_diff = np.diff(progress_list) +# repeated_states_mask = np.isclose(progress_diff, 0.0) + +# progress_states = [ +# ProgressStateSE2(progress=progress, x=point.x, y=point.y, heading=point.heading) +# for point, progress, is_repeated in zip(states, progress_list, repeated_states_mask) +# if not is_repeated +# ] + +# if len(progress_states) < 2: + +# last_state = progress_states[-1] +# last_state.progress = last_state.progress + 1 +# last_state.x = last_state.x + 1 + +# progress_states.append(last_state) + +# return InterpolatedPath(progress_states) + + +# def create_path_from_ego_state(states: List[EgoState]) -> InterpolatedPath: +# """ +# Constructs an InterpolatedPath from a list of EgoState. +# :param states: waypoints to construct an InterpolatedPath. +# :return InterpolatedPath. +# """ +# return create_path_from_se2(ego_path_to_se2(states)) + + +# def rotate_vector( +# vector: Tuple[float, float, float], +# theta: float, +# inverse: bool = False, +# ) -> Tuple[float, float, float]: +# """ +# Apply a 2D rotation around the z axis. + +# :param vector: the vector to be rotated +# :param theta: the amount to rotate by +# :param inverse: direction of rotation +# :return: the transformed vector. +# """ +# assert len(vector) == 3, "vector to be transformed must have length 3" +# rotation_matrix = R.from_rotvec([0, 0, theta]) +# if inverse: +# rotation_matrix = rotation_matrix.inv() +# local_vector = rotation_matrix.apply(vector) +# return cast(Tuple[float, float, float], local_vector.tolist()) + + +# def transform_vector_global_to_local_frame( +# vector: Tuple[float, float, float], theta: float +# ) -> Tuple[float, float, float]: +# """ +# Transform a vector from global frame to local frame. + +# :param vector: the vector to be rotated +# :param theta: the amount to rotate by +# :return: the transformed vector. +# """ +# return rotate_vector(vector, theta) + + +# def transform_vector_local_to_global_frame( +# vector: Tuple[float, float, float], theta: float +# ) -> Tuple[float, float, float]: +# """ +# Transform a vector from local frame to global frame. + +# :param vector: the vector to be rotated +# :param theta: the amount to rotate by +# :return: the transformed vector. +# """ +# return rotate_vector(vector, theta, inverse=True) + + +# def path_to_linestring(path: List[StateSE2]) -> LineString: +# """ +# Converts a List of StateSE2 into a LineString +# :param path: path to be converted +# :return: LineString. +# """ +# return LineString([(point.x, point.y) for point in path]) + + +# def ego_path_to_linestring(path: List[EgoState]) -> LineString: +# """ +# Converts a List of EgoState into a LineString +# :param path: path to be converted +# :return: LineString. +# """ +# return path_to_linestring(ego_path_to_se2(path)) + + +# def is_track_stopped( +# tracked_object: TrackedObject, stopped_speed_threshhold: float = 5e-02 +# ) -> bool: +# """ +# Evaluates if a tracked object is stopped +# :param tracked_object: tracked_object representation +# :param stopped_speed_threshhold: Threshhold for 0 speed due to noise +# :return: True if track is stopped else False. +# """ +# return ( +# True +# if not isinstance(tracked_object, Agent) +# else bool(tracked_object.velocity.magnitude() <= stopped_speed_threshhold) +# ) diff --git a/sledge/simulation/observation/sledge_idm_agents.py b/sledge/simulation/observation/sledge_idm_agents.py new file mode 100644 index 0000000..c197375 --- /dev/null +++ b/sledge/simulation/observation/sledge_idm_agents.py @@ -0,0 +1,144 @@ +from collections import defaultdict +from typing import Dict, List, Optional, Type + +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType +from nuplan.common.maps.maps_datatypes import TrafficLightStatusType +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.history.simulation_history_buffer import SimulationHistoryBuffer +from nuplan.planning.simulation.observation.abstract_observation import AbstractObservation +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks, Observation +from nuplan.planning.simulation.simulation_time_controller.simulation_iteration import SimulationIteration +from nuplan.common.actor_state.ego_state import EgoState + +from sledge.simulation.observation.sledge_idm.sledge_idm_agent_manager import SledgeIDMAgentManager +from sledge.simulation.observation.sledge_idm.sledge_idm_agents_builder import build_idm_agents_on_map_rails + + +class SledgeIDMAgents(AbstractObservation): + """Observation object for simulated agents in sledge.""" + + def __init__( + self, + target_velocity: float, + min_gap_to_lead_agent: float, + headway_time: float, + accel_max: float, + decel_max: float, + static_detections_types: List[str], + scenario: AbstractScenario, + minimum_path_length: float = 20, + radius: float = 64, + ): + """ + Constructor for SledgeIDMAgents + :param target_velocity: [m/s] Desired velocity in free traffic + :param min_gap_to_lead_agent: [m] Minimum relative distance to lead vehicle + :param headway_time: [s] Desired time headway. The minimum possible time to the vehicle in front + :param accel_max: [m/s^2] maximum acceleration + :param decel_max: [m/s^2] maximum deceleration (positive value) + :param static_detections_types: object classes considered static. + :param scenario: scenario interface during simulation + :param minimum_path_length: [m] The minimum path length to maintain., defaults to 20 + :param radius: [m] Agents within this radius around the ego will be simulated, defaults to 64 + """ + self.current_iteration = 0 + + self._target_velocity = target_velocity + self._min_gap_to_lead_agent = min_gap_to_lead_agent + self._headway_time = headway_time + self._accel_max = accel_max + self._decel_max = decel_max + self._scenario = scenario + self._static_detections_types: List[TrackedObjectType] = [] + + self._minimum_path_length = minimum_path_length + self._radius = radius + + # Prepare IDM agent manager + self.current_ego_state: EgoState = scenario.initial_ego_state + self._agent_manager: Optional[SledgeIDMAgentManager] = None + self._initialize_static_detection_types(static_detections_types) + + def reset(self) -> None: + """Inherited, see superclass.""" + self.current_iteration = 0 + self._agent_manager = None + + def _initialize_static_detection_types(self, static_detections_types: List[str]) -> None: + """ + Converts the input static objects types to corresponding enums in nuPlan. + :param static_detections_types: list of static object names as strings + :raises ValueError: if string object type cannot be matched to TrackedObjectType enum + """ + for _type in static_detections_types: + try: + self._static_detections_types.append(TrackedObjectType[_type]) + except KeyError: + raise ValueError(f"The given detection type {_type} does not exist or is not supported!") + + def _get_agent_manager(self) -> SledgeIDMAgentManager: + """ + Create sledge idm agent manager in case it does not already exists + :return: SledgeIDMAgentManager + """ + if not self._agent_manager: + unique_vehicles, unique_pedestrians, unique_static_objects, occupancy_map = build_idm_agents_on_map_rails( + self._target_velocity, + self._min_gap_to_lead_agent, + self._headway_time, + self._accel_max, + self._decel_max, + self._minimum_path_length, + self._scenario, + self._static_detections_types, + ) + self._agent_manager = SledgeIDMAgentManager( + unique_vehicles, + unique_pedestrians, + unique_static_objects, + occupancy_map, + self._scenario.map_api, + self._radius, + ) + + return self._agent_manager + + def observation_type(self) -> Type[Observation]: + """Inherited, see superclass.""" + return DetectionsTracks # type: ignore + + def initialize(self) -> None: + """Inherited, see superclass.""" + pass + + def get_observation(self) -> DetectionsTracks: + """Inherited, see superclass.""" + detections = self._get_agent_manager().get_active_agents(self.current_ego_state) + return detections + + def update_observation( + self, + iteration: SimulationIteration, + next_iteration: SimulationIteration, + history: SimulationHistoryBuffer, + ) -> None: + """Inherited, see superclass.""" + ego_state, _ = history.current_state + + self.current_iteration = next_iteration.index + self.current_ego_state = ego_state + + tspan = next_iteration.time_s - iteration.time_s + traffic_light_data = self._scenario.get_traffic_light_status_at_iteration(self.current_iteration) + + # Extract traffic light data into Dict[traffic_light_status, lane_connector_ids] + traffic_light_status: Dict[TrafficLightStatusType, List[str]] = defaultdict(list) + + for data in traffic_light_data: + traffic_light_status[data.status].append(str(data.lane_connector_id)) + + self._get_agent_manager().propagate_agents( + ego_state, + tspan, + traffic_light_status, + ) diff --git a/sledge/simulation/planner/__init__.py b/sledge/simulation/planner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/planner/pdm_planner/__init__.py b/sledge/simulation/planner/pdm_planner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/planner/pdm_planner/abstract_pdm_closed_planner.py b/sledge/simulation/planner/pdm_planner/abstract_pdm_closed_planner.py new file mode 100644 index 0000000..65be764 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/abstract_pdm_closed_planner.py @@ -0,0 +1,159 @@ +from typing import List, Optional + +import numpy as np +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject +from nuplan.planning.simulation.planner.abstract_planner import PlannerInput +from nuplan.planning.simulation.trajectory.interpolated_trajectory import InterpolatedTrajectory +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.abstract_pdm_planner import AbstractPDMPlanner +from sledge.simulation.planner.pdm_planner.observation.pdm_observation import PDMObservation +from sledge.simulation.planner.pdm_planner.proposal.batch_idm_policy import BatchIDMPolicy +from sledge.simulation.planner.pdm_planner.proposal.pdm_generator import PDMGenerator +from sledge.simulation.planner.pdm_planner.proposal.pdm_proposal import PDMProposalManager +from sledge.simulation.planner.pdm_planner.scoring.pdm_scorer import PDMScorer +from sledge.simulation.planner.pdm_planner.simulation.pdm_simulator import PDMSimulator +from sledge.simulation.planner.pdm_planner.utils.pdm_emergency_brake import PDMEmergencyBrake +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import parallel_discrete_path +from sledge.simulation.planner.pdm_planner.utils.pdm_path import PDMPath + + +class AbstractPDMClosedPlanner(AbstractPDMPlanner): + """ + Interface for planners incorporating PDM-Closed. Used for PDM-Closed and PDM-Hybrid. + """ + + def __init__( + self, + trajectory_sampling: TrajectorySampling, + proposal_sampling: TrajectorySampling, + idm_policies: BatchIDMPolicy, + lateral_offsets: Optional[List[float]], + map_radius: float, + ): + """ + Constructor for AbstractPDMClosedPlanner + :param trajectory_sampling: Sampling parameters for final trajectory + :param proposal_sampling: Sampling parameters for proposals + :param idm_policies: BatchIDMPolicy class + :param lateral_offsets: centerline offsets for proposals (optional) + :param map_radius: radius around ego to consider + """ + + super(AbstractPDMClosedPlanner, self).__init__(map_radius) + + assert ( + trajectory_sampling.interval_length == proposal_sampling.interval_length + ), "AbstractPDMClosedPlanner: Proposals and Trajectory must have equal interval length!" + + # config parameters + self._trajectory_sampling: int = trajectory_sampling + self._proposal_sampling: int = proposal_sampling + self._idm_policies: BatchIDMPolicy = idm_policies + self._lateral_offsets: Optional[List[float]] = lateral_offsets + + # observation/forecasting class + self._observation = PDMObservation(trajectory_sampling, proposal_sampling, map_radius) + + # proposal/trajectory related classes + self._generator = PDMGenerator(trajectory_sampling, proposal_sampling) + self._simulator = PDMSimulator(proposal_sampling) + self._scorer = PDMScorer(proposal_sampling) + self._emergency_brake = PDMEmergencyBrake(trajectory_sampling) + + # lazy loaded + self._proposal_manager: Optional[PDMProposalManager] = None + + def _update_proposal_manager(self, ego_state: EgoState): + """ + Updates or initializes PDMProposalManager class + :param ego_state: state of ego-vehicle + """ + + current_lane = self._get_starting_lane(ego_state) + + # TODO: Find additional conditions to trigger re-planning + create_new_proposals = self._iteration == 0 + + if create_new_proposals: + proposal_paths: List[PDMPath] = self._get_proposal_paths(current_lane) + + self._proposal_manager = PDMProposalManager( + lateral_proposals=proposal_paths, + longitudinal_policies=self._idm_policies, + ) + + # update proposals + self._proposal_manager.update(current_lane.speed_limit_mps) + + def _get_proposal_paths(self, current_lane: LaneGraphEdgeMapObject) -> List[PDMPath]: + """ + Returns a list of path's to follow for the proposals. Inits a centerline. + :param current_lane: current or starting lane of path-planning + :return: lists of paths (0-index is centerline) + """ + centerline_discrete_path = self._get_discrete_centerline(current_lane) + self._centerline = PDMPath(centerline_discrete_path) + + # 1. save centerline path (necessary for progress metric) + output_paths: List[PDMPath] = [self._centerline] + + # 2. add additional paths with lateral offset of centerline + if self._lateral_offsets is not None: + for lateral_offset in self._lateral_offsets: + offset_discrete_path = parallel_discrete_path( + discrete_path=centerline_discrete_path, offset=lateral_offset + ) + output_paths.append(PDMPath(offset_discrete_path)) + + return output_paths + + def _get_closed_loop_trajectory( + self, + current_input: PlannerInput, + ) -> InterpolatedTrajectory: + """ + Creates the closed-loop trajectory for PDM-Closed planner. + :param current_input: planner input + :return: trajectory + """ + + ego_state, observation = current_input.history.current_state + + # 1. Environment forecast and observation update + self._observation.update( + ego_state, + observation, + current_input.traffic_light_data, + self._route_lane_dict, + ) + + # 2. Centerline extraction and proposal update + self._update_proposal_manager(ego_state) + + # 3. Generate/Unroll proposals + proposals_array = self._generator.generate_proposals(ego_state, self._observation, self._proposal_manager) + + # 4. Simulate proposals + simulated_proposals_array = self._simulator.simulate_proposals(proposals_array, ego_state) + + # 5. Score proposals + proposal_scores = self._scorer.score_proposals( + simulated_proposals_array, + ego_state, + self._observation, + self._centerline, + self._route_lane_dict, + self._drivable_area_map, + self._map_api, + ) + + # 6.a Apply brake if emergency is expected + trajectory = self._emergency_brake.brake_if_emergency(ego_state, proposal_scores, self._scorer) + + # 6.b Otherwise, extend and output best proposal + if trajectory is None: + trajectory = self._generator.generate_trajectory(np.argmax(proposal_scores)) + + return trajectory diff --git a/sledge/simulation/planner/pdm_planner/abstract_pdm_planner.py b/sledge/simulation/planner/pdm_planner/abstract_pdm_planner.py new file mode 100644 index 0000000..6e0ea72 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/abstract_pdm_planner.py @@ -0,0 +1,162 @@ +from abc import ABC +from typing import Dict, List, Optional, Tuple + +import numpy as np +import numpy.typing as npt +from shapely.geometry import Point + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateSE2 +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject, RoadBlockGraphEdgeMapObject +from nuplan.common.maps.maps_datatypes import SemanticMapLayer +from nuplan.planning.simulation.planner.abstract_planner import AbstractPlanner + +from sledge.simulation.planner.pdm_planner.utils.graph_search.dijkstra import Dijkstra +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle +from sledge.simulation.planner.pdm_planner.utils.pdm_path import PDMPath +from sledge.simulation.planner.pdm_planner.utils.route_utils import route_roadblock_correction +from sledge.simulation.planner.pdm_planner.observation.pdm_occupancy_map import PDMOccupancyMap + + +class AbstractPDMPlanner(AbstractPlanner, ABC): + """ + Interface for planners incorporating PDM-* variants. + """ + + def __init__( + self, + map_radius: float, + ): + """ + Constructor of AbstractPDMPlanner. + :param map_radius: radius around ego to consider + """ + + self._map_radius: int = map_radius # [m] + self._iteration: int = 0 + + # lazy loaded + self._map_api: Optional[AbstractMap] = None + self._route_roadblock_dict: Optional[Dict[str, RoadBlockGraphEdgeMapObject]] = None + self._route_lane_dict: Optional[Dict[str, LaneGraphEdgeMapObject]] = None + + self._centerline: Optional[PDMPath] = None + self._drivable_area_map: Optional[PDMOccupancyMap] = None + + def _load_route_dicts(self, route_roadblock_ids: List[str]) -> None: + """ + Loads roadblock and lane dictionaries of the target route from the map-api. + :param route_roadblock_ids: ID's of on-route roadblocks + """ + # remove repeated ids while remaining order in list + route_roadblock_ids = list(dict.fromkeys(route_roadblock_ids)) + + self._route_roadblock_dict = {} + self._route_lane_dict = {} + + for id_ in route_roadblock_ids: + block = self._map_api.get_map_object(id_, SemanticMapLayer.ROADBLOCK) + block = block or self._map_api.get_map_object(id_, SemanticMapLayer.ROADBLOCK_CONNECTOR) + + self._route_roadblock_dict[block.id] = block + + for lane in block.interior_edges: + self._route_lane_dict[lane.id] = lane + + def _route_roadblock_correction(self, ego_state: EgoState) -> None: + """ + Corrects the roadblock route and reloads lane-graph dictionaries. + :param ego_state: state of the ego vehicle. + """ + route_roadblock_ids = route_roadblock_correction(ego_state, self._map_api, self._route_roadblock_dict) + self._load_route_dicts(route_roadblock_ids) + + def _get_discrete_centerline(self, current_lane: LaneGraphEdgeMapObject, search_depth: int = 30) -> List[StateSE2]: + """ + Applies a Dijkstra search on the lane-graph to retrieve discrete centerline. + :param current_lane: lane object of starting lane. + :param search_depth: depth of search (for runtime), defaults to 30 + :return: list of discrete states on centerline (x,y,θ) + """ + + roadblocks = list(self._route_roadblock_dict.values()) + roadblock_ids = list(self._route_roadblock_dict.keys()) + + # find current roadblock index + start_idx = np.argmax(np.array(roadblock_ids) == current_lane.get_roadblock_id()) + roadblock_window = roadblocks[start_idx : start_idx + search_depth] + + graph_search = Dijkstra(current_lane, list(self._route_lane_dict.keys())) + route_plan, path_found = graph_search.search(roadblock_window[-1]) + + centerline_discrete_path: List[StateSE2] = [] + for lane in route_plan: + centerline_discrete_path.extend(lane.baseline_path.discrete_path) + + return centerline_discrete_path + + def _get_starting_lane(self, ego_state: EgoState) -> LaneGraphEdgeMapObject: + """ + Returns the most suitable starting lane, in ego's vicinity. + :param ego_state: state of ego-vehicle + :return: lane object (on-route) + """ + starting_lane: LaneGraphEdgeMapObject = None + on_route_lanes, heading_error = self._get_intersecting_lanes(ego_state) + + if on_route_lanes: + # 1. Option: find lanes from lane occupancy-map + # select lane with lowest heading error + starting_lane = on_route_lanes[np.argmin(np.abs(heading_error))] + return starting_lane + + else: + # 2. Option: find any intersecting or close lane on-route + closest_distance = np.inf + for edge in self._route_lane_dict.values(): + if edge.contains_point(ego_state.center): + starting_lane = edge + break + + distance = edge.polygon.distance(ego_state.car_footprint.geometry) + if distance < closest_distance: + starting_lane = edge + closest_distance = distance + + return starting_lane + + def _get_intersecting_lanes(self, ego_state: EgoState) -> Tuple[List[LaneGraphEdgeMapObject], List[float]]: + """ + Returns on-route lanes and heading errors where ego-vehicle intersects. + :param ego_state: state of ego-vehicle + :return: tuple of lists with lane objects and heading errors [rad]. + """ + assert self._drivable_area_map, "AbstractPDMPlanner: Drivable area map must be initialized first!" + + ego_position_array: npt.NDArray[np.float64] = ego_state.rear_axle.array + ego_rear_axle_point: Point = Point(*ego_position_array) + ego_heading: float = ego_state.rear_axle.heading + + intersecting_lanes = self._drivable_area_map.intersects(ego_rear_axle_point) + + on_route_lanes, on_route_heading_errors = [], [] + for lane_id in intersecting_lanes: + if lane_id in self._route_lane_dict.keys(): + # collect baseline path as array + lane_object = self._route_lane_dict[lane_id] + lane_discrete_path: List[StateSE2] = lane_object.baseline_path.discrete_path + lane_state_se2_array = np.array([state.array for state in lane_discrete_path], dtype=np.float64) + # calculate nearest state on baseline + lane_distances = (ego_position_array[None, ...] - lane_state_se2_array) ** 2 + lane_distances = lane_distances.sum(axis=-1) ** 0.5 + + # calculate heading error + heading_error = lane_discrete_path[np.argmin(lane_distances)].heading - ego_heading + heading_error = np.abs(normalize_angle(heading_error)) + + # add lane to candidates + on_route_lanes.append(lane_object) + on_route_heading_errors.append(heading_error) + + return on_route_lanes, on_route_heading_errors diff --git a/sledge/simulation/planner/pdm_planner/observation/__init__.py b/sledge/simulation/planner/pdm_planner/observation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/planner/pdm_planner/observation/pdm_object_manager.py b/sledge/simulation/planner/pdm_planner/observation/pdm_object_manager.py new file mode 100644 index 0000000..d8ddd42 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/observation/pdm_object_manager.py @@ -0,0 +1,217 @@ +import copy +from typing import Dict, Tuple + +import numpy as np +import numpy.typing as npt +from nuplan.common.actor_state.state_representation import Point2D +from nuplan.common.actor_state.tracked_objects import TrackedObject +from nuplan.common.actor_state.tracked_objects_types import AGENT_TYPES, TrackedObjectType + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import BBCoordsIndex +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle + +MAX_DYNAMIC_OBJECTS: Dict[TrackedObjectType, int] = { + TrackedObjectType.VEHICLE: 50, + TrackedObjectType.PEDESTRIAN: 25, + TrackedObjectType.BICYCLE: 10, +} + +MAX_STATIC_OBJECTS: int = 50 + + +class PDMObjectManager: + """Class that stores and sorts tracked objects around the ego-vehicle.""" + + def __init__( + self, + ): + """Constructor of PDMObjectManager.""" + + # all objects + self._unique_objects: Dict[str, TrackedObject] = {} + + # dynamic objects + self._dynamic_object_tokens = {key: [] for key in MAX_DYNAMIC_OBJECTS.keys()} + self._dynamic_object_coords = {key: [] for key in MAX_DYNAMIC_OBJECTS.keys()} + self._dynamic_object_dxy = {key: [] for key in MAX_DYNAMIC_OBJECTS.keys()} + + # static objects + self._static_object_tokens = [] + self._static_object_coords = [] + + @property + def unique_objects(self) -> Dict[str, TrackedObject]: + """ + Getter of unique_objects + :return: Dictionary of uniquely tracked objects + """ + return self._unique_objects + + def add_object(self, object: TrackedObject) -> None: + """ + Add object to manager and sort category (dynamic/static) + :param object: any tracked object + """ + self._unique_objects[object.track_token] = object + + coords_list = [[corner.x, corner.y] for corner in copy.deepcopy(object.box.all_corners())] + coords_list.append([object.center.x, object.center.y]) + + coords: np.ndarray = np.array(coords_list, dtype=np.float64) + + if object.tracked_object_type in AGENT_TYPES: + velocity = object.velocity + velocity_angle = np.arctan2(velocity.y, velocity.x) + agent_drives_forward = np.abs(normalize_angle(object.center.heading - velocity_angle)) < np.pi / 2 + + track_heading = ( + object.center.heading if agent_drives_forward else normalize_angle(object.center.heading + np.pi) + ) + + dxy = np.array( + [ + np.cos(track_heading) * velocity.magnitude(), + np.sin(track_heading) * velocity.magnitude(), + ], + dtype=np.float64, + ).T # x,y velocity [m/s] + + self._add_dynamic_object(object.tracked_object_type, object.track_token, coords, dxy) + + else: + self._add_static_object(object.tracked_object_type, object.track_token, coords) + + def get_nearest_objects(self, position: Point2D) -> Tuple: + """ + Retrieve nearest k objects depending on category. + :param position: global map position + :return: tuple containing tokens, coords, and dynamic information of objects + """ + dynamic_object_tokens, dynamic_object_coords_list, dynamic_object_dxy_list = ( + [], + [], + [], + ) + + for dynamic_object_type in MAX_DYNAMIC_OBJECTS.keys(): + ( + dynamic_object_tokens_, + dynamic_object_coords_, + dynamic_object_dxy_, + ) = self._get_nearest_dynamic_objects(position, dynamic_object_type) + + if dynamic_object_coords_.ndim != 3: + continue + + dynamic_object_tokens.extend(dynamic_object_tokens_) + dynamic_object_coords_list.append(dynamic_object_coords_) + dynamic_object_dxy_list.append(dynamic_object_dxy_) + + if len(dynamic_object_coords_list) > 0: + dynamic_object_coords = np.concatenate(dynamic_object_coords_list, axis=0, dtype=np.float64) + dynamic_object_dxy = np.concatenate(dynamic_object_dxy_list, axis=0, dtype=np.float64) + else: + dynamic_object_coords = np.array([], dtype=np.float64) + dynamic_object_dxy = np.array([], dtype=np.float64) + + static_object_tokens, static_object_coords = self._get_nearest_static_objects(position, None) + + return ( + static_object_tokens, + static_object_coords, + dynamic_object_tokens, + dynamic_object_coords, + dynamic_object_dxy, + ) + + def _add_dynamic_object( + self, + type: TrackedObjectType, + token: str, + coords: npt.NDArray[np.float64], + dxy: npt.NDArray[np.float64], + ) -> None: + """ + Adds dynamic obstacle to the manager. + :param type: Object type (vehicle, pedestrian, etc.) + :param token: Temporally consistent object identifier + :param coords: Bounding-box coordinates + :param dxy: velocity (x,y) [m/s] + """ + self._dynamic_object_tokens[type].append(token) + self._dynamic_object_coords[type].append(coords) + self._dynamic_object_dxy[type].append(dxy) + + def _add_static_object( + self, + type: TrackedObjectType, + token: str, + coords: npt.NDArray[np.float64], + ) -> None: + """ + Adds static obstacle to manager. + :param type: Object type (e.g. generic, traffic cone, etc.), currently ignored + :param token: Temporally consistent object identifier + :param coords: Bounding-box coordinates + """ + self._static_object_tokens.append(token) + self._static_object_coords.append(coords) + + def _get_nearest_dynamic_objects(self, position: Point2D, type: TrackedObjectType) -> Tuple: + """ + Retrieves nearest k dynamic objects depending on type + :param position: Ego-vehicle position + :param type: Object type to sort + :return: Tuple of tokens, coords, and velocity of nearest objects. + """ + position_coords = position.array[None, ...] # shape: (1,2) + + object_tokens = self._dynamic_object_tokens[type] + object_coords = np.array(self._dynamic_object_coords[type], dtype=np.float64) + object_dxy = np.array(self._dynamic_object_dxy[type], dtype=np.float64) + + if len(object_tokens) > 0: + # add axis if single object found + if object_coords.ndim == 1: + object_coords = object_coords[None, ...] + object_dxy = object_dxy[None, ...] + + position_to_center_dist = ((object_coords[..., BBCoordsIndex.CENTER, :] - position_coords) ** 2.0).sum( + axis=-1 + ) ** 0.5 + + object_argsort = np.argsort(position_to_center_dist) + + object_tokens = [object_tokens[i] for i in object_argsort][: MAX_DYNAMIC_OBJECTS[type]] + object_coords = object_coords[object_argsort][: MAX_DYNAMIC_OBJECTS[type]] + object_dxy = object_dxy[object_argsort][: MAX_DYNAMIC_OBJECTS[type]] + + return (object_tokens, object_coords, object_dxy) + + def _get_nearest_static_objects(self, position: Point2D, type: TrackedObjectType) -> Tuple: + """ + Retrieves nearest k static obstacles around ego's position. + :param position: ego's position + :param type: type of static obstacle (currently ignored) + :return: tuple of tokens and coords of nearest objects + """ + position_coords = position.array[None, ...] # shape: (1,2) + + object_tokens = self._static_object_tokens + object_coords = np.array(self._static_object_coords, dtype=np.float64) + + if len(object_tokens) > 0: + # add axis if single object found + if object_coords.ndim == 1: + object_coords = object_coords[None, ...] + + position_to_center_dist = ((object_coords[..., BBCoordsIndex.CENTER, :] - position_coords) ** 2.0).sum( + axis=-1 + ) ** 0.5 + + object_argsort = np.argsort(position_to_center_dist) + + object_tokens = [object_tokens[i] for i in object_argsort][:MAX_STATIC_OBJECTS] + object_coords = object_coords[object_argsort][:MAX_STATIC_OBJECTS] + + return (object_tokens, object_coords) diff --git a/sledge/simulation/planner/pdm_planner/observation/pdm_observation.py b/sledge/simulation/planner/pdm_planner/observation/pdm_observation.py new file mode 100644 index 0000000..68a903e --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/observation/pdm_observation.py @@ -0,0 +1,253 @@ +from typing import Dict, List, Optional, Tuple + +import numpy as np +import shapely.creation +from shapely.geometry import Polygon + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.tracked_objects import TrackedObject +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject +from nuplan.common.maps.maps_datatypes import TrafficLightStatusData, TrafficLightStatusType +from nuplan.planning.simulation.observation.observation_type import Observation +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.observation.pdm_object_manager import PDMObjectManager +from sledge.simulation.planner.pdm_planner.observation.pdm_occupancy_map import PDMOccupancyMap +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import BBCoordsIndex +from sledge.simulation.maps.sledge_map.sledge_lane import SledgeLane + + +class PDMObservation: + """PDM's observation class for forecasted occupancy maps.""" + + def __init__( + self, + trajectory_sampling: TrajectorySampling, + proposal_sampling: TrajectorySampling, + map_radius: float, + observation_sample_res: int = 2, + ): + """ + Constructor of PDMObservation + :param trajectory_sampling: Sampling parameters for final trajectory + :param proposal_sampling: Sampling parameters for proposals + :param map_radius: radius around ego to consider, defaults to 50 + :param observation_sample_res: sample resolution of forecast, defaults to 2 + """ + assert ( + trajectory_sampling.interval_length == proposal_sampling.interval_length + ), "PDMObservation: Proposals and Trajectory must have equal interval length!" + + # observation needs length of trajectory horizon or proposal horizon +1s (for TTC metric) + self._sample_interval: float = trajectory_sampling.interval_length # [s] + + self._observation_samples: int = ( + proposal_sampling.num_poses + int(1 / self._sample_interval) + if proposal_sampling.num_poses + int(1 / self._sample_interval) > trajectory_sampling.num_poses + else trajectory_sampling.num_poses + ) + + self._map_radius: float = map_radius + self._observation_sample_res: int = observation_sample_res + + # useful things + self._global_to_local_idcs = [ + idx // observation_sample_res for idx in range(self._observation_samples + observation_sample_res) + ] + self._collided_track_ids: List[str] = [] + self._red_light_token = "red_light" + + # lazy loaded (during update) + self._occupancy_maps: Optional[List[PDMOccupancyMap]] = None + self._object_manager: Optional[PDMObjectManager] = None + + self._initialized: bool = False + + def __getitem__(self, time_idx) -> PDMOccupancyMap: + """ + Retrieves occupancy map for time_idx and adapt temporal resolution. + :param time_idx: index for future simulation iterations [10Hz] + :return: occupancy map + """ + assert self._initialized, "PDMObservation: Has not been updated yet!" + assert 0 <= time_idx < len(self._global_to_local_idcs), f"PDMObservation: index {time_idx} out of range!" + + local_idx = self._global_to_local_idcs[time_idx] + return self._occupancy_maps[local_idx] + + @property + def collided_track_ids(self) -> List[str]: + """ + Getter for past collided track tokens. + :return: list of tokens + """ + assert self._initialized, "PDMObservation: Has not been updated yet!" + return self._collided_track_ids + + @property + def red_light_token(self) -> str: + """ + Getter for red light token indicator + :return: string + """ + return self._red_light_token + + @property + def unique_objects(self) -> Dict[str, TrackedObject]: + """ + Getter for unique tracked objects + :return: dictionary of tokens, tracked objects + """ + assert self._initialized, "PDMObservation: Has not been updated yet!" + return self._object_manager.unique_objects + + def update( + self, + ego_state: EgoState, + observation: Observation, + traffic_light_data: List[TrafficLightStatusData], + route_lane_dict: Dict[str, LaneGraphEdgeMapObject], + ) -> None: + """ + Update & lazy loads information of PDMObservation. + :param ego_state: state of ego vehicle + :param observation: input observation of nuPlan + :param traffic_light_data: list of traffic light states + :param route_lane_dict: dictionary of on-route lanes + :param map_api: map object of nuPlan + """ + + self._occupancy_maps: List[PDMOccupancyMap] = [] + self._object_manager = self._get_object_manager(ego_state, observation) + + ( + traffic_light_tokens, + traffic_light_polygons, + ) = self._get_traffic_light_geometries(traffic_light_data, route_lane_dict) + + ( + static_object_tokens, + static_object_coords, + dynamic_object_tokens, + dynamic_object_coords, + dynamic_object_dxy, + ) = self._object_manager.get_nearest_objects(ego_state.center.point) + + has_static_object, has_dynamic_object = ( + len(static_object_tokens) > 0, + len(dynamic_object_tokens) > 0, + ) + + if has_static_object and static_object_coords.ndim == 1: + static_object_coords = static_object_coords[None, ...] + + if has_dynamic_object and dynamic_object_coords.ndim == 1: + dynamic_object_coords = dynamic_object_coords[None, ...] + dynamic_object_dxy = dynamic_object_dxy[None, ...] + + if has_static_object: + static_object_coords[..., BBCoordsIndex.CENTER, :] = static_object_coords[..., BBCoordsIndex.FRONT_LEFT, :] + static_object_polygons = shapely.creation.polygons(static_object_coords) + + else: + static_object_polygons = np.array([], dtype=np.object_) + + if has_dynamic_object: + dynamic_object_coords[..., BBCoordsIndex.CENTER, :] = dynamic_object_coords[ + ..., BBCoordsIndex.FRONT_LEFT, : + ] + else: + dynamic_object_polygons = np.array([], dtype=np.object_) + dynamic_object_tokens = [] + + traffic_light_polygons = np.array(traffic_light_polygons, dtype=np.object_) + + for sample in np.arange( + 0, + self._observation_samples + self._observation_sample_res, + self._observation_sample_res, + ): + if has_dynamic_object: + delta_t = float(sample) * self._sample_interval + dynamic_object_coords_t = dynamic_object_coords + delta_t * dynamic_object_dxy[:, None] + dynamic_object_polygons = shapely.creation.polygons(dynamic_object_coords_t) + + all_polygons = np.concatenate( + [ + static_object_polygons, + dynamic_object_polygons, + traffic_light_polygons, + ], + axis=0, + ) + + occupancy_map = PDMOccupancyMap( + static_object_tokens + dynamic_object_tokens + traffic_light_tokens, + all_polygons, + ) + self._occupancy_maps.append(occupancy_map) + + # save collided objects to ignore in the future + ego_polygon: Polygon = ego_state.car_footprint.geometry + intersecting_obstacles = self._occupancy_maps[0].intersects(ego_polygon) + new_collided_track_ids = [] + + for intersecting_obstacle in intersecting_obstacles: + if self._red_light_token in intersecting_obstacle: + within = ego_polygon.within(self._occupancy_maps[0][intersecting_obstacle]) + if not within: + continue + new_collided_track_ids.append(intersecting_obstacle) + + self._collided_track_ids = self._collided_track_ids + new_collided_track_ids + self._initialized = True + + def _get_object_manager(self, ego_state: EgoState, observation: Observation) -> PDMObjectManager: + """ + Creates object manager class, but adding valid tracked objects. + :param ego_state: state of ego-vehicle + :param observation: input observation of nuPlan + :return: PDMObjectManager class + """ + object_manager = PDMObjectManager() + + for object in observation.tracked_objects: + if ( + (object.tracked_object_type == TrackedObjectType.EGO) + or (self._map_radius and ego_state.center.distance_to(object.center) > self._map_radius) + or (object.track_token in self._collided_track_ids) + ): + continue + + object_manager.add_object(object) + + return object_manager + + def _get_traffic_light_geometries( + self, + traffic_light_data: List[TrafficLightStatusData], + route_lane_dict: Dict[str, LaneGraphEdgeMapObject], + ) -> Tuple[List[str], List[Polygon]]: + """ + Collects red traffic lights along ego's route. + :param traffic_light_data: list of traffic light states + :param route_lane_dict: dictionary of on-route lanes + :return: tuple of tokens and polygons of red traffic lights + """ + traffic_light_tokens, traffic_light_polygons = [], [] + + for data in traffic_light_data: + lane_connector_id = str(data.lane_connector_id) + + if (data.status == TrafficLightStatusType.RED) and (lane_connector_id in route_lane_dict.keys()): + lane_connector = route_lane_dict[lane_connector_id] + traffic_light_tokens.append(f"{self._red_light_token}_{lane_connector_id}") + if isinstance(lane_connector, SledgeLane): + polygon = lane_connector.traffic_light_polygon + else: + polygon = lane_connector.polygon + + traffic_light_polygons.append(polygon) + + return traffic_light_tokens, traffic_light_polygons diff --git a/sledge/simulation/planner/pdm_planner/observation/pdm_observation_utils.py b/sledge/simulation/planner/pdm_planner/observation/pdm_observation_utils.py new file mode 100644 index 0000000..287cbb6 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/observation/pdm_observation_utils.py @@ -0,0 +1,42 @@ +from typing import List + +from shapely.geometry import Polygon + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import Point2D +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.maps.maps_datatypes import SemanticMapLayer + +from sledge.simulation.planner.pdm_planner.observation.pdm_occupancy_map import PDMOccupancyMap + +DRIVABLE_MAP_LAYERS = [SemanticMapLayer.ROADBLOCK, SemanticMapLayer.ROADBLOCK_CONNECTOR, SemanticMapLayer.CARPARK_AREA] + + +def get_drivable_area_map( + map_api: AbstractMap, + ego_state: EgoState, + map_radius: float = 50, +) -> PDMOccupancyMap: + + # query all drivable map elements around ego position + position: Point2D = ego_state.center.point + drivable_area = map_api.get_proximal_map_objects(position, map_radius, DRIVABLE_MAP_LAYERS) + + # collect lane polygons in list, save on-route indices + drivable_polygons: List[Polygon] = [] + drivable_polygon_ids: List[str] = [] + + for type in [SemanticMapLayer.ROADBLOCK, SemanticMapLayer.ROADBLOCK_CONNECTOR]: + for roadblock in drivable_area[type]: + for lane in roadblock.interior_edges: + drivable_polygons.append(lane.polygon) + drivable_polygon_ids.append(lane.id) + + for carpark in drivable_area[SemanticMapLayer.CARPARK_AREA]: + drivable_polygons.append(carpark.polygon) + drivable_polygon_ids.append(carpark.id) + + # create occupancy map with lane polygons + drivable_area_map = PDMOccupancyMap(drivable_polygon_ids, drivable_polygons) + + return drivable_area_map diff --git a/sledge/simulation/planner/pdm_planner/observation/pdm_occupancy_map.py b/sledge/simulation/planner/pdm_planner/observation/pdm_occupancy_map.py new file mode 100644 index 0000000..c42a4a5 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/observation/pdm_occupancy_map.py @@ -0,0 +1,99 @@ +from typing import Dict, List + +import numpy as np +import numpy.typing as npt +import shapely.vectorized +from nuplan.planning.simulation.occupancy_map.abstract_occupancy_map import Geometry +from shapely.strtree import STRtree + + +class PDMOccupancyMap: + """Occupancy map class of PDM, based on shapely's str-tree.""" + + def __init__( + self, + tokens: List[str], + geometries: npt.NDArray[np.object_], + node_capacity: int = 10, + ): + """ + Constructor of PDMOccupancyMap + :param tokens: list of tracked tokens + :param geometries: list/array of polygons + :param node_capacity: max number of child nodes in str-tree, defaults to 10 + """ + assert len(tokens) == len( + geometries + ), f"PDMOccupancyMap: Tokens/Geometries ({len(tokens)}/{len(geometries)}) have unequal length!" + + self._tokens: List[str] = tokens + self._token_to_idx: Dict[str, int] = { + token: idx for idx, token in enumerate(tokens) + } + + self._geometries = geometries + self._node_capacity = node_capacity + self._str_tree = STRtree(self._geometries, node_capacity) + + def __getitem__(self, token) -> Geometry: + """ + Retrieves geometry of token. + :param token: geometry identifier + :return: Geometry of token + """ + return self._geometries[self._token_to_idx[token]] + + def __len__(self) -> int: + """ + Number of geometries in the occupancy map + :return: int + """ + return len(self._tokens) + + @property + def tokens(self) -> List[str]: + """ + Getter for track tokens in occupancy map + :return: list of strings + """ + return self._tokens + + @property + def token_to_idx(self) -> Dict[str, int]: + """ + Getter for track tokens in occupancy map + :return: dictionary of tokens and indices + """ + return self._token_to_idx + + def intersects(self, geometry: Geometry) -> List[str]: + """ + Searches for intersecting geometries in the occupancy map + :param geometry: geometries to query + :return: list of tokens for intersecting geometries + """ + indices = self.query(geometry, predicate="intersects") + return [self._tokens[idx] for idx in indices] + + def query(self, geometry: Geometry, predicate=None): + """ + Function to directly calls shapely's query function on str-tree + :param geometry: geometries to query + :param predicate: see shapely, defaults to None + :return: query output + """ + return self._str_tree.query(geometry, predicate=predicate) + + def points_in_polygons( + self, points: npt.NDArray[np.float64] + ) -> npt.NDArray[np.bool_]: + """ + Determines wether input-points are in polygons of the occupancy map + :param points: input-points + :return: boolean array of shape (polygons, input-points) + """ + output = np.zeros((len(self._geometries), len(points)), dtype=bool) + for i, polygon in enumerate(self._geometries): + output[i] = shapely.vectorized.contains(polygon, points[:, 0], points[:, 1]) + + return output diff --git a/sledge/simulation/planner/pdm_planner/pdm_closed_planner.py b/sledge/simulation/planner/pdm_planner/pdm_closed_planner.py new file mode 100644 index 0000000..d321aed --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/pdm_closed_planner.py @@ -0,0 +1,80 @@ +import gc +import logging +import warnings +from typing import List, Optional, Type + +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks, Observation +from nuplan.planning.simulation.planner.abstract_planner import PlannerInitialization, PlannerInput +from nuplan.planning.simulation.trajectory.abstract_trajectory import AbstractTrajectory +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.abstract_pdm_closed_planner import AbstractPDMClosedPlanner +from sledge.simulation.planner.pdm_planner.observation.pdm_observation_utils import get_drivable_area_map +from sledge.simulation.planner.pdm_planner.proposal.batch_idm_policy import BatchIDMPolicy + +warnings.filterwarnings("ignore", category=RuntimeWarning) +logger = logging.getLogger(__name__) + + +class PDMClosedPlanner(AbstractPDMClosedPlanner): + """PDM-Closed planner class.""" + + # Inherited property, see superclass. + requires_scenario: bool = False + + def __init__( + self, + trajectory_sampling: TrajectorySampling, + proposal_sampling: TrajectorySampling, + idm_policies: BatchIDMPolicy, + lateral_offsets: Optional[List[float]], + map_radius: float, + ): + """ + Constructor for PDMClosedPlanner + :param trajectory_sampling: Sampling parameters for final trajectory + :param proposal_sampling: Sampling parameters for proposals + :param idm_policies: BatchIDMPolicy class + :param lateral_offsets: centerline offsets for proposals (optional) + :param map_radius: radius around ego to consider + """ + super(PDMClosedPlanner, self).__init__( + trajectory_sampling, + proposal_sampling, + idm_policies, + lateral_offsets, + map_radius, + ) + + def initialize(self, initialization: PlannerInitialization) -> None: + """Inherited, see superclass.""" + self._iteration = 0 + self._map_api = initialization.map_api + self._load_route_dicts(initialization.route_roadblock_ids) + gc.collect() + + def name(self) -> str: + """Inherited, see superclass.""" + return self.__class__.__name__ + + def observation_type(self) -> Type[Observation]: + """Inherited, see superclass.""" + return DetectionsTracks # type: ignore + + def compute_planner_trajectory(self, current_input: PlannerInput) -> AbstractTrajectory: + """Inherited, see superclass.""" + + gc.disable() + ego_state, _ = current_input.history.current_state + + # Apply route correction on first iteration (ego_state required) + if self._iteration == 0: + self._route_roadblock_correction(ego_state) + + # Update/Create drivable area polygon map + self._drivable_area_map = get_drivable_area_map(self._map_api, ego_state, self._map_radius) + + trajectory = self._get_closed_loop_trajectory(current_input) + + self._iteration += 1 + return trajectory diff --git a/sledge/simulation/planner/pdm_planner/proposal/__init__.py b/sledge/simulation/planner/pdm_planner/proposal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/planner/pdm_planner/proposal/batch_idm_policy.py b/sledge/simulation/planner/pdm_planner/proposal/batch_idm_policy.py new file mode 100644 index 0000000..76cbe84 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/proposal/batch_idm_policy.py @@ -0,0 +1,170 @@ +from typing import List, Union + +import numpy as np +import numpy.typing as npt + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import LeadingAgentIndex, StateIDMIndex + + +class BatchIDMPolicy: + """ + IDM policies operating on a batch of proposals. + """ + + def __init__( + self, + fallback_target_velocity: Union[List[float], float], + speed_limit_fraction: Union[List[float], float], + min_gap_to_lead_agent: Union[List[float], float], + headway_time: Union[List[float], float], + accel_max: Union[List[float], float], + decel_max: Union[List[float], float], + ): + """ + Constructor for BatchIDMPolicy + :param target_velocity: Desired fallback velocity in free traffic [m/s] + :param speed_limit_fraction: Fraction of speed-limit desired in free traffic + :param min_gap_to_lead_agent: Minimum relative distance to lead vehicle [m] + :param headway_time: Desired time headway. Minimum time to the vehicle in front [s] + :param accel_max: maximum acceleration [m/s^2] + :param decel_max: maximum deceleration (positive value) [m/s^2] + """ + parameter_list = [ + fallback_target_velocity, + speed_limit_fraction, + min_gap_to_lead_agent, + headway_time, + accel_max, + decel_max, + ] + num_parameter_policies = [len(item) for item in parameter_list if isinstance(item, list)] + + if len(num_parameter_policies) > 0: + assert all( + item == num_parameter_policies[0] for item in num_parameter_policies + ), "BatchIDMPolicy initial parameters must be float, or lists of equal length" + num_policies = max(num_parameter_policies) + else: + num_policies = 1 + + self._num_policies: int = num_policies + + self._fallback_target_velocities: npt.NDArray[np.float64] = np.zeros((self._num_policies), dtype=np.float64) + self._speed_limit_fractions: npt.NDArray[np.float64] = np.zeros((self._num_policies), dtype=np.float64) + self._min_gap_to_lead_agent: npt.NDArray[np.float64] = np.zeros((self._num_policies), dtype=np.float64) + self._headway_time: npt.NDArray[np.float64] = np.zeros((self._num_policies), dtype=np.float64) + self._accel_max: npt.NDArray[np.float64] = np.zeros((self._num_policies), dtype=np.float64) + + self._decel_max: npt.NDArray[np.float64] = np.zeros((self._num_policies), dtype=np.float64) + + for i in range(self._num_policies): + self._fallback_target_velocities[i] = ( + fallback_target_velocity if isinstance(fallback_target_velocity, float) else fallback_target_velocity[i] + ) + self._speed_limit_fractions[i] = ( + speed_limit_fraction if isinstance(speed_limit_fraction, float) else speed_limit_fraction[i] + ) + self._min_gap_to_lead_agent[i] = ( + min_gap_to_lead_agent if isinstance(min_gap_to_lead_agent, float) else min_gap_to_lead_agent[i] + ) + self._headway_time[i] = headway_time if isinstance(headway_time, float) else headway_time[i] + self._accel_max[i] = accel_max if isinstance(accel_max, float) else accel_max[i] + self._decel_max[i] = decel_max if isinstance(decel_max, float) else decel_max[i] + + # lazy loaded + self._target_velocities: npt.NDArray[np.float64] = np.zeros((self._num_policies), dtype=np.float64) + + @property + def num_policies(self) -> int: + """ + Getter for number of policies + :return: int + """ + return self._num_policies + + @property + def max_target_velocity(self): + """ + Getter for highest target velocity of policies + :return: target velocity [m/s] + """ + return np.max(self._target_velocities) + + def update(self, speed_limit_mps: float): + """ + Updates class with current speed limit + :param speed_limit_mps: speed limit of current lane [m/s] + """ + + if speed_limit_mps is not None: + self._target_velocities = self._speed_limit_fractions * speed_limit_mps + else: + self._target_velocities = self._speed_limit_fractions * self._fallback_target_velocities + + def propagate( + self, + previous_idm_states: npt.NDArray[np.float64], + leading_agent_states: npt.NDArray[np.float64], + longitudinal_idcs: List[int], + sampling_time: float, + ) -> npt.NDArray[np.float64]: + """ + Propagates IDM policies for one time-step + :param previous_idm_states: array containing previous state + :param leading_agent_states: array contains leading vehicle information + :param longitudinal_idcs: indices of policies to be applied over a batch-dim + :param sampling_time: time to propagate forward [s] + :return: array containing propagated state values + """ + + assert len(previous_idm_states) == len(longitudinal_idcs) and len(leading_agent_states) == len( + longitudinal_idcs + ), "PDMIDMPolicy: propagate function requires equal length of input arguments!" + + # state variables + x_agent, v_agent = ( + previous_idm_states[:, StateIDMIndex.PROGRESS], + previous_idm_states[:, StateIDMIndex.VELOCITY], + ) + + x_lead, v_lead, l_r_lead = ( + leading_agent_states[:, LeadingAgentIndex.PROGRESS], + leading_agent_states[:, LeadingAgentIndex.VELOCITY], + leading_agent_states[:, LeadingAgentIndex.LENGTH_REAR], + ) + + # parameters + target_velocity, min_gap_to_lead_agent, headway_time, accel_max, decel_max = ( + self._target_velocities[longitudinal_idcs], + self._min_gap_to_lead_agent[longitudinal_idcs], + self._headway_time[longitudinal_idcs], + self._accel_max[longitudinal_idcs], + self._decel_max[longitudinal_idcs], + ) + + # TODO: add as parameter + acceleration_exponent = 10 + + # convenience definitions + s_star = ( + min_gap_to_lead_agent + + v_agent * headway_time + + (v_agent * (v_agent - v_lead)) / (2 * np.sqrt(accel_max * decel_max)) + ) + + s_alpha = np.maximum(x_lead - x_agent - l_r_lead, min_gap_to_lead_agent) # clamp to avoid zero division + + # differential equations + x_agent_dot = v_agent + v_agent_dot = accel_max * (1 - (v_agent / target_velocity) ** acceleration_exponent - (s_star / s_alpha) ** 2) + + # clip values + v_agent_dot = np.clip(v_agent_dot, -decel_max, accel_max) + + next_idm_states: npt.NDArray[np.float64] = np.zeros( + (len(longitudinal_idcs), len(StateIDMIndex)), dtype=np.float64 + ) + next_idm_states[:, StateIDMIndex.PROGRESS] = x_agent + sampling_time * x_agent_dot + next_idm_states[:, StateIDMIndex.VELOCITY] = v_agent + sampling_time * v_agent_dot + + return next_idm_states diff --git a/sledge/simulation/planner/pdm_planner/proposal/pdm_generator.py b/sledge/simulation/planner/pdm_planner/proposal/pdm_generator.py new file mode 100644 index 0000000..2ecb8c8 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/proposal/pdm_generator.py @@ -0,0 +1,384 @@ +import copy +from typing import Dict, List, Optional + +import numpy as np +import numpy.typing as npt + +from shapely.geometry import Point, Polygon +from shapely.geometry.base import CAP_STYLE + +from nuplan.common.actor_state.agent import Agent +from nuplan.common.actor_state.car_footprint import CarFootprint +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.scene_object import SceneObject +from nuplan.common.actor_state.state_representation import StateSE2, TimePoint +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters +from nuplan.common.geometry.transform import transform +from nuplan.planning.simulation.trajectory.interpolated_trajectory import InterpolatedTrajectory +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.observation.pdm_observation import PDMObservation +from sledge.simulation.planner.pdm_planner.proposal.pdm_proposal import PDMProposalManager +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import state_array_to_ego_states +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import LeadingAgentIndex, StateIDMIndex, StateIndex +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle + + +class PDMGenerator: + """Class to generate proposals in PDM.""" + + def __init__( + self, + trajectory_sampling: TrajectorySampling, + proposal_sampling: TrajectorySampling, + leading_agent_update_rate: int = 2, + ): + """ + Constructor of PDMGenerator + :param trajectory_sampling: Sampling parameters for final trajectory + :param proposal_sampling: Sampling parameters for proposals + :param leading_agent_update_rate: sample update-rate of leading agent state, defaults to 2 + """ + assert ( + trajectory_sampling.interval_length == proposal_sampling.interval_length + ), "PDMGenerator: Proposals and Trajectory must have equal interval length!" + + # trajectory config + self._trajectory_sampling: int = trajectory_sampling + self._proposal_sampling: int = proposal_sampling + self._sample_interval: float = trajectory_sampling.interval_length + + # generation config + self._leading_agent_update: int = leading_agent_update_rate + + # lazy loaded + self._state_array: Optional[npt.NDArray[np.float64]] = None + self._state_idm_array: Optional[npt.NDArray[np.float64]] = None + self._leading_agent_array: Optional[npt.NDArray[np.float64]] = None + + self._proposal_manager: Optional[PDMProposalManager] = None + self._observation: Optional[PDMObservation] = None + + self._initial_ego_state: Optional[EgoState] = None + self._vehicle_parameters: Optional[VehicleParameters] = None + + # caches + self._driving_corridor_cache: Optional[Dict[int, Polygon]] = None + self._time_point_list: Optional[List[TimePoint]] = None + + def generate_proposals( + self, + initial_ego_state: EgoState, + observation: PDMObservation, + proposal_manager: PDMProposalManager, + ) -> npt.NDArray[np.float64]: + """ + Generates proposals by unrolling IDM policies vor varying paths, + and saving the proposal states in array representation. + :param initial_ego_state: state of ego-vehicle at t=0 + :param observation: PDMObservation class + :param proposal_manager: PDMProposalManager class + :return: unrolled proposal states in array representation + """ + self._reset(initial_ego_state, observation, proposal_manager) + self._initialize_time_points() + + # unroll proposals per path, to interpolate along batch-dim + lateral_batch_dict = self._get_lateral_batch_dict() + + for lateral_idx, lateral_batch_idcs in lateral_batch_dict.items(): + self._initialize_states(lateral_batch_idcs) + for time_idx in range(1, self._proposal_sampling.num_poses + 1, 1): + self._update_leading_agents(lateral_batch_idcs, time_idx) + self._update_idm_states(lateral_batch_idcs, time_idx) + self._update_states_se2(lateral_batch_idcs, time_idx) + + return self._state_array + + def generate_trajectory( + self, + proposal_idx: int, + ) -> InterpolatedTrajectory: + """ + Complete unrolling of final trajectory to number of trajectory samples. + :param proposal_idx: index of best-scored proposal + :return: InterpolatedTrajectory class + """ + assert ( + len(self._time_point_list) == self._proposal_sampling.num_poses + 1 + ), "PDMGenerator: Proposals must be generated first!" + + lateral_batch_idcs = [proposal_idx] + current_time_point = copy.deepcopy(self._time_point_list[-1]) + + for time_idx in range( + self._proposal_sampling.num_poses + 1, + self._trajectory_sampling.num_poses + 1, + 1, + ): + current_time_point += TimePoint(int(self._sample_interval * 1e6)) + self._time_point_list.append(current_time_point) + + self._update_leading_agents(lateral_batch_idcs, time_idx) + self._update_idm_states(lateral_batch_idcs, time_idx) + self._update_states_se2(lateral_batch_idcs, time_idx) + + # convert array representation to list of EgoState class + ego_states: List[EgoState] = state_array_to_ego_states( + self._state_array[proposal_idx], + self._time_point_list, + self._vehicle_parameters, + ) + return InterpolatedTrajectory(ego_states) + + def _reset( + self, + initial_ego_state: EgoState, + observation: PDMObservation, + proposal_manager: PDMProposalManager, + ) -> None: + """ + Re-initializes several class attributes for unrolling in new iteration + :param initial_ego_state: ego-vehicle state at t=0 + :param observation: PDMObservation class + :param proposal_manager: PDMProposalManager class + """ + + # lazy loading + self._proposal_manager: PDMProposalManager = proposal_manager + self._observation: PDMObservation = observation + + self._initial_ego_state = initial_ego_state + self._vehicle_parameters = initial_ego_state.car_footprint.vehicle_parameters + + # reset proposal state arrays + self._state_array: npt.NDArray[np.float64] = np.zeros( + ( + len(self._proposal_manager), + self._trajectory_sampling.num_poses + 1, + StateIndex.size(), + ), + dtype=np.float64, + ) # x, y, heading + self._state_idm_array: npt.NDArray[np.float64] = np.zeros( + (len(self._proposal_manager), self._trajectory_sampling.num_poses + 1, 2), + dtype=np.float64, + ) # progress, velocity + self._leading_agent_array: npt.NDArray[np.float64] = np.zeros( + (len(self._proposal_manager), self._trajectory_sampling.num_poses + 1, 3), + dtype=np.float64, + ) # progress, velocity, rear-length + + # reset caches + self._driving_corridor_cache: Dict[int, Polygon] = {} + + self._time_point_list: List[TimePoint] = [] + self._updated: bool = True + + def _initialize_time_points(self) -> None: + """Initializes a list of TimePoint objects for proposal horizon.""" + current_time_point = copy.deepcopy(self._initial_ego_state.time_point) + self._time_point_list = [current_time_point] + for time_idx in range(1, self._proposal_sampling.num_poses + 1, 1): + current_time_point += TimePoint(int(self._sample_interval * 1e6)) + self._time_point_list.append(copy.deepcopy(current_time_point)) + + def _initialize_states(self, lateral_batch_idcs: List[int]) -> None: + """ + Initializes all state arrays for ego, IDM, and leading agent at t=0 + :param lateral_batch_idcs: list of proposal indices, sharing a path. + """ + + # all initial states are identical for shared lateral_idx + # thus states are created for lateral_batch_idcs[0] and repeated + dummy_proposal_idx = lateral_batch_idcs[0] + + ego_position = Point(*self._initial_ego_state.rear_axle.point.array) + + ego_progress = self._proposal_manager[dummy_proposal_idx].linestring.project(ego_position) + ego_velocity = self._initial_ego_state.dynamic_car_state.rear_axle_velocity_2d.x + + self._state_idm_array[lateral_batch_idcs, 0, StateIDMIndex.PROGRESS] = ego_progress + self._state_idm_array[lateral_batch_idcs, 0, StateIDMIndex.VELOCITY] = ego_velocity + + state_array = self._proposal_manager[dummy_proposal_idx].path.interpolate([ego_progress], as_array=True)[0] + self._state_array[lateral_batch_idcs, 0, StateIndex.STATE_SE2] = state_array + + def _update_states_se2(self, lateral_batch_idcs: List[int], time_idx: int) -> None: + """ + Updates state array for ego, at current time-step. + :param lateral_batch_idcs: list of proposal indices, sharing a path. + :param time_idx: index of unrolling iteration (for proposal/trajectory samples) + """ + assert time_idx > 0, "PDMGenerator: call _initialize_states first!" + dummy_proposal_idx = lateral_batch_idcs[0] + current_progress = self._state_idm_array[lateral_batch_idcs, time_idx, StateIDMIndex.PROGRESS] + states_se2_array: npt.NDArray[np.float64] = self._proposal_manager[dummy_proposal_idx].path.interpolate( + current_progress, as_array=True + ) + self._state_array[lateral_batch_idcs, time_idx, StateIndex.STATE_SE2] = states_se2_array + + def _update_idm_states(self, lateral_batch_idcs: List[int], time_idx: int) -> None: + """ + Updates idm state array, by propagating policy for one step. + :param lateral_batch_idcs: list of proposal indices, sharing a path. + :param time_idx: index of unrolling iteration (for proposal/trajectory samples) + """ + assert time_idx > 0, "PDMGenerator: call _initialize_states first!" + longitudinal_idcs = [ + self._proposal_manager[proposal_idx].longitudinal_idx for proposal_idx in lateral_batch_idcs + ] + next_idm_states = self._proposal_manager.longitudinal_policies.propagate( + self._state_idm_array[lateral_batch_idcs, time_idx - 1], + self._leading_agent_array[lateral_batch_idcs, time_idx], + longitudinal_idcs, + self._sample_interval, + ) + self._state_idm_array[lateral_batch_idcs, time_idx] = next_idm_states + + def _update_leading_agents(self, lateral_batch_idcs: List[int], time_idx: int) -> None: + """ + Update leading agent state array by searching for agents/obstacles in driving corridor. + :param lateral_idx: index indicating the path of proposals + :param lateral_batch_idcs: list of proposal indices, sharing a path. + :param time_idx: index of unrolling iteration (for proposal/trajectory samples) + """ + assert time_idx > 0, "PDMGenerator: call _initialize_states first!" + + # update leading agent state at first call or at update rate (runtime) + update_leading_agent: bool = (time_idx % self._leading_agent_update) == 0 + + if not update_leading_agent: + self._leading_agent_array[lateral_batch_idcs, time_idx] = self._leading_agent_array[ + lateral_batch_idcs, time_idx - 1 + ] + + else: + dummy_proposal_idx = lateral_batch_idcs[0] + + leading_agent_array = np.zeros(len(LeadingAgentIndex), dtype=np.float64) + intersecting_objects: List[str] = self._get_intersecting_objects(lateral_batch_idcs, time_idx) + + # collect all leading vehicles ones for all proposals (run-time) + object_progress_dict: Dict[str, float] = {} + for object in intersecting_objects: + if object not in self._observation.collided_track_ids: + object_progress = self._proposal_manager[dummy_proposal_idx].linestring.project( + self._observation[time_idx][object].centroid + ) + object_progress_dict[object] = object_progress + + # select leading agent for each proposal individually + for proposal_idx in lateral_batch_idcs: + current_ego_progress = self._state_idm_array[proposal_idx, time_idx - 1, StateIDMIndex.PROGRESS] + + # filter all objects ahead + agents_ahead: Dict[str, float] = { + agent: progress + for agent, progress in object_progress_dict.items() + if progress > current_ego_progress + } + + if len(agents_ahead) > 0: # red light, object or agent ahead + current_state_se2 = StateSE2(*self._state_array[proposal_idx, time_idx - 1, StateIndex.STATE_SE2]) + ego_polygon: Polygon = CarFootprint.build_from_rear_axle( + current_state_se2, self._vehicle_parameters + ).oriented_box.geometry + + relative_distances = [ + ego_polygon.distance(self._observation[time_idx][agent]) for agent in agents_ahead.keys() + ] + + argmin = np.argmin(relative_distances) + nearest_agent = list(agents_ahead.keys())[argmin] + + # add rel. distance for red light, object or agent + relative_distance = current_ego_progress + relative_distances[argmin] + leading_agent_array[LeadingAgentIndex.PROGRESS] = relative_distance + + # calculate projected velocity if not red light + if self._observation.red_light_token not in nearest_agent: + leading_agent_array[LeadingAgentIndex.VELOCITY] = self._get_leading_agent_velocity( + current_state_se2.heading, + self._observation.unique_objects[nearest_agent], + ) + + else: # nothing ahead, free driving + path_length = self._proposal_manager[proposal_idx].linestring.length + path_rear = self._vehicle_parameters.length / 2 + + leading_agent_array[LeadingAgentIndex.PROGRESS] = path_length + leading_agent_array[LeadingAgentIndex.LENGTH_REAR] = path_rear + + self._leading_agent_array[proposal_idx, time_idx] = leading_agent_array + + @staticmethod + def _get_leading_agent_velocity(ego_heading: float, agent: SceneObject) -> float: + """ + Calculates velocity of leading vehicle projected to ego's heading. + :param ego_heading: heading angle [rad] + :param agent: SceneObject class + :return: projected velocity [m/s] + """ + + if isinstance(agent, Agent): # dynamic object + relative_heading = normalize_angle(agent.center.heading - ego_heading) + projected_velocity = transform( + StateSE2(agent.velocity.magnitude(), 0, 0), + StateSE2(0, 0, relative_heading).as_matrix(), + ).x + else: # static object + projected_velocity = 0.0 + + return projected_velocity + + def _get_intersecting_objects(self, lateral_batch_idcs: List[int], time_idx: int) -> List[str]: + """ + Returns and caches all intersecting objects for the proposals path and time-step. + :param lateral_batch_idcs: list of proposal indices, sharing a path + :param time_idx: index indicating the path of proposals + :return: list of object tokens + """ + dummy_proposal_idx = lateral_batch_idcs[0] + driving_corridor: Polygon = self._get_driving_corridor(dummy_proposal_idx) + return self._observation[time_idx].intersects(driving_corridor) + + def _get_driving_corridor(self, proposal_idx: int) -> Polygon: + """ + Creates and caches driving corridor of ego-vehicle for each proposal path. + :param proposal_idx: index of a proposal + :return: linestring of max trajectory distance and ego's width + """ + lateral_idx = self._proposal_manager[proposal_idx].lateral_idx + + if lateral_idx not in self._driving_corridor_cache.keys(): + ego_distance = self._state_idm_array[proposal_idx, 0, StateIDMIndex.PROGRESS] + trajectory_distance = ( + ego_distance + + abs(self._proposal_manager.max_target_velocity) + * self._trajectory_sampling.num_poses + * self._sample_interval + ) + linestring_ahead = self._proposal_manager[proposal_idx].path.substring(ego_distance, trajectory_distance) + expanded_path = linestring_ahead.buffer(self._vehicle_parameters.width / 2, cap_style=CAP_STYLE.square) + + self._driving_corridor_cache[lateral_idx] = expanded_path + + return self._driving_corridor_cache[lateral_idx] + + def _get_lateral_batch_dict(self) -> Dict[int, List[int]]: + """ + Creates a dictionary for lateral paths and their proposal indices. + :return: dictionary of lateral and proposal indices + """ + lateral_batch_dict: Dict[int, List[int]] = {} + + for proposal_idx in range(len(self._proposal_manager)): + lateral_idx = self._proposal_manager[proposal_idx].lateral_idx + + if lateral_idx not in lateral_batch_dict.keys(): + lateral_batch_dict[lateral_idx] = [proposal_idx] + else: + lateral_batch_dict[lateral_idx].append(proposal_idx) + + return lateral_batch_dict diff --git a/sledge/simulation/planner/pdm_planner/proposal/pdm_proposal.py b/sledge/simulation/planner/pdm_planner/proposal/pdm_proposal.py new file mode 100644 index 0000000..b5940a9 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/proposal/pdm_proposal.py @@ -0,0 +1,96 @@ +from dataclasses import dataclass +from typing import List + +from shapely.geometry import LineString + +from sledge.simulation.planner.pdm_planner.proposal.batch_idm_policy import BatchIDMPolicy +from sledge.simulation.planner.pdm_planner.utils.pdm_path import PDMPath + + +@dataclass +class PDMProposal: + """Dataclass for storing proposal information.""" + + proposal_idx: int + lateral_idx: int + longitudinal_idx: int + path: PDMPath + + @property + def linestring(self) -> LineString: + """Getter for linestring of proposal's path.""" + return self.path.linestring + + @property + def length(self): + """Getter for length [m] of proposal's path.""" + return self.path.length + + +class PDMProposalManager: + """Class to store and manage lateral and longitudinal combination of proposals.""" + + def __init__( + self, + lateral_proposals: List[PDMPath], + longitudinal_policies: BatchIDMPolicy, + ): + """ + Constructor for PDMProposalManager + :param lateral_proposals: list of path's to follow + :param longitudinal_policies: IDM policy class (batch-wise) + """ + + self._num_lateral_proposals: int = len(lateral_proposals) + self._num_longitudinal_proposals: int = longitudinal_policies.num_policies + self._longitudinal_policies: BatchIDMPolicy = longitudinal_policies + + self._proposals: List[PDMProposal] = [] + proposal_idx = 0 + + for lateral_idx in range(self._num_lateral_proposals): + for longitudinal_idx in range(self._num_longitudinal_proposals): + self._proposals.append( + PDMProposal( + proposal_idx=proposal_idx, + lateral_idx=lateral_idx, + longitudinal_idx=longitudinal_idx, + path=lateral_proposals[lateral_idx], + ) + ) + proposal_idx += 1 + + def __len__(self) -> int: + """Returns number of proposals (paths x policies).""" + return len(self._proposals) + + def __getitem__(self, proposal_idx) -> PDMProposal: + """ + Returns the requested proposal. + :param proposal_idx: index for each proposal + :return: PDMProposal dataclass + """ + return self._proposals[proposal_idx] + + def update(self, speed_limit_mps: float) -> None: + """ + Updates target velocities of IDM policies with current speed-limit. + :param speed_limit_mps: current speed-limit [m/s] + """ + self._longitudinal_policies.update(speed_limit_mps) + + @property + def num_lateral_proposals(self) -> int: + return self._num_lateral_proposals + + @property + def num_longitudinal_proposals(self) -> int: + return self._longitudinal_policies._num_longitudinal_proposals + + @property + def max_target_velocity(self) -> float: + return self._longitudinal_policies.max_target_velocity + + @property + def longitudinal_policies(self) -> BatchIDMPolicy: + return self._longitudinal_policies diff --git a/sledge/simulation/planner/pdm_planner/scoring/__init__.py b/sledge/simulation/planner/pdm_planner/scoring/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/planner/pdm_planner/scoring/pdm_comfort_metrics.py b/sledge/simulation/planner/pdm_planner/scoring/pdm_comfort_metrics.py new file mode 100644 index 0000000..2a96fc0 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/scoring/pdm_comfort_metrics.py @@ -0,0 +1,336 @@ +from typing import Optional + +import numpy as np +import numpy.typing as npt +from scipy.signal import savgol_filter + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import StateIndex + +# TODO: Refactor & add to config + +# (1) ego_jerk_metric, +max_abs_mag_jerk = 8.37 # [m/s^3] + +# (2) ego_lat_acceleration_metric +max_abs_lat_accel = 4.89 # [m/s^2] + +# (3) ego_lon_acceleration_metric +max_lon_accel = 2.40 # [m/s^2] +min_lon_accel = -4.05 + +# (4) ego_yaw_acceleration_metric +max_abs_yaw_accel = 1.93 # [rad/s^2] + +# (5) ego_lon_jerk_metric +max_abs_lon_jerk = 4.13 # [m/s^3] + +# (6) ego_yaw_rate_metric +max_abs_yaw_rate = 0.95 # [rad/s] + + +def _extract_ego_acceleration( + states: npt.NDArray[np.float64], + acceleration_coordinate: str, + decimals: int = 8, + poly_order: int = 2, + window_length: int = 8, +) -> npt.NDArray[np.float32]: + """ + Extract acceleration of ego pose in simulation history over batch-dim + :param states: array representation of ego state values + :param acceleration_coordinate: string of axis to extract + :param decimals: decimal precision, defaults to 8 + :param poly_order: polynomial order, defaults to 2 + :param window_length: window size for extraction, defaults to 8 + :raises ValueError: when coordinate not available + :return: array containing acceleration values + """ + + n_batch, n_time, n_states = states.shape + if acceleration_coordinate == "x": + acceleration: npt.NDArray[np.float64] = states[..., StateIndex.ACCELERATION_X] + + elif acceleration_coordinate == "y": + acceleration: npt.NDArray[np.float64] = states[..., StateIndex.ACCELERATION_Y] + + elif acceleration_coordinate == "magnitude": + acceleration: npt.NDArray[np.float64] = np.hypot( + states[..., StateIndex.ACCELERATION_X], + states[..., StateIndex.ACCELERATION_Y], + ) + else: + raise ValueError( + f"acceleration_coordinate option: {acceleration_coordinate} not available. " + f"Available options are: x, y or magnitude" + ) + + acceleration = savgol_filter( + acceleration, + polyorder=poly_order, + window_length=min(window_length, n_time), + axis=-1, + ) + acceleration = np.round(acceleration, decimals=decimals) + return acceleration + + +def _extract_ego_jerk( + states: npt.NDArray[np.float64], + acceleration_coordinate: str, + time_steps_s: npt.NDArray[np.float64], + decimals: int = 8, + deriv_order: int = 1, + poly_order: int = 2, + window_length: int = 15, +) -> npt.NDArray[np.float32]: + """ + Extract jerk of ego pose in simulation history over batch-dim + :param states: array representation of ego state values + :param acceleration_coordinate: string of axis to extract + :param time_steps_s: time steps [s] of time dim + :param decimals: decimal precision, defaults to 8 + :param deriv_order: order of derivative, defaults to 1 + :param poly_order: polynomial order, defaults to 2 + :param window_length: window size for extraction, defaults to 15 + :return: array containing jerk values + """ + n_batch, n_time, n_states = states.shape + ego_acceleration = _extract_ego_acceleration(states, acceleration_coordinate=acceleration_coordinate) + jerk = _approximate_derivatives( + ego_acceleration, + time_steps_s, + deriv_order=deriv_order, + poly_order=poly_order, + window_length=min(window_length, n_time), + ) + jerk = np.round(jerk, decimals=decimals) + return jerk + + +def _extract_ego_yaw_rate( + states: npt.NDArray[np.float64], + time_steps_s: npt.NDArray[np.float64], + deriv_order: int = 1, + poly_order: int = 2, + decimals: int = 8, + window_length: int = 15, +) -> npt.NDArray[np.float32]: + """ + Extract yaw-rate of simulation history over batch-dim + :param states: array representation of ego state values + :param time_steps_s: time steps [s] of time dim + :param deriv_order: order of derivative, defaults to 1 + :param poly_order: polynomial order, defaults to 2 + :param decimals: decimal precision, defaults to 8 + :param window_length: window size for extraction, defaults to 15 + :return: array containing ego's yaw rate + """ + ego_headings = states[..., StateIndex.HEADING] + ego_yaw_rate = _approximate_derivatives( + _phase_unwrap(ego_headings), + time_steps_s, + deriv_order=deriv_order, + poly_order=poly_order, + ) # convert to seconds + ego_yaw_rate = np.round(ego_yaw_rate, decimals=decimals) + return ego_yaw_rate + + +def _phase_unwrap(headings: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]: + """ + Returns an array of heading angles equal mod 2 pi to the input heading angles, + and such that the difference between successive output angles is less than or + equal to pi radians in absolute value + :param headings: An array of headings (radians) + :return The phase-unwrapped equivalent headings. + """ + # There are some jumps in the heading (e.g. from -np.pi to +np.pi) which causes approximation of yaw to be very large. + # We want unwrapped[j] = headings[j] - 2*pi*adjustments[j] for some integer-valued adjustments making the absolute value of + # unwrapped[j+1] - unwrapped[j] at most pi: + # -pi <= headings[j+1] - headings[j] - 2*pi*(adjustments[j+1] - adjustments[j]) <= pi + # -1/2 <= (headings[j+1] - headings[j])/(2*pi) - (adjustments[j+1] - adjustments[j]) <= 1/2 + # So adjustments[j+1] - adjustments[j] = round((headings[j+1] - headings[j]) / (2*pi)). + two_pi = 2.0 * np.pi + adjustments = np.zeros_like(headings) + adjustments[..., 1:] = np.cumsum(np.round(np.diff(headings, axis=-1) / two_pi), axis=-1) + unwrapped = headings - two_pi * adjustments + return unwrapped + + +def _approximate_derivatives( + y: npt.NDArray[np.float32], + x: npt.NDArray[np.float32], + window_length: int = 5, + poly_order: int = 2, + deriv_order: int = 1, + axis: int = -1, +) -> npt.NDArray[np.float32]: + """ + Given two equal-length sequences y and x, compute an approximation to the n-th + derivative of some function interpolating the (x, y) data points, and return its + values at the x's. We assume the x's are increasing and equally-spaced. + :param y: The dependent variable (say of length n) + :param x: The independent variable (must have the same length n). Must be strictly + increasing and equally-spaced. + :param window_length: The order (default 5) of the Savitsky-Golay filter used. + (Ignored if the x's are not equally-spaced.) Must be odd and at least 3 + :param poly_order: The degree (default 2) of the filter polynomial used. Must + be less than the window_length + :param deriv_order: The order of derivative to compute (default 1) + :param axis: The axis of the array x along which the filter is to be applied. Default is -1. + :return Derivatives. + """ + window_length = min(window_length, len(x)) + + if not (poly_order < window_length): + raise ValueError(f"{poly_order} < {window_length} does not hold!") + + dx = np.diff(x, axis=-1) + if not (dx > 0).all(): + raise RuntimeError("dx is not monotonically increasing!") + + dx = dx.mean() + derivative: npt.NDArray[np.float32] = savgol_filter( + y, + polyorder=poly_order, + window_length=window_length, + deriv=deriv_order, + delta=dx, + axis=axis, + ) + return derivative + + +def _within_bound( + metric: npt.NDArray[np.float64], + min_bound: Optional[float] = None, + max_bound: Optional[float] = None, +) -> npt.NDArray[np.bool_]: + """ + Determines wether values in batch-dim are within bounds. + :param metric: metric values + :param min_bound: minimum bound, defaults to None + :param max_bound: maximum bound, defaults to None + :return: array of booleans wether metric values are within bounds + """ + min_bound = min_bound if min_bound else float(-np.inf) + max_bound = max_bound if max_bound else float(np.inf) + metric_values = np.array(metric) + metric_within_bound = (metric_values > min_bound) & (metric_values < max_bound) + return np.all(metric_within_bound, axis=-1) + + +def _compute_lon_acceleration( + states: npt.NDArray[np.float64], time_steps_s: npt.NDArray[np.float64] +) -> npt.NDArray[np.bool_]: + """ + Compute longitudinal acceleration over batch-dim of simulated proposals + :param states: array representation of ego state values + :param time_steps_s: time steps [s] of time dim + :return: longitudinal acceleration within bound + """ + n_batch, n_time, n_states = states.shape + lon_acceleration = _extract_ego_acceleration(states, acceleration_coordinate="x", window_length=n_time) + return _within_bound(lon_acceleration, min_bound=min_lon_accel, max_bound=max_lon_accel) + + +def _compute_lat_acceleration( + states: npt.NDArray[np.float64], time_steps_s: npt.NDArray[np.float64] +) -> npt.NDArray[np.bool_]: + """ + Compute lateral acceleration over batch-dim of simulated proposals + :param states: array representation of ego state values + :param time_steps_s: time steps [s] of time dim + :return: lateral acceleration within bound + """ + n_batch, n_time, n_states = states.shape + lat_acceleration = _extract_ego_acceleration(states, acceleration_coordinate="y", window_length=n_time) + return _within_bound(lat_acceleration, min_bound=-max_abs_lat_accel, max_bound=max_abs_lat_accel) + + +def _compute_jerk_metric( + states: npt.NDArray[np.float64], time_steps_s: npt.NDArray[np.float64] +) -> npt.NDArray[np.bool_]: + """ + Compute absolute jerk over batch-dim of simulated proposals + :param states: array representation of ego state values + :param time_steps_s: time steps [s] of time dim + :return: absolute jerk within bound + """ + n_batch, n_time, n_states = states.shape + jerk_metric = _extract_ego_jerk( + states, + acceleration_coordinate="magnitude", + time_steps_s=time_steps_s, + window_length=n_time, + ) + return _within_bound(jerk_metric, min_bound=-max_abs_mag_jerk, max_bound=max_abs_mag_jerk) + + +def _compute_lon_jerk_metric( + states: npt.NDArray[np.float64], time_steps_s: npt.NDArray[np.float64] +) -> npt.NDArray[np.bool_]: + """ + Compute longitudinal jerk over batch-dim of simulated proposals + :param states: array representation of ego state values + :param time_steps_s: time steps [s] of time dim + :return: longitudinal jerk within bound + """ + n_batch, n_time, n_states = states.shape + lon_jerk_metric = _extract_ego_jerk( + states, + acceleration_coordinate="x", + time_steps_s=time_steps_s, + window_length=n_time, + ) + return _within_bound(lon_jerk_metric, min_bound=-max_abs_lon_jerk, max_bound=max_abs_lon_jerk) + + +def _compute_yaw_accel(states: npt.NDArray[np.float64], time_steps_s: npt.NDArray[np.float64]) -> npt.NDArray[np.bool_]: + """ + Compute acceleration of yaw-angle over batch-dim of simulated proposals + :param states: array representation of ego state values + :param time_steps_s: time steps [s] of time dim + :return: acceleration of yaw-angle within bound + """ + n_batch, n_time, n_states = states.shape + yaw_accel_metric = _extract_ego_yaw_rate(states, time_steps_s, deriv_order=2, poly_order=3, window_length=n_time) + return _within_bound(yaw_accel_metric, min_bound=-max_abs_yaw_accel, max_bound=max_abs_yaw_accel) + + +def _compute_yaw_rate(states: npt.NDArray[np.float64], time_steps_s: npt.NDArray[np.float64]) -> npt.NDArray[np.bool_]: + """ + Compute velocity of yaw-angle over batch-dim of simulated proposals + :param states: array representation of ego state values + :param time_steps_s: time steps [s] of time dim + :return: velocity of yaw-angle within bound + """ + n_batch, n_time, n_states = states.shape + yaw_rate_metric = _extract_ego_yaw_rate(states, time_steps_s, window_length=n_time) + return _within_bound(yaw_rate_metric, min_bound=-max_abs_yaw_rate, max_bound=max_abs_yaw_rate) + + +def ego_is_comfortable(states: npt.NDArray[np.float64], time_point_s: npt.NDArray[np.float64]) -> npt.NDArray[np.bool_]: + """ + Accumulates all within-bound comfortability metrics + :param states: array representation of ego state values + :param time_point_s: time steps [s] of time dim + :return: _description_ + """ + n_batch, n_time, n_states = states.shape + assert n_time == len(time_point_s) + assert n_states == StateIndex.size() + + comfort_metric_functions = [ + _compute_lon_acceleration, + _compute_lat_acceleration, + _compute_jerk_metric, + _compute_lon_jerk_metric, + _compute_yaw_accel, + _compute_yaw_rate, + ] + results: npt.NDArray[np.bool_] = np.zeros((n_batch, len(comfort_metric_functions)), dtype=np.bool_) + for idx, metric_function in enumerate(comfort_metric_functions): + results[:, idx] = metric_function(states, time_point_s) + + return results diff --git a/sledge/simulation/planner/pdm_planner/scoring/pdm_scorer.py b/sledge/simulation/planner/pdm_planner/scoring/pdm_scorer.py new file mode 100644 index 0000000..13d35ac --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/scoring/pdm_scorer.py @@ -0,0 +1,497 @@ +import copy +from typing import Dict, List, Optional + +import numpy as np +import numpy.typing as npt + +from shapely import Point, creation + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateSE2 +from nuplan.common.actor_state.tracked_objects_types import AGENT_TYPES +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject +from nuplan.common.maps.maps_datatypes import SemanticMapLayer +from nuplan.planning.metrics.utils.collision_utils import CollisionType +from nuplan.planning.simulation.observation.idm.utils import is_agent_ahead, is_agent_behind +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.observation.pdm_observation import PDMObservation +from sledge.simulation.planner.pdm_planner.observation.pdm_occupancy_map import PDMOccupancyMap +from sledge.simulation.planner.pdm_planner.scoring.pdm_comfort_metrics import ego_is_comfortable +from sledge.simulation.planner.pdm_planner.scoring.pdm_scorer_utils import get_collision_type +from sledge.simulation.planner.pdm_planner.utils.pdm_path import PDMPath +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import ( + coords_array_to_polygon_array, + state_array_to_coords_array, +) +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import ( + BBCoordsIndex, + EgoAreaIndex, + MultiMetricIndex, + StateIndex, + WeightedMetricIndex, +) + +# constants +# TODO: Add to config +WEIGHTED_METRICS_WEIGHTS = np.zeros(len(WeightedMetricIndex), dtype=np.float64) +WEIGHTED_METRICS_WEIGHTS[WeightedMetricIndex.PROGRESS] = 5.0 +WEIGHTED_METRICS_WEIGHTS[WeightedMetricIndex.TTC] = 5.0 +WEIGHTED_METRICS_WEIGHTS[WeightedMetricIndex.COMFORTABLE] = 2.0 + +# TODO: Add to config +DRIVING_DIRECTION_COMPLIANCE_THRESHOLD = 2.0 # [m] (driving direction) +DRIVING_DIRECTION_VIOLATION_THRESHOLD = 6.0 # [m] (driving direction) +STOPPED_SPEED_THRESHOLD = 5e-03 # [m/s] (ttc) +PROGRESS_DISTANCE_THRESHOLD = 0.1 # [m] (progress) + + +class PDMScorer: + """Class to score proposals in PDM pipeline. Re-implements nuPlan's closed-loop metrics.""" + + def __init__(self, proposal_sampling: TrajectorySampling): + """ + Constructor of PDMScorer + :param proposal_sampling: Sampling parameters for proposals + """ + self._proposal_sampling = proposal_sampling + + # lazy loaded + self._initial_ego_state: Optional[EgoState] = None + self._observation: Optional[PDMObservation] = None + self._centerline: Optional[PDMPath] = None + self._route_lane_dict: Optional[Dict[str, LaneGraphEdgeMapObject]] = None + self._drivable_area_map: Optional[PDMOccupancyMap] = None + self._map_api: Optional[AbstractMap] = None + + self._num_proposals: Optional[int] = None + self._states: Optional[npt.NDArray[np.float64]] = None + self._ego_coords: Optional[npt.NDArray[np.float64]] = None + self._ego_polygons: Optional[npt.NDArray[np.object_]] = None + + self._ego_areas: Optional[npt.NDArray[np.bool_]] = None + + self._multi_metrics: Optional[npt.NDArray[np.float64]] = None + self._weighted_metrics: Optional[npt.NDArray[np.float64]] = None + self._progress_raw: Optional[npt.NDArray[np.float64]] = None + + self._collision_time_idcs: Optional[npt.NDArray[np.float64]] = None + self._ttc_time_idcs: Optional[npt.NDArray[np.float64]] = None + + def time_to_at_fault_collision(self, proposal_idx: int) -> float: + """ + Returns time to at-fault collision for given proposal + :param proposal_idx: index for proposal + :return: time to infraction + """ + return self._collision_time_idcs[proposal_idx] * self._proposal_sampling.interval_length + + def time_to_ttc_infraction(self, proposal_idx: int) -> float: + """ + Returns time to ttc infraction for given proposal + :param proposal_idx: index for proposal + :return: time to infraction + """ + return self._ttc_time_idcs[proposal_idx] * self._proposal_sampling.interval_length + + def score_proposals( + self, + states: npt.NDArray[np.float64], + initial_ego_state: EgoState, + observation: PDMObservation, + centerline: PDMPath, + route_lane_dict: Dict[str, LaneGraphEdgeMapObject], + drivable_area_map: PDMOccupancyMap, + map_api: AbstractMap, + ) -> npt.NDArray[np.float64]: + """ + Scores proposal similar to nuPlan's closed-loop metrics + :param states: array representation of simulated proposals + :param initial_ego_state: ego-vehicle state at current iteration + :param observation: PDM's observation class + :param centerline: path of the centerline + :param route_lane_dict: dictionary containing on-route lanes + :param drivable_area_map: Occupancy map of drivable are polygons + :param map_api: map object + :return: array containing score of each proposal + """ + + # initialize & lazy load class values + self._reset( + states, + initial_ego_state, + observation, + centerline, + route_lane_dict, + drivable_area_map, + map_api, + ) + + # fill value ego-area array (used across multiple metrics) + self._calculate_ego_area() + + # 1. multiplicative metrics + self._calculate_no_at_fault_collision() + self._calculate_driving_direction_compliance() + self._calculate_drivable_area_compliance() + + # 2. weighted metrics + self._calculate_progress() + self._calculate_ttc() + self._calculate_is_comfortable() + + return self._aggregate_scores() + + def _aggregate_scores(self) -> npt.NDArray[np.float64]: + """ + Aggregates metrics with multiplicative and weighted average. + :return: array containing score of each proposal + """ + + # accumulate multiplicative metrics + multiplicate_metric_scores = self._multi_metrics.prod(axis=0) + + # normalize and fill progress values + raw_progress = self._progress_raw * multiplicate_metric_scores + max_raw_progress = np.max(raw_progress) + if max_raw_progress > PROGRESS_DISTANCE_THRESHOLD: + normalized_progress = raw_progress / max_raw_progress + else: + normalized_progress = np.ones(len(raw_progress), dtype=np.float64) + normalized_progress[multiplicate_metric_scores == 0.0] = 0.0 + self._weighted_metrics[WeightedMetricIndex.PROGRESS] = normalized_progress + + # accumulate weighted metrics + weighted_metric_scores = (self._weighted_metrics * WEIGHTED_METRICS_WEIGHTS[..., None]).sum(axis=0) + weighted_metric_scores /= WEIGHTED_METRICS_WEIGHTS.sum() + + # calculate final scores + final_scores = multiplicate_metric_scores * weighted_metric_scores + + return final_scores + + def _reset( + self, + states: npt.NDArray[np.float64], + initial_ego_state: EgoState, + observation: PDMObservation, + centerline: PDMPath, + route_lane_dict: Dict[str, LaneGraphEdgeMapObject], + drivable_area_map: PDMOccupancyMap, + map_api: AbstractMap, + ) -> None: + """ + Resets metric values and lazy loads input classes. + :param states: array representation of simulated proposals + :param initial_ego_state: ego-vehicle state at current iteration + :param observation: PDM's observation class + :param centerline: path of the centerline + :param route_lane_dict: dictionary containing on-route lanes + :param drivable_area_map: Occupancy map of drivable are polygons + :param map_api: map object + """ + assert states.ndim == 3 + assert states.shape[1] == self._proposal_sampling.num_poses + 1 + assert states.shape[2] == StateIndex.size() + + self._initial_ego_state = initial_ego_state + self._observation = observation + self._centerline = centerline + self._route_lane_dict = route_lane_dict + self._drivable_area_map = drivable_area_map + self._map_api = map_api + + self._num_proposals = states.shape[0] + + # save ego state values + self._states = states + + # calculate coordinates of ego corners and center + self._ego_coords = state_array_to_coords_array(states, initial_ego_state.car_footprint.vehicle_parameters) + + # initialize all ego polygons from corners + self._ego_polygons = coords_array_to_polygon_array(self._ego_coords) + + # zero initialize all remaining arrays. + self._ego_areas = np.zeros( + ( + self._num_proposals, + self._proposal_sampling.num_poses + 1, + len(EgoAreaIndex), + ), + dtype=np.bool_, + ) + self._multi_metrics = np.zeros((len(MultiMetricIndex), self._num_proposals), dtype=np.float64) + self._weighted_metrics = np.zeros((len(WeightedMetricIndex), self._num_proposals), dtype=np.float64) + self._progress_raw = np.zeros(self._num_proposals, dtype=np.float64) + + # initialize infraction arrays with infinity (meaning no infraction occurs) + self._collision_time_idcs = np.zeros(self._num_proposals, dtype=np.float64) + self._ttc_time_idcs = np.zeros(self._num_proposals, dtype=np.float64) + self._collision_time_idcs.fill(np.inf) + self._ttc_time_idcs.fill(np.inf) + + def _calculate_ego_area(self) -> None: + """ + Determines the area of proposals over time. + Areas are (1) in multiple lanes, (2) non-drivable area, or (3) oncoming traffic + """ + + n_proposals, n_horizon, n_points, _ = self._ego_coords.shape + coordinates = self._ego_coords.reshape(n_proposals * n_horizon * n_points, 2) + + in_polygons = self._drivable_area_map.points_in_polygons(coordinates) + in_polygons = in_polygons.reshape(len(self._drivable_area_map), n_proposals, n_horizon, n_points).transpose( + 1, 2, 0, 3 + ) # shape: n_proposals, n_horizon, n_polygons, n_points + + drivable_area_on_route_idcs: List[int] = [ + idx for idx, token in enumerate(self._drivable_area_map.tokens) if token in self._route_lane_dict.keys() + ] # index mask for on-route lanes + + corners_in_polygon = in_polygons[..., :-1] # ignore center coordinate + center_in_polygon = in_polygons[..., -1] # only center + + # in_multiple_lanes: if + # - more than one drivable polygon contains at least one corner + # - no polygon contains all corners + batch_multiple_lanes_mask = np.zeros((n_proposals, n_horizon), dtype=np.bool_) + batch_multiple_lanes_mask = (corners_in_polygon.sum(axis=-1) > 0).sum(axis=-1) > 1 + + batch_not_single_lanes_mask = np.zeros((n_proposals, n_horizon), dtype=np.bool_) + batch_not_single_lanes_mask = np.all(corners_in_polygon.sum(axis=-1) != 4, axis=-1) + + multiple_lanes_mask = np.logical_and(batch_multiple_lanes_mask, batch_not_single_lanes_mask) + self._ego_areas[multiple_lanes_mask, EgoAreaIndex.MULTIPLE_LANES] = True + + # in_nondrivable_area: if at least one corner is not within any drivable polygon + batch_nondrivable_area_mask = np.zeros((n_proposals, n_horizon), dtype=np.bool_) + batch_nondrivable_area_mask = (corners_in_polygon.sum(axis=-2) > 0).sum(axis=-1) < 4 + self._ego_areas[batch_nondrivable_area_mask, EgoAreaIndex.NON_DRIVABLE_AREA] = True + + # in_oncoming_traffic: if center not in any drivable polygon that is on-route + batch_oncoming_traffic_mask = np.zeros((n_proposals, n_horizon), dtype=np.bool_) + batch_oncoming_traffic_mask = center_in_polygon[..., drivable_area_on_route_idcs].sum(axis=-1) == 0 + self._ego_areas[batch_oncoming_traffic_mask, EgoAreaIndex.ONCOMING_TRAFFIC] = True + + def _calculate_no_at_fault_collision(self) -> None: + """ + Re-implementation of nuPlan's at-fault collision metric. + """ + no_collision_scores = np.ones(self._num_proposals, dtype=np.float64) + + proposal_collided_track_ids = { + proposal_idx: copy.deepcopy(self._observation.collided_track_ids) + for proposal_idx in range(self._num_proposals) + } + + for time_idx in range(self._proposal_sampling.num_poses + 1): + ego_polygons = self._ego_polygons[:, time_idx] + intersecting = self._observation[time_idx].query(ego_polygons, predicate="intersects") + + if len(intersecting) == 0: + continue + + for proposal_idx, geometry_idx in zip(intersecting[0], intersecting[1]): + token = self._observation[time_idx].tokens[geometry_idx] + if (self._observation.red_light_token in token) or (token in proposal_collided_track_ids[proposal_idx]): + continue + + ego_in_multiple_lanes_or_nondrivable_area = ( + self._ego_areas[proposal_idx, time_idx, EgoAreaIndex.MULTIPLE_LANES] + or self._ego_areas[proposal_idx, time_idx, EgoAreaIndex.NON_DRIVABLE_AREA] + ) + + tracked_object = self._observation.unique_objects[token] + + # classify collision + collision_type: CollisionType = get_collision_type( + self._states[proposal_idx, time_idx], + self._ego_polygons[proposal_idx, time_idx], + tracked_object, + self._observation[time_idx][token], + ) + collisions_at_stopped_track_or_active_front: bool = collision_type in [ + CollisionType.ACTIVE_FRONT_COLLISION, + CollisionType.STOPPED_TRACK_COLLISION, + ] + collision_at_lateral: bool = collision_type == CollisionType.ACTIVE_LATERAL_COLLISION + + # 1. at fault collision + if collisions_at_stopped_track_or_active_front or ( + ego_in_multiple_lanes_or_nondrivable_area and collision_at_lateral + ): + no_at_fault_collision_score = 0.0 if tracked_object.tracked_object_type in AGENT_TYPES else 0.5 + no_collision_scores[proposal_idx] = np.minimum( + no_collision_scores[proposal_idx], no_at_fault_collision_score + ) + self._collision_time_idcs[proposal_idx] = min(time_idx, self._collision_time_idcs[proposal_idx]) + + else: # 2. no at fault collision + proposal_collided_track_ids[proposal_idx].append(token) + + self._multi_metrics[MultiMetricIndex.NO_COLLISION] = no_collision_scores + + def _calculate_ttc(self): + """ + Re-implementation of nuPlan's time-to-collision metric. + """ + + ttc_scores = np.ones(self._num_proposals, dtype=np.float64) + temp_collided_track_ids = { + proposal_idx: copy.deepcopy(self._observation.collided_track_ids) + for proposal_idx in range(self._num_proposals) + } + + # calculate TTC for 1s in the future with less temporal resolution. + future_time_idcs = np.arange(0, 10, 3) + n_future_steps = len(future_time_idcs) + + # create polygons for each ego position and 1s future projection + coords_exterior = self._ego_coords.copy() + coords_exterior[:, :, BBCoordsIndex.CENTER, :] = coords_exterior[:, :, BBCoordsIndex.FRONT_LEFT, :] + coords_exterior_time_steps = np.repeat(coords_exterior[:, :, None], n_future_steps, axis=2) + + speeds = np.hypot( + self._states[..., StateIndex.VELOCITY_X], + self._states[..., StateIndex.VELOCITY_Y], + ) + + dxy_per_s = np.stack( + [ + np.cos(self._states[..., StateIndex.HEADING]) * speeds, + np.sin(self._states[..., StateIndex.HEADING]) * speeds, + ], + axis=-1, + ) + + for idx, future_time_idx in enumerate(future_time_idcs): + delta_t = float(future_time_idx) * self._proposal_sampling.interval_length + coords_exterior_time_steps[:, :, idx] = ( + coords_exterior_time_steps[:, :, idx] + dxy_per_s[:, :, None] * delta_t + ) + + polygons = creation.polygons(coords_exterior_time_steps) + + # check collision for each proposal and projection + for time_idx in range(self._proposal_sampling.num_poses + 1): + for step_idx, future_time_idx in enumerate(future_time_idcs): + current_time_idx = time_idx + future_time_idx + polygons_at_time_step = polygons[:, time_idx, step_idx] + intersecting = self._observation[current_time_idx].query(polygons_at_time_step, predicate="intersects") + + if len(intersecting) == 0: + continue + + for proposal_idx, geometry_idx in zip(intersecting[0], intersecting[1]): + token = self._observation[current_time_idx].tokens[geometry_idx] + if ( + (self._observation.red_light_token in token) + or (token in temp_collided_track_ids[proposal_idx]) + or (speeds[proposal_idx, time_idx] < STOPPED_SPEED_THRESHOLD) + ): + continue + + ego_in_multiple_lanes_or_nondrivable_area = ( + self._ego_areas[proposal_idx, time_idx, EgoAreaIndex.MULTIPLE_LANES] + or self._ego_areas[proposal_idx, time_idx, EgoAreaIndex.NON_DRIVABLE_AREA] + ) + ego_rear_axle: StateSE2 = StateSE2(*self._states[proposal_idx, time_idx, StateIndex.STATE_SE2]) + + centroid = self._observation[current_time_idx][token].centroid + track_heading = self._observation.unique_objects[token].box.center.heading + track_state = StateSE2(centroid.x, centroid.y, track_heading) + if is_agent_ahead(ego_rear_axle, track_state) or ( + ( + ego_in_multiple_lanes_or_nondrivable_area + or self._map_api.is_in_layer(ego_rear_axle, layer=SemanticMapLayer.INTERSECTION) + ) + and not is_agent_behind(ego_rear_axle, track_state) + ): + ttc_scores[proposal_idx] = np.minimum(ttc_scores[proposal_idx], 0.0) + self._ttc_time_idcs[proposal_idx] = min(time_idx, self._ttc_time_idcs[proposal_idx]) + else: + temp_collided_track_ids[proposal_idx].append(token) + + self._weighted_metrics[WeightedMetricIndex.TTC] = ttc_scores + + def _calculate_progress(self) -> None: + """ + Re-implementation of nuPlan's progress metric (non-normalized). + Calculates progress along the centerline. + """ + + # calculate raw progress in meter + progress_in_meter = np.zeros(self._num_proposals, dtype=np.float64) + for proposal_idx in range(self._num_proposals): + start_point = Point(*self._ego_coords[proposal_idx, 0, BBCoordsIndex.CENTER]) + end_point = Point(*self._ego_coords[proposal_idx, -1, BBCoordsIndex.CENTER]) + progress = self._centerline.project([start_point, end_point]) + progress_in_meter[proposal_idx] = progress[1] - progress[0] + + self._progress_raw = progress_in_meter + + def _calculate_is_comfortable(self) -> None: + """ + Re-implementation of nuPlan's comfortability metric. + """ + time_point_s: npt.NDArray[np.float64] = ( + np.arange(0, self._proposal_sampling.num_poses + 1).astype(np.float64) + * self._proposal_sampling.interval_length + ) + is_comfortable = ego_is_comfortable(self._states, time_point_s) + self._weighted_metrics[WeightedMetricIndex.COMFORTABLE] = np.all(is_comfortable, axis=-1) + + def _calculate_drivable_area_compliance(self) -> None: + """ + Re-implementation of nuPlan's drivable area compliance metric + """ + drivable_area_compliance_scores = np.ones(self._num_proposals, dtype=np.float64) + off_road_mask = self._ego_areas[:, :, EgoAreaIndex.NON_DRIVABLE_AREA].any(axis=-1) + drivable_area_compliance_scores[off_road_mask] = 0.0 + self._multi_metrics[MultiMetricIndex.DRIVABLE_AREA] = drivable_area_compliance_scores + + def _calculate_driving_direction_compliance(self) -> None: + """ + Re-implementation of nuPlan's driving direction compliance metric + """ + center_coordinates = self._ego_coords[:, :, BBCoordsIndex.CENTER] + cum_progress = np.zeros( + (self._num_proposals, self._proposal_sampling.num_poses + 1), + dtype=np.float64, + ) + cum_progress[:, 1:] = ((center_coordinates[:, 1:] - center_coordinates[:, :-1]) ** 2.0).sum(axis=-1) ** 0.5 + + # mask out progress along the driving direction + oncoming_traffic_masks = self._ego_areas[:, :, EgoAreaIndex.ONCOMING_TRAFFIC] + cum_progress[~oncoming_traffic_masks] = 0.0 + + driving_direction_compliance_scores = np.ones(self._num_proposals, dtype=np.float64) + + for proposal_idx in range(self._num_proposals): + oncoming_traffic_progress, oncoming_traffic_mask = ( + cum_progress[proposal_idx], + oncoming_traffic_masks[proposal_idx], + ) + + # split progress whenever ego changes traffic direction + oncoming_progress_splits = np.split( + oncoming_traffic_progress, + np.where(np.diff(oncoming_traffic_mask))[0] + 1, + ) + + # sum up progress of splitted intervals + # Note: splits along the driving direction will have a sum of zero. + max_oncoming_traffic_progress = max( + oncoming_progress.sum() for oncoming_progress in oncoming_progress_splits + ) + + if max_oncoming_traffic_progress < DRIVING_DIRECTION_COMPLIANCE_THRESHOLD: + driving_direction_compliance_scores[proposal_idx] = 1.0 + elif max_oncoming_traffic_progress < DRIVING_DIRECTION_VIOLATION_THRESHOLD: + driving_direction_compliance_scores[proposal_idx] = 0.5 + else: + driving_direction_compliance_scores[proposal_idx] = 0.0 + + self._multi_metrics[MultiMetricIndex.DRIVING_DIRECTION] = driving_direction_compliance_scores diff --git a/sledge/simulation/planner/pdm_planner/scoring/pdm_scorer_utils.py b/sledge/simulation/planner/pdm_planner/scoring/pdm_scorer_utils.py new file mode 100644 index 0000000..4891794 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/scoring/pdm_scorer_utils.py @@ -0,0 +1,65 @@ +import numpy as np +import numpy.typing as npt +from shapely import LineString, Polygon + +from nuplan.common.actor_state.state_representation import StateSE2 +from nuplan.common.actor_state.tracked_objects import TrackedObject +from nuplan.planning.metrics.utils.collision_utils import CollisionType +from nuplan.planning.simulation.observation.idm.utils import is_agent_behind, is_track_stopped + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import StateIndex + + +def get_collision_type( + state: npt.NDArray[np.float64], + ego_polygon: Polygon, + tracked_object: TrackedObject, + tracked_object_polygon: Polygon, + stopped_speed_threshold: float = 5e-02, +) -> CollisionType: + """ + Classify collision between ego and the track. + :param ego_state: Ego's state at the current timestamp. + :param tracked_object: Tracked object. + :param stopped_speed_threshold: Threshold for 0 speed due to noise. + :return Collision type. + """ + + ego_speed = np.hypot( + state[StateIndex.VELOCITY_X], + state[StateIndex.VELOCITY_Y], + ) + + is_ego_stopped = float(ego_speed) <= stopped_speed_threshold + + center_point = tracked_object_polygon.centroid + tracked_object_center = StateSE2(center_point.x, center_point.y, tracked_object.box.center.heading) + + ego_rear_axle_pose: StateSE2 = StateSE2(*state[StateIndex.STATE_SE2]) + + # Collisions at (close-to) zero ego speed + if is_ego_stopped: + collision_type = CollisionType.STOPPED_EGO_COLLISION + + # Collisions at (close-to) zero track speed + elif is_track_stopped(tracked_object): + collision_type = CollisionType.STOPPED_TRACK_COLLISION + + # Rear collision when both ego and track are not stopped + elif is_agent_behind(ego_rear_axle_pose, tracked_object_center): + collision_type = CollisionType.ACTIVE_REAR_COLLISION + + # Front bumper collision when both ego and track are not stopped + elif LineString( + [ + ego_polygon.exterior.coords[0], + ego_polygon.exterior.coords[3], + ] + ).intersects(tracked_object_polygon): + collision_type = CollisionType.ACTIVE_FRONT_COLLISION + + # Lateral collision when both ego and track are not stopped + else: + collision_type = CollisionType.ACTIVE_LATERAL_COLLISION + + return collision_type diff --git a/sledge/simulation/planner/pdm_planner/simulation/__init__.py b/sledge/simulation/planner/pdm_planner/simulation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/planner/pdm_planner/simulation/batch_kinematic_bicycle.py b/sledge/simulation/planner/pdm_planner/simulation/batch_kinematic_bicycle.py new file mode 100644 index 0000000..45d3bb3 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/simulation/batch_kinematic_bicycle.py @@ -0,0 +1,182 @@ +import copy + +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import TimePoint +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters, get_pacifica_parameters +from nuplan.common.geometry.compute import principal_value + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import DynamicStateIndex, StateIndex + + +def forward_integrate( + init: npt.NDArray[np.float64], delta: npt.NDArray[np.float64], sampling_time: TimePoint +) -> npt.NDArray[np.float64]: + """ + Performs a simple euler integration. + :param init: Initial state + :param delta: The rate of change of the state. + :param sampling_time: The time duration to propagate for. + :return: The result of integration + """ + return init + delta * sampling_time.time_s + + +class BatchKinematicBicycleModel: + """ + A batch-wise operating class describing the kinematic motion model where the rear axle is the point of reference. + """ + + def __init__( + self, + vehicle: VehicleParameters = get_pacifica_parameters(), + max_steering_angle: float = np.pi / 3, + accel_time_constant: float = 0.2, + steering_angle_time_constant: float = 0.05, + ): + """ + Construct BatchKinematicBicycleModel. + :param vehicle: Vehicle parameters. + :param max_steering_angle: [rad] Maximum absolute value steering angle allowed by model. + :param accel_time_constant: low pass filter time constant for acceleration in s + :param steering_angle_time_constant: low pass filter time constant for steering angle in s + """ + self._vehicle = vehicle + self._max_steering_angle = max_steering_angle + self._accel_time_constant = accel_time_constant + self._steering_angle_time_constant = steering_angle_time_constant + + def get_state_dot(self, states: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]: + """ + Calculates the changing rate of state array representation. + :param states: array describing the state of the ego-vehicle + :return: change rate across several state values + """ + state_dots = np.zeros(states.shape, dtype=np.float64) + + longitudinal_speeds = states[:, StateIndex.VELOCITY_X] + + state_dots[:, StateIndex.X] = longitudinal_speeds * np.cos(states[:, StateIndex.HEADING]) + state_dots[:, StateIndex.Y] = longitudinal_speeds * np.sin(states[:, StateIndex.HEADING]) + state_dots[:, StateIndex.HEADING] = ( + longitudinal_speeds * np.tan(states[:, StateIndex.STEERING_ANGLE]) / self._vehicle.wheel_base + ) + + state_dots[:, StateIndex.VELOCITY_2D] = states[:, StateIndex.ACCELERATION_2D] + state_dots[:, StateIndex.ACCELERATION_2D] = 0.0 + + state_dots[:, StateIndex.STEERING_ANGLE] = states[:, StateIndex.STEERING_RATE] + + return state_dots + + def _update_commands( + self, + states: npt.NDArray[np.float64], + command_states: npt.NDArray[np.float64], + sampling_time: TimePoint, + ) -> EgoState: + """ + This function applies some first order control delay/a low pass filter to acceleration/steering. + :param state: Ego state + :param ideal_dynamic_state: The desired dynamic state for propagation + :param sampling_time: The time duration to propagate for + :return: propagating_state including updated dynamic_state + """ + + propagating_state: npt.NDArray[np.float64] = copy.deepcopy(states) + + dt_control = sampling_time.time_s + + accel = states[:, StateIndex.ACCELERATION_X] + steering_angle = states[:, StateIndex.STEERING_ANGLE] + + ideal_accel_x = command_states[:, DynamicStateIndex.ACCELERATION_X] + ideal_steering_angle = dt_control * command_states[:, DynamicStateIndex.STEERING_RATE] + steering_angle + + updated_accel_x = dt_control / (dt_control + self._accel_time_constant) * (ideal_accel_x - accel) + accel + updated_steering_angle = ( + dt_control / (dt_control + self._steering_angle_time_constant) * (ideal_steering_angle - steering_angle) + + steering_angle + ) + updated_steering_rate = (updated_steering_angle - steering_angle) / dt_control + + propagating_state[:, StateIndex.ACCELERATION_X] = updated_accel_x + propagating_state[:, StateIndex.ACCELERATION_Y] = 0.0 + propagating_state[:, StateIndex.STEERING_RATE] = updated_steering_rate + + return propagating_state + + def propagate_state( + self, + states: npt.NDArray[np.float64], + command_states: npt.NDArray[np.float64], + sampling_time: TimePoint, + ) -> npt.NDArray[np.float64]: + """ + Propagates ego state array forward with motion model. + :param states: state array representation of the ego-vehicle + :param command_states: command array representation of controller + :param sampling_time: time to propagate [s] + :return: updated tate array representation of the ego-vehicle + """ + + assert len(states) == len(command_states), "Batch size of states and command_states does not match!" + + propagating_state = self._update_commands(states, command_states, sampling_time) + output_state = copy.deepcopy(states) + + # Compute state derivatives + state_dot = self.get_state_dot(propagating_state) + + output_state[:, StateIndex.X] = forward_integrate( + states[:, StateIndex.X], state_dot[:, StateIndex.X], sampling_time + ) + output_state[:, StateIndex.Y] = forward_integrate( + states[:, StateIndex.Y], state_dot[:, StateIndex.Y], sampling_time + ) + + output_state[:, StateIndex.HEADING] = principal_value( + forward_integrate( + states[:, StateIndex.HEADING], + state_dot[:, StateIndex.HEADING], + sampling_time, + ) + ) + + output_state[:, StateIndex.VELOCITY_X] = forward_integrate( + states[:, StateIndex.VELOCITY_X], + state_dot[:, StateIndex.VELOCITY_X], + sampling_time, + ) + + # Lateral velocity is always zero in kinematic bicycle model + output_state[:, StateIndex.VELOCITY_Y] = 0.0 + + # Integrate steering angle and clip to bounds + output_state[:, StateIndex.STEERING_ANGLE] = np.clip( + forward_integrate( + propagating_state[:, StateIndex.STEERING_ANGLE], + state_dot[:, StateIndex.STEERING_ANGLE], + sampling_time, + ), + -self._max_steering_angle, + self._max_steering_angle, + ) + + output_state[:, StateIndex.ANGULAR_VELOCITY] = ( + output_state[:, StateIndex.VELOCITY_X] + * np.tan(output_state[:, StateIndex.STEERING_ANGLE]) + / self._vehicle.wheel_base + ) + + output_state[:, StateIndex.ACCELERATION_2D] = state_dot[:, StateIndex.VELOCITY_2D] + + output_state[:, StateIndex.ANGULAR_ACCELERATION] = ( + output_state[:, StateIndex.ANGULAR_VELOCITY] - states[:, StateIndex.ANGULAR_VELOCITY] + ) / sampling_time.time_s + + output_state[:, StateIndex.STEERING_RATE] = state_dot[:, StateIndex.STEERING_ANGLE] + + return output_state diff --git a/sledge/simulation/planner/pdm_planner/simulation/batch_lqr.py b/sledge/simulation/planner/pdm_planner/simulation/batch_lqr.py new file mode 100644 index 0000000..59d885d --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/simulation/batch_lqr.py @@ -0,0 +1,464 @@ +from enum import IntEnum +from typing import Optional, Tuple + +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters, get_pacifica_parameters +from nuplan.planning.simulation.simulation_time_controller.simulation_iteration import SimulationIteration + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import DynamicStateIndex, StateIndex +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle +from sledge.simulation.planner.pdm_planner.simulation.batch_lqr_utils import ( + _generate_profile_from_initial_condition_and_derivatives, + get_velocity_curvature_profiles_with_derivatives_from_poses, +) + + +class LateralStateIndex(IntEnum): + """ + Index mapping for the lateral dynamics state vector. + """ + + LATERAL_ERROR = 0 # [m] The lateral error with respect to the planner centerline at the vehicle's rear axle center. + HEADING_ERROR = 1 # [rad] The heading error "". + STEERING_ANGLE = 2 # [rad] The wheel angle relative to the longitudinal axis of the vehicle. + + +class BatchLQRTracker: + """ + Implements an LQR tracker for a kinematic bicycle model. + + Tracker operates on a batch of proposals. Implementation directly based on the nuplan-devkit + Link: https://github.com/motional/nuplan-devkit + + We decouple into two subsystems, longitudinal and lateral, with small angle approximations for linearization. + We then solve two sequential LQR subproblems to find acceleration and steering rate inputs. + + Longitudinal Subsystem: + States: [velocity] + Inputs: [acceleration] + Dynamics (continuous time): + velocity_dot = acceleration + + Lateral Subsystem (After Linearization/Small Angle Approximation): + States: [lateral_error, heading_error, steering_angle] + Inputs: [steering_rate] + Parameters: [velocity, curvature] + Dynamics (continuous time): + lateral_error_dot = velocity * heading_error + heading_error_dot = velocity * (steering_angle / wheelbase_length - curvature) + steering_angle_dot = steering_rate + + The continuous time dynamics are discretized using Euler integration and zero-order-hold on the input. + In case of a stopping reference, we use a simplified stopping P controller instead of LQR. + + The final control inputs passed on to the motion model are: + - acceleration + - steering_rate + """ + + def __init__( + self, + q_longitudinal: npt.NDArray[np.float64] = [10.0], + r_longitudinal: npt.NDArray[np.float64] = [1.0], + q_lateral: npt.NDArray[np.float64] = [1.0, 10.0, 0.0], + r_lateral: npt.NDArray[np.float64] = [1.0], + discretization_time: float = 0.1, + tracking_horizon: int = 10, + jerk_penalty: float = 1e-4, + curvature_rate_penalty: float = 1e-2, + stopping_proportional_gain: float = 0.5, + stopping_velocity: float = 0.2, + vehicle: VehicleParameters = get_pacifica_parameters(), + ): + """ + Constructor for LQR controller + :param q_longitudinal: The weights for the Q matrix for the longitudinal subystem. + :param r_longitudinal: The weights for the R matrix for the longitudinal subystem. + :param q_lateral: The weights for the Q matrix for the lateral subystem. + :param r_lateral: The weights for the R matrix for the lateral subystem. + :param discretization_time: [s] The time interval used for discretizing the continuous time dynamics. + :param tracking_horizon: How many discrete time steps ahead to consider for the LQR objective. + :param stopping_proportional_gain: The proportional_gain term for the P controller when coming to a stop. + :param stopping_velocity: [m/s] The velocity below which we are deemed to be stopping and we don't use LQR. + :param vehicle: Vehicle parameters + """ + # Longitudinal LQR Parameters + assert len(q_longitudinal) == 1, "q_longitudinal should have 1 element (velocity)." + assert len(r_longitudinal) == 1, "r_longitudinal should have 1 element (acceleration)." + self._q_longitudinal: float = q_longitudinal[0] + self._r_longitudinal: float = r_longitudinal[0] + + # Lateral LQR Parameters + assert len(q_lateral) == 3, "q_lateral should have 3 elements (lateral_error, heading_error, steering_angle)." + assert len(r_lateral) == 1, "r_lateral should have 1 element (steering_rate)." + self._q_lateral: npt.NDArray[np.float64] = np.diag(q_lateral) + self._r_lateral: npt.NDArray[np.float64] = np.diag(r_lateral) + + # Common LQR Parameters + # Note we want a horizon > 1 so that steering rate actually can impact lateral/heading error in discrete time. + assert discretization_time > 0.0, "The discretization_time should be positive." + assert ( + tracking_horizon > 1 + ), "We expect the horizon to be greater than 1 - else steering_rate has no impact with Euler integration." + self._discretization_time = discretization_time + self._tracking_horizon = tracking_horizon + self._wheel_base = vehicle.wheel_base + + # Velocity/Curvature Estimation Parameters + assert jerk_penalty > 0.0, "The jerk penalty must be positive." + assert curvature_rate_penalty > 0.0, "The curvature rate penalty must be positive." + self._jerk_penalty = jerk_penalty + self._curvature_rate_penalty = curvature_rate_penalty + + # Stopping Controller Parameters + assert stopping_proportional_gain > 0, "stopping_proportional_gain has to be greater than 0." + assert stopping_velocity > 0, "stopping_velocity has to be greater than 0." + self._stopping_proportional_gain = stopping_proportional_gain + self._stopping_velocity = stopping_velocity + + # lazy loaded + self._proposal_states: Optional[npt.NDArray[np.float64]] = None + self._initialized: bool = False + + def update(self, proposal_states: npt.NDArray[np.float64]) -> None: + """ + Loads proposal state array and resets velocity, and curvature profile. + :param proposal_states: array representation of proposals. + """ + self._proposal_states: npt.NDArray[np.float64] = proposal_states + self._velocity_profile, self._curvature_profile = None, None + self._initialized = True + + def track_trajectory( + self, + current_iteration: SimulationIteration, + next_iteration: SimulationIteration, + initial_states: npt.NDArray[np.float64], + ) -> npt.NDArray[np.float64]: + """ + Calculates the command values given the proposals to track. + :param current_iteration: current simulation iteration. + :param next_iteration: desired next simulation iteration. + :param initial_states: array representation of current ego states. + :return: command values for motion model. + """ + assert self._initialized, "BatchLQRTracker: Run update first to load proposal states!" + + batch_size = len(initial_states) + ( + initial_velocity, + initial_lateral_state_vector, + ) = self._compute_initial_velocity_and_lateral_state( + current_iteration, initial_states + ) # (batch), (batch, 3) + + ( + reference_velocities, + curvature_profiles, + ) = self._compute_reference_velocity_and_curvature_profile( + current_iteration + ) # (batch), (batch, 10) + + # create output arrays + accel_cmds = np.zeros(batch_size, dtype=np.float64) + steering_rate_cmds = np.zeros(batch_size, dtype=np.float64) + + # 1. Stopping Controller + should_stop_mask = np.logical_and( + reference_velocities <= self._stopping_velocity, + initial_velocity <= self._stopping_velocity, + ) + stopping_accel_cmd, stopping_steering_rate_cmd = self._stopping_controller( + initial_velocity[should_stop_mask], reference_velocities[should_stop_mask] + ) + accel_cmds[should_stop_mask] = stopping_accel_cmd + steering_rate_cmds[should_stop_mask] = stopping_steering_rate_cmd + + # 2. Regular Controller + accel_cmds[~should_stop_mask] = self._longitudinal_lqr_controller( + initial_velocity[~should_stop_mask], reference_velocities[~should_stop_mask] + ) + + velocity_profiles = _generate_profile_from_initial_condition_and_derivatives( + initial_condition=initial_velocity[~should_stop_mask], + derivatives=np.repeat(accel_cmds[~should_stop_mask, None], self._tracking_horizon, axis=-1), + discretization_time=self._discretization_time, + )[:, : self._tracking_horizon] + + steering_rate_cmds[~should_stop_mask] = self._lateral_lqr_controller( + initial_lateral_state_vector[~should_stop_mask], + velocity_profiles, + curvature_profiles[~should_stop_mask], + ) + + command_states = np.zeros((batch_size, len(DynamicStateIndex)), dtype=np.float64) + command_states[:, DynamicStateIndex.ACCELERATION_X] = accel_cmds + command_states[:, DynamicStateIndex.STEERING_RATE] = steering_rate_cmds + + return command_states + + def _compute_initial_velocity_and_lateral_state( + self, + current_iteration: SimulationIteration, + initial_values: npt.NDArray[np.float64], + ) -> Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: + """ + This method projects the initial tracking error into vehicle/Frenet frame. It also extracts initial velocity. + :param current_iteration: Used to get the current time. + :param initial_state: The current state for ego. + :param trajectory: The reference trajectory we are tracking. + :return: Initial velocity [m/s] and initial lateral state. + """ + # Get initial trajectory state. + initial_trajectory_values = self._proposal_states[:, current_iteration.index] + + # Determine initial error state. + x_errors = initial_values[:, StateIndex.X] - initial_trajectory_values[:, StateIndex.X] + y_errors = initial_values[:, StateIndex.Y] - initial_trajectory_values[:, StateIndex.Y] + heading_references = initial_trajectory_values[:, StateIndex.HEADING] + + lateral_errors = -x_errors * np.sin(heading_references) + y_errors * np.cos(heading_references) + heading_errors = normalize_angle(initial_values[:, StateIndex.HEADING] - heading_references) + + # Return initial velocity and lateral state vector. + initial_velocities = initial_values[:, StateIndex.VELOCITY_X] + + initial_lateral_state_vector = np.stack( + [ + lateral_errors, + heading_errors, + initial_values[:, StateIndex.STEERING_ANGLE], + ], + axis=-1, + ) + + return initial_velocities, initial_lateral_state_vector + + def _compute_reference_velocity_and_curvature_profile( + self, + current_iteration: SimulationIteration, + ) -> Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: + """ + This method computes reference velocity and curvature profile based on the reference trajectory. + We use a lookahead time equal to self._tracking_horizon * self._discretization_time. + :param current_iteration: Used to get the current time. + :param trajectory: The reference trajectory we are tracking. + :return: The reference velocity [m/s] and curvature profile [rad] to track. + """ + + poses = self._proposal_states[..., StateIndex.STATE_SE2] + + if self._velocity_profile is None or self._curvature_profile is None: + ( + self._velocity_profile, + acceleration_profile, + self._curvature_profile, + curvature_rate_profile, + ) = get_velocity_curvature_profiles_with_derivatives_from_poses( + discretization_time=self._discretization_time, + poses=poses, + jerk_penalty=self._jerk_penalty, + curvature_rate_penalty=self._curvature_rate_penalty, + ) + + batch_size, num_poses = self._velocity_profile.shape + reference_idx = min(current_iteration.index + self._tracking_horizon, num_poses - 1) + reference_velocities = self._velocity_profile[:, reference_idx] + + reference_curvature_profiles = np.zeros((batch_size, self._tracking_horizon), dtype=np.float64) + + reference_length = reference_idx - current_iteration.index + reference_curvature_profiles[:, 0:reference_length] = self._curvature_profile[ + :, current_iteration.index : reference_idx + ] + + if reference_length < self._tracking_horizon: + reference_curvature_profiles[:, reference_length:] = self._curvature_profile[:, reference_idx, None] + + return reference_velocities, reference_curvature_profiles + + def _stopping_controller( + self, + initial_velocities: npt.NDArray[np.float64], + reference_velocities: npt.NDArray[np.float64], + ) -> Tuple[float, float]: + """ + Apply proportional controller when at near-stop conditions. + :param initial_velocity: [m/s] The current velocity of ego. + :param reference_velocity: [m/s] The reference velocity to track. + :return: Acceleration [m/s^2] and zero steering_rate [rad/s] command. + """ + accel = -self._stopping_proportional_gain * (initial_velocities - reference_velocities) + return accel, 0.0 + + def _longitudinal_lqr_controller( + self, + initial_velocities: npt.NDArray[np.float64], + reference_velocities: npt.NDArray[np.float64], + ) -> npt.NDArray[np.float64]: + """ + This longitudinal controller determines an acceleration input to minimize velocity error at a lookahead time. + :param initial_velocity: [m/s] The current velocity of ego. + :param reference_velocity: [m/s] The reference_velocity to track at a lookahead time. + :return: Acceleration [m/s^2] command based on LQR. + """ + # We assume that we hold the acceleration constant for the entire tracking horizon. + # Given this, we can show the following where N = self._tracking_horizon and dt = self._discretization_time: + # velocity_N = velocity_0 + (N * dt) * acceleration + + batch_size = len(initial_velocities) + + A: npt.NDArray[np.float64] = np.ones(batch_size, dtype=np.float64) + + B: npt.NDArray[np.float64] = np.zeros(batch_size, dtype=np.float64) + B.fill(self._tracking_horizon * self._discretization_time) + + g: npt.NDArray[np.float64] = np.zeros(batch_size, dtype=np.float64) + + accel_cmds = self._solve_one_step_longitudinal_lqr( + initial_state=initial_velocities, + reference_state=reference_velocities, + A=A, + B=B, + g=g, + ) + + return accel_cmds + + def _lateral_lqr_controller( + self, + initial_lateral_state_vector: npt.NDArray[np.float64], + velocity_profile: npt.NDArray[np.float64], + curvature_profile: npt.NDArray[np.float64], + ) -> float: + """ + This lateral controller determines a steering_rate input to minimize lateral errors at a lookahead time. + It requires a velocity sequence as a parameter to ensure linear time-varying lateral dynamics. + :param initial_lateral_state_vector: The current lateral state of ego. + :param velocity_profile: [m/s] The velocity over the entire self._tracking_horizon-step lookahead. + :param curvature_profile: [rad] The curvature over the entire self._tracking_horizon-step lookahead.. + :return: Steering rate [rad/s] command based on LQR. + """ + assert velocity_profile.shape[-1] == self._tracking_horizon, ( + f"The linearization velocity sequence should have length {self._tracking_horizon} " + f"but is {len(velocity_profile)}." + ) + assert curvature_profile.shape[-1] == self._tracking_horizon, ( + f"The linearization curvature sequence should have length {self._tracking_horizon} " + f"but is {len(curvature_profile)}." + ) + + batch_dim = velocity_profile.shape[0] + + # Set up the lateral LQR problem using the constituent linear time-varying (affine) system dynamics. + # Ultimately, we'll end up with the following problem structure where N = self._tracking_horizon: + # lateral_error_N = A @ lateral_error_0 + B @ steering_rate + g + n_lateral_states = len(LateralStateIndex) + + I: npt.NDArray[np.float64] = np.eye(n_lateral_states, dtype=np.float64) + + in_matrix: npt.NDArray[np.float64] = np.zeros((n_lateral_states, 1), np.float64) # no batch dim + in_matrix[LateralStateIndex.STEERING_ANGLE] = self._discretization_time + + states_matrix_at_step: npt.NDArray[np.float64] = np.tile( + I[None, None, ...], [self._tracking_horizon, batch_dim, 1, 1] + ) # (horizon, batch, 3, 3) + + states_matrix_at_step[:, :, LateralStateIndex.LATERAL_ERROR, LateralStateIndex.HEADING_ERROR] = ( + velocity_profile.T * self._discretization_time + ) + + states_matrix_at_step[:, :, LateralStateIndex.HEADING_ERROR, LateralStateIndex.STEERING_ANGLE] = ( + velocity_profile.T * self._discretization_time / self._wheel_base + ) + + affine_terms: npt.NDArray[np.float64] = np.zeros( + (self._tracking_horizon, batch_dim, n_lateral_states), dtype=np.float64 + ) + + affine_terms[:, :, LateralStateIndex.HEADING_ERROR] = ( + -velocity_profile.T * curvature_profile.T * self._discretization_time + ) + + A: npt.NDArray[np.float64] = np.tile(I[None, ...], [batch_dim, 1, 1]) # (batch, 3, 3) + B: npt.NDArray[np.float64] = np.zeros((batch_dim, n_lateral_states, 1), dtype=np.float64) # (batch, 3, 1) + g: npt.NDArray[np.float64] = np.zeros((batch_dim, n_lateral_states), dtype=np.float64) # (batch, 3) + + for index_step, (state_matrix_at_step, affine_term) in enumerate(zip(states_matrix_at_step, affine_terms)): + # state_matrix_at_step (batch, 3, 3) + # affine_term (batch, 3) + A = np.einsum("bij, bjk -> bik", state_matrix_at_step, A) # (batch, 3, 3) + B = np.einsum("bij, bjk -> bik", state_matrix_at_step, B) + in_matrix # (batch, 3, 1) + g = np.einsum("bij, bj -> bi", state_matrix_at_step, g) + affine_term # (batch, 3) + + steering_rate_cmd = self._solve_one_step_lateral_lqr( + initial_state=initial_lateral_state_vector, + A=A, + B=B, + g=g, + ) + + return np.squeeze(steering_rate_cmd, axis=-1) + + def _solve_one_step_longitudinal_lqr( + self, + initial_state: npt.NDArray[np.float64], + reference_state: npt.NDArray[np.float64], + A: npt.NDArray[np.float64], + B: npt.NDArray[np.float64], + g: npt.NDArray[np.float64], + ) -> npt.NDArray[np.float64]: + """ + This function uses LQR to find an optimal input to minimize tracking error in one step of dynamics. + The dynamics are next_state = A @ initial_state + B @ input + g and our target is the reference_state. + :param initial_state: The current state. + :param reference_state: The desired state in 1 step (according to A,B,g dynamics). + :param A: The state dynamics matrix. + :param B: The input dynamics matrix. + :param g: The offset/affine dynamics term. + :return: LQR optimal input for the 1-step longitudinal problem. + """ + state_error_zero_input = A * initial_state + g - reference_state + inverse = -1 / (B * self._q_longitudinal * B + self._r_longitudinal) + lqr_input = inverse * B * self._q_longitudinal * state_error_zero_input + + return lqr_input + + def _solve_one_step_lateral_lqr( + self, + initial_state: npt.NDArray[np.float64], + A: npt.NDArray[np.float64], + B: npt.NDArray[np.float64], + g: npt.NDArray[np.float64], + ) -> npt.NDArray[np.float64]: + """ + This function uses LQR to find an optimal input to minimize tracking error in one step of dynamics. + The dynamics are next_state = A @ initial_state + B @ input + g and our target is the reference_state. + :param initial_state: The current state. + :param A: The state dynamics matrix. + :param B: The input dynamics matrix. + :param g: The offset/affine dynamics term. + :return: LQR optimal input for the 1-step lateral problem. + """ + + Q, R = self._q_lateral, self._r_lateral + angle_diff_indices = [ + LateralStateIndex.HEADING_ERROR.value, + LateralStateIndex.STEERING_ANGLE.value, + ] + BT = B.transpose(0, 2, 1) + + state_error_zero_input = np.einsum("bij, bj -> bi", A, initial_state) + g + + angle = state_error_zero_input[..., angle_diff_indices] + state_error_zero_input[..., angle_diff_indices] = np.arctan2(np.sin(angle), np.cos(angle)) + + BT_x_Q = np.einsum("bij, jk -> bik", BT, Q) + Inv = -1 / (np.einsum("bij, bji -> bi", BT_x_Q, B) + R) + Tail = np.einsum("bij, bj -> bi", BT_x_Q, state_error_zero_input) + + lqr_input = Inv * Tail + + return lqr_input diff --git a/sledge/simulation/planner/pdm_planner/simulation/batch_lqr_utils.py b/sledge/simulation/planner/pdm_planner/simulation/batch_lqr_utils.py new file mode 100644 index 0000000..bedba44 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/simulation/batch_lqr_utils.py @@ -0,0 +1,249 @@ +from typing import Tuple + +import numpy as np +import numpy.typing as npt + +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle + +# Utils functions for BatchLQRTracker +# Code re-written based on nuPlan's implementation: +# https://github.com/motional/nuplan-devkit + +# Default regularization weight for initial curvature fit. Users shouldn't really need to modify this, +# we just want it positive and small for improved conditioning of the associated least squares problem. +INITIAL_CURVATURE_PENALTY = 1e-10 + +# helper function to apply matrix multiplication over a batch-dim +batch_matmul = lambda a, b: np.einsum("bij, bjk -> bik", a, b) + + +def _generate_profile_from_initial_condition_and_derivatives( + initial_condition: npt.NDArray[np.float64], + derivatives: npt.NDArray[np.float64], + discretization_time: float, +) -> npt.NDArray[np.float64]: + """ + Returns the corresponding profile (i.e. trajectory) given an initial condition and derivatives at + multiple timesteps by integration. + :param initial_condition: The value of the variable at the initial timestep. + :param derivatives: The trajectory of time derivatives of the variable at timesteps 0,..., N-1. + :param discretization_time: [s] Time discretization used for integration. + :return: The trajectory of the variable at timesteps 0,..., N. + """ + assert discretization_time > 0.0, "Discretization time must be positive." + cumsum = np.cumsum(derivatives * discretization_time, axis=-1) + profile = initial_condition[..., None] + np.pad(cumsum, [(0, 0), (1, 0)], mode="constant") + return profile + + +def _get_xy_heading_displacements_from_poses( + poses: npt.NDArray[np.float64], +) -> Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]: + """ + Returns position and heading displacements given a pose trajectory. + :param poses: A trajectory of poses (x, y, heading). + :return: Tuple of xy displacements with shape (num_poses-1, 2) and heading displacements with shape (num_poses-1,). + """ + assert len(poses.shape) == 3, "Expect a 2D matrix representing a trajectory of poses." + assert poses.shape[1] > 1, "Cannot get displacements given an empty or single element pose trajectory." + assert poses.shape[2] == 3, "Expect pose to have three elements (x, y, heading)." + + # Compute displacements that are used to complete the kinematic state and input. + pose_differences = np.diff(poses, axis=1) # (b, num_poses-1, 3) + xy_displacements = pose_differences[..., :2] + heading_displacements = normalize_angle(pose_differences[..., 2]) + + return xy_displacements, heading_displacements + + +def _make_banded_difference_matrix(number_rows: int) -> npt.NDArray[np.float64]: + """ + Returns a banded difference matrix with specified number_rows. + When applied to a vector [x_1, ..., x_N], it returns [x_2 - x_1, ..., x_N - x_{N-1}]. + :param number_rows: The row dimension of the banded difference matrix (e.g. N-1 in the example above). + :return: A banded difference matrix with shape (number_rows, number_rows+1). + """ + banded_matrix = np.zeros((number_rows, number_rows + 1), dtype=np.float64) + eye = np.eye(number_rows, dtype=np.float64) + banded_matrix[:, 1:] = eye + banded_matrix[:, :-1] = -eye + return banded_matrix + + +def _fit_initial_velocity_and_acceleration_profile( + xy_displacements: npt.NDArray[np.float64], + heading_profile: npt.NDArray[np.float64], + discretization_time: float, + jerk_penalty: float, +) -> Tuple[float, npt.NDArray[np.float64]]: + """ + Estimates initial velocity (v_0) and acceleration ({a_0, ...}) using least squares with jerk penalty regularization. + :param xy_displacements: [m] Deviations in x and y occurring between M+1 poses, a M by 2 matrix. + :param heading_profile: [rad] Headings associated to the starting timestamp for xy_displacements, a M-length vector. + :param discretization_time: [s] Time discretization used for integration. + :param jerk_penalty: A regularization parameter used to penalize acceleration differences. Should be positive. + :return: Least squares solution for initial velocity (v_0) and acceleration profile ({a_0, ..., a_M-1}) + for M displacement values. + """ + assert discretization_time > 0.0, "Discretization time must be positive." + assert jerk_penalty > 0, "Should have a positive jerk_penalty." + + assert len(xy_displacements.shape) == 3, "Expect xy_displacements to be a matrix." + assert xy_displacements.shape[2] == 2, "Expect xy_displacements to have 2 columns." + + num_displacements = xy_displacements.shape[1] # aka M in the docstring + assert heading_profile.shape[0] == xy_displacements.shape[0] + + batch_size = heading_profile.shape[0] + # Core problem: minimize_x ||y-Ax||_2 + y = xy_displacements.reshape(batch_size, -1) # Flatten to a vector, [delta x_0, delta y_0, ...] + + headings = np.array(heading_profile, dtype=np.float64) + A_column = np.zeros(y.shape, dtype=np.float64) + A_column[:, 0::2] = np.cos(headings) + A_column[:, 1::2] = np.sin(headings) + + A = np.repeat(A_column[..., None] * discretization_time**2, num_displacements, axis=2) + A[..., 0] = A_column * discretization_time + + upper_triangle_mask = np.triu(np.ones((num_displacements, num_displacements), dtype=bool), k=1) + upper_triangle_mask = np.repeat(upper_triangle_mask, 2, axis=0) + A[:, upper_triangle_mask] = 0.0 + + # Regularization using jerk penalty, i.e. difference of acceleration values. + # If there are M displacements, then we have M - 1 acceleration values. + # That means we have M - 2 jerk values, thus we make a banded difference matrix of that size. + banded_matrix = _make_banded_difference_matrix(num_displacements - 2) + R: npt.NDArray[np.float64] = np.block([np.zeros((len(banded_matrix), 1)), banded_matrix]) + R = np.repeat(R[None, ...], batch_size, axis=0) + + A_T, R_T = np.transpose(A, (0, 2, 1)), np.transpose(R, (0, 2, 1)) + + # Compute regularized least squares solution. + intermediate_solution = batch_matmul( + np.linalg.pinv(batch_matmul(A_T, A) + jerk_penalty * batch_matmul(R_T, R)), A_T + ) + x = np.einsum("bij, bj -> bi", intermediate_solution, y) + + # Extract profile from solution. + initial_velocity = x[:, 0] + acceleration_profile = x[:, 1:] + + return initial_velocity, acceleration_profile + + +def _fit_initial_curvature_and_curvature_rate_profile( + heading_displacements: npt.NDArray[np.float64], + velocity_profile: npt.NDArray[np.float64], + discretization_time: float, + curvature_rate_penalty: float, + initial_curvature_penalty: float = INITIAL_CURVATURE_PENALTY, +) -> Tuple[float, npt.NDArray[np.float64]]: + """ + Estimates initial curvature (curvature_0) and curvature rate ({curvature_rate_0, ...}) + using least squares with curvature rate regularization. + :param heading_displacements: [rad] Angular deviations in heading occuring between timesteps. + :param velocity_profile: [m/s] Estimated or actual velocities at the timesteps matching displacements. + :param discretization_time: [s] Time discretization used for integration. + :param curvature_rate_penalty: A regularization parameter used to penalize curvature_rate. Should be positive. + :param initial_curvature_penalty: A regularization parameter to handle zero initial speed. Should be positive and small. + :return: Least squares solution for initial curvature (curvature_0) and curvature rate profile + (curvature_rate_0, ..., curvature_rate_{M-1}) for M heading displacement values. + """ + assert discretization_time > 0.0, "Discretization time must be positive." + assert curvature_rate_penalty > 0.0, "Should have a positive curvature_rate_penalty." + assert initial_curvature_penalty > 0.0, "Should have a positive initial_curvature_penalty." + + # Core problem: minimize_x ||y-Ax||_2 + y = heading_displacements + batch_dim, dim = y.shape + + A: npt.NDArray[np.float64] = np.repeat( + np.tri(dim, dtype=np.float64)[None, ...], batch_dim, axis=0 + ) # lower triangular matrix + + A[:, :, 0] = velocity_profile * discretization_time + + velocity = velocity_profile * discretization_time**2 + A[:, 1:, 1:] *= velocity[:, None, 1:].transpose(0, 2, 1) + + # Regularization on curvature rate. We add a small but nonzero weight on initial curvature too. + # This is since the corresponding row of the A matrix might be zero if initial speed is 0, leading to singularity. + # We guarantee that Q is positive definite such that the minimizer of the least squares problem is unique. + Q: npt.NDArray[np.float64] = curvature_rate_penalty * np.eye(dim) + Q[0, 0] = initial_curvature_penalty + + # Compute regularized least squares solution. + A_T = A.transpose(0, 2, 1) + + intermediate = batch_matmul(np.linalg.pinv(batch_matmul(A_T, A) + Q), A_T) + x = np.einsum("bij,bj->bi", intermediate, y) + + # Extract profile from solution. + initial_curvature = x[:, 0] + curvature_rate_profile = x[:, 1:] + + return initial_curvature, curvature_rate_profile + + +def get_velocity_curvature_profiles_with_derivatives_from_poses( + discretization_time: float, + poses: npt.NDArray[np.float64], + jerk_penalty: float, + curvature_rate_penalty: float, +) -> Tuple[ + npt.NDArray[np.float64], + npt.NDArray[np.float64], + npt.NDArray[np.float64], + npt.NDArray[np.float64], +]: + """ + Main function for joint estimation of velocity, acceleration, curvature, and curvature rate given N poses + sampled at discretization_time. This is done by solving two least squares problems with the given penalty weights. + :param discretization_time: [s] Time discretization used for integration. + :param poses: A trajectory of N poses (x, y, heading). + :param jerk_penalty: A regularization parameter used to penalize acceleration differences. Should be positive. + :param curvature_rate_penalty: A regularization parameter used to penalize curvature_rate. Should be positive. + :return: Profiles for velocity (N-1), acceleration (N-2), curvature (N-1), and curvature rate (N-2). + """ + xy_displacements, heading_displacements = _get_xy_heading_displacements_from_poses(poses) + + ( + initial_velocity, + acceleration_profile, + ) = _fit_initial_velocity_and_acceleration_profile( + xy_displacements=xy_displacements, + heading_profile=poses[:, :-1, 2], + discretization_time=discretization_time, + jerk_penalty=jerk_penalty, + ) + + velocity_profile = _generate_profile_from_initial_condition_and_derivatives( + initial_condition=initial_velocity, + derivatives=acceleration_profile, + discretization_time=discretization_time, + ) + + # Compute initial curvature + curvature rate least squares solution and extract results. It relies on velocity fit. + ( + initial_curvature, + curvature_rate_profile, + ) = _fit_initial_curvature_and_curvature_rate_profile( + heading_displacements=heading_displacements, + velocity_profile=velocity_profile, + discretization_time=discretization_time, + curvature_rate_penalty=curvature_rate_penalty, + ) + + curvature_profile = _generate_profile_from_initial_condition_and_derivatives( + initial_condition=initial_curvature, + derivatives=curvature_rate_profile, + discretization_time=discretization_time, + ) + + return ( + velocity_profile, + acceleration_profile, + curvature_profile, + curvature_rate_profile, + ) diff --git a/sledge/simulation/planner/pdm_planner/simulation/pdm_simulator.py b/sledge/simulation/planner/pdm_planner/simulation/pdm_simulator.py new file mode 100644 index 0000000..a591a0a --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/simulation/pdm_simulator.py @@ -0,0 +1,79 @@ +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import TimeDuration, TimePoint +from nuplan.planning.simulation.simulation_time_controller.simulation_iteration import SimulationIteration +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.simulation.batch_kinematic_bicycle import BatchKinematicBicycleModel +from sledge.simulation.planner.pdm_planner.simulation.batch_lqr import BatchLQRTracker +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import ego_state_to_state_array + + +class PDMSimulator: + """ + Re-implementation of nuPlan's simulation pipeline. Enables batch-wise simulation. + """ + + def __init__(self, proposal_sampling: TrajectorySampling): + """ + Constructor of PDMSimulator. + :param proposal_sampling: Sampling parameters for proposals + """ + + # time parameters + self._proposal_sampling = proposal_sampling + + # simulation objects + self._motion_model = BatchKinematicBicycleModel() + self._tracker = BatchLQRTracker() + + def simulate_proposals( + self, states: npt.NDArray[np.float64], initial_ego_state: EgoState + ) -> npt.NDArray[np.float64]: + """ + Simulate all proposals over batch-dim + :param initial_ego_state: ego-vehicle state at current iteration + :param states: proposal states as array + :return: simulated proposal states as array + """ + + # TODO: find cleaner way to load parameters + # set parameters of motion model and tracker + self._motion_model._vehicle = initial_ego_state.car_footprint.vehicle_parameters + self._tracker._discretization_time = self._proposal_sampling.interval_length + + proposal_states = states[:, : self._proposal_sampling.num_poses + 1] + self._tracker.update(proposal_states) + + # state array representation for simulated vehicle states + simulated_states = np.zeros(proposal_states.shape, dtype=np.float64) + simulated_states[:, 0] = ego_state_to_state_array(initial_ego_state) + + # timing objects + current_time_point = initial_ego_state.time_point + delta_time_point = TimeDuration.from_s(self._proposal_sampling.interval_length) + + current_iteration = SimulationIteration(current_time_point, 0) + next_iteration = SimulationIteration(current_time_point + delta_time_point, 1) + + for time_idx in range(1, self._proposal_sampling.num_poses + 1): + sampling_time: TimePoint = next_iteration.time_point - current_iteration.time_point + + command_states = self._tracker.track_trajectory( + current_iteration, + next_iteration, + simulated_states[:, time_idx - 1], + ) + + simulated_states[:, time_idx] = self._motion_model.propagate_state( + states=simulated_states[:, time_idx - 1], + command_states=command_states, + sampling_time=sampling_time, + ) + + current_iteration = next_iteration + next_iteration = SimulationIteration(current_iteration.time_point + delta_time_point, 1 + time_idx) + + return simulated_states diff --git a/sledge/simulation/planner/pdm_planner/utils/graph_search/bfs_roadblock.py b/sledge/simulation/planner/pdm_planner/utils/graph_search/bfs_roadblock.py new file mode 100644 index 0000000..a797420 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/graph_search/bfs_roadblock.py @@ -0,0 +1,146 @@ +from collections import deque +from typing import Dict, List, Optional, Tuple, Union + +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.maps.abstract_map_objects import RoadBlockGraphEdgeMapObject + + +class BreadthFirstSearchRoadBlock: + """ + A class that performs iterative breadth first search. The class operates on the roadblock graph. + """ + + def __init__( + self, + start_roadblock_id: int, + map_api: Optional[AbstractMap], + forward_search: str = True, + ): + """ + Constructor of BreadthFirstSearchRoadBlock class + :param start_roadblock_id: roadblock id where graph starts + :param map_api: map class in nuPlan + :param forward_search: whether to search in driving direction, defaults to True + """ + self._map_api: Optional[AbstractMap] = map_api + self._queue = deque([self.id_to_roadblock(start_roadblock_id), None]) + self._parent: Dict[str, Optional[RoadBlockGraphEdgeMapObject]] = dict() + self._forward_search = forward_search + + # lazy loaded + self._target_roadblock_ids: List[str] = None + + def search( + self, target_roadblock_id: Union[str, List[str]], max_depth: int + ) -> Tuple[List[RoadBlockGraphEdgeMapObject], bool]: + """ + Apply BFS to find route to target roadblock. + :param target_roadblock_id: id of target roadblock + :param max_depth: maximum search depth + :return: tuple of route and whether a path was found + """ + + if isinstance(target_roadblock_id, str): + target_roadblock_id = [target_roadblock_id] + self._target_roadblock_ids = target_roadblock_id + + start_edge = self._queue[0] + + # Initial search states + path_found: bool = False + end_edge: RoadBlockGraphEdgeMapObject = start_edge + end_depth: int = 1 + depth: int = 1 + + self._parent[start_edge.id + f"_{depth}"] = None + + while self._queue: + current_edge = self._queue.popleft() + + # Early exit condition + if self._check_end_condition(depth, max_depth): + break + + # Depth tracking + if current_edge is None: + depth += 1 + self._queue.append(None) + if self._queue[0] is None: + break + continue + + # Goal condition + if self._check_goal_condition(current_edge, depth, max_depth): + end_edge = current_edge + end_depth = depth + path_found = True + break + + neighbors = current_edge.outgoing_edges if self._forward_search else current_edge.incoming_edges + + # Populate queue + for next_edge in neighbors: + # if next_edge.id in self._candidate_lane_edge_ids_old: + self._queue.append(next_edge) + self._parent[next_edge.id + f"_{depth + 1}"] = current_edge + end_edge = next_edge + end_depth = depth + 1 + + return self._construct_path(end_edge, end_depth), path_found + + def id_to_roadblock(self, id: str) -> RoadBlockGraphEdgeMapObject: + """ + Retrieves roadblock from map-api based on id + :param id: id of roadblock + :return: roadblock class + """ + block = self._map_api._get_roadblock(id) + block = block or self._map_api._get_roadblock_connector(id) + return block + + @staticmethod + def _check_end_condition(depth: int, max_depth: int) -> bool: + """ + Check if the search should end regardless if the goal condition is met. + :param depth: The current depth to check. + :param target_depth: The target depth to check against. + :return: whether depth exceeds the target depth. + """ + return depth > max_depth + + def _check_goal_condition( + self, + current_edge: RoadBlockGraphEdgeMapObject, + depth: int, + max_depth: int, + ) -> bool: + """ + Check if the current edge is at the target roadblock at the given depth. + :param current_edge: edge to check. + :param depth: current depth to check. + :param max_depth: maximum depth the edge should be at. + :return: True if the lane edge is contain the in the target roadblock. False, otherwise. + """ + return current_edge.id in self._target_roadblock_ids and depth <= max_depth + + def _construct_path(self, end_edge: RoadBlockGraphEdgeMapObject, depth: int) -> List[RoadBlockGraphEdgeMapObject]: + """ + Constructs a path when goal was found. + :param end_edge: The end edge to start back propagating back to the start edge. + :param depth: The depth of the target edge. + :return: The constructed path as a list of RoadBlockGraphEdgeMapObject + """ + path = [end_edge] + path_id = [end_edge.id] + + while self._parent[end_edge.id + f"_{depth}"] is not None: + path.append(self._parent[end_edge.id + f"_{depth}"]) + path_id.append(path[-1].id) + end_edge = self._parent[end_edge.id + f"_{depth}"] + depth -= 1 + + if self._forward_search: + path.reverse() + path_id.reverse() + + return (path, path_id) diff --git a/sledge/simulation/planner/pdm_planner/utils/graph_search/dijkstra.py b/sledge/simulation/planner/pdm_planner/utils/graph_search/dijkstra.py new file mode 100644 index 0000000..fcd89fe --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/graph_search/dijkstra.py @@ -0,0 +1,145 @@ +from typing import Dict, List, Optional, Tuple + +import numpy as np +from nuplan.common.maps.abstract_map_objects import LaneGraphEdgeMapObject, RoadBlockGraphEdgeMapObject + + +class Dijkstra: + """ + A class that performs dijkstra's shortest path. The class operates on lane level graph search. + The goal condition is specified to be if the lane can be found at the target roadblock or roadblock connector. + """ + + def __init__(self, start_edge: LaneGraphEdgeMapObject, candidate_lane_edge_ids: List[str]): + """ + Constructor for the Dijkstra class. + :param start_edge: The starting edge for the search + :param candidate_lane_edge_ids: The candidates lane ids that can be included in the search. + """ + self._queue = list([start_edge]) + self._parent: Dict[str, Optional[LaneGraphEdgeMapObject]] = dict() + self._candidate_lane_edge_ids = candidate_lane_edge_ids + + def search(self, target_roadblock: RoadBlockGraphEdgeMapObject) -> Tuple[List[LaneGraphEdgeMapObject], bool]: + """ + Performs dijkstra's shortest path to find a route to the target roadblock. + :param target_roadblock: The target roadblock the path should end at. + :return: + - A route starting from the given start edge + - A bool indicating if the route is successfully found. Successful means that there exists a path + from the start edge to an edge contained in the end roadblock. + If unsuccessful the shortest deepest path is returned. + """ + start_edge = self._queue[0] + + # Initial search states + path_found: bool = False + end_edge: LaneGraphEdgeMapObject = start_edge + + self._parent[start_edge.id] = None + self._frontier = [start_edge.id] + self._dist = [1] + self._depth = [1] + + self._expanded = [] + self._expanded_id = [] + self._expanded_dist = [] + self._expanded_depth = [] + + while len(self._queue) > 0: + dist, idx = min((val, idx) for (idx, val) in enumerate(self._dist)) + current_edge = self._queue[idx] + current_depth = self._depth[idx] + + del self._dist[idx], self._queue[idx], self._frontier[idx], self._depth[idx] + + if self._check_goal_condition(current_edge, target_roadblock): + end_edge = current_edge + path_found = True + break + + self._expanded.append(current_edge) + self._expanded_id.append(current_edge.id) + self._expanded_dist.append(dist) + self._expanded_depth.append(current_depth) + + # Populate queue + for next_edge in current_edge.outgoing_edges: + if next_edge.id not in self._candidate_lane_edge_ids: + continue + + alt = dist + self._edge_cost(next_edge) + if next_edge.id not in self._expanded_id and next_edge.id not in self._frontier: + self._parent[next_edge.id] = current_edge + self._queue.append(next_edge) + self._frontier.append(next_edge.id) + self._dist.append(alt) + self._depth.append(current_depth + 1) + end_edge = next_edge + + elif next_edge.id in self._frontier: + next_edge_idx = self._frontier.index(next_edge.id) + current_cost = self._dist[next_edge_idx] + if alt < current_cost: + self._parent[next_edge.id] = current_edge + self._dist[next_edge_idx] = alt + self._depth[next_edge_idx] = current_depth + 1 + + if not path_found: + # filter max depth + max_depth = max(self._expanded_depth) + idx_max_depth = list(np.where(np.array(self._expanded_depth) == max_depth)[0]) + dist_at_max_depth = [self._expanded_dist[i] for i in idx_max_depth] + + dist, _idx = min((val, idx) for (idx, val) in enumerate(dist_at_max_depth)) + end_edge = self._expanded[idx_max_depth[_idx]] + + return self._construct_path(end_edge), path_found + + @staticmethod + def _edge_cost(lane: LaneGraphEdgeMapObject) -> float: + """ + Edge cost of given lane. + :param lane: lane class + :return: length of lane + """ + return lane.baseline_path.length + + @staticmethod + def _check_end_condition(depth: int, target_depth: int) -> bool: + """ + Check if the search should end regardless if the goal condition is met. + :param depth: The current depth to check. + :param target_depth: The target depth to check against. + :return: True if: + - The current depth exceeds the target depth. + """ + return depth > target_depth + + @staticmethod + def _check_goal_condition( + current_edge: LaneGraphEdgeMapObject, + target_roadblock: RoadBlockGraphEdgeMapObject, + ) -> bool: + """ + Check if the current edge is at the target roadblock at the given depth. + :param current_edge: The edge to check. + :param target_roadblock: The target roadblock the edge should be contained in. + :return: whether the current edge is in the target roadblock + """ + return current_edge.get_roadblock_id() == target_roadblock.id + + def _construct_path(self, end_edge: LaneGraphEdgeMapObject) -> List[LaneGraphEdgeMapObject]: + """ + :param end_edge: The end edge to start back propagating back to the start edge. + :param depth: The depth of the target edge. + :return: The constructed path as a list of LaneGraphEdgeMapObject + """ + path = [end_edge] + while self._parent[end_edge.id] is not None: + node = self._parent[end_edge.id] + path.append(node) + end_edge = node + path.reverse() + + return path diff --git a/sledge/simulation/planner/pdm_planner/utils/pdm_array_representation.py b/sledge/simulation/planner/pdm_planner/utils/pdm_array_representation.py new file mode 100644 index 0000000..ffab838 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/pdm_array_representation.py @@ -0,0 +1,199 @@ +from typing import List + +import numpy as np +import numpy.typing as npt +import shapely + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateSE2, StateVector2D, TimePoint +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import BBCoordsIndex, SE2Index, StateIndex +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import translate_lon_and_lat + + +def array_to_state_se2(array: npt.NDArray[np.float64]) -> StateSE2: + """ + Converts array representation to single StateSE2. + :param array: array filled with (x,y,θ) + :return: StateSE2 class + """ + return StateSE2(array[0], array[1], array[2]) + + +# use numpy vectorize function to apply on last dim +array_to_state_se2_vectorize = np.vectorize(array_to_state_se2, signature="(n)->()") + + +def array_to_states_se2(array: npt.NDArray[np.float64]) -> npt.NDArray[np.object_]: + """ + Converts array representation to StateSE2 over last dim. + :param array: array filled with (x,y,θ) on last dim + :return: array of StateSE2 class + """ + assert array.shape[-1] == len(SE2Index) + return array_to_state_se2_vectorize(array) + + +def state_se2_to_array(state_se2: StateSE2) -> npt.NDArray[np.float64]: + """ + Converts StateSE2 to array representation. + :param state_se2: class containing (x,y,θ) + :return: array containing (x,y,θ) + """ + array = np.zeros(len(SE2Index), dtype=np.float64) + array[SE2Index.X] = state_se2.x + array[SE2Index.Y] = state_se2.y + array[SE2Index.HEADING] = state_se2.heading + return array + + +def states_se2_to_array(states_se2: List[StateSE2]) -> npt.NDArray[np.float64]: + """ + Converts list of StateSE2 object to array representation + :param states_se2: list of StateSE2 object's + :return: array representation of states + """ + state_se2_array = np.zeros((len(states_se2), len(SE2Index)), dtype=np.float64) + for i, state_se2 in enumerate(states_se2): + state_se2_array[i] = state_se2_to_array(state_se2) + return state_se2_array + + +def ego_state_to_state_array(ego_state: EgoState) -> npt.NDArray[np.float64]: + """ + Converts an ego state into an array representation (drops time-stamps and vehicle parameters) + :param ego_state: ego state class + :return: array containing ego state values + """ + state_array = np.zeros(StateIndex.size(), dtype=np.float64) + + state_array[StateIndex.STATE_SE2] = ego_state.rear_axle.serialize() + state_array[StateIndex.VELOCITY_2D] = ego_state.dynamic_car_state.rear_axle_velocity_2d.array + state_array[StateIndex.ACCELERATION_2D] = ego_state.dynamic_car_state.rear_axle_acceleration_2d.array + + state_array[StateIndex.STEERING_ANGLE] = ego_state.tire_steering_angle + state_array[StateIndex.STEERING_RATE] = ego_state.dynamic_car_state.tire_steering_rate + + state_array[StateIndex.ANGULAR_VELOCITY] = ego_state.dynamic_car_state.angular_velocity + state_array[StateIndex.ANGULAR_ACCELERATION] = ego_state.dynamic_car_state.angular_acceleration + + return state_array + + +def ego_states_to_state_array(ego_states: List[EgoState]) -> npt.NDArray[np.float64]: + """ + Converts a list of ego states into an array representation (drops time-stamps and vehicle parameters) + :param ego_state: ego state class + :return: array containing ego state values + """ + state_array = np.array( + [ego_state_to_state_array(ego_state) for ego_state in ego_states], + dtype=np.float64, + ) + return state_array + + +def state_array_to_ego_state( + state_array: npt.NDArray[np.float64], + time_point: TimePoint, + vehicle_parameters: VehicleParameters, +) -> EgoState: + """ + Converts array representation of ego state back to ego state class. + :param state_array: array representation of ego states + :param time_point: time point of state + :param vehicle_parameters: vehicle parameter of ego + :return: nuPlan's EgoState object + """ + return EgoState.build_from_rear_axle( + rear_axle_pose=StateSE2(*state_array[StateIndex.STATE_SE2]), + rear_axle_velocity_2d=StateVector2D(*state_array[StateIndex.VELOCITY_2D]), + rear_axle_acceleration_2d=StateVector2D(*state_array[StateIndex.ACCELERATION_2D]), + tire_steering_angle=state_array[StateIndex.STEERING_ANGLE], + time_point=time_point, + vehicle_parameters=vehicle_parameters, + is_in_auto_mode=True, + angular_vel=state_array[StateIndex.ANGULAR_VELOCITY], + angular_accel=state_array[StateIndex.ANGULAR_ACCELERATION], + tire_steering_rate=state_array[StateIndex.STEERING_RATE], + ) + + +def state_array_to_ego_states( + state_array: npt.NDArray[np.float64], + time_points: List[TimePoint], + vehicle_parameter: VehicleParameters, +) -> List[EgoState]: + """ + Converts array representation of ego states back to list of ego state class. + :param state_array: array representation of ego states + :param time_point: list of time point of state array + :param vehicle_parameters: vehicle parameter of ego + :return: list nuPlan's EgoState object + """ + ego_states_list: List[EgoState] = [] + for i, time_point in enumerate(time_points): + state = state_array[i] if i < len(state_array) else state_array[-1] + ego_states_list.append(state_array_to_ego_state(state, time_point, vehicle_parameter)) + return ego_states_list + + +def state_array_to_coords_array( + states: npt.NDArray[np.float64], + vehicle_parameters: VehicleParameters, +) -> npt.NDArray[np.float64]: + """ + Converts multi-dim array representation of ego states to bounding box coordinates + :param state_array: array representation of ego states + :param vehicle_parameters: vehicle parameter of ego + :return: multi-dim array bounding box coordinates + """ + n_batch, n_time, n_states = states.shape + + half_length, half_width, rear_axle_to_center = ( + vehicle_parameters.half_length, + vehicle_parameters.half_width, + vehicle_parameters.rear_axle_to_center, + ) + + headings = states[..., StateIndex.HEADING] + cos, sin = np.cos(headings), np.sin(headings) + + # calculate ego center from rear axle + rear_axle_to_center_translate = np.stack([rear_axle_to_center * cos, rear_axle_to_center * sin], axis=-1) + + ego_centers: npt.NDArray[np.float64] = states[..., StateIndex.POINT] + rear_axle_to_center_translate + + coords_array: npt.NDArray[np.float64] = np.zeros((n_batch, n_time, len(BBCoordsIndex), 2), dtype=np.float64) + + coords_array[:, :, BBCoordsIndex.CENTER] = ego_centers + + coords_array[:, :, BBCoordsIndex.FRONT_LEFT] = translate_lon_and_lat(ego_centers, headings, half_length, half_width) + coords_array[:, :, BBCoordsIndex.FRONT_RIGHT] = translate_lon_and_lat( + ego_centers, headings, half_length, -half_width + ) + coords_array[:, :, BBCoordsIndex.REAR_LEFT] = translate_lon_and_lat(ego_centers, headings, -half_length, half_width) + coords_array[:, :, BBCoordsIndex.REAR_RIGHT] = translate_lon_and_lat( + ego_centers, headings, -half_length, -half_width + ) + + return coords_array + + +def coords_array_to_polygon_array( + coords: npt.NDArray[np.float64], +) -> npt.NDArray[np.object_]: + """ + Converts multi-dim array of bounding box coords of to polygons + :param coords: bounding box coords (including corners and center) + :return: array of shapely's polygons + """ + # create coords copy and use center point for closed exterior + coords_exterior: npt.NDArray[np.float64] = coords.copy() + coords_exterior[..., BBCoordsIndex.CENTER, :] = coords_exterior[..., BBCoordsIndex.FRONT_LEFT, :] + + # load new coordinates into polygon array + polygons = shapely.creation.polygons(coords_exterior) + + return polygons diff --git a/sledge/simulation/planner/pdm_planner/utils/pdm_emergency_brake.py b/sledge/simulation/planner/pdm_planner/utils/pdm_emergency_brake.py new file mode 100644 index 0000000..77ce2d1 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/pdm_emergency_brake.py @@ -0,0 +1,136 @@ +from typing import Optional + +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateSE2, StateVector2D, TimePoint +from nuplan.common.geometry.convert import relative_to_absolute_poses +from nuplan.planning.simulation.trajectory.interpolated_trajectory import InterpolatedTrajectory +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.scoring.pdm_scorer import PDMScorer + + +class PDMEmergencyBrake: + """Class for emergency brake maneuver of PDM-Closed.""" + + def __init__( + self, + trajectory_sampling: TrajectorySampling, + time_to_infraction_threshold: float = 2.0, + max_ego_speed: float = 5.0, + max_long_accel: float = 2.40, + min_long_accel: float = -4.05, + infraction: str = "collision", + ): + """ + Constructor for PDMEmergencyBrake + :param trajectory_sampling: Sampling parameters for final trajectory + :param time_to_infraction_threshold: threshold for applying brake, defaults to 2.0 + :param max_ego_speed: maximum speed to apply brake, defaults to 5.0 + :param max_long_accel: maximum longitudinal acceleration for braking, defaults to 2.40 + :param min_long_accel: min longitudinal acceleration for braking, defaults to -4.05 + :param infraction: infraction to determine braking (collision or ttc), defaults to "collision" + """ + + # trajectory parameters + self._trajectory_sampling = trajectory_sampling + + # braking parameters + self._max_ego_speed: float = max_ego_speed # [m/s] + self._max_long_accel: float = max_long_accel # [m/s^2] + self._min_long_accel: float = min_long_accel # [m/s^2] + + # braking condition parameters + self._time_to_infraction_threshold: float = time_to_infraction_threshold + self._infraction: str = infraction + + assert self._infraction in [ + "collision", + "ttc", + ], f"PDMEmergencyBraking: Infraction {self._infraction} not available as brake condition!" + + def brake_if_emergency( + self, ego_state: EgoState, scores: npt.NDArray[np.float64], scorer: PDMScorer + ) -> Optional[InterpolatedTrajectory]: + """ + Applies emergency brake only if an infraction is expected within horizon. + :param ego_state: state object of ego + :param scores: array of proposal scores + :param metric: scorer class of PDM + :return: brake trajectory or None + """ + + trajectory = None + ego_speed: float = ego_state.dynamic_car_state.speed + + proposal_idx = np.argmax(scores) + + # retrieve time to infraction depending on brake detection mode + if self._infraction == "ttc": + time_to_infraction = scorer.time_to_ttc_infraction(proposal_idx) + + elif self._infraction == "collision": + time_to_infraction = scorer.time_to_at_fault_collision(proposal_idx) + + # check time to infraction below threshold + if time_to_infraction <= self._time_to_infraction_threshold and ego_speed <= self._max_ego_speed: + trajectory = self._generate_trajectory(ego_state) + + return trajectory + + def _generate_trajectory(self, ego_state: EgoState) -> InterpolatedTrajectory: + """ + Generates trajectory for reach zero velocity. + :param ego_state: state object of ego + :return: InterpolatedTrajectory for braking + """ + current_time_point = ego_state.time_point + current_velocity = ego_state.dynamic_car_state.center_velocity_2d.x + current_acceleration = ego_state.dynamic_car_state.center_acceleration_2d.x + + target_velocity = 0.0 + + if current_velocity > 0.2: + k_p = 10.0 + k_d = 0.0 + + error = -current_velocity + dt_error = -current_acceleration + u_t = k_p * error + k_d * dt_error + + error = max(min(u_t, self._max_long_accel), self._min_long_accel) + correcting_velocity = 11 / 10 * (current_velocity + error) + + else: + k_p = 4 + k_d = 1 + + error = target_velocity - current_velocity + dt_error = -current_acceleration + + u_t = k_p * error + k_d * dt_error + + correcting_velocity = max(min(u_t, self._max_long_accel), self._min_long_accel) + + trajectory_states = [] + + # Propagate planned trajectory for set number of samples + for sample in range(self._trajectory_sampling.num_poses + 1): + time_t = self._trajectory_sampling.interval_length * sample + pose = relative_to_absolute_poses(ego_state.center, [StateSE2(correcting_velocity * time_t, 0, 0)])[0] + + ego_state_ = EgoState.build_from_center( + center=pose, + center_velocity_2d=StateVector2D(0, 0), + center_acceleration_2d=StateVector2D(0, 0), + tire_steering_angle=0.0, + time_point=current_time_point, + vehicle_parameters=ego_state.car_footprint.vehicle_parameters, + ) + trajectory_states.append(ego_state_) + + current_time_point += TimePoint(int(self._trajectory_sampling.interval_length * 1e6)) + + return InterpolatedTrajectory(trajectory_states) diff --git a/sledge/simulation/planner/pdm_planner/utils/pdm_enums.py b/sledge/simulation/planner/pdm_planner/utils/pdm_enums.py new file mode 100644 index 0000000..a2ab26c --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/pdm_enums.py @@ -0,0 +1,171 @@ +from enum import IntEnum + + +class StateIndex: + """Index mapping for array representation of ego states.""" + + _X = 0 + _Y = 1 + _HEADING = 2 + _VELOCITY_X = 3 + _VELOCITY_Y = 4 + _ACCELERATION_X = 5 + _ACCELERATION_Y = 6 + _STEERING_ANGLE = 7 + _STEERING_RATE = 8 + _ANGULAR_VELOCITY = 9 + _ANGULAR_ACCELERATION = 10 + + @classmethod + def size(cls): + valid_attributes = [ + attribute + for attribute in dir(cls) + if attribute.startswith("_") + and not attribute.startswith("__") + and not callable(getattr(cls, attribute)) + ] + return len(valid_attributes) + + @classmethod + @property + def X(cls): + return cls._X + + @classmethod + @property + def Y(cls): + return cls._Y + + @classmethod + @property + def HEADING(cls): + return cls._HEADING + + @classmethod + @property + def VELOCITY_X(cls): + return cls._VELOCITY_X + + @classmethod + @property + def VELOCITY_Y(cls): + return cls._VELOCITY_Y + + @classmethod + @property + def ACCELERATION_X(cls): + return cls._ACCELERATION_X + + @classmethod + @property + def ACCELERATION_Y(cls): + return cls._ACCELERATION_Y + + @classmethod + @property + def STEERING_ANGLE(cls): + return cls._STEERING_ANGLE + + @classmethod + @property + def STEERING_RATE(cls): + return cls._STEERING_RATE + + @classmethod + @property + def ANGULAR_VELOCITY(cls): + return cls._ANGULAR_VELOCITY + + @classmethod + @property + def ANGULAR_ACCELERATION(cls): + return cls._ANGULAR_ACCELERATION + + @classmethod + @property + def POINT(cls): + # assumes X, Y have subsequent indices + return slice(cls._X, cls._Y + 1) + + @classmethod + @property + def STATE_SE2(cls): + # assumes X, Y, HEADING have subsequent indices + return slice(cls._X, cls._HEADING + 1) + + @classmethod + @property + def VELOCITY_2D(cls): + # assumes velocity X, Y have subsequent indices + return slice(cls._VELOCITY_X, cls._VELOCITY_Y + 1) + + @classmethod + @property + def ACCELERATION_2D(cls): + # assumes acceleration X, Y have subsequent indices + return slice(cls._ACCELERATION_X, cls._ACCELERATION_Y + 1) + + +class SE2Index(IntEnum): + """Index mapping for state se2 (x,y,θ) arrays.""" + + X = 0 + Y = 1 + HEADING = 2 + + +class DynamicStateIndex(IntEnum): + """Index mapping for dynamic car state (output of controller).""" + + ACCELERATION_X = 0 + STEERING_RATE = 1 + + +class StateIDMIndex(IntEnum): + """Index mapping for IDM states.""" + + PROGRESS = 0 + VELOCITY = 1 + + +class LeadingAgentIndex(IntEnum): + """Index mapping for leading agent state (for IDM policies).""" + + PROGRESS = 0 + VELOCITY = 1 + LENGTH_REAR = 2 + + +class BBCoordsIndex(IntEnum): + """Index mapping for corners and center of bounding boxes.""" + + FRONT_LEFT = 0 + REAR_LEFT = 1 + REAR_RIGHT = 2 + FRONT_RIGHT = 3 + CENTER = 4 + + +class EgoAreaIndex(IntEnum): + """Index mapping for area of ego agent (used in PDMScorer).""" + + MULTIPLE_LANES = 0 + NON_DRIVABLE_AREA = 1 + ONCOMING_TRAFFIC = 2 + + +class MultiMetricIndex(IntEnum): + """Index mapping multiplicative metrics (used in PDMScorer).""" + + NO_COLLISION = 0 + DRIVABLE_AREA = 1 + DRIVING_DIRECTION = 2 + + +class WeightedMetricIndex(IntEnum): + """Index mapping weighted metrics (used in PDMScorer).""" + + PROGRESS = 0 + TTC = 1 + COMFORTABLE = 2 diff --git a/sledge/simulation/planner/pdm_planner/utils/pdm_geometry_utils.py b/sledge/simulation/planner/pdm_planner/utils/pdm_geometry_utils.py new file mode 100644 index 0000000..7811820 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/pdm_geometry_utils.py @@ -0,0 +1,96 @@ +from typing import List + +import numpy as np +import numpy.typing as npt + +from nuplan.common.actor_state.state_representation import StateSE2 + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import SE2Index + + +def normalize_angle(angle): + """ + Map a angle in range [-π, π] + :param angle: any angle as float + :return: normalized angle + """ + return np.arctan2(np.sin(angle), np.cos(angle)) + + +def parallel_discrete_path(discrete_path: List[StateSE2], offset=float) -> List[StateSE2]: + """ + Creates a parallel discrete path for a given offset. + :param discrete_path: baseline path (x,y,θ) + :param offset: parall loffset + :return: parallel discrete path + """ + parallel_discrete_path = [] + for state in discrete_path: + theta = state.heading + np.pi / 2 + x_new = state.x + np.cos(theta) * offset + y_new = state.y + np.sin(theta) * offset + parallel_discrete_path.append(StateSE2(x_new, y_new, state.heading)) + return parallel_discrete_path + + +def translate_lon_and_lat( + centers: npt.NDArray[np.float64], + headings: npt.NDArray[np.float64], + lon: float, + lat: float, +) -> npt.NDArray[np.float64]: + """ + Translate the position component of an centers point array + :param centers: array to be translated + :param headings: array with heading angles + :param lon: [m] distance by which a point should be translated in longitudinal direction + :param lat: [m] distance by which a point should be translated in lateral direction + :return array of translated coordinates + """ + half_pi = np.pi / 2.0 + translation: npt.NDArray[np.float64] = np.stack( + [ + (lat * np.cos(headings + half_pi)) + (lon * np.cos(headings)), + (lat * np.sin(headings + half_pi)) + (lon * np.sin(headings)), + ], + axis=-1, + ) + return centers + translation + + +def calculate_progress(path: List[StateSE2]) -> List[float]: + """ + Calculate the cumulative progress of a given path. + :param path: a path consisting of StateSE2 as waypoints + :return: a cumulative list of progress + """ + x_position = [point.x for point in path] + y_position = [point.y for point in path] + x_diff = np.diff(x_position) + y_diff = np.diff(y_position) + points_diff: npt.NDArray[np.float64] = np.concatenate(([x_diff], [y_diff]), axis=0, dtype=np.float64) + progress_diff = np.append(0.0, np.linalg.norm(points_diff, axis=0)) + return np.cumsum(progress_diff, dtype=np.float64) # type: ignore + + +def convert_absolute_to_relative_se2_array( + origin: StateSE2, state_se2_array: npt.NDArray[np.float64] +) -> npt.NDArray[np.float64]: + """ + Converts an StateSE2 array from global to relative coordinates. + :param origin: origin pose of relative coords system + :param state_se2_array: array of SE2 states with (x,y,θ) in last dim + :return: SE2 coords array in relative coordinates + """ + assert len(SE2Index) == state_se2_array.shape[-1] + + theta = -origin.heading + origin_array = np.array([[origin.x, origin.y, origin.heading]], dtype=np.float64) + + R = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) + + points_rel = state_se2_array - origin_array + points_rel[..., :2] = points_rel[..., :2] @ R.T + points_rel[:, 2] = normalize_angle(points_rel[:, 2]) + + return points_rel diff --git a/sledge/simulation/planner/pdm_planner/utils/pdm_path.py b/sledge/simulation/planner/pdm_planner/utils/pdm_path.py new file mode 100644 index 0000000..ea22f85 --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/pdm_path.py @@ -0,0 +1,93 @@ +from typing import Any, List, Union + +import numpy as np +import numpy.typing as npt +from scipy.interpolate import interp1d +from shapely.creation import linestrings +from shapely.geometry import LineString +from shapely.ops import substring + +from nuplan.common.actor_state.state_representation import StateSE2 + +from sledge.simulation.planner.pdm_planner.utils.pdm_enums import SE2Index +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import calculate_progress, normalize_angle +from sledge.simulation.planner.pdm_planner.utils.pdm_array_representation import ( + array_to_states_se2, + states_se2_to_array, +) + + +class PDMPath: + """Class representing a path to interpolate for PDM.""" + + def __init__(self, discrete_path: List[StateSE2]): + """ + Constructor for PDMPath + :param discrete_path: list of (x,y,θ) values + """ + + self._discrete_path = discrete_path + self._states_se2_array = states_se2_to_array(discrete_path) + self._states_se2_array[:, SE2Index.HEADING] = np.unwrap(self._states_se2_array[:, SE2Index.HEADING], axis=0) + self._progress = calculate_progress(discrete_path) + + self._linestring = linestrings(self._states_se2_array[:, : SE2Index.HEADING]) + self._interpolator = interp1d(self._progress, self._states_se2_array, axis=0) + + @property + def discrete_path(self): + """Getter for discrete StateSE2 objects of path.""" + return self._discrete_path + + @property + def length(self): + """Getter for length of path.""" + return self._progress[-1] + + @property + def linestring(self) -> LineString: + """Getter for shapely's linestring of path.""" + return self._linestring + + def project(self, points: Any) -> Any: + return self._linestring.project(points) + + def interpolate( + self, + distances: Union[List[float], npt.NDArray[np.float64]], + as_array=False, + ) -> Union[npt.NDArray[np.object_], npt.NDArray[np.float64]]: + """ + Calculates (x,y,θ) for a given distance along the path. + :param distances: list of array of distance values + :param as_array: whether to return in array representation, defaults to False + :return: array of StateSE2 class or (x,y,θ) values + """ + clipped_distances = np.clip(distances, 1e-5, self.length) + interpolated_se2_array = self._interpolator(clipped_distances) + interpolated_se2_array[..., 2] = normalize_angle(interpolated_se2_array[..., 2]) + interpolated_se2_array[np.isnan(interpolated_se2_array)] = 0.0 + + if as_array: + return interpolated_se2_array + + return array_to_states_se2(interpolated_se2_array) + + def substring(self, start_distance: float, end_distance: float) -> LineString: + """ + Creates a sub-linestring between start and ending distances. + :param start_distance: distance along the path to start [m] + :param end_distance: distance along the path to end [m] + :return: LineString + """ + + # try faster method fist + start_distance = np.clip(start_distance, 0.0, self.length) + end_distance = np.clip(end_distance, 0.0, self.length) + in_interval = np.logical_and(start_distance <= self._progress, self._progress <= end_distance) + coordinates = self._states_se2_array[in_interval, :2] + if len(coordinates) > 1: + return LineString(coordinates) + + # fallback to slower method of shapely + return substring(self.linestring, start_distance, end_distance) diff --git a/sledge/simulation/planner/pdm_planner/utils/route_utils.py b/sledge/simulation/planner/pdm_planner/utils/route_utils.py new file mode 100644 index 0000000..dc2f47c --- /dev/null +++ b/sledge/simulation/planner/pdm_planner/utils/route_utils.py @@ -0,0 +1,216 @@ +from typing import Dict, List, Tuple + +import numpy as np + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateSE2 +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.maps.abstract_map_objects import RoadBlockGraphEdgeMapObject +from nuplan.common.maps.maps_datatypes import SemanticMapLayer +from nuplan.planning.simulation.occupancy_map.strtree_occupancy_map import STRTreeOccupancyMapFactory + +from sledge.simulation.planner.pdm_planner.utils.graph_search.bfs_roadblock import BreadthFirstSearchRoadBlock +from sledge.simulation.planner.pdm_planner.utils.pdm_geometry_utils import normalize_angle + + +def get_current_roadblock_candidates( + ego_state: EgoState, + map_api: AbstractMap, + route_roadblocks_dict: Dict[str, RoadBlockGraphEdgeMapObject], + heading_error_thresh: float = np.pi / 4, + displacement_error_thresh: float = 3, +) -> Tuple[RoadBlockGraphEdgeMapObject, List[RoadBlockGraphEdgeMapObject]]: + """ + Determines a set of roadblock candidate where ego is located + :param ego_state: class containing ego state + :param map_api: map object + :param route_roadblocks_dict: dictionary of on-route roadblocks + :param heading_error_thresh: maximum heading error, defaults to np.pi/4 + :param displacement_error_thresh: maximum displacement, defaults to 3 + :return: tuple of most promising roadblock and other candidates + """ + ego_pose: StateSE2 = ego_state.rear_axle + roadblock_candidates = [] + + layers = [SemanticMapLayer.ROADBLOCK, SemanticMapLayer.ROADBLOCK_CONNECTOR] + roadblock_dict = map_api.get_proximal_map_objects(point=ego_pose.point, radius=1.0, layers=layers) + roadblock_candidates = ( + roadblock_dict[SemanticMapLayer.ROADBLOCK] + roadblock_dict[SemanticMapLayer.ROADBLOCK_CONNECTOR] + ) + + if not roadblock_candidates: + for layer in layers: + roadblock_id_, distance = map_api.get_distance_to_nearest_map_object(point=ego_pose.point, layer=layer) + roadblock = map_api.get_map_object(roadblock_id_, layer) + + if roadblock: + roadblock_candidates.append(roadblock) + + on_route_candidates, on_route_candidate_displacement_errors = [], [] + candidates, candidate_displacement_errors = [], [] + + roadblock_displacement_errors = [] + roadblock_heading_errors = [] + + for idx, roadblock in enumerate(roadblock_candidates): + lane_displacement_error, lane_heading_error = np.inf, np.inf + + for lane in roadblock.interior_edges: + lane_discrete_path: List[StateSE2] = lane.baseline_path.discrete_path + lane_discrete_points = np.array([state.point.array for state in lane_discrete_path], dtype=np.float64) + lane_state_distances = ((lane_discrete_points - ego_pose.point.array[None, ...]) ** 2.0).sum(axis=-1) ** 0.5 + argmin = np.argmin(lane_state_distances) + + heading_error = np.abs(normalize_angle(lane_discrete_path[argmin].heading - ego_pose.heading)) + displacement_error = lane_state_distances[argmin] + + if displacement_error < lane_displacement_error: + lane_heading_error, lane_displacement_error = ( + heading_error, + displacement_error, + ) + + if heading_error < heading_error_thresh and displacement_error < displacement_error_thresh: + if roadblock.id in route_roadblocks_dict.keys(): + on_route_candidates.append(roadblock) + on_route_candidate_displacement_errors.append(displacement_error) + else: + candidates.append(roadblock) + candidate_displacement_errors.append(displacement_error) + + roadblock_displacement_errors.append(lane_displacement_error) + roadblock_heading_errors.append(lane_heading_error) + + if on_route_candidates: # prefer on-route roadblocks + return ( + on_route_candidates[np.argmin(on_route_candidate_displacement_errors)], + on_route_candidates, + ) + elif candidates: # fallback to most promising candidate + return candidates[np.argmin(candidate_displacement_errors)], candidates + + # otherwise, just find any close roadblock + return ( + roadblock_candidates[np.argmin(roadblock_displacement_errors)], + roadblock_candidates, + ) + + +def route_roadblock_correction( + ego_state: EgoState, + map_api: AbstractMap, + route_roadblock_dict: Dict[str, RoadBlockGraphEdgeMapObject], + search_depth_backward: int = 15, + search_depth_forward: int = 30, +) -> List[str]: + """ + Applies several methods to correct route roadblocks. + :param ego_state: class containing ego state + :param map_api: map object + :param route_roadblocks_dict: dictionary of on-route roadblocks + :param search_depth_backward: depth of forward BFS search, defaults to 15 + :param search_depth_forward: depth of backward BFS search, defaults to 30 + :return: list of roadblock id's of corrected route + """ + starting_block, starting_block_candidates = get_current_roadblock_candidates( + ego_state, map_api, route_roadblock_dict + ) + starting_block_ids = [roadblock.id for roadblock in starting_block_candidates] + + route_roadblocks = list(route_roadblock_dict.values()) + route_roadblock_ids = list(route_roadblock_dict.keys()) + + # Fix 1: when agent starts off-route + if starting_block.id not in route_roadblock_ids: + # Backward search if current roadblock not in route + graph_search = BreadthFirstSearchRoadBlock(route_roadblock_ids[0], map_api, forward_search=False) + (path, path_id), path_found = graph_search.search(starting_block_ids, max_depth=search_depth_backward) + + if path_found: + route_roadblocks[:0] = path[:-1] + route_roadblock_ids[:0] = path_id[:-1] + + else: + # Forward search to any route roadblock + graph_search = BreadthFirstSearchRoadBlock(starting_block.id, map_api, forward_search=True) + (path, path_id), path_found = graph_search.search(route_roadblock_ids[:3], max_depth=search_depth_forward) + + if path_found: + end_roadblock_idx = np.argmax(np.array(route_roadblock_ids) == path_id[-1]) + + route_roadblocks = route_roadblocks[end_roadblock_idx + 1 :] + route_roadblock_ids = route_roadblock_ids[end_roadblock_idx + 1 :] + + route_roadblocks[:0] = path + route_roadblock_ids[:0] = path_id + + # Fix 2: check if roadblocks are linked, search for links if not + roadblocks_to_append = {} + for i in range(len(route_roadblocks) - 1): + next_incoming_block_ids = [_roadblock.id for _roadblock in route_roadblocks[i + 1].incoming_edges] + is_incoming = route_roadblock_ids[i] in next_incoming_block_ids + + if is_incoming: + continue + + graph_search = BreadthFirstSearchRoadBlock(route_roadblock_ids[i], map_api, forward_search=True) + (path, path_id), path_found = graph_search.search(route_roadblock_ids[i + 1], max_depth=search_depth_forward) + + if path_found and path and len(path) >= 3: + path, path_id = path[1:-1], path_id[1:-1] + roadblocks_to_append[i] = (path, path_id) + + # append missing intermediate roadblocks + offset = 1 + for i, (path, path_id) in roadblocks_to_append.items(): + route_roadblocks[i + offset : i + offset] = path + route_roadblock_ids[i + offset : i + offset] = path_id + offset += len(path) + + # Fix 3: cut route-loops + route_roadblocks, route_roadblock_ids = remove_route_loops(route_roadblocks, route_roadblock_ids) + + return route_roadblock_ids + + +def remove_route_loops( + route_roadblocks: List[RoadBlockGraphEdgeMapObject], + route_roadblock_ids: List[str], +) -> Tuple[List[str], List[RoadBlockGraphEdgeMapObject]]: + """ + Remove ending of route, if the roadblock are intersecting the route (forming a loop). + :param route_roadblocks: input route roadblocks + :param route_roadblock_ids: input route roadblocks ids + :return: tuple of ids and roadblocks of route without loops + """ + + roadblock_occupancy_map = None + loop_idx = None + + for idx, roadblock in enumerate(route_roadblocks): + # loops only occur at intersection, thus searching for roadblock-connectors. + if str(roadblock.__class__.__name__) == "NuPlanRoadBlockConnector": + if not roadblock_occupancy_map: + roadblock_occupancy_map = STRTreeOccupancyMapFactory.get_from_geometry( + [roadblock.polygon], [roadblock.id] + ) + continue + + strtree, index_by_id = roadblock_occupancy_map._build_strtree() + indices = strtree.query(roadblock.polygon) + if len(indices) > 0: + for geom in strtree.geometries.take(indices): + area = geom.intersection(roadblock.polygon).area + if area > 1: + loop_idx = idx + break + if loop_idx: + break + + roadblock_occupancy_map.insert(roadblock.id, roadblock.polygon) + + if loop_idx: + route_roadblocks = route_roadblocks[:loop_idx] + route_roadblock_ids = route_roadblock_ids[:loop_idx] + + return route_roadblocks, route_roadblock_ids diff --git a/sledge/simulation/scenarios/__init__.py b/sledge/simulation/scenarios/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/scenarios/sledge_scenario/__init__.py b/sledge/simulation/scenarios/sledge_scenario/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/simulation/scenarios/sledge_scenario/sledge_scenario.py b/sledge/simulation/scenarios/sledge_scenario/sledge_scenario.py new file mode 100644 index 0000000..6fea851 --- /dev/null +++ b/sledge/simulation/scenarios/sledge_scenario/sledge_scenario.py @@ -0,0 +1,335 @@ +from __future__ import annotations + +# import warnings +from pathlib import Path +from typing import Any, Generator, List, Optional, Set, Tuple, Type, cast + +import numpy as np +from shapely.geometry import Point + +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks, SensorChannel, Sensors +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling +from nuplan.planning.training.preprocessing.utils.feature_cache import FeatureCachePickle +from nuplan.common.actor_state.vehicle_parameters import get_pacifica_parameters +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import StateVector2D, StateSE2, TimePoint +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters +from nuplan.common.maps.abstract_map import AbstractMap +from nuplan.common.maps.maps_datatypes import ( + TrafficLightStatusData, + TrafficLightStatuses, + TrafficLightStatusType, + Transform, +) + +from sledge.simulation.maps.sledge_map.sledge_map import SledgeMap +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVector +from sledge.simulation.scenarios.sledge_scenario.sledge_scenario_utils import ( + project_sledge_vector, + sledge_vector_to_detection_tracks, + sample_future_indices, + get_route, +) + + +# TODO: add to some config +FUTURE_SAMPLING = TrajectorySampling(time_horizon=15, interval_length=0.1) +TRAFFIC_LIGHT_FLIP = 10.0 # [s] +ROUTE_LENGTH = 32 # [m] + + +class SledgeScenario(AbstractScenario): + """Scenario implementation for sledge that is used for simulation in Lane & Agent mode.""" + + def __init__(self, data_root: Path) -> None: + """ """ + + self._data_root = data_root + self._initial_lidar_token = data_root.parent.name + self._sledge_vector: SledgeVector = FeatureCachePickle().load_computed_feature_from_folder( + data_root, SledgeVector + ) + self._map_api = SledgeMap(self._sledge_vector) + + self._log_file = data_root + self._log_name: str = data_root.parent.parent.parent.name + self._scenario_type: str = data_root.parent.parent.name + + self._ego_vehicle_parameters = get_pacifica_parameters() + + # TODO: add to some config + self._future_sampling = FUTURE_SAMPLING + self._time_points = [ + TimePoint(int(time_s * 1e6)) + for time_s in np.arange(0, self._future_sampling.time_horizon, self._future_sampling.interval_length) + ] + self._number_of_iterations = len(self._time_points) + + self._route_roadblock_ids, self._route_path = get_route(self._map_api) + + def __reduce__(self) -> Tuple[Type[SledgeScenario], Tuple[Any, ...]]: + """ + Hints on how to reconstruct the object when pickling. + :return: Object type and constructor arguments to be used. + """ + return (self.__class__, (self._data_root,)) + + @property + def num_agents(self) -> int: + detection_tracks = self.get_tracked_objects_at_iteration(0) + detection_tracks.tracked_objects.get_agents() + return len(detection_tracks.tracked_objects.get_agents()) + + def _is_iteration_flipped(self, iteration: int) -> bool: + flip_iteration_interval = int(TRAFFIC_LIGHT_FLIP / self._future_sampling.interval_length) + segment = iteration // flip_iteration_interval + return segment % 2 == 1 + + @property + def ego_vehicle_parameters(self) -> VehicleParameters: + """Inherited, see superclass.""" + return self._ego_vehicle_parameters + + @property + def token(self) -> str: + """Inherited, see superclass.""" + return self._initial_lidar_token + + @property + def log_name(self) -> str: + """Inherited, see superclass.""" + return self._log_name + + @property + def scenario_name(self) -> str: + """Inherited, see superclass.""" + return self.token + + @property + def scenario_type(self) -> str: + """Inherited, see superclass.""" + return self._scenario_type + + @property + def map_api(self) -> AbstractMap: + """Inherited, see superclass.""" + return self._map_api + + @property + def map_root(self) -> str: + """Get the map root folder.""" + return self._map_root + + @property + def map_version(self) -> str: + """Get the map version.""" + return self._map_version + + @property + def database_interval(self) -> float: + """Inherited, see superclass.""" + return 0.05 # 20Hz + + def get_number_of_iterations(self) -> int: + """Inherited, see superclass.""" + return self._future_sampling.num_poses + + def get_lidar_to_ego_transform(self) -> Transform: + """Inherited, see superclass.""" + raise NotImplementedError + + def get_mission_goal(self) -> Optional[StateSE2]: + """Inherited, see superclass.""" + last_iteration = self.get_number_of_iterations() - 1 + return self.get_ego_state_at_iteration(last_iteration).center + + def get_route_roadblock_ids(self) -> List[str]: + """Inherited, see superclass.""" + + roadblock_ids = self._route_roadblock_ids + return cast(List[str], roadblock_ids) + + def get_expert_goal_state(self) -> StateSE2: + """Inherited, see superclass.""" + last_iteration = self.get_number_of_iterations() - 1 + return self.get_ego_state_at_iteration(last_iteration).center + + def get_time_point(self, iteration: int) -> TimePoint: + """Inherited, see superclass.""" + assert ( + 0 <= iteration < self.get_number_of_iterations() + ), f"Iteration {iteration} out of bound of {self.get_number_of_iterations()} iterations!" + + return self._time_points[iteration] + + def get_ego_state_at_iteration(self, iteration: int) -> EgoState: + """Inherited, see superclass.""" + initial_distance = self._route_path.project(Point(0, 0)) + + distance_per_iteration = ROUTE_LENGTH / self._future_sampling.num_poses + + center = self._route_path.interpolate([initial_distance + distance_per_iteration * iteration])[0] + center_velocity_2d = StateVector2D(float(self._sledge_vector.ego.states), 0) + center_acceleration_2d = StateVector2D(0, 0) + + # project ego with constant velocity and heading + time_point = self.get_time_point(iteration) + + return EgoState.build_from_center( + center=center, + center_velocity_2d=center_velocity_2d, + center_acceleration_2d=center_acceleration_2d, + tire_steering_angle=0.0, + time_point=time_point, + vehicle_parameters=self._ego_vehicle_parameters, + ) + + def get_tracked_objects_at_iteration( + self, + iteration: int, + future_trajectory_sampling: Optional[TrajectorySampling] = None, + ) -> DetectionsTracks: + """Inherited, see superclass.""" + assert 0 <= iteration < self.get_number_of_iterations(), f"Iteration is out of scenario: {iteration}!" + + # if not future_trajectory_sampling: + # warnings.warn("SledgeScenario: TrajectorySampling in get_tracked_objects_at_iteration() not supported.") + + time_point = self.get_time_point(iteration) + projected_sledge_vector = project_sledge_vector(self._sledge_vector, time_point.time_s) + detection_tracks = sledge_vector_to_detection_tracks(projected_sledge_vector, time_point.time_us) + return detection_tracks + + def get_tracked_objects_within_time_window_at_iteration( + self, + iteration: int, + past_time_horizon: float, + future_time_horizon: float, + filter_track_tokens: Optional[Set[str]] = None, + future_trajectory_sampling: Optional[TrajectorySampling] = None, + ) -> DetectionsTracks: + """Inherited, see superclass.""" + assert 0 <= iteration < self.get_number_of_iterations(), f"Iteration is out of scenario: {iteration}!" + raise NotImplementedError + + def get_sensors_at_iteration(self, iteration: int, channels: Optional[List[SensorChannel]] = None) -> Sensors: + """Inherited, see superclass.""" + raise NotImplementedError + + def get_future_timestamps( + self, iteration: int, time_horizon: float, num_samples: Optional[int] = None + ) -> Generator[TimePoint, None, None]: + """Inherited, see superclass.""" + indices = sample_future_indices(self._future_sampling, iteration, time_horizon, num_samples) + for idx in indices: + yield self.get_time_point(idx) + + def get_past_timestamps( + self, iteration: int, time_horizon: float, num_samples: Optional[int] = None + ) -> Generator[TimePoint, None, None]: + """Inherited, see superclass.""" + yield from [] # placeholder + + def get_ego_past_trajectory( + self, iteration: int, time_horizon: float, num_samples: Optional[int] = None + ) -> Generator[EgoState, None, None]: + """Inherited, see superclass.""" + # indices = sample_future_indices(self._future_sampling, iteration, time_horizon, num_samples) + for idx in [0]: + yield self.get_ego_state_at_iteration(idx) + + def get_ego_future_trajectory( + self, iteration: int, time_horizon: float, num_samples: Optional[int] = None + ) -> Generator[EgoState, None, None]: + """Inherited, see superclass.""" + indices = sample_future_indices(self._future_sampling, iteration, time_horizon, num_samples) + for idx in indices: + yield self.get_ego_state_at_iteration(idx) + + def get_past_tracked_objects( + self, + iteration: int, + time_horizon: float, + num_samples: Optional[int] = None, + future_trajectory_sampling: Optional[TrajectorySampling] = None, + ) -> Generator[DetectionsTracks, None, None]: + """Inherited, see superclass.""" + # indices = sample_future_indices(self._future_sampling, iteration, time_horizon, num_samples) + for idx in [0]: + yield self.get_tracked_objects_at_iteration(idx) + + def get_future_tracked_objects( + self, + iteration: int, + time_horizon: float, + num_samples: Optional[int] = None, + future_trajectory_sampling: Optional[TrajectorySampling] = None, + ) -> Generator[DetectionsTracks, None, None]: + """Inherited, see superclass.""" + + # if not future_trajectory_sampling: + # warnings.warn("SledgeScenario: TrajectorySampling not available for get_future_tracked_objects") + + indices = sample_future_indices(self._future_sampling, iteration, time_horizon, num_samples) + for idx in indices: + yield self.get_tracked_objects_at_iteration(idx) + + def get_past_sensors( + self, + iteration: int, + time_horizon: float, + num_samples: Optional[int] = None, + channels: Optional[List[SensorChannel]] = None, + ) -> Generator[Sensors, None, None]: + """Inherited, see superclass.""" + raise NotImplementedError + + def get_traffic_light_status_at_iteration(self, iteration: int) -> Generator[TrafficLightStatusData, None, None]: + """Inherited, see superclass.""" + + flip_traffic_lights = self._is_iteration_flipped(iteration) + + def _get_status_type(status_type: TrafficLightStatusType) -> TrafficLightStatusType: + if flip_traffic_lights: + if status_type == TrafficLightStatusType.RED: + return TrafficLightStatusType.GREEN + else: + return TrafficLightStatusType.RED + else: + return status_type + + for lane_id, traffic_light in self._map_api.sledge_map_graph.traffic_light_dict.items(): + yield TrafficLightStatusData(_get_status_type(traffic_light.status), lane_id, self._time_points[iteration]) + + def get_past_traffic_light_status_history( + self, iteration: int, time_horizon: float, num_samples: Optional[int] = None + ) -> Generator[TrafficLightStatuses, None, None]: + """ + Gets past traffic light status. + + :param iteration: iteration within scenario 0 <= scenario_iteration < get_number_of_iterations. + :param time_horizon [s]: the desired horizon to the past. + :param num_samples: number of entries in the future, if None it will be deduced from the DB. + :return: Generator object for traffic light history to the past. + """ + # FIXME: add traffic light stats + yield from [] # placeholder + + def get_future_traffic_light_status_history( + self, iteration: int, time_horizon: float, num_samples: Optional[int] = None + ) -> Generator[TrafficLightStatuses, None, None]: + """ + Gets future traffic light status. + + :param iteration: iteration within scenario 0 <= scenario_iteration < get_number_of_iterations. + :param time_horizon [s]: the desired horizon to the future. + :param num_samples: number of entries in the future, if None it will be deduced from the DB. + :return: Generator object for traffic light history to the future. + """ + # FIXME: add traffic light stats + yield from [] # placeholder + + def get_scenario_tokens(self) -> List[str]: + """Return the list of lidarpc tokens from the DB that are contained in the scenario.""" + raise NotImplementedError diff --git a/sledge/simulation/scenarios/sledge_scenario/sledge_scenario_utils.py b/sledge/simulation/scenarios/sledge_scenario/sledge_scenario_utils.py new file mode 100644 index 0000000..cbdced7 --- /dev/null +++ b/sledge/simulation/scenarios/sledge_scenario/sledge_scenario_utils.py @@ -0,0 +1,302 @@ +from typing import List, Optional, Tuple + +import numpy as np +import numpy.typing as npt +import networkx as nx + +from nuplan.common.actor_state.agent import Agent +from nuplan.common.actor_state.state_representation import StateSE2 +from nuplan.common.actor_state.tracked_objects import TrackedObjects, TrackedObject +from nuplan.common.actor_state.tracked_objects_types import TrackedObjectType, AGENT_TYPES +from nuplan.common.actor_state.static_object import StaticObject +from nuplan.common.maps.maps_datatypes import SemanticMapLayer +from nuplan.common.actor_state.oriented_box import OrientedBox +from nuplan.common.actor_state.scene_object import SceneObjectMetadata +from nuplan.common.actor_state.state_representation import StateVector2D +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks +from nuplan.planning.simulation.trajectory.trajectory_sampling import TrajectorySampling + +from sledge.simulation.planner.pdm_planner.utils.pdm_path import PDMPath +from sledge.simulation.maps.sledge_map.sledge_map import SledgeMap +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import SledgeVector, SledgeVectorElement +from sledge.autoencoder.preprocessing.features.sledge_vector_feature import ( + SledgeVectorElementType, + AgentIndex, + StaticObjectIndex, + BoundingBoxIndex, +) + +# TODO: add to some config +LABEL_THRESH = 0.3 +OBJECT_HEIGHT = 1.0 # placeholder + + +def sledge_vector_to_detection_tracks( + sledge_vector: SledgeVector, + timestamp_us: int, +) -> DetectionsTracks: + """ + Converts sledge vector dataclass into detection tracks dataclass for simulation pipeline. + :param sledge_vector: sledge vector dataclass + :param timestamp_us: time step as integer [μs] + :return: detection track dataclass in nuPlan. + """ + tracked_objects: List[TrackedObject] = [] + + # 1. vehicles + vehicles = sledge_element_to_tracked_objects( + sledge_vector.vehicles, + timestamp_us, + TrackedObjectType.VEHICLE, + ) + tracked_objects.extend(vehicles) + + # 2. pedestrians + pedestrians = sledge_element_to_tracked_objects( + sledge_vector.pedestrians, + timestamp_us, + TrackedObjectType.PEDESTRIAN, + ) + tracked_objects.extend(pedestrians) + + # 3. static objects + static_objects = sledge_element_to_tracked_objects( + sledge_vector.static_objects, + timestamp_us, + TrackedObjectType.GENERIC_OBJECT, + ) + tracked_objects.extend(static_objects) + + return DetectionsTracks(TrackedObjects(tracked_objects)) + + +def sledge_element_to_tracked_objects( + sledge_vector_element: SledgeVectorElement, timestamp_us: int, tracked_object_type: TrackedObjectType +) -> List[TrackedObject]: + """ + Collects all valid bounding box entities from vector element dataclass. + :param sledge_vector_element: vector element dataclass of bounding boxes. + :param timestamp_us: time step as integer [μs] + :param tracked_object_type: tracked object enum + :return: list of valid tracked objects + """ + assert sledge_vector_element.get_element_type() in [SledgeVectorElementType.AGENT, SledgeVectorElementType.STATIC] + + tracked_objects: List[TrackedObject] = [] + for element_idx, (state, mask) in enumerate(zip(sledge_vector_element.states, sledge_vector_element.mask)): + invalid = not mask if type(mask) is np.bool_ else mask < LABEL_THRESH + if invalid: + continue + tracked_object = state_array_to_tracked_object(state, timestamp_us, tracked_object_type, str(element_idx)) + tracked_objects.append(tracked_object) + + return tracked_objects + + +def state_array_to_tracked_object( + state: npt.NDArray[np.float32], timestamp_us: int, tracked_object_type: TrackedObjectType, token: str +) -> TrackedObject: + """ + Converts state array of bounding box element to tracked object. + :param state: numpy array, containing information about the bounding box state + :param timestamp_us: time step as integer [μs] + :param tracked_object_type: tracked object enum + :param token: enumerator token of object + :return: tracked object of bounding box + """ + assert state.ndim == 1, f"Expected state array to have one dimension, but got {state.ndim}" + assert state.shape[-1] in [ + AgentIndex.size(), + StaticObjectIndex.size(), + ], f"Invalid state array size of {state.shape[-1]}" + + is_agent = tracked_object_type in AGENT_TYPES + object_index: BoundingBoxIndex = AgentIndex if is_agent else StaticObjectIndex + + # 1. OrientedBox + center = StateSE2(*state[object_index.STATE_SE2]) + oriented_box = OrientedBox( + center, + state[object_index.LENGTH], + state[object_index.WIDTH], + OBJECT_HEIGHT, + ) + + # 2. SceneObjectMetadata + track_token = f"{tracked_object_type.value}_{token}" + metadata = SceneObjectMetadata( + timestamp_us, + token=track_token, + track_id=None, + track_token=track_token, + ) # NOTE: assume token is equal to track_token + + if is_agent: + # 3. StateVector2D + velocity = StateVector2D(*get_agent_dxy(state)) + return Agent(tracked_object_type, oriented_box, velocity, metadata) + + return StaticObject(tracked_object_type, oriented_box, metadata) + + +def interpolate_state_se2( + state_se2: StateSE2, trajectory_sampling: TrajectorySampling, velocity: float +) -> List[StateSE2]: + """ + Projects poses linearly, baseline on trajectory sampling and velocity. + :param state_se2: pose to interpolate + :param trajectory_sampling: dataclass for trajectory details + :param velocity: absolute velocity [m/s] + :return: list of interpolated poses + """ + time_s = np.arange(0.0, trajectory_sampling.time_horizon, trajectory_sampling.interval_length) + interpolated_states = [project_state_se2(state_se2, delta_t, velocity) for delta_t in time_s] + return interpolated_states + + +def project_state_se2(state_se2: StateSE2, delta_t: float, velocity: float) -> StateSE2: + """ + Linearly projects a pose, based on time interval and velocity. + :param state_se2: pose object + :param delta_t: time interval for projection + :param velocity: absolute velocity [m/s] + :return: projected poses along heading angle + """ + x, y, heading = state_se2.serialize() + new_x = x + velocity * delta_t * np.cos(heading) + new_y = y + velocity * delta_t * np.sin(heading) + return StateSE2(new_x, new_y, heading) + + +def get_agent_dxy(states: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]: + """ + Convert (x,y) velocity of agent bounding box, based on origin. + :param states: numpy array, containing information about the bounding box state + :return: numpy array of (x,y) velocity in [m/s] + """ + assert states.shape[-1] == AgentIndex.size() + headings = states[..., AgentIndex.HEADING] + velocities = states[..., AgentIndex.VELOCITY] + dxy = np.array( + [ + np.cos(headings) * velocities, + np.sin(headings) * velocities, + ], + dtype=np.float64, + ).T + return dxy + + +def project_sledge_vector(sledge_vector: SledgeVector, delta_t: float) -> SledgeVector: + """ + Projects agents (vehicles, pedestrians) with constant velocities in vector dataclass. + :param sledge_vector: sledge vector dataclass + :param delta_t: time interval for projection in [m/s] + :return: projected vector dataclass + """ + if len(sledge_vector.vehicles.states) > 0: + projected_vehicles = project_agents(sledge_vector.vehicles, delta_t) + else: + projected_vehicles = sledge_vector.vehicles + + if len(sledge_vector.pedestrians.states) > 0: + projected_pedestrians = project_agents(sledge_vector.pedestrians, delta_t) + else: + projected_pedestrians = sledge_vector.pedestrians + + return SledgeVector( + lines=sledge_vector.lines, + vehicles=projected_vehicles, + pedestrians=projected_pedestrians, + static_objects=sledge_vector.static_objects, + green_lights=sledge_vector.green_lights, + red_lights=sledge_vector.red_lights, + ego=sledge_vector.ego, + ) + + +def project_agents(sledge_vector_element: SledgeVectorElement, delta_t: float) -> SledgeVectorElement: + """ + Project sledge vector element based on constant velocity and heading angle. + :param sledge_vector_element: sledge vector dataclass of entity + :param delta_t: time interval for projection in [m/s] + :return: projected sledge vector element + """ + assert sledge_vector_element.get_element_type() == SledgeVectorElementType.AGENT + states, mask = sledge_vector_element.states, sledge_vector_element.mask + projected_states = states.copy() + projected_states[..., AgentIndex.POINT] += delta_t * get_agent_dxy(states) + return SledgeVectorElement(projected_states, mask) + + +def sample_future_indices( + future_sampling: TrajectorySampling, + iteration: int, + time_horizon: float, + num_samples: Optional[int], +) -> List[int]: + """ + Helper to sample iterations indices during simulation. + :param future_sampling: future sampling dataclass. + :param iteration: starting iteration index + :param time_horizon: future time to sample indices for [s] + :param num_samples: optional number of samples to return + :raises ValueError: invalid input arguments + :return: list of iteration indices + """ + time_interval = future_sampling.interval_length + if time_horizon <= 0.0 or time_interval <= 0.0 or time_horizon < time_interval: + raise ValueError( + f"Time horizon {time_horizon} must be greater or equal than target time interval {time_interval}" + " and both must be positive." + ) + + num_samples = num_samples if num_samples else int(time_horizon / time_interval) + num_intervals = int(time_horizon / time_interval) + 1 + step_size = num_intervals // num_samples + time_idcs = np.arange(iteration, num_intervals, step_size) + return list(time_idcs) + + +def get_route(map_api: SledgeMap) -> Tuple[List[str], PDMPath]: + """ + Heuristic to find route to drive along during simulation. + TODO: Refactor & Update + :param map_api: map interface in sledge lane & agent simulation + :return: tuple of roadblock ids and interpolatable path + """ + + # 1. find starting lane + baseline_paths_dict = map_api.sledge_map_graph.baseline_paths_dict + lane_distances = [np.linalg.norm(poses[..., :2], axis=-1).min(-1) for poses in baseline_paths_dict.values()] + current_node = str(np.argmin(lane_distances)) + + # 2. fine lane sinks (ending lanes) and check if path exists + graph = map_api.sledge_map_graph.directed_lane_graph + sink_nodes = [node for node in graph.nodes() if graph.out_degree(node) == 0] + + available_paths = [] + for sink_node in sink_nodes: + try: + path_to_sink = nx.shortest_path(graph, source=current_node, target=sink_node) + except nx.NetworkXNoPath: + continue + available_paths.append(path_to_sink) + + if len(available_paths) == 0: + available_paths = [current_node] + + lengths = [len(path) for path in available_paths] + + # TODO: add different path strategies + route_roadblock_ids = available_paths[np.argmax(lengths)] + discrete_route_path: List[StateSE2] = [] + + # NOTE: Slight abuse, because roadblock and lanes have same id's in SledgeMap + for route_roadblock_id in route_roadblock_ids: + lane = map_api.get_map_object(route_roadblock_id, SemanticMapLayer.LANE) + discrete_route_path.extend(lane.baseline_path.discrete_path) + + route_path = PDMPath(discrete_route_path) + + return route_roadblock_ids, route_path diff --git a/sledge/sledgeboard/__init__.py b/sledge/sledgeboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/sledgeboard/base/__init__.py b/sledge/sledgeboard/base/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/sledgeboard/base/base_tab.py b/sledge/sledgeboard/base/base_tab.py new file mode 100644 index 0000000..79b6990 --- /dev/null +++ b/sledge/sledgeboard/base/base_tab.py @@ -0,0 +1,243 @@ +import logging +import pathlib +from typing import Any, List, Optional + +from bokeh.document.document import Document +from bokeh.models import CheckboxGroup, MultiChoice +from bokeh.plotting.figure import Figure + +from sledge.sledgeboard.base.data_class import SelectedMetricStatisticDataFrame, SimulationScenarioKey +from sledge.sledgeboard.base.experiment_file_data import ExperimentFileData +from sledge.sledgeboard.style import base_tab_style, simulation_tile_style + +logger = logging.getLogger(__name__) + + +class BaseTab: + """Base tab for other tabs.""" + + def __init__(self, doc: Document, experiment_file_data: ExperimentFileData): + """ + Base tabs for common properties. + Metric board to render metrics. + :doc: A bokeh HTML document. + :param experiment_file_data: Experiment file data. + """ + self._doc = doc + self._experiment_file_data: ExperimentFileData = experiment_file_data + + self._simulation_scenario_keys: List[SimulationScenarioKey] = [] + self._experiment_file_active_index: List[int] = [] + + # UI + self.scatter_signs = [ + "circle", + "diamond", + "plus", + "square", + "triangle", + "inverted_triangle", + "star", + "asterisk", + "dot_circle", + "diamond_cross", + ] + self.search_criteria_selection_size = base_tab_style["search_criteria_sizes"] + self.plot_sizes = base_tab_style["plot_sizes"] + self.simulation_figure_sizes = simulation_tile_style["figure_sizes"] + self.plot_frame_sizes = base_tab_style["plot_frame_sizes"] + self.window_width = 0 + self.window_height = 0 + + self.planner_checkbox_group = CheckboxGroup( + labels=[], active=[], inline=True, css_classes=["planner-checkbox-group"], sizing_mode="scale_both" + ) + self.planner_checkbox_group.on_click(self._click_planner_checkbox_group) + + def file_paths_on_change( + self, experiment_file_data: ExperimentFileData, experiment_file_active_index: List[int] + ) -> None: + """ + Interface to update layout when file_paths is changed. + :param experiment_file_data: Experiment file data. + :param experiment_file_active_index: Active indexes for experiment files. + """ + raise NotImplementedError + + def _click_planner_checkbox_group(self, attr: Any) -> None: + """ + Click event handler for planner_checkbox_group. + :param attr: Clicked attributes. + """ + raise NotImplementedError + + @property + def experiment_file_data(self) -> ExperimentFileData: + """Return experiment file data.""" + return self._experiment_file_data + + @experiment_file_data.setter + def experiment_file_data(self, experiment_file_data: ExperimentFileData) -> None: + """ + Update experiment file data. + :param experiment_file_data: New experiment file data. + """ + self._experiment_file_data = experiment_file_data + + @property + def enable_planner_names(self) -> List[str]: + """Return a list of enable planner names.""" + enable_planner_names = [ + self.planner_checkbox_group.labels[index] for index in self.planner_checkbox_group.active + ] + return enable_planner_names + + def get_plot_cols( + self, plot_width: int, default_col_width: int = 1024, offset_width: int = 0, default_ncols: int = 0 + ) -> int: + """ + Return number of columns for a grid plot. + :param plot_width: Plot width. + :param default_col_width: The number of columns would be 1 if window width is lower than this value. + :param offset_width: Additional offset width. + :param default_ncols: Default number of columns. + :return: Get a number of columns for a grid plot. + """ + if default_ncols and not self.window_width: + return default_ncols + + window_width = self.window_width - offset_width + if window_width <= default_col_width: + return 1 + col_num = 1 + round((window_width - default_col_width) / plot_width) + return col_num + + def get_scatter_sign(self, index: int) -> str: + """ + Get scatter index sign based on the index. + :param index: Index for the scatter sign. + :return A scatter sign name. + """ + index = index % len(self.scatter_signs) # Repeat if out of the available scatter signs + return self.scatter_signs[index] + + @staticmethod + def get_scatter_render_func(scatter_sign: str, scatter_figure: Figure) -> Any: + """ + Render a scatter plot. + :param scatter_sign: Scatter sign. + :param scatter_figure: Scatter figure. + :return A scatter render function. + """ + if scatter_sign == "circle": + renderer = scatter_figure.circle + elif scatter_sign == "diamond": + renderer = scatter_figure.diamond + elif scatter_sign == "plus": + renderer = scatter_figure.plus + elif scatter_sign == "square": + renderer = scatter_figure.square + elif scatter_sign == "triangle": + renderer = scatter_figure.triangle + elif scatter_sign == "inverted_triangle": + renderer = scatter_figure.inverted_triangle + elif scatter_sign == "star": + renderer = scatter_figure.star + elif scatter_sign == "asterisk": + renderer = scatter_figure.asterisk + elif scatter_sign == "diamond_cross": + renderer = scatter_figure.diamond_cross + else: + raise NotImplementedError(f"{scatter_sign} is not a valid option for scatter plots!") + + return renderer + + def get_file_path_last_name(self, index: int) -> str: + """ + Get last name of a file path. + :param index: Index for the file path. + :return: A file path string name. + """ + file_path = self._experiment_file_data.file_paths[index] + default_experiment_file_path_stem = pathlib.Path(file_path.metric_main_path) + if file_path.current_path is None: + return str(default_experiment_file_path_stem.name) + + metric_path = pathlib.Path(file_path.current_path, file_path.metric_folder) + if metric_path.exists(): + experiment_file_path_stem = file_path.current_path + else: + experiment_file_path_stem = default_experiment_file_path_stem + return str(experiment_file_path_stem.name) + + def load_log_name(self, scenario_type: str) -> List[str]: + """ + Load a list of log names based on the scenario type. + :param scenario_type: A selected scenario type. + :return a list of log names. + """ + log_names = self._experiment_file_data.available_scenarios.get(scenario_type, []) + + # Remove duplicates + sorted_log_names: List[str] = sorted(list(set(log_names)), reverse=False) + + return sorted_log_names + + def load_scenario_names(self, scenario_type: str, log_name: str) -> List[str]: + """ + Load a list of scenario names based on the log name. + :param scenario_type: A selected scenario type. + :param log_name: A selected log name. + :return a list of scenario names. + """ + log_dict = self._experiment_file_data.available_scenarios.get(scenario_type, []) + if not log_dict: + return [] + + scenario_names = log_dict.get(log_name, []) + + # Remove duplicates + sorted_scenario_names: List[str] = sorted(list(set(scenario_names)), reverse=False) + + return sorted_scenario_names + + def _init_multi_search_criteria_selection( + self, scenario_type_multi_choice: MultiChoice, metric_name_multi_choice: MultiChoice + ) -> None: + """ + Init histogram and scenario selection options. + :param scenario_type_multi_choice: Scenario type multi choice. + :param metric_name_multi_choice: Metric type multi choice. + """ + # Scenario types. + scenario_type_multi_choice.options = ["all"] + sorted(self.experiment_file_data.available_scenario_types) + + # Metrics results + metric_name_multi_choice.options = sorted(self.experiment_file_data.available_metric_statistics_names) + + def search_metric_statistics_dataframe( + self, scenario_types: Optional[List[str]] = None, metric_choices: Optional[List[str]] = None + ) -> List[SelectedMetricStatisticDataFrame]: + """ + Search metric statistics dataframe based on scenario types and metric choices. + :param scenario_types: A list of scenario types. + :param metric_choices: A list of metric choices. + :return: A list of selected metric statistic dataframe. + """ + data: List[SelectedMetricStatisticDataFrame] = [] + if not scenario_types and not metric_choices: + return data + + # Loop through all metric statistics in the dataframe + for index, metric_statistics_dataframes in enumerate(self.experiment_file_data.metric_statistics_dataframes): + for metric_statistics_dataframe in metric_statistics_dataframes: + + # Only run if it matches with the searched values or types + if metric_choices and metric_statistics_dataframe.metric_statistic_name not in metric_choices: + continue + + data.append( + SelectedMetricStatisticDataFrame(dataframe_index=index, dataframe=metric_statistics_dataframe) + ) + + return data diff --git a/sledge/sledgeboard/base/data_class.py b/sledge/sledgeboard/base/data_class.py new file mode 100644 index 0000000..89bd840 --- /dev/null +++ b/sledge/sledgeboard/base/data_class.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import pathlib +import pickle +from dataclasses import dataclass +from typing import Dict, List, Optional + +from nuplan.common.utils.io_utils import save_object_as_pickle +from nuplan.planning.metrics.metric_dataframe import MetricStatisticsDataFrame + + +@dataclass +class MetricScenarioKey: + """Metric key for scenario in SledgeBoard.""" + + log_name: str + planner_name: str + scenario_type: str + scenario_name: str + metric_result_name: str + file: pathlib.Path + + +@dataclass +class SimulationScenarioKey: + """Simulation key for scenario in SledgeBoard.""" + + log_name: str + planner_name: str + scenario_type: str + scenario_name: str + files: List[pathlib.Path] + sledgeboard_file_index: int + + +@dataclass +class SledgeBoardFile: + """Data class to save SledgeBoard file info.""" + + simulation_main_path: str # Simulation main path + metric_main_path: str # Metric main path + metric_folder: str # Metric folder + aggregator_metric_folder: str # Aggregated metric folder + + simulation_folder: Optional[str] = None # Simulation folder, or None if the SimulationLog wasn't serialized + current_path: Optional[pathlib.Path] = None # Current path of the sledgeboard file + + @classmethod + def extension(cls) -> str: + """Return sledgeboard file extension.""" + return ".nuboard" + + def __eq__(self, other: object) -> bool: + """ + Comparison between two SledgeBoardFile. + :param other: Other object. + :return True if both objects are same. + """ + if not isinstance(other, SledgeBoardFile): + return NotImplemented + + return ( + other.simulation_main_path == self.simulation_main_path + and other.simulation_folder == self.simulation_folder + and other.metric_main_path == self.metric_main_path + and other.metric_folder == self.metric_folder + and other.aggregator_metric_folder == self.aggregator_metric_folder + and other.current_path == self.current_path + ) + + def save_sledgeboard_file(self, filename: pathlib.Path) -> None: + """ + Save SledgeBoardFile data class to a file. + :param filename: The saved file path. + """ + save_object_as_pickle(filename, self.serialize()) + + @classmethod + def load_sledgeboard_file(cls, filename: pathlib.Path) -> SledgeBoardFile: + """ + Read a SledgeBoard file to SledgeBoardFile data class. + :file: SledgeBoard file path. + """ + with open(filename, "rb") as file: + data = pickle.load(file) + + return cls.deserialize(data=data) + + def serialize(self) -> Dict[str, str]: + """ + Serialization of SledgeBoardFile data class to dictionary. + :return A serialized dictionary class. + """ + as_dict = { + "simulation_main_path": self.simulation_main_path, + "metric_main_path": self.metric_main_path, + "metric_folder": self.metric_folder, + "aggregator_metric_folder": self.aggregator_metric_folder, + } + + if self.simulation_folder is not None: + as_dict["simulation_folder"] = self.simulation_folder + + return as_dict + + @classmethod + def deserialize(cls, data: Dict[str, str]) -> SledgeBoardFile: + """ + Deserialization of a SledgeBoard file into SledgeBoardFile data class. + :param data: A serialized sledgeboard file data. + :return A SledgeBoard file data class. + """ + simulation_main_path = data["simulation_main_path"].replace("//", "/") + metric_main_path = data["metric_main_path"].replace("//", "/") + return SledgeBoardFile( + simulation_main_path=simulation_main_path, + simulation_folder=data.get("simulation_folder", None), + metric_main_path=metric_main_path, + metric_folder=data["metric_folder"], + aggregator_metric_folder=data["aggregator_metric_folder"], + ) + + +@dataclass +class SelectedMetricStatisticDataFrame: + """ + Selected metric statistics dataframe + """ + + dataframe_index: int # dataframe index + dataframe: MetricStatisticsDataFrame # metric statistics dataframe diff --git a/sledge/sledgeboard/base/experiment_file_data.py b/sledge/sledgeboard/base/experiment_file_data.py new file mode 100644 index 0000000..d905715 --- /dev/null +++ b/sledge/sledgeboard/base/experiment_file_data.py @@ -0,0 +1,288 @@ +import logging +from collections import defaultdict +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Dict, List + +import pandas as pd +from bokeh.palettes import Dark2, Pastel1, Pastel2, Set1, Set2, Set3 + +from nuplan.planning.metrics.metric_dataframe import MetricStatisticsDataFrame +from sledge.sledgeboard.base.data_class import SledgeBoardFile, SimulationScenarioKey + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class ScenarioTokenInfo: + """Scenario info corresponding to a scenario token.""" + + scenario_token: str # Note that scenario token and name are the same thing in nuPlan + scenario_name: str + scenario_type: str + log_name: str + + +@dataclass +class ExperimentFileData: + """Data for experiment files.""" + + file_paths: List[SledgeBoardFile] # Experiment file path + color_palettes: List[str] = field(default_factory=list) # Color choices + expert_color_palettes: List[str] = field(default_factory=list) # Color choices for expert plots + available_metric_statistics_names: List[str] = field(default_factory=list) # Metric statistics name + metric_statistics_dataframes: List[List[MetricStatisticsDataFrame]] = field( + default_factory=list + ) # Metric dataframe + metric_aggregator_dataframes: List[Dict[str, pd.DataFrame]] = field( + default_factory=list + ) # Metric aggregator dataframe + simulation_files: Dict[str, Any] = field(default_factory=dict) # Simulation files + simulation_scenario_keys: List[SimulationScenarioKey] = field(default_factory=list) # Simulation scenario keys + available_scenario_types: List[str] = field(default_factory=list) # Available scenario types in search + available_scenarios: Dict[str, Dict[str, List[str]]] = field( + default_factory=dict + ) # Scenario types -> scenario logs -> scenario names + available_scenario_tokens: Dict[str, ScenarioTokenInfo] = field( + default_factory=dict + ) # Scenario token: [scenario type, scenario log, scenario name] + file_path_colors: Dict[int, Dict[str, str]] = field(default_factory=dict) # Color for each experiment file + color_index: int = 0 # Current color index + + def __post_init__(self) -> None: + """Post initialization.""" + if not self.simulation_files: + self.simulation_files = defaultdict(set) + + if not self.available_scenario_tokens: + self.available_scenario_tokens = defaultdict() + + if not self.color_palettes: + self.color_palettes = Set1[9] + Set2[8] + Set3[12] + + if not self.expert_color_palettes: + self.expert_color_palettes = Pastel2[8] + Pastel1[9] + Dark2[8] + + if not self.available_scenarios: + # Scenario types -> scenario logs -> a list of scenario names + self.available_scenarios = defaultdict(lambda: defaultdict(list)) + + if self.file_paths: + # Reset file paths + file_paths = self.file_paths + self.file_paths = [] + self.update_data(file_paths=file_paths) + + def update_data(self, file_paths: List[SledgeBoardFile]) -> None: + """ + Update experiment data with a new list of sledgeboard file paths. + :param file_paths: A list of new sledgeboard file paths. + """ + starting_file_path_index = len(self.file_paths) + # Update file path color + self._update_file_path_color(file_paths=file_paths, starting_file_path_index=starting_file_path_index) + + # Add new metric files + self._add_metric_files(file_paths=file_paths, starting_file_path_index=starting_file_path_index) + + # Add new metric aggregator files + self._add_metric_aggregator_files(file_paths=file_paths, starting_file_path_index=starting_file_path_index) + + # Add new simulation files + self._add_simulation_files(file_paths=file_paths, starting_file_path_index=starting_file_path_index) + + # Add to file paths + self.file_paths += file_paths + + @staticmethod + def _get_base_path(current_path: Path, base_path: Path, sub_folder: str) -> Path: + """ + Get valid base path. + :param current_path: Current sledgeboard file path. + :Param base_path: Alternative base path. + :param sub_folder: Sub folder. + :return A base path. + """ + default_path = base_path / sub_folder + if current_path is None: + return default_path + + base_folder = current_path / sub_folder + if not base_folder.exists(): + base_folder = default_path + return base_folder + + def _update_file_path_color(self, file_paths: List[SledgeBoardFile], starting_file_path_index: int) -> None: + """ + Update file path colors. + :param file_paths: A list of new sledgeboard file paths. + :param starting_file_path_index: Starting file path index. + """ + for index, file_path in enumerate(file_paths): + file_path_index = starting_file_path_index + index + self.file_path_colors[file_path_index] = defaultdict(str) + metric_path = self._get_base_path( + current_path=file_path.current_path, + base_path=Path(file_path.metric_main_path), + sub_folder=file_path.metric_folder, + ) + planner_names: List[str] = [] + if not metric_path.exists(): + continue + + # Loop through metric parquet files + for file in metric_path.iterdir(): + try: + data_frame = MetricStatisticsDataFrame.load_parquet(file) + planner_names += data_frame.planner_names + except (FileNotFoundError, Exception) as e: + # Ignore the file + logger.info(e) + pass + + # Find from simulation data if no metrics found + if not planner_names: + simulation_path = self._get_base_path( + current_path=file_path.current_path, + base_path=Path(file_path.simulation_main_path), + sub_folder=file_path.simulation_folder, + ) + if not simulation_path.exists(): + continue + planner_name_paths = simulation_path.iterdir() + for planner_name_path in planner_name_paths: + planner_name = planner_name_path.name + planner_names.append(planner_name) + + # Remove duplicate planner names + planner_names = list(set(planner_names)) + for planner_name in planner_names: + self.file_path_colors[file_path_index][planner_name] = self.color_palettes[self.color_index] + self.color_index += 1 + + def _add_metric_files(self, file_paths: List[SledgeBoardFile], starting_file_path_index: int) -> None: + """ + Add and load metric files. + Folder hierarchy: planner_name -> scenario_type -> metric result name -> scenario_name.pkl + :param file_paths: A list of new sledgeboard files. + :param starting_file_path_index: Starting file path index. + """ + for index, file_path in enumerate(file_paths): + file_path_index = starting_file_path_index + index + self.metric_statistics_dataframes.append([]) + metric_path = self._get_base_path( + current_path=file_path.current_path, + base_path=Path(file_path.metric_main_path), + sub_folder=file_path.metric_folder, + ) + if not metric_path.exists(): + continue + + # Loop through metric parquet files + for file in metric_path.iterdir(): + if file.is_dir(): + continue + try: + data_frame = MetricStatisticsDataFrame.load_parquet(file) + self.metric_statistics_dataframes[file_path_index].append(data_frame) + self.available_metric_statistics_names.append(data_frame.metric_statistic_name) + except (FileNotFoundError, Exception): + # Ignore the file + pass + + # Remove duplicates + self.available_metric_statistics_names = sorted( + list(set(self.available_metric_statistics_names)), reverse=False + ) + + def _add_metric_aggregator_files(self, file_paths: List[SledgeBoardFile], starting_file_path_index: int) -> None: + """ + Load metric aggregator files. + :param file_paths: A list of new sledgeboard files. + :param starting_file_path_index: Starting file path index. + """ + for index, file_path in enumerate(file_paths): + file_path_index = starting_file_path_index + index + self.metric_aggregator_dataframes.append({}) + metric_aggregator_path = self._get_base_path( + current_path=file_path.current_path, + base_path=Path(file_path.metric_main_path), + sub_folder=file_path.aggregator_metric_folder, + ) + if not metric_aggregator_path.exists(): + continue + # Loop through metric parquet files + for file in metric_aggregator_path.iterdir(): + if file.is_dir(): + continue + try: + data_frame = pd.read_parquet(file) + self.metric_aggregator_dataframes[file_path_index][file.stem] = data_frame + except (FileNotFoundError, Exception): + # Ignore the file + pass + + def _add_simulation_files(self, file_paths: List[SledgeBoardFile], starting_file_path_index: int) -> None: + """ + Load simulation files. + Folder hierarchy: planner_name -> scenario_type -> scenario_names -> iteration.pkl. + :param file_paths: A list of new sledgeboard files. + :param starting_file_path_index: Starting file path index. + """ + for index, file_path in enumerate(file_paths): + # If the SimulationLog wasn't serialized, skip because we don't have data to render a tile + if file_path.simulation_folder is None: + continue + + file_path_index = starting_file_path_index + index + simulation_path = self._get_base_path( + current_path=file_path.current_path, + base_path=Path(file_path.simulation_main_path), + sub_folder=file_path.simulation_folder, + ) + if not simulation_path.exists(): + continue + planner_name_paths = simulation_path.iterdir() + for planner_name_path in planner_name_paths: + planner_name = planner_name_path.name + scenario_type_paths = planner_name_path.iterdir() + for scenario_type_path in scenario_type_paths: + log_name_paths = scenario_type_path.iterdir() + scenario_type = scenario_type_path.name + for log_name_path in log_name_paths: + scenario_name_paths = log_name_path.iterdir() + log_name = log_name_path.name + for scenario_name_path in scenario_name_paths: + scenario_name = scenario_name_path.name + scenario_key = ( + f"{simulation_path.parents[0].name}/{planner_name}/" + f"{scenario_type}/{log_name}/{scenario_name}" + ) + if scenario_key in self.simulation_files: + continue + files = scenario_name_path.iterdir() + for file in files: + self.simulation_files[scenario_key].add(file) + + self.available_scenarios[scenario_type][log_name].append(scenario_name) + # We save scenario name because it is the same thing as token in nuPlan + self.available_scenario_tokens[scenario_name] = ScenarioTokenInfo( + scenario_name=scenario_name, + scenario_token=scenario_name, + scenario_type=scenario_type, + log_name=log_name, + ) + self.simulation_scenario_keys.append( + SimulationScenarioKey( + sledgeboard_file_index=file_path_index, + log_name=log_name, + planner_name=planner_name, + scenario_type=scenario_type, + scenario_name=scenario_name, + files=list(self.simulation_files[scenario_key]), + ) + ) + + # Add scenario types + available_scenario_types = list(set(self.available_scenarios.keys())) + self.available_scenario_types = sorted(available_scenario_types, reverse=False) diff --git a/sledge/sledgeboard/base/plot_data.py b/sledge/sledgeboard/base/plot_data.py new file mode 100644 index 0000000..30b2f9c --- /dev/null +++ b/sledge/sledgeboard/base/plot_data.py @@ -0,0 +1,1002 @@ +from __future__ import annotations + +import abc +import threading +from dataclasses import dataclass, field +from typing import Any, Dict, List, NamedTuple, Optional, Union + +import numpy as np +from bokeh.document import Document +from bokeh.models import Button, ColumnDataSource, GlyphRenderer, HoverTool, LayoutDOM, Legend, Slider, Title +from bokeh.plotting.figure import Figure + +from nuplan.common.actor_state.state_representation import Point2D, StateSE2 +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters +from nuplan.common.geometry.transform import translate_longitudinally +from nuplan.common.maps.abstract_map_objects import LaneConnector +from nuplan.common.maps.maps_datatypes import SemanticMapLayer +from sledge.sledgeboard.style import ( + simulation_map_layer_color, + simulation_tile_agent_style, + simulation_tile_style, + simulation_tile_trajectory_style, +) +from nuplan.planning.scenario_builder.abstract_scenario import AbstractScenario +from nuplan.planning.simulation.history.simulation_history import SimulationHistory +from nuplan.planning.simulation.observation.observation_type import DetectionsTracks +from nuplan.planning.utils.serialization.to_scene import tracked_object_types + +from sledge.simulation.maps.sledge_map.sledge_lane import SledgeLane + + +class BokehAgentStates(NamedTuple): + """Agent states in bokeh.""" + + xs: List[List[List[List[float]]]] # [m], [[list of [[Polygon connected corners in x]]]] + ys: List[List[List[List[float]]]] # [m], [[list of [[Polygon connected corners in y]]]] + agent_type: List[str] # A list of agent's category + track_id: List[Union[int, float]] # A list of agent's track id + track_token: List[str] # A list of agent's track token + center_xs: List[float] # [m], a list of center in x. + center_ys: List[float] # [m], a list of center in y. + velocity_xs: List[float] # [m/s], A list of velocity in x (body frame). + velocity_ys: List[float] # [m/s], A list of velocity in y (body frame). + speeds: List[float] # [m/s], A list of speed. + headings: List[float] # [m], a list of headings + + +@dataclass(frozen=True) +class MapPoint: + """A dataclass to render map polygons in scenario.""" + + point_2d: List[List[Point2D]] = field(default_factory=list) # A list of a list of 2D points + + @property + def polygon_xs(self) -> List[List[List[List[float]]]]: + """Return a list of xs from point 2d to render polygons.""" + polygon_xs = [] + for points in self.point_2d: + xs = [] + for point in points: + xs.append(point.x) + polygon_xs.append([[xs]]) + return polygon_xs + + @property + def polygon_ys(self) -> List[List[List[List[float]]]]: + """Return a list of ys from point 2d to render polygons.""" + polygon_ys = [] + for points in self.point_2d: + ys = [] + for point in points: + ys.append(point.y) + polygon_ys.append([[ys]]) + return polygon_ys + + @property + def line_xs(self) -> List[List[float]]: + """Return a list of xs from point 2d to render lines.""" + line_xs = [] + for points in self.point_2d: + xs = [] + for point in points: + xs.append(point.x) + line_xs.append(xs) + return line_xs + + @property + def line_ys(self) -> List[List[float]]: + """Return a list of ys from point 2d to render lines.""" + line_ys = [] + for points in self.point_2d: + ys = [] + for point in points: + ys.append(point.y) + line_ys.append(ys) + return line_ys + + +@dataclass(frozen=True) +class TrafficLightMapLine(MapPoint): + """Line plot data in traffic light map.""" + + line_colors: List[str] = field(default_factory=list) # A list of color hex codes. + line_color_alphas: List[float] = field(default_factory=list) # A list of color alphas. + + +@dataclass +class BaseScenarioPlot(abc.ABC): + """Base class for scenario plot classes.""" + + # Threading condition to synchronize data source production & consumption: + data_source_condition: Optional[threading.Condition] = field(default=None, init=False) + + # Threading event that will be set when rendering starts and cleared when rendering ends. + render_event: Optional[threading.Event] = field(default=None, init=False) + + def __post_init__(self) -> None: + """Initialize threading properties.""" + if not self.data_source_condition: + self.data_source_condition = threading.Condition(threading.Lock()) + if not self.render_event: + self.render_event = threading.Event() + + +@dataclass +class TrafficLightPlot(BaseScenarioPlot): + """A dataclass for traffic light plot.""" + + data_sources: Dict[int, ColumnDataSource] = field(default_factory=dict) # A dict of data sources for each frame + plot: Optional[GlyphRenderer] = None # A bokeh glyph element + + def update_plot(self, main_figure: Figure, frame_index: int, doc: Document) -> None: + """ + Update the plot. + :param main_figure: The plotting figure. + :param frame_index: Frame index. + :param doc: The Bokeh document that the plot lives in. + """ + if not self.data_source_condition: + return + + self.render_event.set() # type: ignore + + with self.data_source_condition: + while self.data_sources.get(frame_index, None) is None: + self.data_source_condition.wait() + + def update_main_figure() -> None: + """Wrapper for the main_figure update logic to support multi-threading.""" + data_sources = dict(self.data_sources[frame_index].data) + if self.plot is None: + self.plot = main_figure.multi_line( + xs="xs", + ys="ys", + line_color="line_colors", + line_alpha="line_color_alphas", + line_width=3.0, + line_dash="dashed", + source=data_sources, + ) + else: + self.plot.data_source.data = data_sources + + self.render_event.clear() # type: ignore + + # Defer updating the main_figure to the next tick to be compatible with multi-threading approach + doc.add_next_tick_callback(lambda: update_main_figure()) + + def update_data_sources( + self, scenario: AbstractScenario, history: SimulationHistory, lane_connectors: Dict[str, LaneConnector] + ) -> None: + """ + Update traffic light status datasource of each frame. + :param scenario: Scenario traffic light status information. + :param history: SimulationHistory time-series data. + :param lane_connectors: Lane connectors. + """ + if not self.data_source_condition: + return + + with self.data_source_condition: + for frame_index in range(len(history.data)): + traffic_light_status = history.data[frame_index].traffic_light_status + + traffic_light_map_line = TrafficLightMapLine(point_2d=[], line_colors=[], line_color_alphas=[]) + lane_connector_colors = simulation_map_layer_color[SemanticMapLayer.LANE_CONNECTOR] + for traffic_light in traffic_light_status: + lane_connector = lane_connectors.get(str(traffic_light.lane_connector_id), None) + + if lane_connector is not None: + + if isinstance(lane_connector, SledgeLane): + path = lane_connector.traffic_light_baseline_path.discrete_path + else: + path = lane_connector.baseline_path.discrete_path + points = [Point2D(x=pose.x, y=pose.y) for pose in path] + traffic_light_map_line.line_colors.append(traffic_light.status.name) + traffic_light_map_line.line_color_alphas.append(lane_connector_colors["line_color_alpha"]) + traffic_light_map_line.point_2d.append(points) + + line_source = ColumnDataSource( + dict( + xs=traffic_light_map_line.line_xs, + ys=traffic_light_map_line.line_ys, + line_colors=traffic_light_map_line.line_colors, + line_color_alphas=traffic_light_map_line.line_color_alphas, + ) + ) + self.data_sources[frame_index] = line_source + self.data_source_condition.notify() + + +@dataclass +class EgoStatePlot(BaseScenarioPlot): + """A dataclass for ego state plot.""" + + vehicle_parameters: VehicleParameters # Ego vehicle parameters + data_sources: Dict[int, ColumnDataSource] = field(default_factory=dict) # A dict of data sources for each frame + init_state: bool = True # True to indicate it is in init state + plot: Optional[GlyphRenderer] = None # A bokeh glyph element + + def update_plot(self, main_figure: Figure, radius: float, frame_index: int, doc: Document) -> None: + """ + Update the plot. + :param main_figure: The plotting figure. + :param radius: Figure radius. + :param frame_index: Frame index. + :param doc: Bokeh document that the plot lives in. + """ + if not self.data_source_condition: + return + + self.render_event.set() # type: ignore + + with self.data_source_condition: + while self.data_sources.get(frame_index, None) is None: + self.data_source_condition.wait() + + data_sources = dict(self.data_sources[frame_index].data) + center_x = data_sources["center_x"][0] + center_y = data_sources["center_y"][0] + + def update_main_figure() -> None: + """Wrapper for the main_figure update logic to support multi-threading.""" + if self.plot is None: + self.plot = main_figure.multi_polygons( + xs="xs", + ys="ys", + fill_color=simulation_tile_agent_style["ego"]["fill_color"], + fill_alpha=simulation_tile_agent_style["ego"]["fill_alpha"], + line_color=simulation_tile_agent_style["ego"]["line_color"], + line_width=simulation_tile_agent_style["ego"]["line_width"], + source=data_sources, + ) + ego_hover = HoverTool( + renderers=[self.plot], + tooltips=[ + ("center_x [m]", "@center_x{0.2f}"), + ("center_y [m]", "@center_y{0.2f}"), + ("velocity_x [m/s]", "@velocity_x{0.2f}"), + ("velocity_y [m/s]", "@velocity_y{0.2f}"), + ("speed [m/s", "@speed{0.2f}"), + ("acceleration_x [m/s^2]", "@acceleration_x{0.2f}"), + ("acceleration_y [m/s^2]", "@acceleration_y{0.2f}"), + ("acceleration [m/s^2]", "@acceleration{0.2f}"), + ("heading [rad]", "@heading{0.2f}"), + ("steering_angle [rad]", "@steering_angle{0.2f}"), + ("yaw_rate [rad/s]", "@yaw_rate{0.2f}"), + ("type", "Ego"), + ], + ) + main_figure.add_tools(ego_hover) + else: + self.plot.data_source.data = data_sources + + # This will (re)center the view around the ego, actually making the plot visible + if self.init_state: + main_figure.x_range.start = center_x - radius / 2 + main_figure.x_range.end = center_x + radius / 2 + main_figure.y_range.start = center_y - radius / 2 + main_figure.y_range.end = center_y + radius / 2 + self.init_state = False + else: + x_radius = main_figure.x_range.end - main_figure.x_range.start + y_radius = main_figure.y_range.end - main_figure.y_range.start + main_figure.x_range.start = center_x - x_radius / 2 + main_figure.x_range.end = center_x + x_radius / 2 + main_figure.y_range.start = center_y - y_radius / 2 + main_figure.y_range.end = center_y + y_radius / 2 + + # if self.init_state: + # main_figure.x_range.start = 0.0 - radius / 2 + # main_figure.x_range.end = 0.0 + radius / 2 + # main_figure.y_range.start = 0.0 - radius / 2 + # main_figure.y_range.end = 0.0 + radius / 2 + # self.init_state = False + # else: + # x_radius = main_figure.x_range.end - main_figure.x_range.start + # y_radius = main_figure.y_range.end - main_figure.y_range.start + # main_figure.x_range.start = 0.0 - x_radius / 2 + # main_figure.x_range.end = 0.0 + x_radius / 2 + # main_figure.y_range.start = 0.0 - y_radius / 2 + # main_figure.y_range.end = 0.0 + y_radius / 2 + + self.render_event.clear() # type: ignore + + # Defer updating the main_figure to the next tick to be compatible with multi-threading approach + doc.add_next_tick_callback(lambda: update_main_figure()) + + def update_data_sources(self, history: SimulationHistory) -> None: + """ + Update ego_pose state data sources. + :param history: SimulationHistory time-series data. + """ + if not self.data_source_condition: + return + + with self.data_source_condition: + for frame_index, sample in enumerate(history.data): + ego_pose = sample.ego_state.car_footprint + dynamic_car_state = sample.ego_state.dynamic_car_state + ego_corners = ego_pose.all_corners() + + corner_xs = [corner.x for corner in ego_corners] + corner_ys = [corner.y for corner in ego_corners] + + # Connect to the first point + corner_xs.append(corner_xs[0]) + corner_ys.append(corner_ys[0]) + source = ColumnDataSource( + dict( + center_x=[ego_pose.center.x], + center_y=[ego_pose.center.y], + velocity_x=[dynamic_car_state.rear_axle_velocity_2d.x], + velocity_y=[dynamic_car_state.rear_axle_velocity_2d.y], + speed=[dynamic_car_state.speed], + acceleration_x=[dynamic_car_state.rear_axle_acceleration_2d.x], + acceleration_y=[dynamic_car_state.rear_axle_acceleration_2d.y], + acceleration=[dynamic_car_state.acceleration], + heading=[ego_pose.center.heading], + steering_angle=[sample.ego_state.tire_steering_angle], + yaw_rate=[sample.ego_state.dynamic_car_state.angular_velocity], + xs=[[[corner_xs]]], + ys=[[[corner_ys]]], + ) + ) + self.data_sources[frame_index] = source + self.data_source_condition.notify() + + +@dataclass +class EgoStateTrajectoryPlot(BaseScenarioPlot): + """A dataclass for ego state trajectory plot.""" + + data_sources: Dict[int, ColumnDataSource] = field(default_factory=dict) # A dict of data sources for each frame + plot: Optional[GlyphRenderer] = None # A bokeh glyph element + + def update_plot(self, main_figure: Figure, frame_index: int, doc: Document) -> None: + """ + Update the plot. + :param main_figure: The plotting figure. + :param frame_index: Frame index. + :param doc: Bokeh document that the plot lives in. + """ + if not self.data_source_condition: + return + + self.render_event.set() # type: ignore + + with self.data_source_condition: + while self.data_sources.get(frame_index, None) is None: + self.data_source_condition.wait() + + data_sources = dict(self.data_sources[frame_index].data) + + def update_main_figure() -> None: + """Wrapper for the main_figure update logic to support multi-threading.""" + if self.plot is None: + self.plot = main_figure.line( + x="xs", + y="ys", + line_color=simulation_tile_trajectory_style["ego"]["line_color"], + line_width=simulation_tile_trajectory_style["ego"]["line_width"], + line_alpha=simulation_tile_trajectory_style["ego"]["line_alpha"], + source=data_sources, + ) + else: + self.plot.data_source.data = data_sources + + self.render_event.clear() # type: ignore + + # Defer updating the main_figure to the next tick to be compatible with multi-threading approach + doc.add_next_tick_callback(lambda: update_main_figure()) + + def update_data_sources(self, history: SimulationHistory) -> None: + """ + Update ego_pose trajectory data sources. + :param history: SimulationHistory time-series data. + """ + if not self.data_source_condition: + return + + with self.data_source_condition: + for frame_index, sample in enumerate(history.data): + trajectory = sample.trajectory.get_sampled_trajectory() + + x_coords = [] + y_coords = [] + for state in trajectory: + x_coords.append(state.center.x) + y_coords.append(state.center.y) + + source = ColumnDataSource(dict(xs=x_coords, ys=y_coords)) + self.data_sources[frame_index] = source + self.data_source_condition.notify() + + +@dataclass +class AgentStatePlot(BaseScenarioPlot): + """A dataclass for agent state plot.""" + + data_sources: Dict[int, Dict[str, ColumnDataSource]] = field(default_factory=dict) # A dict of data for each frame + plots: Dict[str, GlyphRenderer] = field(default_factory=dict) # A dict of plots for each type + track_id_history: Optional[Dict[str, int]] = None # Track id history + + def __post_init__(self) -> None: + """Initialize track id history.""" + super().__post_init__() + + if not self.track_id_history: + self.track_id_history = {} + + def _get_track_id(self, track_id: str) -> Union[int, float]: + """ + Get a number representation for track ids. + :param track_id: Agent track id. + :return A number representation for a track id. + """ + if track_id == "null" or not self.track_id_history: + return np.nan + + number_track_id = self.track_id_history.get(track_id, None) + if not number_track_id: + self.track_id_history[track_id] = len(self.track_id_history) + number_track_id = len(self.track_id_history) + + return number_track_id + + def update_plot(self, main_figure: Figure, frame_index: int, doc: Document) -> None: + """ + Update the plot. + :param main_figure: The plotting figure. + :param frame_index: Frame index. + :param doc: Bokeh document that the plot lives in. + """ + if not self.data_source_condition: + return + + self.render_event.set() # type: ignore + + with self.data_source_condition: + while self.data_sources.get(frame_index, None) is None: + self.data_source_condition.wait() + + def update_main_figure() -> None: + """Wrapper for the main_figure update logic to support multi-threading.""" + data_sources = self.data_sources.get(frame_index, None) + if not data_sources: + return + + for category, data_source in data_sources.items(): + plot = self.plots.get(category, None) + data = dict(data_source.data) + if plot is None: + agent_color = simulation_tile_agent_style.get(category) + self.plots[category] = main_figure.multi_polygons( + xs="xs", + ys="ys", + fill_color=agent_color["fill_color"], + fill_alpha=agent_color["fill_alpha"], + line_color=agent_color["line_color"], + line_width=agent_color["line_width"], + source=data, + ) + agent_hover = HoverTool( + renderers=[self.plots[category]], + tooltips=[ + ("center_x [m]", "@center_xs{0.2f}"), + ("center_y [m]", "@center_ys{0.2f}"), + ("velocity_x [m/s]", "@velocity_xs{0.2f}"), + ("velocity_y [m/s]", "@velocity_ys{0.2f}"), + ("speed [m/s]", "@speeds{0.2f}"), + ("heading [rad]", "@headings{0.2f}"), + ("type", "@agent_type"), + ("track token", "@track_token"), + ], + ) + main_figure.add_tools(agent_hover) + else: + self.plots[category].data_source.data = data + + self.render_event.clear() # type: ignore + + # Defer updating the main_figure to the next tick to be compatible with multi-threading approach + doc.add_next_tick_callback(lambda: update_main_figure()) + + def update_data_sources(self, history: SimulationHistory) -> None: + """ + Update agents data sources. + :param history: SimulationHistory time-series data. + """ + if not self.data_source_condition: + return + + with self.data_source_condition: + for frame_index, sample in enumerate(history.data): + if not isinstance(sample.observation, DetectionsTracks): + continue + + tracked_objects = sample.observation.tracked_objects + frame_dict = {} + for tracked_object_type_name, tracked_object_type in tracked_object_types.items(): + corner_xs = [] + corner_ys = [] + track_ids = [] + track_tokens = [] + agent_types = [] + center_xs = [] + center_ys = [] + velocity_xs = [] + velocity_ys = [] + speeds = [] + headings = [] + + for tracked_object in tracked_objects.get_tracked_objects_of_type(tracked_object_type): + agent_corners = tracked_object.box.all_corners() + corners_x = [corner.x for corner in agent_corners] + corners_y = [corner.y for corner in agent_corners] + corners_x.append(corners_x[0]) + corners_y.append(corners_y[0]) + corner_xs.append([[corners_x]]) + corner_ys.append([[corners_y]]) + center_xs.append(tracked_object.center.x) + center_ys.append(tracked_object.center.y) + velocity_xs.append(tracked_object.velocity.x) + velocity_ys.append(tracked_object.velocity.y) + speeds.append(tracked_object.velocity.magnitude()) + headings.append(tracked_object.center.heading) + agent_types.append(tracked_object_type.fullname) + track_ids.append(self._get_track_id(tracked_object.track_token)) + track_tokens.append(tracked_object.track_token) + + agent_states = BokehAgentStates( + xs=corner_xs, + ys=corner_ys, + track_id=track_ids, + track_token=track_tokens, + agent_type=agent_types, + center_xs=center_xs, + center_ys=center_ys, + velocity_xs=velocity_xs, + velocity_ys=velocity_ys, + speeds=speeds, + headings=headings, + ) + + frame_dict[tracked_object_type_name] = ColumnDataSource(agent_states._asdict()) + + self.data_sources[frame_index] = frame_dict + self.data_source_condition.notify() + + +@dataclass +class AgentStateHeadingPlot(BaseScenarioPlot): + """A dataclass for agent state heading plot.""" + + data_sources: Dict[int, Dict[str, ColumnDataSource]] = field(default_factory=dict) # A dict of data for each frame + plots: Dict[str, GlyphRenderer] = field(default_factory=dict) # A dict of plots for each type + + def update_plot(self, main_figure: Figure, frame_index: int, doc: Document) -> None: + """ + Update the plot. + :param main_figure: The plotting figure. + :param frame_index: Frame index. + :param doc: Bokeh document that the plot lives in. + """ + if not self.data_source_condition: + return + + self.render_event.set() # type: ignore + + with self.data_source_condition: + while self.data_sources.get(frame_index, None) is None: + self.data_source_condition.wait() + + def update_main_figure() -> None: + """Wrapper for the main_figure update logic to support multi-threading.""" + data_sources = self.data_sources.get(frame_index, None) + if not data_sources: + return + + for category, data_source in data_sources.items(): + plot = self.plots.get(category, None) + data = dict(data_source.data) + if plot is None: + agent_color = simulation_tile_agent_style.get(category) + self.plots[category] = main_figure.multi_line( + xs="trajectory_x", + ys="trajectory_y", + line_color=agent_color["line_color"], + line_width=agent_color["line_width"], + source=data, + ) + else: + self.plots[category].data_source.data = data + + self.render_event.clear() # type: ignore + + doc.add_next_tick_callback(lambda: update_main_figure()) + + def update_data_sources(self, history: SimulationHistory) -> None: + """ + Update agent heading data sources. + :param history: SimulationHistory time-series data. + """ + if not self.data_source_condition: + return + + with self.data_source_condition: + for frame_index, sample in enumerate(history.data): + if not isinstance(sample.observation, DetectionsTracks): + continue + + tracked_objects = sample.observation.tracked_objects + frame_dict: Dict[str, Any] = {} + for tracked_object_type_name, tracked_object_type in tracked_object_types.items(): + trajectory_xs = [] + trajectory_ys = [] + for tracked_object in tracked_objects.get_tracked_objects_of_type(tracked_object_type): + object_box = tracked_object.box + agent_trajectory = translate_longitudinally( + object_box.center, distance=object_box.length / 2 + 1 + ) + trajectory_xs.append([object_box.center.x, agent_trajectory.x]) + trajectory_ys.append([object_box.center.y, agent_trajectory.y]) + + trajectories = ColumnDataSource( + dict( + trajectory_x=trajectory_xs, + trajectory_y=trajectory_ys, + ) + ) + frame_dict[tracked_object_type_name] = trajectories + + self.data_sources[frame_index] = frame_dict + self.data_source_condition.notify() + + +@dataclass +class SimulationFigure: + """Simulation figure data.""" + + # Required simulation data + planner_name: str # Planner name + scenario: AbstractScenario # Scenario + simulation_history: SimulationHistory # SimulationHistory + vehicle_parameters: VehicleParameters # Ego parameters + + # Rendering objects + figure: Figure # Bokeh figure + file_path_index: int # Experiment file index + slider: Slider # Bokeh slider to this figure + video_button: Button # Bokeh video button to this figure + first_button: Button # Bokeh button to go to the first frame + prev_button: Button # Bokeh button to go back one frame + play_button: Button # Bokeh button to automatically advance frames + next_button: Button # Bokeh button to advance one frame + last_button: Button # Bokeh button to go to the last frame + figure_title_name: str # Figure title name + x_y_coordinate_title: Title # Title renderer for x and y coordinate + time_us: Optional[List[int]] = None # Timestamp in microsecond + mission_goal_plot: Optional[GlyphRenderer] = None # Mission goal plot + expert_trajectory_plot: Optional[GlyphRenderer] = None # Expert trajectory plot + legend_state: bool = False # Legend states + map_polygon_plots: Dict[str, GlyphRenderer] = field(default_factory=dict) # Polygon plots for map layers + map_line_plots: Dict[str, GlyphRenderer] = field(default_factory=dict) # Line plots for map layers + traffic_light_plot: Optional[TrafficLightPlot] = None # Traffic light plot + ego_state_plot: Optional[EgoStatePlot] = None # Ego state plot + ego_state_trajectory_plot: Optional[EgoStateTrajectoryPlot] = None # Ego state trajectory plot + agent_state_plot: Optional[AgentStatePlot] = None # Agent state plot + agent_state_heading_plot: Optional[AgentStateHeadingPlot] = None # Agent state heading plot + + # Optional simulation data + lane_connectors: Optional[Dict[str, LaneConnector]] = None # Lane connector id: lane connector + + # Dataclass + glyph_names_from_checkbox_group: Optional[Dict[str, str]] = None # Correct glyph names from checkbox groups + + def __post_init__(self) -> None: + """Initialize all plots and data sources.""" + if self.lane_connectors is None: + self.lane_connectors = {} + + if self.time_us is None: + self.time_us = [] + + if self.traffic_light_plot is None: + self.traffic_light_plot = TrafficLightPlot() + + if self.ego_state_plot is None: + self.ego_state_plot = EgoStatePlot(vehicle_parameters=self.vehicle_parameters) + + if self.ego_state_trajectory_plot is None: + self.ego_state_trajectory_plot = EgoStateTrajectoryPlot() + + if self.agent_state_plot is None: + self.agent_state_plot = AgentStatePlot() + + if self.agent_state_heading_plot is None: + self.agent_state_heading_plot = AgentStateHeadingPlot() + + def is_rendering(self) -> bool: + """:return: true if at least one plot is currently rendering a frame request.""" + plots = [ + self.traffic_light_plot, + self.ego_state_plot, + self.ego_state_trajectory_plot, + self.agent_state_plot, + self.agent_state_heading_plot, + ] + return any(plot.render_event.is_set() if plot.render_event else False for plot in plots if plot) + + def figure_title_name_with_timestamp(self, frame_index: int) -> str: + """ + Return figure title with a timestamp. + :param frame_index: Frame index. + """ + if self.time_us: + return f"{self.figure_title_name} (Frame: {frame_index}, Time_us: {self.time_us[frame_index]})" + else: + return self.figure_title_name + + def copy_datasources(self, other: SimulationFigure) -> None: + """ + Copy data sources from another simulation figure. + :param other: Another SimulationFigure object. + """ + self.time_us = other.time_us + self.scenario = other.scenario + self.simulation_history = other.simulation_history + self.lane_connectors = other.lane_connectors + self.traffic_light_plot.data_sources = other.traffic_light_plot.data_sources # type: ignore + self.ego_state_plot.data_sources = other.ego_state_plot.data_sources # type: ignore + self.ego_state_trajectory_plot.data_sources = other.ego_state_trajectory_plot.data_sources # type: ignore + self.agent_state_plot.data_sources = other.agent_state_plot.data_sources # type: ignore + self.agent_state_heading_plot.data_sources = other.agent_state_heading_plot.data_sources # type: ignore + + def update_data_sources(self) -> None: + """ + Update data sources in a multi-threading manner to speed up loading and initialization in + scenario rendering. + """ + if len(self.simulation_history.data) == 0: + raise ValueError("SimulationHistory cannot be empty!") + + # Update slider steps + self.slider.end = len(self.simulation_history.data) - 1 + + # Update time_us + self.time_us = [sample.ego_state.time_us for sample in self.simulation_history.data] + + # Update plot data sources + for plot in [ + self.ego_state_plot, + self.ego_state_trajectory_plot, + self.agent_state_plot, + self.agent_state_heading_plot, + ]: + if plot: + t = threading.Thread(target=plot.update_data_sources, args=(self.simulation_history,), daemon=True) + t.start() + + def update_map_dependent_data_sources(self) -> None: + """ + Update data sources in a multi-threading manner to speed up loading and initialization in + scenario rendering. + """ + if len(self.simulation_history.data) == 0: + raise ValueError("SimulationHistory cannot be empty!") + + if self.lane_connectors is not None and len(self.lane_connectors): + if not self.traffic_light_plot: + return + + thread = threading.Thread( + target=self.traffic_light_plot.update_data_sources, + args=( + self.scenario, + self.simulation_history, + self.lane_connectors, + ), + daemon=True, + ) + thread.start() + + def render_mission_goal(self, mission_goal_state: StateSE2) -> None: + """ + Render the mission goal. + :param mission_goal_state: Mission goal state. + """ + source = ColumnDataSource( + dict(xs=[mission_goal_state.x], ys=[mission_goal_state.y], heading=[mission_goal_state.heading]) + ) + self.mission_goal_plot = self.figure.rect( + x="xs", + y="ys", + height=self.vehicle_parameters.height, + width=self.vehicle_parameters.length, + angle="heading", + fill_alpha=simulation_tile_style["mission_goal_alpha"], + color=simulation_tile_style["mission_goal_color"], + line_width=simulation_tile_style["mission_goal_line_width"], + source=source, + ) + + def render_expert_trajectory(self, expert_ego_trajectory_state: ColumnDataSource) -> None: + """ + Render expert trajectory. + :param expert_ego_trajectory_state: A list of trajectory states. + """ + self.expert_trajectory_plot = self.figure.line( + x="xs", + y="ys", + line_color=simulation_tile_trajectory_style["expert_ego"]["line_color"], + line_alpha=simulation_tile_trajectory_style["expert_ego"]["line_alpha"], + line_width=simulation_tile_trajectory_style["expert_ego"]["line_width"], + source=expert_ego_trajectory_state, + ) + + @staticmethod + def _update_glyph_visibility(glyphs: List[Optional[GlyphRenderer]]) -> None: + """ + Update visibility in a list of glyphs. + :param glyphs: A list of glyphs. + """ + for glyph in glyphs: + if glyph is not None: + glyph.visible = not glyph.visible + + def get_glyph_name_from_checkbox_group(self, glyph_checkbox_group_name: str) -> str: + """ + Get the correct glyph name of each glyph type based on the name from checkbox group. + :param glyph_checkbox_group_name: glyph name from a checkbox group. + :return Correct glyph name based on the glyph name from checkbox groups. + """ + if not self.glyph_names_from_checkbox_group: + self.glyph_names_from_checkbox_group = { + "Vehicle": "vehicles", + "Pedestrian": "pedestrians", + "Bicycle": "bicycles", + "Generic": "genericobjects", + "Traffic Cone": "traffic_cone", + "Barrier": "barrier", + "Czone Sign": "czone_sign", + "Lane": SemanticMapLayer.LANE.name, + "Intersection": SemanticMapLayer.INTERSECTION.name, + "Stop Line": SemanticMapLayer.STOP_LINE.name, + "Crosswalk": SemanticMapLayer.CROSSWALK.name, + "Walkway": SemanticMapLayer.WALKWAYS.name, + "Carpark": SemanticMapLayer.CARPARK_AREA.name, + "RoadBlock": SemanticMapLayer.ROADBLOCK.name, + "Lane Connector": SemanticMapLayer.LANE_CONNECTOR.name, + "Lane Line": SemanticMapLayer.LANE.name, + } + + name = self.glyph_names_from_checkbox_group.get(glyph_checkbox_group_name, None) + if not name: + raise ValueError(f"{glyph_checkbox_group_name} is not a valid glyph name!") + return name + + def _get_trajectory_glyph_to_update(self, glyph_name: str) -> List[Optional[GlyphRenderer]]: + """ + Get a trajectory glyph to update its visibility. + :param glyph_name: Glyph name. + :return A list of glyphs to be updated. + """ + if glyph_name == "Expert Trajectory": + return [self.expert_trajectory_plot if self.expert_trajectory_plot is not None else None] + elif glyph_name == "Ego Trajectory": + return [self.ego_state_trajectory_plot.plot if self.ego_state_trajectory_plot is not None else None] + elif glyph_name == "Goal": + return [self.mission_goal_plot] + elif glyph_name == "Traffic Light": + return [self.traffic_light_plot.plot if self.traffic_light_plot is not None else None] + else: + raise ValueError(f"{glyph_name} is not a valid trajectory name.") + + def _get_agent_glyph_to_update(self, glyph_name: str) -> List[Optional[GlyphRenderer]]: + """ + Update an agent glyph to update its visibility. + :param glyph_name: Glyph name. + :return A list of glyphs to be updated. + """ + object_type_name = self.get_glyph_name_from_checkbox_group(glyph_checkbox_group_name=glyph_name) + return [ + self.agent_state_plot.plots.get(object_type_name, None) if self.agent_state_plot is not None else None, + ( + self.agent_state_heading_plot.plots.get(object_type_name, None) + if self.agent_state_heading_plot is not None + else None + ), + ] + + def update_glyphs_visibility(self, glyph_names: Optional[List[str]] = None) -> None: + """ + Update glyphs' visibility based on a list of glyph names. + :param glyph_names: List of glyph names to update their visibility. + """ + if not glyph_names: + return + glyphs = [] + for glyph_name in glyph_names: + if glyph_name == "Ego": + glyphs += [self.ego_state_plot.plot if self.ego_state_plot is not None else None] + elif glyph_name in ["Expert Trajectory", "Ego Trajectory", "Goal", "Traffic Light"]: + glyphs += self._get_trajectory_glyph_to_update(glyph_name=glyph_name) + elif glyph_name in ["Vehicle", "Pedestrian", "Bicycle", "Generic", "Traffic Cone", "Barrier", "Czone Sign"]: + glyphs += self._get_agent_glyph_to_update(glyph_name=glyph_name) + elif glyph_name in ["Lane", "Intersection", "Stop Line", "Crosswalk", "Walkway", "Carpark", "RoadBlock"]: + map_polygon_name = self.get_glyph_name_from_checkbox_group(glyph_checkbox_group_name=glyph_name) + glyphs += [self.map_polygon_plots.get(map_polygon_name, None)] + elif glyph_name in ["Lane Connector", "Lane Line"]: + map_line_name = self.get_glyph_name_from_checkbox_group(glyph_checkbox_group_name=glyph_name) + glyphs += [self.map_line_plots.get(map_line_name, None)] + + self._update_glyph_visibility(glyphs=glyphs) + + def update_legend(self) -> None: + """Update legend.""" + if self.legend_state: + return + + if not self.agent_state_heading_plot or not self.agent_state_plot: + return + + agent_legends = [ + (category.capitalize(), [plot, self.agent_state_heading_plot.plots[category]]) + for category, plot in self.agent_state_plot.plots.items() + ] + + selected_map_polygon_layers = [ + SemanticMapLayer.LANE.name, + SemanticMapLayer.INTERSECTION.name, + SemanticMapLayer.STOP_LINE.name, + SemanticMapLayer.CROSSWALK.name, + SemanticMapLayer.WALKWAYS.name, + SemanticMapLayer.CARPARK_AREA.name, + ] + map_polygon_legend_items = [] + for map_polygon_layer in selected_map_polygon_layers: + map_polygon_legend_items.append( + (map_polygon_layer.capitalize(), [self.map_polygon_plots[map_polygon_layer]]) + ) + selected_map_line_layers = [SemanticMapLayer.LANE.name, SemanticMapLayer.LANE_CONNECTOR.name] + map_line_legend_items = [] + for map_line_layer in selected_map_line_layers: + map_line_legend_items.append((map_line_layer.capitalize(), [self.map_line_plots[map_line_layer]])) + + if not self.ego_state_plot or not self.ego_state_trajectory_plot: + return + + legend_items = [ + ("Ego", [self.ego_state_plot.plot]), + ("Ego traj", [self.ego_state_trajectory_plot.plot]), + ] + if self.mission_goal_plot is not None: + legend_items.append(("Goal", [self.mission_goal_plot])) + + if self.expert_trajectory_plot is not None: + legend_items.append(("Expert traj", [self.expert_trajectory_plot])) + + legend_items += agent_legends + legend_items += map_polygon_legend_items + legend_items += map_line_legend_items + if self.traffic_light_plot and self.traffic_light_plot.plot is not None: + legend_items.append(("Traffic light", [self.traffic_light_plot.plot])) + + legend = Legend(items=legend_items) + legend.click_policy = "hide" + + self.figure.add_layout(legend) + self.legend_state = True + self.figure.legend.label_text_font_size = "0.8em" + + +@dataclass +class SimulationData: + """Simulation figure data.""" + + planner_name: str # Planner name + simulation_figure: SimulationFigure # Simulation figure data + plot: LayoutDOM # Figure plot diff --git a/sledge/sledgeboard/base/simulation_tile.py b/sledge/sledgeboard/base/simulation_tile.py new file mode 100644 index 0000000..4330e40 --- /dev/null +++ b/sledge/sledgeboard/base/simulation_tile.py @@ -0,0 +1,963 @@ +import concurrent.futures +import json +import logging +import lzma +import math +import os +import pathlib +import pickle +import threading +import time +from concurrent.futures import ThreadPoolExecutor +from functools import partial +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +import cv2 +import msgpack +import numpy as np +from bokeh.document import without_document_lock +from bokeh.document.document import Document +from bokeh.events import PointEvent +from bokeh.io.export import get_screenshot_as_png +from bokeh.layouts import column, gridplot, row +from bokeh.models import Button, ColumnDataSource, Slider, Title +from bokeh.plotting.figure import Figure +from bokeh.server.callbacks import PeriodicCallback +from bokeh.util.callback_manager import EventCallback +from selenium import webdriver +from tornado import gen +from tqdm import tqdm + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.state_representation import Point2D +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters +from nuplan.common.maps.abstract_map import AbstractMap, MapObject +from nuplan.common.maps.abstract_map_factory import AbstractMapFactory +from nuplan.common.maps.maps_datatypes import SemanticMapLayer, StopLineType +from nuplan.planning.simulation.simulation_log import SimulationLog + +from sledge.sledgeboard.base.data_class import SimulationScenarioKey +from sledge.sledgeboard.base.experiment_file_data import ExperimentFileData +from sledge.sledgeboard.base.plot_data import MapPoint, SimulationData, SimulationFigure +from sledge.sledgeboard.style import simulation_map_layer_color, simulation_tile_style +from sledge.simulation.scenarios.sledge_scenario.sledge_scenario import SledgeScenario +from sledge.sledgeboard.tabs.config.scenario_tab_config import ( + ScenarioTabFrameButtonConfig, + first_button_config, + last_button_config, + next_button_config, + play_button_config, + prev_button_config, +) + +try: + import chromedriver_binary +except ImportError: + chromedriver_binary = None + + +logger = logging.getLogger(__name__) + + +def extract_source_from_states(states: List[EgoState]) -> ColumnDataSource: + """Helper function to get the xy coordinates into ColumnDataSource format from a list of states. + :param states: List of states (containing the pose) + :return: A ColumnDataSource object containing the xy coordinates. + """ + x_coords = [] + y_coords = [] + for state in states: + x_coords.append(state.center.x) + y_coords.append(state.center.y) + source = ColumnDataSource(dict(xs=x_coords, ys=y_coords)) + return source + + +def _extract_serialization_type(first_file: pathlib.Path) -> str: + """ + Deduce the serialization type + :param first_file: serialized file + :return: one from ["msgpack", "pickle", "json"]. + """ + msg_pack = first_file.suffixes == [".msgpack", ".xz"] + msg_pickle = first_file.suffixes == [".pkl", ".xz"] + msg_json = first_file.suffix == ".json" + number_of_available_types = int(msg_pack) + int(msg_json) + int(msg_pickle) + + # We can handle only conclusive serialization type + if number_of_available_types != 1: + raise RuntimeError(f"Inconclusive file type: {first_file}!") + + if msg_pickle: + return "pickle" + elif msg_json: + return "json" + elif msg_pack: + return "msgpack" + else: + raise RuntimeError("Unknown condition!") + + +def _load_data(file_name: pathlib.Path, serialization_type: str) -> Any: + """ + Load data from file_name + :param file_name: the name of a file which we want to deserialize + :param serialization_type: type of serialization of the file + :return: deserialized type + """ + if serialization_type == "json": + with open(str(file_name), "r") as f: + return json.load(f) + elif serialization_type == "msgpack": + with lzma.open(str(file_name), "rb") as f: + return msgpack.unpackb(f.read()) + elif serialization_type == "pickle": + with lzma.open( + str(file_name), + "rb", + ) as f: + return pickle.load(f) + else: + raise ValueError(f"Unknown serialization type: {serialization_type}!") + + +class SimulationTile: + """Scenario simulation tile for visualization.""" + + def __init__( + self, + doc: Document, + experiment_file_data: ExperimentFileData, + vehicle_parameters: VehicleParameters, + map_factory: AbstractMapFactory, + period_milliseconds: int = 5000, + radius: float = 100.0, + async_rendering: bool = True, + frame_rate_cap_hz: int = 60, + ): + """ + Scenario simulation tile. + :param doc: Bokeh HTML document. + :param experiment_file_data: Experiment file data. + :param vehicle_parameters: Ego pose parameters. + :param map_factory: Map factory for building maps. + :param period_milliseconds: Milliseconds to update the tile. + :param radius: Map radius. + :param async_rendering: When true, will use threads to render asynchronously. + :param frame_rate_cap_hz: Maximum frames to render per second. Internally this value is capped at 60. + """ + self._doc = doc + self._vehicle_parameters = vehicle_parameters + self._map_factory = map_factory + self._experiment_file_data = experiment_file_data + self._period_milliseconds = period_milliseconds + self._radius = radius + self._selected_scenario_keys: List[SimulationScenarioKey] = [] + self._executor = ThreadPoolExecutor(max_workers=4) + self._maps: Dict[str, AbstractMap] = {} + self._figures: List[SimulationFigure] = [] + self._nearest_vector_map: Dict[SemanticMapLayer, List[MapObject]] = {} + self._async_rendering = async_rendering + self._plot_render_queue: Optional[Tuple[SimulationFigure, int]] = None + self._doc.add_periodic_callback(self._periodic_callback, period_milliseconds=1000) + self._last_frame_time = time.time() + self._current_frame_index = 0 + self._last_frame_index = 0 + self._playback_callback_handle: Optional[PeriodicCallback] = None + + # Recheck that we are passed the correct frame rate cap. + if frame_rate_cap_hz < 1 or frame_rate_cap_hz > 60: + raise ValueError("frame_rate_cap_hz should be between 1 and 60") + + self._minimum_frame_time_seconds = 1.0 / float(frame_rate_cap_hz) + + logger.info("Minimum frame time=%4.3f s", self._minimum_frame_time_seconds) + + @property + def get_figure_data(self) -> List[SimulationFigure]: + """Return figure data.""" + return self._figures + + @property + def is_in_playback(self) -> bool: + """Returns True if we're currently rendering a playback of a figure.""" + return self._playback_callback_handle is not None + + def _on_mouse_move(self, event: PointEvent, figure_index: int) -> None: + """ + Event when mouse moving in a figure. + :param event: Point event. + :param figure_index: Figure index where the mouse is moving. + """ + main_figure = self._figures[figure_index] + # Update x and y coordinate values. + main_figure.x_y_coordinate_title.text = ( + f"x [m]: {np.round(event.x, simulation_tile_style['decimal_points'])}, " + f"y [m]: {np.round(event.y, simulation_tile_style['decimal_points'])}" + ) + + def _create_frame_control_button( + self, + button_config: ScenarioTabFrameButtonConfig, + click_callback: EventCallback, + figure_index: int, + ) -> Button: + """ + Helper function to create a frame control button (prev, play, etc.) based on the provided config. + :param button_config: Configuration object for the frame control button. + :param click_callback: Button click event callback that will be registered to the created button. + :param figure_index: The figure index to be passed to the button's click event callback. + :return: The created Bokeh Button instance. + """ + button_instance = Button( + label=button_config.label, + margin=button_config.margin, + css_classes=button_config.css_classes, + width=button_config.width, + ) + button_instance.on_click(partial(click_callback, figure_index=figure_index)) + return button_instance + + def _create_initial_figure( + self, figure_index: int, figure_sizes: List[int], backend: Optional[str] = "webgl" + ) -> SimulationFigure: + """ + Create an initial Bokeh figure. + :param figure_index: Figure index. + :param figure_sizes: width and height in pixels. + :param backend: Bokeh figure backend. + :return: A Bokeh figure. + """ + selected_scenario_key = self._selected_scenario_keys[figure_index] + + experiment_path = Path( + self._experiment_file_data.file_paths[selected_scenario_key.sledgeboard_file_index].metric_main_path + ) + planner_name = selected_scenario_key.planner_name + presented_planner_name = planner_name + f" ({experiment_path.stem})" + simulation_figure = Figure( + x_range=(-self._radius, self._radius), + y_range=(-self._radius, self._radius), + width=figure_sizes[0], + height=figure_sizes[1], + title=f"{presented_planner_name}", + tools=["pan", "wheel_zoom", "save", "reset"], + match_aspect=True, + active_scroll="wheel_zoom", + margin=simulation_tile_style["figure_margins"], + background_fill_color=simulation_tile_style["background_color"], + output_backend=backend, + ) + simulation_figure.on_event("mousemove", partial(self._on_mouse_move, figure_index=figure_index)) + # simulation_figure.axis.visible = False + simulation_figure.xgrid.visible = False + simulation_figure.ygrid.visible = False + # simulation_figure.outline_line_width = 7 + + # simulation_figure.xgrid[0].level = 'underlay' + # simulation_figure.ygrid[0].level = 'underlay' + + simulation_figure.title.text_font_size = simulation_tile_style["figure_title_text_font_size"] + x_y_coordinate_title = Title(text="x [m]: , y [m]: ") + simulation_figure.add_layout(x_y_coordinate_title, "below") + slider = Slider( + start=0, + end=1, + value=0, + step=1, + title="Frame", + margin=simulation_tile_style["slider_margins"], + css_classes=["scenario-frame-slider"], + ) + slider.on_change("value", partial(self._slider_on_change, figure_index=figure_index)) + video_button = Button( + label="Render video", + margin=simulation_tile_style["video_button_margins"], + css_classes=["scenario-video-button"], + ) + video_button.on_click(partial(self._video_button_on_click, figure_index=figure_index)) + + first_button = self._create_frame_control_button(first_button_config, self._first_button_on_click, figure_index) + prev_button = self._create_frame_control_button(prev_button_config, self._prev_button_on_click, figure_index) + play_button = self._create_frame_control_button(play_button_config, self._play_button_on_click, figure_index) + next_button = self._create_frame_control_button(next_button_config, self._next_button_on_click, figure_index) + last_button = self._create_frame_control_button(last_button_config, self._last_button_on_click, figure_index) + + assert len(selected_scenario_key.files) == 1, "Expected one file containing the serialized SimulationLog." + simulation_file = next(iter(selected_scenario_key.files)) + simulation_log = SimulationLog.load_data(simulation_file) + + simulation_figure_data = SimulationFigure( + figure=simulation_figure, + file_path_index=selected_scenario_key.sledgeboard_file_index, + figure_title_name=presented_planner_name, + slider=slider, + video_button=video_button, + first_button=first_button, + prev_button=prev_button, + play_button=play_button, + next_button=next_button, + last_button=last_button, + vehicle_parameters=self._vehicle_parameters, + planner_name=planner_name, + scenario=simulation_log.scenario, + simulation_history=simulation_log.simulation_history, + x_y_coordinate_title=x_y_coordinate_title, + ) + + return simulation_figure_data + + def _map_api(self, map_name: str) -> AbstractMap: + """ + Get a map api. + :param map_name: Map name. + :return Map api. + """ + if map_name not in self._maps: + self._maps[map_name] = self._map_factory.build_map_from_name(map_name) + + return self._maps[map_name] + + def init_simulations(self, figure_sizes: List[int]) -> None: + """ + Initialization of the visualization of simulation panel. + :param figure_sizes: Width and height in pixels. + """ + self._figures = [] + for figure_index in range(len(self._selected_scenario_keys)): + simulation_figure = self._create_initial_figure(figure_index=figure_index, figure_sizes=figure_sizes) + self._figures.append(simulation_figure) + + @property + def figures(self) -> List[SimulationFigure]: + """ + Access bokeh figures. + :return A list of bokeh figures. + """ + return self._figures + + def _render_simulation_layouts(self) -> List[SimulationData]: + """ + Render simulation layouts. + :return: A list of columns or rows. + """ + grid_layouts: List[SimulationData] = [] + for simulation_figure in self.figures: + grid_layouts.append( + SimulationData( + planner_name=simulation_figure.planner_name, + simulation_figure=simulation_figure, + plot=gridplot( + [ + [simulation_figure.slider], + [ + row( + [ + simulation_figure.first_button, + simulation_figure.prev_button, + simulation_figure.play_button, + simulation_figure.next_button, + simulation_figure.last_button, + ] + ) + ], + [simulation_figure.figure], + [simulation_figure.video_button], + ], + toolbar_location="left", + ), + ) + ) + return grid_layouts + + def render_simulation_tiles( + self, + selected_scenario_keys: List[SimulationScenarioKey], + figure_sizes: List[int] = simulation_tile_style["figure_sizes"], + hidden_glyph_names: Optional[List[str]] = None, + ) -> List[SimulationData]: + """ + Render simulation tiles. + :param selected_scenario_keys: A list of selected scenario keys. + :param figure_sizes: Width and height in pixels. + :param hidden_glyph_names: A list of glyph names to be hidden. + :return A list of bokeh layouts. + """ + self._selected_scenario_keys = selected_scenario_keys + self.init_simulations(figure_sizes=figure_sizes) + for main_figure in tqdm(self._figures, desc="Rendering a scenario"): + self._render_scenario(main_figure, hidden_glyph_names=hidden_glyph_names) + + layouts = self._render_simulation_layouts() + return layouts + + @gen.coroutine + @without_document_lock + def _video_button_on_click(self, figure_index: int) -> None: + """ + Callback to video button click event. + Note that this callback in run on a background thread. + :param figure_index: Figure index. + """ + self._figures[figure_index].video_button.disabled = True + self._figures[figure_index].video_button.label = "Rendering video now..." + + self._executor.submit(self._video_button_next_tick, figure_index) + + def _reset_video_button(self, figure_index: int) -> None: + """ + Reset a video button after exporting is done. + :param figure_index: Figure index. + """ + self.figures[figure_index].video_button.label = "Render video" + self.figures[figure_index].video_button.disabled = False + + def _update_video_button_label(self, figure_index: int, label: str) -> None: + """ + Update a video button label to show progress when rendering a video. + :param figure_index: Figure index. + :param label: New video button text. + """ + self.figures[figure_index].video_button.label = label + + def _video_button_next_tick(self, figure_index: int) -> None: + """ + Synchronous callback to the video button on click event. + :param figure_index: Figure index. + """ + if not len(self._figures): + return + + images = [] + scenario_key = self._selected_scenario_keys[figure_index] + scenario_name = scenario_key.scenario_name + scenario_type = scenario_key.scenario_type + planner_name = scenario_key.planner_name + video_name = scenario_type + "_" + planner_name + "_" + scenario_name + ".avi" + sledgeboard_file_index = scenario_key.sledgeboard_file_index + video_path = ( + Path(self._experiment_file_data.file_paths[sledgeboard_file_index].simulation_main_path) + / "video_screenshot" + ) + if not video_path.exists(): + video_path.mkdir(parents=True, exist_ok=True) + video_save_path = video_path / video_name + + scenario = self.figures[figure_index].scenario + database_interval = scenario.database_interval + selected_simulation_figure = self._figures[figure_index] + + try: + if len(selected_simulation_figure.ego_state_plot.data_sources): + options = webdriver.ChromeOptions() + options.add_argument("--headless=new") + driver = webdriver.Chrome(options=options) + driver.set_window_size(1920, 1080) + shape = None + simulation_figure = self._create_initial_figure( + figure_index=figure_index, + backend="canvas", + figure_sizes=simulation_tile_style["render_figure_sizes"], + ) + # Copy the data sources + simulation_figure.copy_datasources(selected_simulation_figure) + self._render_scenario(main_figure=simulation_figure) + length = len(selected_simulation_figure.ego_state_plot.data_sources) + images_save_path = video_path / simulation_figure.scenario.token + + os.makedirs(images_save_path, exist_ok=True) + + for frame_index in tqdm(range(length), desc="Rendering video"): + self._render_plots(main_figure=simulation_figure, frame_index=frame_index) + image = get_screenshot_as_png(column(simulation_figure.figure), driver=driver) + shape = image.size + images.append(image) + label = f"Rendering video now... ({frame_index}/{length})" + self._doc.add_next_tick_callback( + partial(self._update_video_button_label, figure_index=figure_index, label=label) + ) + image.save(images_save_path / f"frame_{frame_index}.png") + + fourcc = cv2.VideoWriter_fourcc("M", "J", "P", "G") + if database_interval: + fps = 1 / database_interval + else: + fps = 20 # Assume default fps is 20 + video_obj = cv2.VideoWriter(filename=str(video_save_path), fourcc=fourcc, fps=fps, frameSize=shape) + for index, image in enumerate(images): + cv2_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) + video_obj.write(cv2_image) + + video_obj.release() + logger.info("Video saved to %s" % str(video_save_path)) + except (RuntimeError, Exception) as e: + logger.warning("%s" % e) + try: + fourcc = cv2.VideoWriter_fourcc("M", "J", "P", "G") + if database_interval: + fps = 1 / database_interval + else: + fps = 20 # Assume default fps is 20 + video_obj = cv2.VideoWriter(filename=str(video_save_path), fourcc=fourcc, fps=fps, frameSize=shape) + for index, image in enumerate(images): + cv2_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) + video_obj.write(cv2_image) + + video_obj.release() + logger.info("Video saved to %s" % str(video_save_path)) + except: + logger.info("Couldn't save video on failure") + + self._doc.add_next_tick_callback(partial(self._reset_video_button, figure_index=figure_index)) + + def _first_button_on_click(self, figure_index: int) -> None: + """ + Will be called when the first button is clicked. + :param figure_index: The SimulationFigure index to render. + """ + figure = self._figures[figure_index] + self._request_specific_frame(figure=figure, frame_index=0) + + def _prev_button_on_click(self, figure_index: int) -> None: + """ + Will be called when the prev button is clicked. + :param figure_index: The SimulationFigure index to render. + """ + figure = self._figures[figure_index] + self._request_previous_frame(figure) + + def _play_button_on_click(self, figure_index: int) -> None: + """ + Will be called when the play button is clicked. + :param figure_index: The SimulationFigure index to render. + """ + figure = self._figures[figure_index] + self._process_play_request(figure) + + def _next_button_on_click(self, figure_index: int) -> None: + """ + Will be called when the next button is clicked. + :param figure_index: The SimulationFigure index to render. + """ + figure = self._figures[figure_index] + self._request_next_frame(figure) + + def _last_button_on_click(self, figure_index: int) -> None: + """ + Will be called when the last button is clicked. + :param figure_index: The SimulationFigure index to render. + """ + figure = self._figures[figure_index] + self._request_specific_frame(figure=figure, frame_index=len(figure.simulation_history.data) - 1) + + def _slider_on_change(self, attr: str, old: int, frame_index: int, figure_index: int) -> None: + """ + The function that's called every time the slider's value has changed. + All frame requests are routed through slider's event handling since currently there's no way to manually + set the slider's value programatically (to sync the slider value) without triggering this event. + :param attr: Attribute name. + :param old: Old value. + :param frame_index: The new value of the slider, which is the requested frame index. + :param figure_index: Figure index. + """ + del attr, old # unused params + selected_figure = self._figures[figure_index] + self._request_plot_rendering(figure=selected_figure, frame_index=frame_index) + + def _request_specific_frame(self, figure: SimulationFigure, frame_index: int) -> None: + """ + Requests to render the previous frame of the specified SimulationFigure. + :param figure: The SimulationFigure render. + :param frame_index: The frame index to render + """ + figure.slider.value = frame_index + + def _request_previous_frame(self, figure: SimulationFigure) -> None: + """ + Requests to render the previous frame of the specified SimulationFigure. + :param figure: The SimulationFigure render. + """ + if self._current_frame_index > 0: + figure.slider.value = self._current_frame_index - 1 + + def _request_next_frame(self, figure: SimulationFigure) -> bool: + """ + Requests to render next frame of the specified SimulationFigure. + :param figure: The SimulationFigure render. + :return True if the request is valid, False otherwise. + """ + result = False + + if self._current_frame_index < len(figure.simulation_history.data) - 1: + figure.slider.value = self._current_frame_index + 1 + result = True + + return result + + def _request_plot_rendering(self, figure: SimulationFigure, frame_index: int) -> None: + """ + Request the SimulationTile to render a frame of the plot. The requested frame will be enqueued if frame rate cap + is reached or the figure is currently rendering a frame. + :param figure: The SimulationFigure to render. + :param frame_index: The requested frame index to render. + """ + current_time = time.time() + if current_time - self._last_frame_time < self._minimum_frame_time_seconds or figure.is_rendering(): + logger.info("Frame deferred: %d", frame_index) + self._plot_render_queue = (figure, frame_index) + else: + self._process_plot_render_request(figure=figure, frame_index=frame_index) + self._last_frame_time = time.time() + + def _stop_playback(self, figure: SimulationFigure) -> None: + """ + Stops the playback for the given figure. + :param figure: SimulationFigure to stop rendering. + """ + if self._playback_callback_handle: + self._doc.remove_periodic_callback(self._playback_callback_handle) + self._playback_callback_handle = None + figure.play_button.label = "play" + + def _start_playback(self, figure: SimulationFigure) -> None: + """ + Starts the playback for the given figure. + :param figure: SimulationFigure to stop rendering. + """ + callback_period_seconds = figure.simulation_history.interval_seconds + callback_period_seconds = max(self._minimum_frame_time_seconds, callback_period_seconds) + callback_period_ms = 1000.0 * callback_period_seconds + self._playback_callback_handle = self._doc.add_periodic_callback( + partial(self._playback_callback, figure), callback_period_ms + ) + figure.play_button.label = "stop" + + def _playback_callback(self, figure: SimulationFigure) -> None: + """The callback that will advance the simulation frame. Will automatically stop the playback once we reach the final frame.""" + if not self._request_next_frame(figure): + self._stop_playback(figure) + + def _process_play_request(self, figure: SimulationFigure) -> None: + """ + Processes play request. When play mode is activated, the frame auto-advances, at the rate of the currently set frame rate cap. + :param figure: The SimulationFigure to render. + """ + if self._playback_callback_handle: + self._stop_playback(figure) + else: + self._start_playback(figure) + + def _process_plot_render_request(self, figure: SimulationFigure, frame_index: int) -> None: + """ + Process plot render requests, coming either from the slider or the render queue. + :param figure: The SimulationFigure to render. + :param frame_index: The requested frame index to render. + """ + if frame_index != len(figure.simulation_history.data): + if self._async_rendering: + thread = threading.Thread( + target=self._render_plots, + kwargs={"main_figure": figure, "frame_index": frame_index}, + daemon=True, + ) + thread.start() + else: + self._render_plots(main_figure=figure, frame_index=frame_index) + + def _render_scenario(self, main_figure: SimulationFigure, hidden_glyph_names: Optional[List[str]] = None) -> None: + """ + Render scenario. + :param main_figure: Simulation figure object. + :param hidden_glyph_names: A list of glyph names to be hidden. + """ + if self._async_rendering: + # Spawn 2 child threads to load the data for plots and render them once they are available. + # We don't wait for the threads to join so this function can immediately return, shortening the time the + # loading indicator is shown to the user. + + def render() -> None: + """Wrapper for the non-map-dependent parts of the rendering logic.""" + main_figure.update_data_sources() + + mission_goal = main_figure.scenario.get_mission_goal() + if mission_goal is not None: + main_figure.render_mission_goal(mission_goal_state=mission_goal) + + self._render_plots(main_figure=main_figure, frame_index=0, hidden_glyph_names=hidden_glyph_names) + self._render_expert_trajectory(main_figure=main_figure) + + def render_map_dependent() -> None: + """Wrapper for the map-dependent parts of the rendering logic.""" + self._load_map_data(main_figure=main_figure) + main_figure.update_map_dependent_data_sources() + self._render_map(main_figure=main_figure) + + executor = concurrent.futures.ThreadPoolExecutor(max_workers=2) + executor.submit(render) + executor.submit(render_map_dependent) + executor.shutdown(wait=False) + else: + # Trigger background threads to fetch non-map dependent data + main_figure.update_data_sources() + + # Load map data and then trigger thread(s) to fetch data sources that depend on it + self._load_map_data(main_figure=main_figure) + main_figure.update_map_dependent_data_sources() + + # Render the scenario + self._render_map(main_figure=main_figure) + + mission_goal = main_figure.scenario.get_mission_goal() + if mission_goal is not None: + main_figure.render_mission_goal(mission_goal_state=mission_goal) + + self._render_plots(main_figure=main_figure, frame_index=0, hidden_glyph_names=hidden_glyph_names) + self._render_expert_trajectory(main_figure=main_figure) + + def _load_map_data(self, main_figure: SimulationFigure) -> None: + """ + Load the map data of the simulation tile. + :param main_figure: Simulation figure. + """ + + if isinstance(main_figure.scenario, SledgeScenario): + map_api = main_figure.scenario.map_api + else: + map_name = main_figure.scenario.map_api.map_name + map_api = self._map_api(map_name) + + layer_names = [ + SemanticMapLayer.LANE_CONNECTOR, + SemanticMapLayer.LANE, + SemanticMapLayer.CROSSWALK, + SemanticMapLayer.INTERSECTION, + SemanticMapLayer.STOP_LINE, + SemanticMapLayer.WALKWAYS, + SemanticMapLayer.CARPARK_AREA, + ] + + assert main_figure.simulation_history.data, "No simulation history samples, unable to render the map." + ego_pose = main_figure.simulation_history.data[0].ego_state.center + center = Point2D(ego_pose.x, ego_pose.y) + + start_pose, end_pose = ( + main_figure.simulation_history.data[0].ego_state.center, + main_figure.simulation_history.data[-1].ego_state.center, + ) + distance = ((start_pose.array - end_pose.array) ** 2).sum(-1) ** 0.5 + + self._nearest_vector_map = map_api.get_proximal_map_objects(center, distance + 64, layer_names) + # Filter out stop polygons in turn stop + if SemanticMapLayer.STOP_LINE in self._nearest_vector_map: + stop_polygons = self._nearest_vector_map[SemanticMapLayer.STOP_LINE] + self._nearest_vector_map[SemanticMapLayer.STOP_LINE] = [ + stop_polygon for stop_polygon in stop_polygons if stop_polygon.stop_line_type != StopLineType.TURN_STOP + ] + + # Populate our figure's lane connectors. This variable is required by traffic_light_plot.update_data_sources + + if isinstance(main_figure.scenario, SledgeScenario): + map_layer = SemanticMapLayer.LANE + else: + map_layer = SemanticMapLayer.LANE_CONNECTOR + + main_figure.lane_connectors = { + lane_connector.id: lane_connector for lane_connector in self._nearest_vector_map[map_layer] + } + + def _render_map_polygon_layers(self, main_figure: SimulationFigure) -> None: + """Renders the polygon layers of the map.""" + polygon_layer_names = [ + (SemanticMapLayer.LANE, simulation_map_layer_color[SemanticMapLayer.LANE]), + ( + SemanticMapLayer.INTERSECTION, + simulation_map_layer_color[SemanticMapLayer.INTERSECTION], + ), + (SemanticMapLayer.STOP_LINE, simulation_map_layer_color[SemanticMapLayer.STOP_LINE]), + (SemanticMapLayer.CROSSWALK, simulation_map_layer_color[SemanticMapLayer.CROSSWALK]), + (SemanticMapLayer.WALKWAYS, simulation_map_layer_color[SemanticMapLayer.WALKWAYS]), + ( + SemanticMapLayer.CARPARK_AREA, + simulation_map_layer_color[SemanticMapLayer.CARPARK_AREA], + ), + ] + roadblock_ids = main_figure.scenario.get_route_roadblock_ids() + if roadblock_ids: + polygon_layer_names.append( + (SemanticMapLayer.ROADBLOCK, simulation_map_layer_color[SemanticMapLayer.ROADBLOCK]) + ) + + for layer_name, color in polygon_layer_names: + map_polygon = MapPoint(point_2d=[]) + # Render RoadBlock + if layer_name == SemanticMapLayer.ROADBLOCK: + layer = ( + self._nearest_vector_map[SemanticMapLayer.LANE] + + self._nearest_vector_map[SemanticMapLayer.LANE_CONNECTOR] + ) + for map_obj in layer: + roadblock_id = map_obj.get_roadblock_id() + if roadblock_id in roadblock_ids: + coords = map_obj.polygon.exterior.coords + points = [Point2D(x=x, y=y) for x, y in coords] + map_polygon.point_2d.append(points) + else: + layer = self._nearest_vector_map[layer_name] + for map_obj in layer: + coords = map_obj.polygon.exterior.coords + points = [Point2D(x=x, y=y) for x, y in coords] + map_polygon.point_2d.append(points) + + polygon_source = ColumnDataSource( + dict( + xs=map_polygon.polygon_xs, + ys=map_polygon.polygon_ys, + ) + ) + layer_map_polygon_plot = main_figure.figure.multi_polygons( + xs="xs", + ys="ys", + fill_color=color["fill_color"], + fill_alpha=color["fill_color_alpha"], + line_color=color["line_color"], + line_width=color["line_width"], + source=polygon_source, + ) + # underlay = default rendering level for grids, one level below `glyph`, the default level for plots + # https://docs.bokeh.org/en/latest/docs/user_guide/styling/plots.html#setting-render-levels + layer_map_polygon_plot.level = "underlay" + main_figure.map_polygon_plots[layer_name.name] = layer_map_polygon_plot + + def _render_map_line_layers(self, main_figure: SimulationFigure) -> None: + """Renders the line layers of the map.""" + line_layer_names = [ + (SemanticMapLayer.LANE, simulation_map_layer_color[SemanticMapLayer.BASELINE_PATHS]), + ( + SemanticMapLayer.LANE_CONNECTOR, + simulation_map_layer_color[SemanticMapLayer.LANE_CONNECTOR], + ), + ] + for layer_name, color in line_layer_names: + layer = self._nearest_vector_map[layer_name] + map_line = MapPoint(point_2d=[]) + for map_obj in layer: + path = map_obj.baseline_path.discrete_path + points = [Point2D(x=pose.x, y=pose.y) for pose in path] + map_line.point_2d.append(points) + + line_source = ColumnDataSource(dict(xs=map_line.line_xs, ys=map_line.line_ys)) + layer_map_line_plot = main_figure.figure.multi_line( + xs="xs", + ys="ys", + line_color=color["line_color"], + line_alpha=color["line_color_alpha"], + line_width=color["line_width"], + line_dash="dashed", + source=line_source, + ) + # underlay = default rendering level for grids, one level below `glyph`, the default level for plots + # https://docs.bokeh.org/en/latest/docs/user_guide/styling/plots.html#setting-render-levels + layer_map_line_plot.level = "underlay" + main_figure.map_line_plots[layer_name.name] = layer_map_line_plot + + def _render_map(self, main_figure: SimulationFigure) -> None: + """ + Render a map. + :param main_figure: Simulation figure. + """ + + def render() -> None: + """Wrapper for the actual render logic, for multi-threading compatibility.""" + self._render_map_polygon_layers(main_figure) + self._render_map_line_layers(main_figure) + + self._doc.add_next_tick_callback(lambda: render()) + + @staticmethod + def _render_expert_trajectory(main_figure: SimulationFigure) -> None: + """ + Render expert trajectory. + :param main_figure: Main simulation figure. + """ + expert_ego_trajectory = main_figure.scenario.get_expert_ego_trajectory() + source = extract_source_from_states(expert_ego_trajectory) + main_figure.render_expert_trajectory(expert_ego_trajectory_state=source) + + def _render_plots( + self, + main_figure: SimulationFigure, + frame_index: int, + hidden_glyph_names: Optional[List[str]] = None, + ) -> None: + """ + Render plot with a frame index. + :param main_figure: Main figure to render. + :param frame_index: A frame index. + :param hidden_glyph_names: A list of glyph names to be hidden. + """ + # main_figure.lane_connectors might still be loading the first time the function is called (for the initial + # frame), but if the user renders another frame and go back, it should be there if it's available for the tile. + if main_figure.lane_connectors is not None and len(main_figure.lane_connectors): + main_figure.traffic_light_plot.update_plot( + main_figure=main_figure.figure, + frame_index=frame_index, + doc=self._doc, + ) + + # Update ego state plot. + main_figure.ego_state_plot.update_plot( + main_figure=main_figure.figure, + frame_index=frame_index, + radius=self._radius, + doc=self._doc, + ) + + # Update ego pose trajectory state plot. + main_figure.ego_state_trajectory_plot.update_plot( + main_figure=main_figure.figure, + frame_index=frame_index, + doc=self._doc, + ) + + # Update agent state plot. + main_figure.agent_state_plot.update_plot( + main_figure=main_figure.figure, + frame_index=frame_index, + doc=self._doc, + ) + + # Update agent heading plot. + main_figure.agent_state_heading_plot.update_plot( + main_figure=main_figure.figure, + frame_index=frame_index, + doc=self._doc, + ) + + def update_decorations() -> None: + main_figure.figure.title.text = main_figure.figure_title_name_with_timestamp(frame_index=frame_index) + main_figure.update_glyphs_visibility(glyph_names=hidden_glyph_names) + + self._doc.add_next_tick_callback(lambda: update_decorations()) + + self._last_frame_index = self._current_frame_index + self._current_frame_index = frame_index + + def _periodic_callback(self) -> None: + """Periodic callback registered to the bokeh.Document.""" + # Process plot render queue + if self._plot_render_queue: + figure, frame_index = self._plot_render_queue + + # Prevent the frame from jumping back after settling + last_frame_direction = math.copysign(1, self._current_frame_index - self._last_frame_index) + request_frame_direction = math.copysign(1, frame_index - self._current_frame_index) + if request_frame_direction != last_frame_direction: + logger.info("Frame dropped %d", frame_index) + self._plot_render_queue = None + + # Render the queue if it made it this far + elif not figure.is_rendering(): + logger.info("Processing render queue for frame %d", frame_index) + self._plot_render_queue = None + self._process_plot_render_request(figure=figure, frame_index=frame_index) diff --git a/sledge/sledgeboard/resource/__init__.py b/sledge/sledgeboard/resource/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/sledgeboard/resource/css/cloud.css b/sledge/sledgeboard/resource/css/cloud.css new file mode 100644 index 0000000..e815482 --- /dev/null +++ b/sledge/sledgeboard/resource/css/cloud.css @@ -0,0 +1,169 @@ +.cloud-input-form { + display: None; + position: fixed; + left: 30%; + top: 20%; + z-index: 3000; +} + +.cloud-tab-section { + height: 95vh; + width: 100%; +} + +.cloud-tab-section a { + color: #DE7061; +} + +.col-extend-height { + height: 100%; + width: 100%; +} + +.panel-body-top-input-form { + margin-top: 10px; + height: 8%; +} + +.panel-extend-height { + height: 100%; + width: 100%; +} + +.panel-body-extend-height { + height: 100%; + width: 100%; +} + +.cloud-table-section { + margin-top: 5px; + height: 90% !important; +} + +.cloud-tab-section .cloud-table-section .bk-root { + height: 100%; + box-sizing: border-box !important; +} + +.s3-data-table { + width: 100% !important; + height: 100% !important; + margin: auto !important; + overflow-y: auto; + overflow-x: auto; + box-sizing: border-box !important; +} + +.s3-data-table .slick-cell { + border-right: 1px solid #ddd; + word-wrap: break-word; + word-break: break-all; + overflow-wrap: break-word; + white-space: normal; + font-size: 14px; + cursor: pointer; +} + +.s3-data-table .slick-row { + border-left: 1px solid #ddd !important; + border-bottom: 1px solid #ddd !important; + box-sizing: border-box !important; +} + +.s3-data-table .slick-pane-header { + height: 30px; + border-top: 1px solid #ddd; + width: 100% !important; + box-sizing: border-box !important; +} + +.s3-data-table .slick-pane-top { + border-top: 3px solid #ddd; +} + +.s3-data-table .slick-header-column { + height: 30px !important; + word-wrap: break-word; + word-break: break-all; + overflow-wrap: break-word; + white-space: normal; + font-size: 12px; + font-weight: bold; + box-sizing: border-box !important; +} + +.s3-tab-modal-query-btn { + width: unset !important; + height: unset !important; + margin: unset !important; +} + +.s3-tab-modal-query-btn .bk-btn-default { + background-color: #DE7061 !important; + border-color: #DE7061 !important; + color: white; +} + +.title { + padding: 6px 12px; +} + +.modal-input-group .input-group .bk-root .bk { + height: unset !important; + margin: unset !important; +} + +.cloud-input-form .modal-input-group .input-group .bk-root .bk { + width: 100% !important; +} + +.input-group-addon { + min-width: 150px; +} + +.bottom-marg-10 { + margin-bottom: 10px; +} + +.breadcrumb { + margin-top: 0; + margin-bottom: 0; +} + +.s3-download-text-input { + display: inline-block; +} + +.cloud-download-section .bk-root { + display: inline-block; +} +.panel-body-top-error-message { + height: 8%; + color: red; +} + +.s3-download-btn .bk-btn-default { + background-color: #DE7061 !important; + border-color: #DE7061 !important; + color: white; +} + +.s3-download-btn .bk-btn[disabled] { + opacity: unset !important; + pointer-events: unset !important; +} + +.s3-error-text { + width: 100% !important; +} + +#navbuttons .btn { + padding: 3px 6px; +} + +#cloud-modal-loading { + position: absolute; + top: 20%; + left: 50%; + visibility: hidden; +} diff --git a/sledge/sledgeboard/resource/css/histogram.css b/sledge/sledgeboard/resource/css/histogram.css new file mode 100644 index 0000000..7692f23 --- /dev/null +++ b/sledge/sledgeboard/resource/css/histogram.css @@ -0,0 +1,86 @@ +.histogram-setting-form { + display: None; + position: fixed; + left: 30%; + top: 20%; + z-index: 3000; +} + +.histogram-setting-form .modal-body .input-group .bk-root { + width: 100%; +} + +.histogram-bin-spinner { + width: 100% !important; +} + +.histogram-tab-modal-query-btn { + width: unset !important; + height: unset !important; + margin: unset !important; +} + +.histogram-tab-modal-query-btn .bk-btn-default { + background-color: #DE7061 !important; + border-color: #DE7061 !important; + color: white; +} + +.histogram-plot-section { + width: 100%; + box-sizing: border-box; + border: 1px solid #f0f0f0; + height: 95%; +} + +.histogram-plot-section .planner-search-row { + text-align: right; + padding: 5px 24px; + height: 30px; + background: #fff; + border-bottom: 1px solid #f0f0f0; +} + +.histogram-plot-section .histogram-table-row { + margin-top: 10px; + height: 95%; +} + +.histogram-plot-section .histogram-table-row .bk-root { + height: 100%; +} + +.histogram-default-div { + width: 80% !important; + font-size: 1.2em !important; +} + +.histogram-plots { + width: 100% !important; + height: 100% !important; + margin: auto !important; + overflow-y: auto; + overflow-x: auto; +} + +#histogram-loading { + position: absolute; + top: 20%; + left: 50%; + visibility: hidden; +} + +.histogram-plots .bk-tooltip { + overflow-y: auto; + overflow-x: hidden; + min-height: 100px; + max-height: 250px; +} + +.histogram-plots .bk .bk-left { + left: 15px !important; +} + +.histogram-plots .bk .bk-right { + right: 15px !important; +} diff --git a/sledge/sledgeboard/resource/css/overview.css b/sledge/sledgeboard/resource/css/overview.css new file mode 100644 index 0000000..cd65b13 --- /dev/null +++ b/sledge/sledgeboard/resource/css/overview.css @@ -0,0 +1,77 @@ +.overview-table { + width: 100% !important; + height: 100% !important; + margin: auto !important; + overflow-y: auto; + overflow-x: auto; + box-sizing: border-box !important; +} + +.overview-table .slick-cell { + border-right: 1px solid #ddd; + word-wrap: break-word; + word-break: break-all; + overflow-wrap: break-word; + white-space: normal; + font-size: 14px; + cursor: pointer; +} + +.overview-table .slick-row { + border-left: 1px solid #ddd !important; + border-bottom: 1px solid #ddd !important; + box-sizing: border-box !important; +} + +.overview-table .slick-pane-header { + height: 30px; + border-top: 1px solid #ddd; + width: 100% !important; + box-sizing: border-box !important; +} + +.overview-table .slick-pane-top { + border-top: 3px solid #ddd; +} + +.overview-table .slick-header-column { + height: 30px !important; + word-wrap: break-word; + word-break: break-all; + overflow-wrap: break-word; + white-space: normal; + font-size: 12px; + font-weight: bold; + box-sizing: border-box !important; +} + +.overview-section { + width: 100%; + box-sizing: border-box; + border: 1px solid #f0f0f0; + height: 95%; +} + +.overview-section .planner-search-row { + text-align: right; + padding: 5px 24px; + height: 30px; + background: #fff; + border-bottom: 1px solid #f0f0f0; +} + +.overview-section .overview-table-row { + margin-top: 10px; + height: 95%; +} + +.overview-section .overview-table-row .bk-root { + height: 100%; +} + +#overview-loading { + position: absolute; + top: 20%; + left: 50%; + visibility: hidden; +} diff --git a/sledge/sledgeboard/resource/css/scenario.css b/sledge/sledgeboard/resource/css/scenario.css new file mode 100644 index 0000000..45bf89f --- /dev/null +++ b/sledge/sledgeboard/resource/css/scenario.css @@ -0,0 +1,273 @@ +.panel-primary>.panel-heading { + color: #fff; + background-color: #DE7061; + border-color: #DE7061; +} + +.panel-primary { + border-color: #DE7061; +} + +.scenario-setting-form { + display: None; + position: fixed; + left: 30%; + top: 20%; + z-index: 3000; +} + +.scenario-token-multi-choice .choices__inner { + height: 40px; +} + +.scenario-setting-form .modal-input-group .input-group .bk-root .bk { + width: 100% !important; +} + +.scenario-type-multi-choice { + width: auto !important; + background: #fff; +} + +.scenario-type-multi-choice .choices__inner { + overflow: auto; + height: 50px; +} + +.scenario-plot-section { + width: 100%; + box-sizing: border-box; + border: 1px solid #f0f0f0; + height: 95% +} + +.scenario-plot-section .panel-row { + padding: 10px 24px; + border-bottom: 1px solid #f0f0f0; + text-align: left; + background: #fafafa; + height: 40px; + background: #fff; + font-size: 1.1em; +} + +.scenario-plot-section .planner-search-row { + text-align: right; + padding: 5px 24px; + height: 30px; + background: #fff; + border-bottom: 1px solid #f0f0f0; +} + +.scenario-plot-section .checkbox-group-row .bk-inline label { + text-align: left !important; + width: 10% !important; +} + +.scenario-simulation-default-div { + width: 80% !important; + font-size: 14px !important; +} + +.scenario-default-div { + width: 80% !important; + font-size: 1.2em !important; +} + +.scenario-plot-section .scenario-simulation-row { + border-bottom: 1px solid rgba(191, 191, 191, 0.6); + height: 40%; + min-height: 600px; + background: #fff; + overflow-y: auto; +} + +.scenario-plot-section .scenario-simulation-row .scenario-simulation-column { + height: 40%; + min-height: 600px; + border-right: 1px solid rgba(191, 191, 191, 0.6); +} + +.scenario-plot-section .scenario-simulation-row .scenario-simulation-column .bk-root { + height: 100%; + background: #fff; +} + +.scenario-plot-section .scenario-simulation-row .col-sm-3 { + padding-right: 5px; + padding-left: 0; +} + +.scenario-plot-section .scenario-simulation-row .scenario-simulation-legend-section { + background: #fff; + height: 40%; + min-height: 600px; + border-left: 1px solid rgba(191, 191, 191, 0.6); +} + +.scenario-tab-modal-query-btn { + width: unset !important; + height: unset !important; + margin: unset !important; +} + +.scenario-tab-modal-query-btn .bk-btn-default { + background-color: #de7061 !important; + border-color: #DE7061 !important; + color: white; +} + +.scenario-tab-panel-title .bk-root { + display: inline-block; +} + +.scenario-tab-title-div { + font-size: 12px !important; + height: unset !important; + width: unset !important; +} + +.scenario-setting-form .input-group-option-title { + padding-top: 10px; + border-top: 1px solid rgba(191, 191, 191, 0.4); + font-size: 12px; +} + +.scenario-legend-checkbox-group { + cursor: pointer; + width: 100% !important; +} + +.scenario-legend-checkbox-group .first-item { + border-top: 1px solid rgba(191, 191, 191, 0.6); +} + +.scenario-legend-checkbox-group .item { + background-color: white; + color: black; + border-bottom: 1px solid rgba(191, 191, 191, 0.6); + padding: 14px 28px; + font-size: 1.0em; + cursor: pointer; +} + +.scenario-legend-checkbox-group .checkbox-group-item { + display: None; + height: 100%; +} + +.scenario-traj-checkbox-group { + width: 100% !important; +} + +.scenario-object-checkbox-group { + width: 100% !important; +} + +.scenario-map-checkbox-group { + width: 100% !important; +} + +.scenario-plot-section .scenario-legend-checkbox-group .checkbox-group-item .bk-input-group label { + color: black; + border-bottom: 1px solid rgba(191, 191, 191, 0.6); + padding: 14px 28px; + font-size: 0.9em; + width: 100% !important; +} + +.scenario-plot-section .scenario-table-scenario-score-row { + height: 10%; + background: #fff; + min-height: 400px; + display: none; +} + +.scenario-plot-section .scenario-table-scenario-score-row .bk-root { + padding-top: 24px; + height: 100%; +} + +.scenario-plot-section .scenario-table-time-series-row { + height: 20%; + background: #fff; + display: none; + min-height: 400px; + border-bottom: 1px solid rgba(191, 191, 191, 0.6); +} + +.scenario-plot-section .scenario-table-time-series-row .bk-root { + padding-top: 24px; + height: 100%; +} + +.scenario-plot-section .scenario-table-ego-expert-states-row { + height: 10%; + background: #fff; + display: none; + min-height: 400px; + border-bottom: 1px solid rgba(191, 191, 191, 0.6); +} + +.scenario-plot-section .scenario-table-ego-expert-states-row .bk-root { + padding-top: 24px; + height: 100%; +} + +.scenario-score-layout { + width: 100% !important; + height: 100% !important; + margin: auto !important; + overflow-y: auto; + overflow-x: auto; +} + +.scenario-ego-expert-states-layout { + width: 100% !important; + height: 100% !important; + margin: auto !important; + overflow-y: auto; + overflow-x: auto; +} + +.scenario-time-series-layout { + width: 100% !important; + height: 100% !important; + margin: auto !important; + overflow-y: auto; + overflow-x: auto; +} + +.scenario-simulation-layout { + width: 100% !important; + margin: auto !important; +} + +.bk .scenario-video-button { + width: 40% !important; +} + +.bk .scenario-video-button button { + font-size: 12px; +} + +.bk .frame-control-button button { + font-size: 12px; +} + +.scalar-scenario-type-select { + width: auto !important; + background: #fff; +} + +.scalar-scenario-name-select { + width: auto !important; + background: #fff; +} + +#scenario-loading { + position: absolute; + top: 20%; + left: 50%; + visibility: hidden; +} \ No newline at end of file diff --git a/sledge/sledgeboard/resource/scripts/utils.js b/sledge/sledgeboard/resource/scripts/utils.js new file mode 100644 index 0000000..7ca5a35 --- /dev/null +++ b/sledge/sledgeboard/resource/scripts/utils.js @@ -0,0 +1,61 @@ +function openTab(evt, tab_name) { + var i, tab_content, header_bar_tab_btn; + tab_content = document.getElementsByClassName("tab-content"); + const navEles = document.getElementsByClassName("nav-item"); + + for (const navEle of navEles) { + const navName = navEle.innerText; + navEle.style.fontWeight = + tab_name === navName.toLowerCase() ? "bold" : "unset"; + } + for (i = 0; i < tab_content.length; i++) { + tab_content[i].style.display = "none"; + } + document.getElementById(tab_name).style.display = "block"; + evt.currentTarget.className += " active"; +} + +function toggleNav() { + const navbar = document.getElementsByClassName("navbar")[0]; + const menuIcon = document.getElementById("menu-icon"); + const file_header = document.getElementById("file-header"); + + // Close the menu bar + if (file_header.style.marginLeft == "10rem" || file_header.style.marginLeft == "") { + navbar.style.marginLeft = "-10rem"; + file_header.style.marginLeft = 0; + menuIcon.classList.add("icon-menu"); + menuIcon.classList.remove("icon-arrow-left"); + } else { + navbar.style.marginLeft = 0; + file_header.style.marginLeft = "10rem"; + menuIcon.classList.add("icon-arrow-left"); + menuIcon.classList.remove("icon-menu"); + } +} + +function openScenarioBar(evt, bar_name, svg_path_name) { + const scenario_bar = document.getElementById(bar_name); + const scenario_bar_svg_path = document.getElementById(svg_path_name); + if (scenario_bar.style.display == "none" || scenario_bar.style.display == "") { + scenario_bar.style.display = "block"; + scenario_bar_svg_path.setAttribute("d", "M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"); + } else { + scenario_bar.style.display = "none"; + scenario_bar_svg_path.setAttribute("d", "M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"); + } +} + +function openModal(evt, modal_name) { + const overlay = document.getElementsByClassName("overlay")[0]; + overlay.style.display = "block"; + + document.getElementsByClassName(modal_name)[0].style.display = "block"; +} + +function closeModal(evt, modal_name) { + const overlay = document.getElementsByClassName("overlay")[0]; + overlay.style.display = "none"; + + document.getElementsByClassName(modal_name)[0].style.display = "none"; +} diff --git a/sledge/sledgeboard/resource/sledge_logo_transparent.png b/sledge/sledgeboard/resource/sledge_logo_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..d011c9e3b2b06addb0cf33827e0abaee82d05d09 GIT binary patch literal 61902 zcmeFYWm}uw7d;rRYKXCRFt|G7}Nx@ROU=4@QV0TkzOMtt(JBPKsjf=Udqa}xfvsLP`5E%eK z4UqdNuIZ6>xa^r0r<2V4d;n6>x(AlgbP@C6gMj?-req@cf{m=UE=Zqc$~8JucZX=oAH!##em+ zPkWZ^Kl_S2B7oB|S`LbLmc|8cvgt|8Be?qi_iKuZ0}S|o|J3+aR%G%2yl8$u7B2sP zzY+GHg4O^3#s3do1odnL5_3hGa{ZaK;uRfk4r!hh7Rnu<^tMolz&$9eo?ZjOipU=j zf@F#2=6#n-e?vQSLG6}DdstLDXkYqp*z-Wma@jfZT4zJED>K^vmY)AW$d7)TMw?T# zRepDoTzkU7oyMYv$P0XgmP(k>)1uVKh#j`I5bog!WB)#YsLz$BU2hVckVRZJ5cV9n za~ih%4S8-PGap8<jF}|}uD9BS2Ub2{WU&6Qw=s&3qk!Uz)v|6v6+A?0*4|S5SmBPJY z9qft`6Tw=`Hc%NCO~0595ws(Wh4rNdass_AMA)+*^z>)N|Qgr4cWt{nDv%!A2yz>Px|jH-vE)q`LeBCL>q(~>RU z9fkJWMG;MnH`Tq5{EVz>&J>l+O}iAVB>Kn)fDp+`9czhjK1 zTIj}7Uu8IT80_mjdR$Im&*wecvrWB07r610g`%c!k%*og3t!Gy8-H?T21i9LEN-iW zr9G(NY)DwK=mZ`MpD+EJu`boV+IC5UGGNRgB&CdVNq9Q7j?>6Kh#%tC{aCXP6+}BE zsQ&5G=}w)1g?9{?Xy?3sxSnsIa^an6=*abwRa)lnwKpSZEgwV^M z<_|LKyEU3T_hXbE_`gTQ{sI6oI?DT!5z(hK(JnaozD9mdJvW){zAMOd!+=cu?}|Z=5FFi6X!A{tw|^*iF{Qn^8ho{}x$&7hD>WLp!BgvT6l~3jsyB z0vONo{g~jMo=?CCbCS-)YsL`P#=@mtJHsEuw#0Sml=@y^@Wu7&kw^C5@Q2Tk=tDiB z&Wy>3f75iIyEe>Dz}dZMCU}`9>G9Ec6%oM7(=|%@Uj<9M#;)vN@BC!UUk)yg+Z^0? zA$?zfQ6lIG7SXFoq83sN=89^%vrB2klo}6TrHV_b+obvvPVF#rq^<@H4vi zr#+XS_caKK;g~ZH%#5n!yF36JaMy%si2nTWj+L}W!&zUgp89?+3KBO8(Vd1Fs5~yJ zv!18cUTk9OV3!YRwh{f}|0Il$;0kNJ+dg2_`B~a5)k_GF z_8$aD-A7ebHu0cOW(K4s7n46nM)tA$ao=6XcVbZ-DZ19zY1UwEFlaFyF0OA?Wi*Q5 z*;wx~n@01iNJ#AhEp1h_eZ4!#Zysd1<$Wf-rSB_RKlpU=|I62?6QgvR$OZ=q{%vl` zN9gjyJ5#9=^=dQS8qPJ9OqHM=`uSs9IqtS&Aw1k2H-+9YX~KP3aYjb}wa zmZd+S;;jDVpM(9~{$@*XVHLPoX-0)cgv?4e8q}%i=NlvLMFS3WHhH~uW_In(&8&s{ zeej}aS$KGO^W1ximlW7S1l^0MqlX4wVxyw zlc%804*V_k&CTc=He{%I@~--tleoKP!#jRbIs82xeTCU4F7)v4WY?&1Ab%0p*v0l8 zGz-Tl6!bcF%pN6s&f`1L$!9k8ch+qR-RkPUA$EDoPKyWJ+kZelUcQ_xRORXreOTfA zjyf&>h4x*i%Spj>)84+yhn2gDf{C-3QNcGV;u}9M$c^D;;dH4$(gIa5P+|Ne+rZi8 zrMk)bIjlYt+?5Z=iFp)y_VKrU1L4FZOYpMXSw9frz8kp_(pL?qbavp6kQ8!<=@_71Hz9u2H_e^mqUB?ml6OQ!Cc4u8lO zhvLJ%K}*j~3)=@bD;lX#r*+<@`~mcDyF}D{Bd*Sb-+C;Z*`dt1^fP)vTJ{^Ooro?CnnwbwJM0^!!PfUTpY+Rk^;#co!yioIZ{Hs9_!4%){~ zWrc3caLWScfT)WP(ix(mgD1wjB}vl;vwu3Rt{~U%4~!(7+~3{~U6aaoiQ}^)ak^Qy zd~vMCc_kBQCFV{eTN3y75I%U>6`~RxSv)fFFVU5Qb*}8za)+$3qe+VSD22h$v-iEq zxPrs&ojt)pI@q*alz0&UVCtxvgTJF>ySK>+D$%rwcRg^yib9-L!~4EnZy@$wcvEvCV~R^czgrI7TO=nQds5X=P_W!p4BBSR z8L@CV%F4U_GeJ5W^pr(zj((g7D`Ho6drybAFCXxBE{}#pe`6X8RC4bfXsNR7;rEmT zx1)?CFdV0hp=(RX8>xjYK@BvorSdf`?GY*PXHQCOWoet+nGzS{$4xn?jJc!h@Sg+r zA3EIu&}^60W4*eyf03z`sdoxC1_n6#4)zrbO<#_Y(iMol2}?Z!*Vq93-YSUaqBpL< zgSC$JkkgDvDfXSXX?cVO%npova1P=6!G)SC;E{w%ImU2Oa~7d(jWVc7M?dTmLJXq zu-a_`-!W&JC;3vdBE1;NV@LkZBIjj)G%??mWhiYHSeWGXlb@Mg+BDd2^=YrLCI6Ch zd8O|q8b*a(dgx<3=`DptfX6Li#cb0XUE%Wzyv?t`TVRp_<1ijN)7}_eFDjdR6a#5R zc=D>bLOFtbuV^tL{;W#x2N&ax;74GjKTx3?Jv-Ha=P>_X#x$ZAH#;|uO&&$gE;;sw zak)b+jxatZ?*y((bI?z3@Aik`54{Qxi|fNYL$)6aGpVVipn=(EDGujGv_}HSkev6a z*)w3Cd4RJoo{-O--P#(Jf+_Qr$?0|7!P^0Y>1_JC=ZV{X@>p(C40;Ic z;sv;!4<|>FQQQud^No{JfrekGV?Ds9zAAR;6ts0a*mh;k7Q$iY^Y&3f546rM9Vpp< zQRn3IEzIHr-NSWZ)NOS189_+6iL61DYR#-&@^7@Giv4>PJ<@y%e5C@Dx% zp!!D@7h`2c^Kpx~T3!&Ih+}6T!))_F zq6bBGG&WWw`Y(R_CULXz@}KxT2>jLD?AYNaO;Bei3U%C;=VIDyth&oPVR|ZgDTp9bC z3h@*dhmd!V6hJSh4Cgpqz%Yv}JZ&=&lYpFN?@%TYO>Z)u@qOGDgMy(BRgIn61_v09 z6T2kxk9>75cHA`SfCAyGrpUlMgv~zuYS}hu;hpMicfuz*p=oX>J^d*d{@?5m$9dyS zm;t^Y`Q{*A`d}Yyp5?;O*`-BKPu{9Nf8!1jwsAmxWsI-cW-x|C1|zsUt6Q1{_KSxM zB~P^>4O@1QXs=qUOD`Tilve9KA`a{i*J!w(B#yZMQ-2J39SgZs7`m~?s;HNMZ@ec6 zEFYwE2;;-wgjvg;9YfnwS65ct^2?h9{KxnVgzIu!ZuGE)na|ND821@MiV!_R_8skH zY+MX?`K?M%R$jOnZ`rzj2gUn=0SxW4e%-y4Qdb|&H zDt44Y*eCqmFG6!I?-p3LJ?aEbLVifYmR7Ay9CuI)qYveRlzHd|A}S)g>TF6Ing``4 zE>>9_-*wPlumm(BBmqIchUlyb232xe3M?;R%o>wo$F|B%72&sdLkb5FyVKu#VaNf= zf-ki{32t_J=A0ER6V9}7X>&Fe$Yw-t?~&LRFR!l6HPTU3hZSme7BC?&Og0}IlQFb| zvnYC*&5Apd*V^THF{cnn(sTCE8J?f}CnU482 zRA#%xnI*;~W{0t~-Wu)mg^~lPW~;!Klmt-^>P&MFOXQJ<;BS$p=-uL9^KQeST!ajsz4v>sVoOfS zoakeuyBO#fgks1jLz>!|4MfyP@9^Q$`n1bJwn3c*2j2UA^rDE>bO}E=!PzWU#C#O; z3Za@>E%%0$w#bKhjqvitlgkFD;&eu+3@6-yT@I2~vH@U5{og!(1-q*N){Bl zmD5kRc1I?d4_Qcc?s#!?o!z(NWc9Cd&gY_~i-MA+_Jnv5q zo1UEE6!@3b19-;MJ=3&l2MH@#_#M@_MPzw2^UeCAI(ujuPnQcI??NDwMacieD$eL~ z@Nk~N(BzYJ3pD7$c#qB(t@lAE51e21@)vyh6yX@}wS?b3If&u(~{PIQ)`|2Kj^bgvwzNYAvi^0>?$EhVdbszEd)A8W zVvw?=^-4z?FdF%kQTlf6K6M)tn_@?kN$-UD&wJcjJpMxOWnLdjEY;FK_7P@`u5G)Kr5 zx#<#-(c=`HM;I?p;1BxPlCP`%<#OQtr0emCmtDdwZP@G|We;v9KGcj0ZVp$Z1O|^Q zBNrEXGoXJ!1Km5nO^8s#m|iU-F-Z1*7v?SI6G;#)$a0@&MxU4lkM38=^`k_@R$C@K zvnNxhnvJB4Y(P)^2jV%dB~vsHo>g-Tn;f33fVLea>TaGW1y!@fwF*6<7us-a)ik)p z4$ph}i|Z{OYs55>m1>vC%?UYWi;K`p6F@BDTuG~=8v^SCK1A}BvusD$eY*6=p!Q+5 zX44+lwAAaU#z8={g^y~=QKw-%m!b(#kdR?^2Rp6!5#<{2U{%=>F;oOh5E>@(w6)WV zSS7N%9PzO?y+wNd0lAO4z^ZlJCr66b7kalGQ;395CnCGjjN!F4l7dvg7SNd0LDP5c{pL^wd7K(WFbFg zUxl)S5A35AVnUbWi{)V@^{V(slSzAZ(3664zCaC$SZTJ}i|Tx>22}CP@#o^6q9*69 zBtuXm^YA&*DRRrAQP%19ejXw6kL$E;qB$w<-wHHa<<5Yvzop zlWP0ideD)*lqmk61JF=ZvQji5VV4}HXGzX8aX0-}|jdnvPXJVxBz ztdC@n&_!4gV9xrRvzR7Sh6|KPuc>`IAD;$?D9_%Yl zQe)j(W>(t}H^y$c*!+;Lso_aEy0$V|UwB=P*#$GR;2`DrQT_v(x_@}`=q}&(vYNU8 zx4fMA`qQ#IEDwo0f+wY9^(_B2fpyne6DN1vs77tmxa{?Yke4VHbvjudo1 z98lLH%tXHPr)?%x=00GB0}d{tVr@_}+ULh~6<%9pM376Mpha*Mb{E6Xyzr99skk04 zA6RWVMGQS06!xnhopp{&s2QXvz2L;-EmqmiH>%u`Cg6})W(p2#NjD0fez2^Skc$~K zjC%gDrsDi}ywu}187(Aao6__8(-BZW-9@|dItpt06K81JWxGjW%H4TcgU;~toBhF( z+2~_-`bvQ6uc>wb!G&c23Bww~f&FvWc!AFzxwpi?o^ZsQQYWL=W(DC%IeG7rY?Y8O z%E26m;?gD)ydbtNL@I>0Xq%?+qCbR1xh-{{Dq*8FhuwkMad>Bdn=owDOC^x@vqj7o zIAlDNW{PytD4*Xf5I!)Y=ol$^&fA}S7C1B3WMhZLCLx3N18axjviM3_Y&lz!Akh66 zU!~E}MRO_rb&}J1Y;C|8L3`egEbt;Psh*k^l?6@<5lA19)fFm%*ySGz5xtN1J=5_d zpi@TpJ?nOS{1_{}wklyI5HI}dw6MN6#(!5z;;x&{$oUVo_&W}7^sO520M(x(k232! zY_G_tky>Q4k8&;$5CM48p25Ys46+i zp!VuhgWc5O3Cd()!D&ai9)hc*G+6=^pCw8mPfhcR!AAPc5sbJSyxL7J3p2jEj(5<5aUUpHdLQU4|UX8*Fox*axX^bp)dPmf*Febr~ zU0zW?EkkI&H0xM2V!x_=3 zUsXK(^Q(KSa4{K9Z|oo_uAU034i$_1!puj&E@&KG9-*N9DCM_WoV~n}B*Au$L7Tmp zv2*bwy$R>Nukgd9gF9??-#-lui=&cq1aBJTvX6$Z|DP;CqLKKzv$%%5y;MIH?WD4; zI83C!&dGX8Y*tbJdC4Z{t*~7xth(J3Q$Hpti(X!KCJT8udsko9r+D2gqriQ8X-?gK zdY{;T6S&)MBzs+Q@JxCX#5Qwsc)RB*Ry9rvVm#$hcKvRz@trVE5l;8N64Bk`NSwm~ z4V+mw=5@}`8lF|&%^`nyhCaBtzqP-^44XwLBHNv%LJe>h+qU!pBBQ4VOQa=L&WdWp zfgJN%4s}@7{?Nlo#_;~6J6)V0l}Aek$(XsWdx0@p1^pT=6@1`=y_{0V6NvrbMm`rV zxbsyw?|Co_jfVyD^{*L6ePI<#3&1}uV8^H==R1pR#jni|qF*E)f5KK{I4jYSP4F~715L(Nq#g$*avyvk-@_y*X$>>d({pTr0kW|M<8EKCHFWO3>RtE%nJ z4_BiKiP$J*iHIu9Zl>Im5uG45|BL=q>$?Qr8_XA;FZmRa6NI>eNbB&ljsCMY)OOPf z&I6{J62Wiv94j_OC3YY2n3+BCR`b>U3`OgFBx4?Qu72bdblePzx_$d{>{mPNgS81H zcyi!@jP|5->iPs zdtUSZh7~_zDjBD(GRX)Fs>ns|+7*$+CR##O{_1GYe0Jr)Z@5zFu8^HmJ639${lI5c z(m$&hs2N+7sUpX%mDtwa}p0a7UWuMG*bQ*O#X!Z_pqigbS{&?9|NDKC2zg7gGYcEELyGf|z3jADxVZBDj!dJ>)i|sQUw7$I#-ktC{Z)JK7woO||UVAZ_J=AU2D`zINRX zA#RwkpYj%i4l_9~gYdCq4tDnt$#|S^Mueq^tfR0inv)l-vu$>#5}- zF~A!Cyu zzgDwHGl&>ZN0YWncwi+x+a)l8PP*I0b{9BM{oCpn5RD>7v+bt8SyDXWdtBX+w=#dq zp9ZlGWV5NZBs7wyA+46c>Y*5hH8VwJ`)BRNkt)~%cs@w{$PLeXGiH|D|` zKfFQ$=>Rff&_Ff4aO#Dc`z|n_wnsBJ>$jYKw6wKCwGgg@Zu9wWoGE7M8K!h?`s(AN4Ll$SfW? zuFshSm~N7@%)GiJn+`up-qnJOGf(qT7F?XJ6(aRecUmN5_Bqkwai9mlL^52n$Lj_* zHg&?i&KR&$=-D{3f6r-CNN8o|<5IL1W@9-_?Q7sEK28p2{;}cGs$+7)3VZ)26&*w? z2Qtu};8Ib6-rW{Y&?w&hCqLurW-}E`RblHtS3Op(^fg*Djn&iKcdZ`RR}1Uylhdc5 zS)=Y6llqW6_Wkv90g8xmbw^Aq-nJy(vz@wX))0?s+>h(w^5=ccE!oz0-%*a+pH}>o z`{vzc7l0s@&L{$YQZ<6Sq%`@T&GKu4)C?7!vvP8+NJ|Upf^~Uk*AE%YAuDoEPA?QU z&pM;Gm2j>wRVfX8u8ZoahqyOrkAr$Lyo}|$D7&6g9vw?Rd=V^NU0goq zz)2|Wz8#3Vc{`kg2Y4I**vq;4BG}bQ*|qUe;$fQtVq1vDPU2^`tHF z≶LjCmdF<*~6I_2^8MeZ;Y_tMy7mTrcW!|MkwIm-FRSGzzA@&+LY=rQtQ$V!CKG zgIIbKQKlaxF=o1qa5-`C+?0?3|7Nr4lFnbJ-=i#jU3tAq768>H{ar2w2ImLz-#Z26 zdhv<2sW1Ec*@)!lXA4$kmA|J~j%~+XvU3B8?hM!=L)k9C0V3=p7`Ry6mT={vuLNFe zB7*a{wHW@)mifx=;K#l2CJwS1Y z?6<7f>OiF^)Qxz4dlp-E2~hgRKnm9!E7(Z;7Vrac~f@A2Vu>a*1uidNIJ#2im8MWZnM2=R-nQ@ zP~#XGhe_awU0gJ4(kRM(XMKf}kKNDFW|zOO0M-ZknRcyi)(iO@$n!X$acde@q;&nz z?1jYz)*MtN*%eyMXcz^pe4| z^FX`L0Dq(CnLuY!igzbK9K!6}M=px^D%VS5O8$WHRVt98Txbv%m?XZc^c_IN@Sl%0 zLHtCcg!#sm3zcL1u@?NwP~a+?;K0!6H9=8GL?*^LrtH#Km(x+=W&PLj#s0T`j#j(x z`u3N$u!UBtJ)ULKPT3i%8n;KP;f;36?Cm4BH=EH~)msO8blxD~U;9#LILvu@DD09H zw9(&xpCrLwnc3cEji{7cC|2i8UU4-aNeYJNJ)mq|_m-M5HlaR^&&CHTs5>CCv6e6M z(vj(H6)+t{5=AwoPRy9uoTq5MGFZt{uEB33&Bq-Z~z>qflQiZ2RE58_L1>7(o5ubr-<9?7sz9s0}KcD}&AiT!y4-OCbJ z#dRuT1zP%69DM-L_0O(&F5KqPDIOqn9ZZ+@6<^2v%4<~l15Z#w-IiB>Qs5p1hzEg2 zejSYyuT<6lhLKBuh#JjVdY6P#^7?b_d<`e-v{r<3UO@=d>8*Neo9^{f1@bU8u*^lw z=e)ud)RmNa(N0Sp$YR{FLrnd`1&*A0N58)apUU_8?PPVnCHH21i{z#b%%#@mu@%0Z z#bndb6St-=(<3Z&ey&C2rFAl#`z4MyF%jyk4i}vAriyoU<1#U~w%nm$%{Cu4EX;G} zPg1U>jYj$+K^k_l3ZAN8EkyxI?yyMd+v~m4o}7C%u;*GibqueWIx$N~qCsg}b$Rh* zfMZ(o1Jn0n22useX2MBLcLv01u%h|6^6`<@tevVWbV5xz8?MW5`=IZDwN?kdX+6+S zT(;K?6DH+>$i z{zee8$rt<@N!|~e$_?u!d=kpqX>(+c`nf=#i(&wF**gpe({sjxxZQC)04 z6yS~e?JYPV7S{klcH6wwdiE!~dCnVq#3ahySxQ;NNI;9|POXAf`KMTn^uoV9Wd@#? z^E0K|g{<7;!^Q8x8L?q~D_>7Cp*tihqv`q@f}=|Gx@YI%`w9*{0C(l?KZ8=8Y>!<9 zOaJL(_4!bzVDlG(;a!@Kf=Ezw=H^y0XWP(uJGWBD(P!Oiq8VzIO- z%g`9tG_Igr+SbE%en*4N5P5sEq{e@8OtOa4hwOW19p5`x*T3V01Vf!^>H9emn!s6& zjM0DBJx`FA-lUaTN}v8hxkA6i3LA&vNtMNqwCwcO^?!x{Up$Zup61U`8I3>8^vBz% zsg9SsE3X^5+-3rixuSEG;IzWKp^-s36WTSA=y2)ItLXXKa$S+AUX0k;0VGEwZB2Nw zf#hs%ph7S1Ni_{PQvD2ODX{R*;FrK!goJW!JF>8G>xuaxiY%DNHh91fmj81mYrmZF z_nU@F&8A_T`oWRBKdlrIqsP7V#JcVFX=YDCo@aID*c+4?CCdg zu@JMHo1G=!lMRLVLvKP2klB4TymT!r$Afoi#%xQeQVE3C0>X8J2=)~a2 zGZ}h+p@ zM>@HP#(3S!Cx?Q%veNW;?5FT39L~ZLNUrei6XqjBwC*g=WBltXtzLKDp(}RwRkSMI z3_y${25T3UW@LG@??z5DvF&SA4qTM8$*n=HP4$A^!+wHnIj{vkciWvX6cxF;!?1ZN zzrYKFOeQyYBir>{r(j17$sPWLJYs@}8fI2Yx?`l-S1a82N}GA((4Soj(~ z%!0s^2GY_WZ}Q=O>v%t{Pb8I3BXfO%QpXF9#5ZRzI006%z64^Bhp4w9i<#-Lh~EZj z9LG*+m+xaz^u;AUafNuC#KO3WKh$M{>Rc(owU zfFJlj_PJ9ft_}nZd^*ri=aJ~L#Hk25?fIug{9I!`^mxGcGCe(TqNGuPrE?j9#P!~q ztpycqp#jT#(H}>{hO-hoL9mWOZ~t^?0J9WXaPdD02{GSRObDQdg}cKokbwrmE-&*? zEb`SKlr9Ppz8#3QX^W^n%g}3^_o3TJ+oJ!dR>jBfLOFIR-}lK`8K(5zcRzq*`um!ZeX#zuCJjaN%i#>VDTrAc^~6CmWj zZCC|!g^1z&U45f|U^Y;uGZI)iG-|t-nBG7n>MZ)ASLCmiyZ|cA! z8rK*}K04w;=>3(PwDk=a!0JWMliE52;!W<*>8`Q+wm_(!^P3Iw-)8FEiBHd{%@}Lf z`3hQ;r7}wE|5JV3+GYF_SzwhVS{4(MH4z+vN9I_!)xEOgC&z*AX70sBq6h=&gys5} z$PH%tiX!^8;^kx}SpFbR-kLS8tUL@r-C`UBRh0)3xBC%vp=G-~;>yKbKNvKWFj^kT5k$tBr+VVhjk4;@X|3j#J_`3s2dQW zFeO>YZ^ojUE1rkmx}!T_q|=>*$_G@UoO&h7yQ=DOSC(&C!5r)7)#H`Xe$uU~%?$d4 zx`^0;stL778cva`r`DN%ZtcKGJZ+AdJ1j-bi_ZYa?DcCBEHE+}dw46a$jWc(TuTsx zw}AC?RQ5teCaUln-|Gfd){uQ&a82n1Y^UGhwLxNczissCDs zD08E;FwKm?ZpeWWElIHku1nCTmnm51`Wd{DbGMp#zU7Vot?qJO-@PX-h1CgujV*wJ z&J75UB_%eiK7Vd6lkp?)gBl7z{RH98D%)jK3N*bDcTWUCLH|Hc@tNRj*_8AqvyPij zZ3^rFMxl$`hzLLc5VlB}o(tXgAE+(Y`6;WV^T?8qI`lOk1^3hN3(3P5HX0?>if%0l zAwKa&X#fB_P4WyM{C4PQhj4;hDN*^303w`So3!zIxL~`^)~DuTKKKWDCLpS*p!$x! z%b!HqBmsrfz3ZVRq3*GSuU~kzZDZwywGUnf>1&9DuNizQ*g>MKMsz;!b($5h5kELY z6{~|tN=<(gc3&8P#R^qi*bEYbYFO%M!UH&u+&}Vl!FJHVq6ga!5sgoGdSaNMW7n7b z<mfvupgCIX0`F+g*0ODRO*4bHvEvOh zabV8up1yO5R<;Y`N4$FKaeNOYP`CRwM~0u@Kz9O0toAnneGUq2q<1i8o2X$GIS*TB zFFroQ8}|ClR@w==-Q<9u(x7*qQs3a3oA`V|;7ANO-MvNdA-E1)@dm3zGiS;SPE^ zG!S};j7;55UtDpsJoj2k^G~u*N`gt|u1HCgDNAF`04w1v8ra^Fohy5X8AN7#5rc#Y z*+>7dLM13iW%2LS-$2XzUkyD<*xRCylmt-~Bwn|Qo!66G^%^vWZ`Kg?zlJ&LXS=R3 z*BA$U*x^N6!`I#A^o-f9|FN)}`hv8+7)a2ApPARM&2{Qjm5fr|$$n(!_g?1kKA7yKhAOHwB(9*LHaZvtM z_oaZDQvXBpqpaO*7*@(vmB8fHOCr-CI*vYQAeIwk~0hyLd# zZNub@4742Sa%AEYUKo9zPV{5l`Xw=lc*(?ujE7Db z37a}n1Ubp-{;33doQ6q6%9xSm(N}1C@qmL>H*sUp42nOdoxveHDG6b%=gV#nRfra? z|EZXuiGTx4-?_+P!OX0s&;{0(_H37=*#A!U7`ss_+nue8iq~K~o?VtJlSwzdFr1gB zYYAt1t>?PfNDP1smj|`bTnt6OfG%rtKVakbeg@ z+yDg(s(g9n;+4(m{kdRyB^rSu6RCXhsTz!ErZ9D?>>ZWvi1uj0(@$^s zs-wa-zoH5r2A|}~zC*D7Jae`xm{8YN+Fs87N2yK2aYGgnqG>W&ybO? zZo$7&*5C~9l-k!TYo{x6LL3JFaLtqi!>q4*n18qv?c0p4Gar#TSB}HSg`` z4p@8{2q{`TWflX(q*67^dw)n;c?eLPW~3%^#ABz^7r_$gj^^T6m=yU-vTwgo(V!?x z!>3kb+$!z9q50Fw3K=@Ek{0=;uTnL7L7zil(-zkjR&xoTEmP=YIh{SB76AvjZm>Z{ zU=AnT$q$_5P+OGPc~|)Pm3@;YU&m{Ov<(nb3sZm;{!sVJ-hXNAF&3#kZE=DnYOs@g zbMUTzTWjp%WV^nRVjq;AvvNlbVq=XCG@yg@UYX%*&PXu`Ov@L&N#EFdys$dgYA*)U z4c+@IyUc`M34E2@CFyUYmzUas8xDl3xeXfPE?rM>jTSQ;YvP(Ko_*2t9dC*3!+0j^ zl82|0*kM86R7EXMmZfX8x4jz)t}IiWIrsgS6Pw0JiCj%3&?o?om~gHr;idb)SgGW= zm&>*kW_5&7Nf}4mFE>oB)*AEoN7;+vE&^Cn} z0{0-2XP8#hP@{hX!LWao+vzAjB8l$qK)^jFUIw07@JgXKlVYD8osxTLJk z8ze;!fjT}IYsZMOw}^cHp?YE`7uFG9U{T^u>pS7FFJkTg9YP_Sg6R(;f1l8A^Oc(F zL6wBLunA!t#om{{Q~FhA;TL~MsH}PLXLf?S65dn^f+I~}f!HE5iac;mxKGU5yr;A= z`l#|mCn7-1j0(ae}lsBc&SNspA@XqiVHj1z;`4<+c`Yf_vi=#`M*z>+$bQ(?P1f%# zkFr=7Oh#*aEIId9R>~J zlwo?vq^84e8$-zSsYbI0-aT9hW2Z`nqtLj%0&(@~>Ya zTiHa2RM;?ipM5b$og98q>e;L3 zo1L9aDRogfJI*O@`kZoSL;8ZC<^e@p7P2@h96XKKps+w<`|Qq*OdTDnY>W$3Zzt`_ zxBWgOu68zL^U&qm_gZhf`!*uNy+$^s#_|CJ<)WboF@G3!LFW*Eo+7NolsG;v;iiBN zS`>;1&?`w0<;m&a$R$)?p$Eh&MI7b9{h>so;a%tnBq(8FmXo62*<_K;%wc1-#L&@n z1~OT)CVr^fg&5Cnf^bRSr4OJpof#-V(CCT#!}0xo-eV|nDEK!1uKp#tu31r3*Pc6} z0n>}f?>6^z952R5Sz;VIk04<0UadUQ&BNni$2!V zY8(EaEI{;4J{|N6Y81|zmc~YiP(}_!uAtvt{l^-dzvzClyq+q17Hn-qXqQukut&82 z)|mfQyKFf-rU|Ln4sfryGent&IsB8|FkbpP&C~pDxtOtE1UJT~&nW7Kn6U4Rl#;rl zD9#k|KIYQR^6R#x8oxTDi4orCbKPfmVDwiMA%Ob5bJjf|K+TMeS_Rjc7YZPVPh!{sA4@(i|?F&{SC zYa+U|)@LL$)3WRDPo|^BlSx*-^P%Lqw%(wfwiFveY~R28T!q;Esla8E9L_C_-uwLr zqqo1EL+4;*SdZ3s!VVP3b>)hSqzQ2w)5b9olXROcGe{vcQp?c^ zU-GgGiIxpn1BMkNPVzF4v2VkAi^Ao}_c`Cdm8b)9kI>G4hJL}RG+de^NrJAY0sp&j z?4K)*mi5~kE7f8%0k` zLQrAIZxzxQyD4(=koGuE!bjO6g18d%=S+BvPKtDVTI4{XKs6}%EcQKNT$tktBmz%0 zMDkl77;9& z^qbaXc}eD-)X1oiI@A#S9xZ!7 z8EO_W1yi2)hv}1b>u+&KR;&Ps!*kA_G3LxdZfZkAAjHKw^$=-qAd;{Yq8s7;1w<_7 ztm3(CHIE^+>2+s*y{$S0Y#AddK2hB_%WtU5U4Nb+Swg_f4mk&@Y5 z$x0wV7CyjmYUDE*^P%1%eong$kviP2zdylIz{FCN+X@kJj!i^ndGixbVH8*@{|()Q zUTwjj4|`DBilR+^Woa!An=jU$);r~_Yp(wnOtqTBH7;HRKaXX{y}YfyPZ!tBo$U?K4P7K7i+ol z`kzow3rjn#T$v5H&6sQ}b3I%P&hb!dmjUF??FVzaUuIzV~)W$qe#Di}3r&|26I!=KY0) z6Ezy@wEUfl+|kFt1-w)VgwsM;5bZ%`Ttd7=D+|6{OgqiUn4C8o+FrUstOpxBGN^en z(>Gw)AEOz1)rA=(FytEd6FevsIr4qorKs3n6ee(%!{Q?+DjNj%jkb6Og|UFypcV}&$KPjLAD4cGIYcG{~leObLzWyb-Et4%j!*4+qOm&{?ufPu%1{Vn|~B1 zo%W_eq+mIB%J1^Kh5Yj7Z*HRGDq!doRzH0Di7;-c0s1rdUxtRY7Fn4hTK!R%fx}lc zRc9YAtaux;7FJAji(P3zp7PNAVk9ys+O6h19g;KPe*%+&25vsxgck{sdSt|SH2P2k zxGmytLEcB!m`UIi$jvgF$g>WX@#ma=0+RpI-v~Y4oxqZAlikfa5%M&gR~RA*OzS(7 z9;h9nL>P~Y_TsH9^rMM9@Z~5F)^hS>Vmu-ep7{DvtGx26%&8gr;Xx|dZ_aLL_QtNH~qZ&j{e zz&5w6W4IYOMT<_GXeHfY;qjQhG8)K& ze>$A;M0=S8MV~s?eJI}HiM!&GUwoSPs9tq{yL5ZYxHdD!7>!2RKt9(65$G41iLmBd zwJ^8wFey9Hr6cy;yAt_mz;^O^9F}zEl~c}@!Z}G6|2{$&$f?Va3!7nW0(ZEZ;9@bE z*q$8D51g$g!xd6JlhJaW4kKAVyBBd!YQ!upDWiaBEk8y?JJm!w=WvjDOFoZC`J`Kb zBT-pt@Dj64XMr5axMxYfk10B37rnpNP+Wuv7)lFD+S}7UCCi^`=X$pkc4zjZRU1@% z=7TMR1hR&K5Y@f5fFH=4HXX5wIfl%P@21Ps4uv6RyNkYco9; zIM?^kyncXlPJXG!XDPE%Q(nWY#?!R7De-y1^@sVNnS|#ate$jit^E26cFFwTjGTqp zj@$7A1Tew+RzEg-@LC-;Mo>P)i;0b7lwbdqF7$yEksuB1l|7mAW*kJxd2O{F+K5gF1XciKgq&yDRlWwMp(^wN$7~Dv?;&rc5mks zSakMm-b&`>oOx;9G%aT4c^{T5J8pLS#X#-ilGP{~P7ONBy}bsCMFa0(4amih z>N^rnV!IIL5y)j9F1b+(%~7UgZk(m%;rTly>Ze^XAk-3wrEo;H`ZFvCSggoK(@UNd z6un*qglCjkJ4i_ZlEuwoXwe+^s#w3-a`x?IP_HvpiN$ADrg1PUsCPr;wtjT)$F(XQ z8kg*d6wd47j`@oFgmn0=1P&hMO6-=n@C*Pu!fOX9QQY=>y7l8HiI4ZWnl`Ij+8YSR z1O`s*U)C#YyS>TR>^NzV=Bsodjm~Gi?`0q0106xWxEA41gHJTOh`R+zDWsZXJTP$$ z4cToBglg`?SmS`7hiz{XzUa4WoB%G;O|!zg+ZE2M5m5`vl3|~$-INcZ|CFhtnyt8E zye_PF>}D=aK{MvB2tjR;sKFt*p(MnUoDFPot~cN z<$N2bi?;pxm92RqcGseRC^i-tz5sjq9W~{>$_^&q1s&#*U$%hMjPhk0RCpLia&v`I zcbo+xY|4dBX;piP$=yDmMaz+BBJ{GYI@n3v7cI1{UInUzmm{&?o<-AZ@;7R5tAEO6 zbg1llI_>1c%&IA8po>cJhc~H)GLDE{a#yC+p6YpzRA7;chjmig9330dZEn=r`cD3- zzx^F!z0D|UWg!;qqqYF2l#)?6i_(n*&r{VmBJ%h6FUs8W%W|F0Z&wVfdjnNE>87S8 zyqoLmwZBiVb$N|2=YGPz_vhOICnqN_{|dyzySZKd{;pNbU>mA7cl(A`ZUx6rK3l*0 z9U(vi<`p&gNOWql(plYR3bA1Nr;VyuR4RLdDGF=gm4YB@ZGkz<@e`eMU$2lijox;NIka*%g zDr(G_N@6zpebo0y+>~YlXqnnQqB$QZrYBXY|a=Sv2o#2@xZC%YW92q&GGd_$=%$b8vSbs z)yRnZM#K_b_FmctO!0wIJjjrg!4VwknbH6wkQTYEbWlY9HKwl0eJduA_8}m(+OMme zN9*W-IqN$t%YMUv^cQPHEuZ<TC8snmbTz0ho4iDR-i8g+w1qB6-0ecs3 zxAbc}vNX@X9sBfUdoSPXXz{6(xx+DfVJr-E3cF;^2N_cdPT@MdO<|r(fT(%>#~moba7mlRIm7dOpGLi0chjG!HbicMJ3Cn{6`%@3e!j%?=~1YZ&?d7g-4 zf9&}eeHYiBUfFnxhb4?xrxq&#Q!qU}=c%WHCd#9}%WDH6g}-;Mh+-Nm`yFPV2S*_* z(%crDWX{l`^nr#dMGXxnQet1mNFLkI&o5wRe!l7CVuZBkE>+to1LjW1`J2jQ6q0wv z?d=;9XZMeHM-T)?1t`o*AuNoNB9vAAClfUv5KQO@rJ!^(zLC`kyHt3Co)FyS+q0`u zF^gcgA#u^WzkU~;HnUw%hw>v)qg@HR90y!6!G5{t@l77waqT0iGOC!O4AFA1-^tP& z{ANno>c#6k1&DzxP$m63ezlxkF+i6XgU6-(aU1Q@kz0wzr`1d}Zn@D0657|-r!Vb8 zw0SqMzP_Hf+~IL1KP_W|rC?l|+X;o*Gmp%pyXHN@Ui5+Mz`obn<74h9mG z3;eZ40VB7@`|Rp=svCdx9F#z&&H`GAy=E11LsdFS)WS0C+5T8y=so&sFY>-53~j)l z(mN+&sjW#smONL1dcR47h`FlW2VSP6s=%549iBD~Jzj23yggsJ zIr+6_8;H*?j`_&EZn&l>pbBJ!?AmA8M<61~G%s**Q7c%n%1OY6je!+7i1(2x$?NSW zh6UZ5HIZ7@awjKb{itYSxVG;C7r?bH$%B1}o|P*1akzHYyAo{g9$iyGSR3r2N;7@)_J!3}ehL!F3A z#sC1jIG4lPeK+t4P#CF0LPDxsU0Hd(sv9AB+D%p#;$ObLeA$SBBdY zv!fH?ZhJ$uR@K}FgXxCY!#-qH>=s9XtCQtUg)R8PfvHlUS%?TH^`iELtGVxltyfcS6%uJ9{c1dXCJFUio{ZQV?QNAaa+Ihn|N$|ZZg zaGSdR4YdM|zWSFKgf;oY0f5+v0gef{HrQg^@_i}t3;3IV$?V^Ut7uK0#=t@2z75?a zXY#y_hn=a(a)C0iPnE$dA_z9VcR3S!@<09j1=5fS;9+Iv+!vQ zNTN`#voC?~^OIa5ykR!y@@cJ^Vjw;+l<2b}`&C_bXGMwebz7V+kC?VTYhg+QRzgL- zkHAKdTR0df%SD@h;g`fRj3IZ##3jct}6HRBSxnfAZ zij&>GJa3Bi(paWy3#K0KCap35@>&SN^%)FrHn=Ijw$oBEuFY4KO`L5c+LRSG>rpv1 z9-r%uN67__*h}4|OTcYe@_H4&P4@QkFH`Zd>gsBtM41mZ|KzZNl4eCB;CbGHghAxF z2V_iM!XHe_PT9$&E3(I&IXgOHz{L%Abk|WVfo=zMyK|m`o24Gn5udl{U^6WECRBZq zZxymyS62{wmZfm1(HL!qF+@-@QN7Oyf|i1Qmx^OJD0w&V=j`i&1oWeh>NRp|RPHLdpiXq5x!1SEpmA zDJwgcq?pC^0s*=fkMyY&WxXGaw3r-Lk)B#cpJSnd*R!GX@j7>NLlAP(+-8Smq#&*D zl$^?6C2{dt(zBAxi@|-N$d}z^9Dg{;HQ1DsN|>J)}*<^KA{1 zW&nNeg7g4vW7n`-@=z*+{f2H97ok~v;&Bj#N~A+KRVa1AO4{DFys$B-9JOX) zqA@~%udOj|wDa@|SG11P`tOuFnj(=33E(ye*wIauU(Jo^Ps9#DP1zA)QJVxj44w+ zSyM(zTA}rd3=2wxZ?x#slNxdvI&Gf~OjoVm_2TzVO`=MDeJYGDlvJmF;pY^lS)NIM zPyxex7_BX(FXT$+_J8lCv1hO&kpw;bIVG&U*BCmnCHYKDt!-!ANPFnDu}F3)3v4JI z-Jg~zqpJp?Dy5gc^8I`ERD~QWN<6^xLUg3N5b>K=S6S#`Z~wxxbJ#U5xuEF>(BDL_ zuCD6m`Cg9IHZ?cLg=l&x@J7&*ec%L)#MetYt@ExC=BCI9@F%NAdSgfa6awi{;zQzz z4I+fo!rj(`ciU*?zO!D|CK1Q3`Fl{TY zKTK!;cv_H=ABmPcZ@XUgx3eLaM3H45n#V`Uc({K4tL4M9-~#k-*OwD4@h3+|`j&hn zP5{8!0XosmwYjv16ilQ`E7y8W2USK=0*&@WvH#wpCZR z@lr`rVdaMHD!Os=geSlNMhB3jZp)`s&v#GR7alM4f5i1n6q`!ZiN3?r^R1*clN_3C|bTs&f zr>CzD6p6I#Ip#&!k%QMENSIvLx3@wMz`qUv>D~_gcn8l&X{rc!_w$&Qc*`>hnR>Q6 zD}I3Nwy46i5_u{(Y{HV$RgSREk}1kfk}42Rul%G$eGk>hO;ZN9lFV%0ii7;6a7J(W zPr__Yv%-?!O|khd!dR_!pLRn@K0F5OvzPARYIZqR*kz?cETY?!oUbo%WbOP+ zESvrgrD=kwp{^gyGehpm$9mTDm5wfPb z&)ZV=YAj?-5CE)_Ty{%fVB5L}?)>sCH)8B@IL_(;raK@2~rD|e@3!I_C zQs{25HBDciJPTkOD>Pb>MdObGJz-qb7x{|>b8D8xa^4}sHl z@#piJ1d5`Bi;Igcz}Hpl9c42&51Q2x03d$4+PLR6ZCd#485;Pdd$MsQurEUGtxB;sa+JfrNx(Y6`p~zfd7S|dmx}!LDLZcx0lys0& zFvaoL*>~2G$9Xx(#2myHI){=c-P_||r?Z|l6}dp)Dxfk0Bd>vFwXa&{9wpMbk0GS! z-U-5@l$Gw!>LNL~scW|@O=-G4Kl4x~(T>KMjlT^Evl0o2ZV5oVq?FvrDY*@^|C&{X zhI|vDpXD7!K7nsIT64Wj10-)GUVsf6K|(}4*27Pv*0Y74oxM5v_&lM-3%}kA>bE%L zMA`YYo2dj8@mvq%&@rGK>;GsmH*1Vam;jJHDompUrKF<1q=fn;tW$QzXG`RKx;|rR}NReQd#YhDx7O;Bu>KztvAu&7P<443&yVb&_2XE6xK}UyI82sH<ns=f;gbAMbY2t&9%;Ob4pOu-p;m8+%Vg@5fh0j7LFmq~- zcr4IyiH+d9<{D@IFL>0vDK<7zgGd3BvQ(63quEFw)(++7yZWW$Z_EEGbck8aFRy10 z$;GX1Pc2-+dIS_S$G~N{!Xeiv9PC6xZljA07~w{Nd?VSNWZlM&)jA@rEhd10DYE$V zJ+Y#zqYhIp7qM;8A`UAG^`cG$0iZPZF7M_S<&8EL?Oys*i{%twvFR8<4?dvc682bF z1kX^}D!IFQZjgvy*KDr_xEXJI2jrpL{`P9Olr!UOpgQJ0IXU^|<>e(>;ox&@5l|p= z>ueWmZvH#HEJmK&VUZ>5%k-)u@%VRz*nMr)+-oL7*j2=f#zJXw-BPQ~OT>EE<+n zh7}m)Vr+Q0M~}+L4`07fCe8e90lMvj(%)gZ4oOJN3D)m0j-@YF4qIvx z1^0=oEIEE5RU6?&)Yhm^kC4&VUww&`=wB&T$iRmWS_F=?2f*&wc^#IQ;tSu*so$=L z65L9?x3rzMhaG}x7dGRQo=8GSl^gRw_|-rfa?Z`kuuxQG!(3h}Zepk!S!m?d+Q4q# z(QXl)V1hB!Ph2UH%SlY0);aSwR72Gph`1jJC195>>-jtfv|10Rg!s<`Q$uNQLAe6d z3G(4w>c7qf2r!Z;7o#uFYIQFH@#OXC9c`Ctdq+T)U*7Ox#K6B{=HoL<@dH|*nYMCv zS*clSZf+a5l4;W`UIW3Jg|Bc|wcRrU3NUG3tszNQE6)7#A1!1?X6=T&4x@7J;Y1$eJ^T z@2>z0>kQ#ll1N6&tEgmG16RFQom@0>|46q93(TorxTt`yI7sEzFC^$D0k?~~`|r7P zyjH4D>T67Clz)+#vQ1^QHROsxJe$B6zKoc2^bGvX&EXIg+PHDg_V^(Hf?mGJTkSK2 z3Y0fJ<0g3VlrOj3-YPw^CxMrl_Ao7(d)_!9&Ku*Hb1b0c)Oq$#5&~Gd1|LYCPo}8S z{H)J_tlhHb!_f>l2HI`Mo*`MKc*bX5m-5yIrdPv^0~Bmzd+mif*D#ERnRQ5t76Pb! z$AHLQ-zepy)9DW3B|5P9@`O8<-Q;!kcLz{1I+DhUG$E|^kj+hFb{?MlEKh;g@TsXO zmWS>)H2{YgvCK!%nERVH@>p)#y|wHZD`n_i#)EBs|3j#XFlhJe<0W$}JU2Ik;y4ym z)#}vUdabo_QH|K@`aXQXh78qzNrYeZJK29pZVf*=>}-R`DdCXW&94(-!RrY+@<}O? zvwSArKKaK!>Z=MZZ*T99OpY$`MumWCzo9$Th!}&)u4vVe(K)rp=qTXeUn}QWUW@IA zvNBWO>AN#`KevE+iFE@4dhY@c`#d9r9lB4W@=}akCLuUy-5t}e6^UQlOKOV941C*5 zOMJ=lyCo#15b`%3_~L&#K@TpTl@$;?G}e6_rF@VdDt;|}sy@8o)$kpD%~eULUp|aW zcF=t-PV5jX_#^G(?Y&{oC(ZRoud=45rYCa9?7fDT)FeVa{Ux{bj&;?-BWB+OO-Yco8)Y2l@SX;nn7^*B4)pr%naAWts!JVk|zq2{2 zTcrZ!R4T~Z#Udy~EcP}HwF01BL4-;E=Y9-mM3f22k7w^D{u~#f!1`(l zylUpX_i6N)NsX`A_$#c*RTZBR=TYzeU&|r<(cjUu=sqOW7qr(#k>rD!6=_AeH4D++ z*!%%4=prk_E*`e&Au`J@s=gQl-QW ziYW7CriBsl=o}%{=GnN7rj{ISD}0#GiDr5FI)CYw1>s$aBdkp?DEVQ4d1*|hh7x@y z1aGj36)&Kh(Qp3P%M;|cx(~ayEj!wsRf!%Azl}=D81m&zJ~+!hOEGa$51)~?SEszm zAq*cY+{u<6qsg={459RgQWjXoS`9XvSx%Ey2BFpDJDFFM+Se+?kzgVQiek_PEk)#) zv)9I@eK_Ncu1g_X4|QLs{gB$p9{;`&&^Pmt4_NWNP*CO2z$TPcljW@Q^)g&?%n>jk zx&qHhpu@4Xo-hBEKT_&VFVX62A36-hfUvYeo`$3U60tB+Bz5)t!B?#swt6BB_=DbO z%tKnUH;3#v7hE6%L!`ZZe5SdGDY7Dv$0sI8Zv-4epo}GxIgeLMQ&STiAnPUWtL?RhD6n=pa?~I#21&dF7Ax+UDROb z&`gJAO&r`}^xz&xqiEqAGDI%8I}! zEX>XHe##UCn2GqI@V5+c7@xja z{)3DL{63iT$ChVM%qJdEm}JWz+KLe&74RM!_%>DwgWfoXFG++I7ZInd=|sNJU5OK=_o-m~Y)kY zRjea^-N{v6Hu(1Ed$n_vf@e~6Ji}<(o&DM6Zz+Pf2HFi~^Jv4^wAtqR1$p_jeKrs0 zFO~3csUB4XGfm0n?}6v7pso(t0~%m|QQMq>P4aSWJ-rouHMMWHU%q@<*vyVwQTu8X zFhq`Z4NJ>Td@x7}d+zt~u!TR3Q3t=fz+w5iK18t9OZ;;oY;wfbUoRt{%^2&R+E-o4 z=rm;I=DR>sN`%WJGOVKJa7Dka;9KhGKkfVIxV{STr0hAL@KQVjU_aLvVyn%C{h?GXK{bG@~`I7d%YSB;mBvKQ-erl%A~(>ZSH@k&oU@Hk(4V+==HU zar3*Mg6>S|Uu`31?!+yAXW`rhZfA2h-zxDbl@xrxA*SqUJt+P$62rw&$TzZgK0=Y8 zPJ3S%+cTp9`S_{Z{o66Xpt~npusv`Ch-%?P%%p+alCBy^k^alB`1;6R$PzRXj4bqB zvQeVGR6$8$9qn=fp+ocWheO(2;jq_;AEK!2AcGEB52ct0I?<3Qng9#Fai@E}hx`LcW|(tk zi0-Tc8PEG*m&kK&)nP&YZAXEgU-xaq@-<78{UONUkbgY8F_FOZ0G9kP&hCv!Ku?X} zF8bRvf9m`qL+4|KUEbPz`nd2;&zXA}Fd7B=QACncL8u1S2L*~^cLS^`LT%djfg3g& zDuH_odtEr>vWXZ7zNP=1&PM@p$xmIC3t+@saW`yEFGqK#hAt;A7?Ka9o z)4e6T!z4i-|9)#9VHZ`Umm)?Pxuha6HK+^%qwrnk)~_ZpDB_vEYCgH#pO4Q!2A49l zv^~1ydX`96)yC3l(o^O$Ld}v%%)%@_EmY=?hoVf-3?)7JXy@a7?178qkRYaeCI&lE z>#8ywHJSx^Qp*z32L!mkcN%OL z1G0C5){R(h87x+|@*Xf#@Aa;!Aq3fTKy9cc;`;nqWpQy)+Xg7kSXeQ{{O&V=20QC7 zo}gG5M074_aWzee+Ghu9c#>F2ex#d{=Q%dCMgE)nnzO zhNvm>;oNa%W2&Eoi-aT<{TnVhwbfe|k58mY-u*@6l*U)1)?W%yDllyEC6s%dq`m4S zPPY4owk>AGU4AKX#m#lBbwC33Ctkb<2L_1y<9Fc&qJR}ZPJdU^ics=j?+mE-WAl=T zoOSzKYOGuix-rKE6Uka#G7-WSM9v+Y-%CqLCc6R#ZwVoPSsk1ItTVZ}xsHHE!hSf0 z;7UwdTKXrHs5$XZuTYW?>s-}poj%dt+2HQk4`xNb1{FMJdWu2oiC_ciLS=bqGgcQ5 za0M%MDOaLPJu-FZ($m&B5~LliK;lI%(46eFhV38`cgszM#m~WbbJ%WO^deHw?@8hu z9B2vA%|52F0FB$5Qne^|*yiA23WgoRLI^HDF1HCkR5W#%@(t`p$^8H)Rz5yHQfEvh zDvA}TsAy@q-*kFi#Z-?p&tLO@L_)BJ{bS1)?E*^)bE=u!&2P9$c+^`9%S%l>;{IwU zKN*F0fz_o}t_}jGR@EaMnCukqPL<_U-bEc{rM|cLeC~WQ><6b@RK4tiXaLy8L;u_n zg5yC%962LnW)>pnS(4j()sQnCw!gyrG(AB`L;U;1*pSF94`<)-;8KU#VnE=)L#XW(N`yIQ3uuo*d0K=hOsRbu44qa2FOkX5llV zqkVoC$nZ!&5v+vOT;fB-RQ5feXMNWKH}Pn-{T-%k0xL268y9dhm#~G4C{z)0W=(|d zYSyO0`Q<2vVI;rcPQd`hfk_s_*Jsw_lzl}UTHWmTnv4U|0yc9o^pu1-9PmdY^lzVA zd3$?P19st(a`jTP+vDX3sh=f3iL`l$NYZ(n&0emdkabAI?s@R}1SS7kSbh_7-|mUe z(~nXYFhXw5)Gj{=E2fN=r<{itp_qQ&E88^RmHrT@>xD)VLG4kab%nEpT_;4qu5@nV z7;xI|w@WLI$QEbfVK$;(yo+cJ*O(e3)I(fB7(WS-^*b2sAcV9JdbU!CUMq;K`Tdd& z1ypIU2O9x!3FK)VKz?wqeA_fOFoM6B-R_3+Yt=E#~e zV99=}2Pu&-3O=f|ps621bDA{f;3{ePOrLb5FP0^#W)o9ZmZFfa5ejS)sw`|c=DWe$ zVwd!IWA=b5+6)Q@my|UL?BE=+hx*h0fs);#ffZ-Gc;uTLkW}+;Zk8oyVnt)gt%_9qkdP-NKHG{plawUHc!?>D!%ujY=9j!nRzM<%-!hFC$t(S|ig z)YY^wX|B~G#wY>;LO7r*c5CYEXMN5Y`bycfqfF)`O!MRlb4ZBCu)tC5k`S^uml^>~ z)EQCasEiqY@+!YoLG1_Tb+GaVH`?yF+)G_je!b5X>&;vqRYFY+9aXY^nKbJ;+XjU4 zbf^sUmn&3FFoCEQc;i#s^7ND}5BxTh;QIPRiSJ!O<7pMI8g4G#snF5~RDMcEpZ~vb z#ID;F+g*Ns{`a=k)#)Na&N~tcnH)%_-G&JN$s-nS+Ti zAx6(IhbDm+wC?wdoYKe&n^!k%&j|LxGB?&o$1sM4}lMr6rr|znCM>;&x;{{r9g`%GQ49%;a)! zjFyQ>aAz8WR48Uj$>)(R3OOn|y0)sSs_`~@T#9e!4LleKC3@I~&#?{vfo_LqwhY`b zH=!W>xvztrO^M{^sBKP8&a0EL$LU@-I7qLBJx`VO&_tgP$rJD?d}58IFb5dvWKl}l zMzw7gtBtzTQcec{3}J4mqFheX1w_?LCJ@q^8qY!OT_AN`Zm@jik6J%;`82)t2}pFI z9@)_8(<0e7{ddFr(^gg_zY6mc<55vjzXEwz$b=8+Gl#s*R5q{lbH>6)MMWF?J2pEi zDysONr}eYf5Gc{I<$}j4zlc+D>-Y7)=r`*;WW6~@!(7i?pUk6>C7DM@b-d&P17VsM zgr3iTS<&N-Sb0)ZDM~W~V#mu%j?B^J7JErNU5E_A+KrY2nHg9>sUlu$b0#5A(K3yB zp8T2<)ww0YwY^`Bgs{CC3U?m3@Yt~YO$nJ7t*x2Rii;fXox{j@YR!QyhVy$~M8JSX z?)vXfL0I>hw1O2(AOX8&e%Zg#W`?J`GieVGJ)bm;=mRf*QPDmcfNs^oGUJq83?Ii$ z{J^0VR^)gxTl`zb*>P1fEr!Mb=b!7WNU8KXS5j7msO$GICrPRh+9l^(zq^}Kml!9z zUqnSf5rIR##(oY6IzQB9(CYch7ccu?nhteB2&*KoVUgRwK1TXQfqedui3hl*)0|;5 z(-D3Ry2d?IAxTDOLDS6vvM>-x5J`Z6PF~>v6sSTQy4+wzdSTUlX1?0oScCkb8L$~^ zo_S6C=>4}~f6QAICsJge_L_r8?vE}3#@_eqP!A3a%dZBSxNiJeMTBh=i^s%c@?A20 zJg8Phd`av?^}Hva%BwG2=Dy{weCysfad03^)uTp7Tf@ zMlAmef^n5E9L1iC!RH3Ev@D`*El<^fsj8=*! zR&lq{azfDXn&UQ%Rgmio?5s>6zGQ0j2cCuO(%%{?vjbX?_y zYF7E$SMkL!NO0qaHzQA84#z2 z=xbC|UZs?lh7ct$IA=GKUxZr(_MDJ-K1f4O^%hO};$#1=HPw7dky%6BS_K*GuCKWx z?^P%5DQYsD`AoT{C;rh>L>M|5wn?x)Hf7sBmHZY1u;xo>ES*mN0RcUD@+6j1*^y2> z&j)YRk$^r>*dtucME$)uKi}kOFcRl%i2np~+WGT84?5UKr+2fXWZmX^Scz8Ua74uE z%4So)g?pDk0)9}JfRww(+WNRA_whX9M^B=wBvtZg-}_sd<`S2P<~sAAf!wPnpHsfb z1V^BFTD2+p9yg}GtDWrf_^H3)2K(PQ$ym_qs^|uWSuYdjZ{ClCM<4s1EBFZ=$~!wu z1DapsB09VFJmU%Yy{Scr6hAs5)K>I2K>jX_7uY#N6a~h zeLNoQSwfV2gXV-%vz}euMv+_D9E0DKu*q2fAeX*C;v!qiBKL3k?3tUGA%QX-v?qzJ zedh{qOwzIgj1taM%BD0*E@F}hM1a)mG6Gayb$2$yreRrTeFgp=5&F$J|G z_=6*#G|3T}E8+Tq_!MAZME3?b-jbV}n=ZikVPZYf5*!>X{KhSNyxKBdVI(&+G^G3< z7xx)B^ahanwtbJnRz$E*Nzp#~1of<(W#)Lk(x+J7q4X~| zZ4zfk5U(RmZ(xMLyG!}`r$=GUhMl0QO~%rj@9kninTXDd(j~~A5p(u{TfQ7{%M;*6 z4+)(;`H#BB0lB84qT&-Z>wUMQCJR+@im>JH^rNq~wuZYSxLGa!UBbi48*ru}K1u0( zu;?Q9eiZkYRs326_J8GeG(eKn?0b9&HZ;MviJe?9Zw4R-{0v>2Go(UsT0B$D_7P{P z$cUh4ywNNPAp3o>&Gk^8k*=vPAlI7bI`;$1%T$HyJLDOD-+1!jxc3Xx);>8h4&j(S z{GAdw^DP+9X|rb-{B0C64uoyO1*~-UQoEZi6!1{stIIc9nx(=Uqt{uxJ97Mcu*AC22A><*f&OQ?A2&vZb($ zu8WG$pJQ;ccm%4dvSI>@_P?z%i+aWwy*}ngL4@Mi*(fFVI2CJP&#w;o7rPz!)TXR~ zNzEW*j?G^tX@!ilTg8E`%jC_UP!cyoyuT>CdoI6?lY+~NDN<%bj=vFTAuKgTax2{Y zZ~a;2DdZQPz8hOx`4{^ba&&WZb40B2k(8NPS?@~t)M#-J^FEvQH=W9wdQU!X_#weg zvLtxUy8d8XgdN@47$^LaIXyy_9mQM$0!c~f znkb+`VIm?TqEmTy?Ck8=0-qC`)0Bb#CX{D<*GpfC3(o1m?^}nb9GwgE2D1>96VoVr zcgFE-U(+*G&Z>Ty{l>kxfse{6NnsYdRWn_b`4KM!fUz9mrhB_q^~n#g`tD7Q_o+}4 znO!0ed_4>6PYCa}ocGHKEjPLk5g>`*)(J6@FhQ-=UNc{&qYtV5P+;(~DQCo7uEfM4J}knoT#Co|m! z^K7c?%){Ry*2@!*5t!f%uaMJQdJAio2$xzprjjW6Oe13g>FW`g*jOj9m&C zXulZUM7i}qBKvv}&Ow3htw!>H(V1M10=HV`?pyl&UbMA077|w3#}1DuuICt?ap-?z zU5ncKe7xtQ+Xtfe&oUr30cR;2N(9i1v$dZZ#nv3{ORnF2HM)G)^HR1Z zK%{UPIPVA`X8)k$T3Bt0ffKe`RW@wE6%O>riXp%a#97%=&>4{l86)~arEs#O9f_&) z&X_DmrK9B862ik_RZ4Brdn+V1Q0u$F0IbK4oACjims4)>>2aykmg_HhKBo+MZ6H9O zH^6k-n=rX_t`SFlorK3w-P4}CkMG-fm9IFnyZ>TuZa(E0)zI}x4Rpv0Jk&OS%tkBf zkYu$Qy)4WB;bg#@GWR{hABRRsV5g^PLF+~>O6upd5@5*W27X@&m|F)kv|kT|AVF*l*a%LUP+Tut>j!7$Z0{B*t&(>uS}q3z9bHB{ zFer7GJ_Q2;v|r>fv9gMaU^UbZ_Y*Nk$6Bb4<1B!g-&EDrakz;o^EB7m+#$I~HP3}- z;|6-y|K|m8CWOyJQn`&|;p@-3v%%kB#kAsX`D7%GZH=FJHsbe2Biw%UYADadL<@|I z{jxSl%tW*X#(7sF@JBbI&IISed`O7KVE(FroqLGY_sGVuPFA=(P4|Ku<4vQ@{g18g zJ52tuw;2Ws!*A6}j-gfH)M|5a46@<$65uDz_a0>(O!qW65&4sf{WA;H7O^;%CEe9= zWQt}HRWrMPV}_l@Jl8G+UPaNzKHWUxZVXge5B6EKZOLTpY|H&2eIYRU=sG6@iIZZx zO9}&habmK{mIc0&41-^Hrb<_XQRjXBPM`X}`DFi8xt8C=q!oM!*_7D+@3moMjIW5))^z#jv+-Y9ETXZBB(%|0TYX{x?!II7X17xqNQrAXGzj5=j zqCe84vcKZ6#RRzO)R(g4d_?s)Uay>wo}!ZZPN*59K3~X0G^tD@Rie)Zi;7e{E70zJ zQ*pA=WP65_v+Gc<5v5k1^6!H>rR*#t{=`V`e+Dm){lO}|?)w}bEWd^`O#h$yv->?e zF~YLhjCU*y3eOIL5bKr+*hs?zgSR}zynyYItM?VZaFcwqA#B%pS91HmThpiCw^!Zn zBhs+Q2|IuI$>84MvmsAq*61olzkXHfMdCl`*u~{Cd_88C=C1$c8cnYGamd-zJ^LG(^P{}kvtrCYlkA-L#IS!c<%+#hlu8YuuI-Ah7!58wM4RhvE(ZuR zye91>)16LjPaYc=wnA201Qf4WI~vL{$AvDF(PQf>NrOw8QrGWxp7TBmzlrI%RxKw) zJn;O~o8iUhoYD-D_3AQm7O$bPX;zmjNN5$+rj@;F=O5~&@&EZl26gQ%(TNoClbo{> ztC_$!<-!Ed36<<7J4s8P?b~dc#tclsFL!4L@JwXxUH4s@!F*gey6ht{MRRO~_N;+4 z3)6;}EY%MA@KRtObR@gn*!F&{$C&k~J-*QC`#Mk9PSJ#cSb1Yafkvh@;6|^ms92FW z_{^^RgI%lg=1%e;aB<5D0kk=3rWeWg;o zF81P=(c7=C@Bh_tdTbM;9*RTyX;|vRjH20f)Zn#xyXbmso{#C*pv7XoFst&`Cc@>7 z^)tV-SH<~Ld&&zB)iE64)N030UN@hdghbv;=8~)B zpmoSUWciOj;gbuO9_+Ei%Q@m_q1R_oQzq^h;ky%4&>4qPYB^ooMJV)@QI82@R#tB~De|qWp=vXoZ@#y}E^I|+RV_E$MNBYZoiZHTH zQvEhV!`KK-k!|>&IceF!pge(|u;@L{r;izD{W@xUZlTE=PcsqL!4a5PGh|qV7Hc2& zBtq7<78wU?4q3S(!kW{X_0i-=OAeM2yX)?_*(*CkT)Yz^9QU)U&8s|x1NOqMII#HT zW0XE-D>)7DtYp{aeTrG}@rxI#`15^OtMevmkcIi_*yDG@Eh;V%a8~ObH-#iMH5um~ z_;4jDC`Ojn?{|=+>H14Mc-v{6+GEukPmmCqm1I%hpdD| z3E3;hKK3lzF%ufLlD!UctSB-nAuD7bA<0Tv$Ceec{`cwA=llJA|L3~8t}c%Ec%J*Y z@7MjhU-$Dk%V63<`%V+-{fg-xx>x2aI#}#cHX1UrpK#Z3ipqUP#dg*jcgpqSG4b(3 z9nVtbXs6`NBkCWPSh0Pk&Ngk#lHZ3gltL^(z^?ozsi=LKuZ-d``z8+b9 zKQzUzo4!Iy9*3ETT(a#D=bpiGSbti-WV&B*}r3`Tk9*2rz=Pq4)n zF*Z~XLaS9{mL0VR{I%`gwQl>xdAeHm+wP;Wl@eubp2vuO90`Hxro(1F@yb?S=+ooq zM~3Bvi&5t00D0k_qaEZ}S_EwQayDmF5Ai*a1!2?f7lGY8r3 zNU9>m8^PC4m3-e6|@@)6=+;Q2np; z?>EA&1+$F)9gJLiR=TwM4y2MYPxwkA-o0*k_-vb`_grnphW}di0RuPb`lPjXTz6Ju zt1?cbCR(E=qia9)cOj*5q>z(~Hsf+^WOunW<~L#G;?5A}7Jseqk}G{UztiDMua<3N z>T&ooludX+dJA!myy%JI@Cvc6ichaNlir<1!k>K&iy$K zJS!Cy=J)2SSGtP+#uoEJV{7Myrp)CUO?OdjHOB8&H$sKfaiy>$xvOs&%|ZzU$1j0x z!qr@|-G7eu7fLvA+m&GDkbVVnL@QR3eQ~ z8!&!ubkh}F`5w97*bV7U!EYbJSf(PK%^C|rVJ`Xv)Y};Kd3>4yWTlZygo7Q1peHy;!G2W^?C$m2p?ISLYsC#fN zUzAe7_}$M0KE0wHV+cGk))EWrzNudGUxm0OPog489^kX%^A|#jCVWc2&-UaG=vpMgmL}2@R)*qAH*eI&c?UD(gz3kLXAHMK{d(HcFEI2sSIn1s zPgKz>wP=-}p}UXn)wd`mns#XRkl{M7B!hQc-fnp7}Ubi)CW% zfS35b4$hhD%oElNcuTK6t$mTMudP|$;{D3WY!V(f+=XM9JHC5f5=9rdc^#A$Fyh&p>K)riwXAvP+&MjUs#|UbT4L6l4u)DB+D)Mn@3Lbs_Hj%vDSE zl_UQ14Mbo?{aX1!l^kv7e$h@g;fK95*-#$W=8ebSzklE?BjW|NUO9!7F|1_mHMyzd zhyMPR&y9Q?efW~^i|NyV`zJiBS{FQToj?6-uK!hUvdEZi>VU)Ba{&EisMG%an<7zKg zJ73IuZr8kh`&Ou1;_sKA32O4vDMD_-AFq6sP56*yBn+F|;>jS{?1F-4auVru%zwX$ z0Gz3PUmr!EQBZJ4&gQ4LlvT-ulhkD~PJfrDoi^u|rl-Z(fdBpHLrbBYbMQn+LpvJH zPm^3vT&1#fn0;F`M@tcMgfj)%zH(XK_U~5%Kgj0moL~0dIfZlg+`mc4xOV>OEnfQ+ zPQPfUj|>`|u>t&|hyT4Sj!m+TEz_!O;BqMWc_nHesr39IoI7NSANp^V>Z9&OUH$d- z*{9Qgzpm*5rh&L|`+KlQ$it-T{pwSB}o297OE@Eosy}MUA)Oru+vc*_$oq>LN zrOXR;^ZFVZQBAkskYOfRNPlT|LG8^_%eWS|8puU6c% z-WjP2qKA{x@!XH|b!M}=?LJ@pXFb*V!FWTw50q_y7kp-)@`+~bN?r|>T7vSjx3Ol5 z5`Dy^oQjHyzTNS^gPe33kij&FOVkPq$j&V{F=UmNmg2TKIXPPd$*+fTN-#Y+6Q=0! zKVMhuea$7$GU`+wJL1$;;cR6k2Um_k+QY^Xqcmp^{=M=PopO#ukEmlFyg^*t#LGN6 zIg&jLIP_-(>}}nVz47;Zb?;^KMQ`F(kIAvrW@jUbV30QY8Z(Xrh{x(C9H-tT_XA zvW+gv2L-&P; zRcFd2n*Z~-@F>S%*e`T5iWB1$k#fyR7H!p*rTyRY|$-z`2Wn-2Ti$S z+`70`YxtwEzT2lr>hVf2*nd97f`V{AoIBjahOK?&R5#<_Uzq)Prqf14e6Isntp9xZ zpPvSD9L~{Zfk9uux#vwYarUxH{{1=v#q=^iE(9yNAWYtHVRkbH*&R;D*D~oAjcT`I zIEtzDPXiN`lV|&&$no#Bj=;6Z8(bKJ&1pRRsz%fS5g?UPnCw1E|5X58?l4;~mULIqqdvgBu79pZm3=kFkge5n&N3E#S;Hs1kB z>tb+Qyo*XKFE znhJ5EsJOU(vDSV#{%QYQnlBuzDe}RA_V>MRr}A6SK>fK(x2A$ROs?KUBP`Td;7o-v za9lovwuu9wa%@L-F^2<_!P;0eb9zQth2@ICvu1NE%h@3U55i467l)N}=Y|71PkOki ziEd@S$jZt(-{rBjGWkG?l1d8zkmJzX&MhB&x7~Fasmr3GpDI1(rgo&D3H=CvJQRrY zLBDC%rFT41sGz7wpv4^;g5WQ^CzdQOxVaMSA;coWH~~97n4Dc$;J=1f5=eYuw*Mqg zBSWBN^)!`F^|uETXV0DEv&*MFez;|Bov`IY$dEBDrWsgSaT_d!^U&L}ha|6DIjPIr z*Wcd@cV7{>uqY}mZBndl4}96@PoD&dL?QBrM~@vNvP>s*rd}qAi*OlSv}APi_p}nM zeSg*;Yn{OowkjY<=oV|Y{&3L*aolzPw~z`1YM;1vS~Z~kkfo|nZ;G;)$s`SuQxb74 z^XtMd2~1Gh#h}ge;;hC3de_sNZOZ5-e4O}nimJSZa4&|~*%4-*WV=;%3KStXu#fH< z&M(OXA(diy_L&{UjIOt}$%>3`kRfck{A|_b1KkDI&|8Z%CeaaC8nwkePEo|Q17Umj zeEB)Kqn7eDD^T<**Nb$%={`3O5~%9#5nGl=0T*_P1UHk(@~&S3Oz$7c1xz=4Lf z@}1-S;~l!ZN-;X`nhX$!RXAg@AnpOw#}?8ci4hr8^`jdcG9Ez`(Ee) zB5@j|)8(UQmg48uzsT@S(3ZJ~tDd=Ng8NPG zx_$NcVn*r}cU0pN8=`}P2BS!u1p&ZsVyr1*|NQLTa(ALSFF$`;>K%0NF!v%evpNGpf=i-#*TO4wzEy;7J|M^)R^+WCa<0BYeI^PQe z1HyAp{{9>x7yz+2q|38BN0th6ek9`g(%&Ql5&BTR@F<-aee zLY`YYd0!}jgUy}a0r}LNLE!(~=%2XB9q(rT``mwD9-g=a1>@1fk8{6B)ryCcasPbk zK3va_%OE$;?x7|47-(ov{%`OS++CmuHjhh8NWjA>9e+OhvM^%Twc^MvJ5HxHpvW7z zRglzQmHy@fW+zD3dkp|!`m~KXgF;xRO~Zeo0x7&=F&cs&O93zinKL}G61G3{pDd6b z@+7J0%JeuS4`3|=7lHv>^z;8FiGP009Uk)($-@s&3`rdZSm>W;Li~A+5LM1ttH>On zifKOTr~MahxxfQw=Hzf(ms9GN=#cw^5dZ$&A9zHWXf44j`6$_i(EndFdGaO|^D}*+ zj}O?QOm%e?pxb7`tpdWMCW<~Q>t+;Ld)Az9R`J8X$n6gd;ZllG0DL~atm)yg{@KwW zK`Fty#{;9tg6ZFtrT7>9$(sE(g0OmsEh?ka#@(sCR8=fhP4g@prk{Uw``ZTZqkj!B z@CX=_Mr4UebhAc|+vSW{q>^2}oE#+7tkh=_h-WPjwM$VEPyPTCL3UUQI);Y6S`*7l z0`Ux%@U(|>lEDppLy960*QlU@Di`|qBU$dPX~BY|V?Ks3GkGR{454of!?TnNGxq$U ztuW&ljyO#8tU8jD+$b@M`tzq2ky@6P?2d!4!)WC%03?Ad&FYPz{LMoMZtfR-%0uPO zdB1=Ac5A^iT`Sh~N)HYRVGqY=1NaCF9d{@2L@Q3vD&bWi&C@-33L$^9e_fpFu~N)L zT4z6F@cnjNv7P9pOWg(N7okF89vJ`(6WcF2?n)s7EpoWY-jtW$GB98SJV9+436NF3 z`Jlcg&i#E!1O6cl4`8W5m5bG)7YcOP*utAEB;;e7$Dy$Bno9K~(99ZMUgde!)mPb& z2wLU_!6KB=+xhCsqAT*Y$&ay;j_hm~8WeTVZ{{k~yGkHaW%gl;qS7*X{04w0VJnjC zT7%&5JVDobMLJnXY{9lckUZ6#L5z(}DJIFB!HF$KoG6g8bGK*$*FO>KlSzuI@D^^l z4_yc|u&>OY{600cr>CbO26->UUBE^~J5c_S5a&$go)wDzt|~UcueDSa+Wi0n zR3$K2V%KNU^)ogK36t;^r`W=&sB9yf7%e4w|BtAZab%C3qLr7p3G zAdyOAG009y%8u5n0Ro6{Mo9mjT5lbtx)dtu&rOSm>?MClEcNkAT|R4J3zHY*4Q4ag z-%4!1gP$Tav4E|Z+3{t-!;USlX6(No5sW;9Z(AQYe)!<$o*v;sO|qj8vA^aG%+DO3 zYMWc%!@qyOqxa>dfyf2PlXVuZGd(%41V8TaoPAuQ0dFJtG1rlsy zo2jfB6^t~9YN5_7DRCWq@TNpR@(J@5g%nPQ)*6Xr-)fnnf&$+wWE_DX46A)T9EgN8 zR&E}itrg5e$%qHJ_MiLuE-jSJEl{aID58yXn1b{K8QZ#hv3iY~{?S3iyF)NW#ri17 zre(l5hixL0%`qIPFQ*VEonULv!Wd*4cW!Hp4kfsP}|Egs?Pn$=WoSi5g)y&S_iB#*IC9%L80Z7C(^ZAh~FC)pc#m}N@ z^O~Io%79IPTh&I#vqc|=ZN4D9;4LR}UcQ_^X7Xx74@f(GG6sgetLz5XjGH75?pF_W@bQ9uBm_V0D_hQp5ZI(jmHtu#5NxzOvJH84__+T^{;&< z8Vf5TL^g8)a__nr*R|1oUuHt=gw`nLL-0}O*jWa|AQuPXLOT=k34u%mVAJrKbtB=k~^|w z4dzF&X4*IP zKnk7f87xwUmBdI(O8zd3<4s~g&=;kPetlmY?Oi(?YKqsr_`nb4g z&Pz!l^k0KO+BjMf`2<)IStZ0r25^M_O~loE&+_wm+(-ky)${FJI&AtxdS(@B#Q)Ufh1P1n8yK|a!R@l*8csNffXqi`=VwD#0z8dj z4AOr;BsC(}9t^bPZc&Mjsi_8Rjh4$=64z=qTu_jH#eGVZ&zzSDff9l#R_tF-l&`se zE?CHs4A%vxC{LloqGmfv?SiWK)p4NIUAigWz-9-ku89IgpljtgI$J!CbN+ah3<_s2`kpFEKkk{Rxr+ zRjc5tGjpC5#t;-CG-emfz7?@0(DwcMhA=aHSygp+?&r^+=?-FZbj&t97Lm(9?Yz=2 z{1U})^cKY-Nr@PaHPqAFc-cR9~vs0tU5D2J*@%C z@ZOTU8%_pT7DQlBAdsoF@-t_Ab`+7qj_+LT^S^dJg|9Lb-(LqhP@nb*t4xixpWhCI zt0uw%<^yP-_d1=*1Mt<=%y+mCB!NhzUE8zjUgGG|eyT+pnb*pM(%G|TQ+N|#(W##i zFisMC(E74;;H53RdKF~UQKxf1z^jvPj8vMXCf!&T#L>fCz8DU+7}%re7SKx9T(5vA zywXf?)16k?Lh$2r@FWr7Nj5G!4KvWE9YZX>g1BjbMz1><7z~s(>7!;U)d9fc0k~>8 zJ6}w@;#FivmNNuH&c#5!H{BA=ZX|De0FmSjeDJlHY}QaIf2b5#2`9T}j9&C#eFNQ! zi|OesFMzYs#6ibRFK~EI%uaC#0ajV}-6uGe@B0HqY7B#xR1q8;yavj~Jm?;h(T;4Q zCgLEK)qtD>RVh337Ut-Q9NR_KE1nKnq=>S5B1ijxj-DQCJhJuOK0C2%eXbweHZa!0 z$=M|(LO?)<0?c-@iVSluM?9N`&2a3}a_w!)23{Ir6LO}%9M7rspUw=~E3U4_RPXB{ z0tcja*Lr_9P|EDcM>Vf@gJY*Zgho5on-$N2(2|mhG{9^@Fu(wE+AK;UBRX6b1}{OV z#V4BjCxiM-1-4LS8$V4IZO)L8nVIPo4kZcspfpJj=$M!CmqVlTrUf@F%Lbm8l$3DO zKTYHL$gr`!oAKiL^RFDhDR-n56ztEhZtb11RGmKZDMuNNCRt|yvyA`F3APVoBZ%T@ zD1t=3#B-mKJNg~hySXXOL46`xD7V{V_Yf?lYu%(O5OYIAgA5wox5gglAa)M;hsU5c zvIAZ<0q;6yLyW|(6AuJd%)v^5KIwhw)5ra^W01RhrU!y@?Njc7b;?v$ zRgKn=iuWX|mW46}0=C91z;lNH-i9zaWa_spp9+&}<~tA4>!>GDQTx|=UsHFRFA_a7 zb+ol*BZY3?zRmmFVn#P}&5KX>D-bc_+9@T#Y`ce^KjWRCs@ok4rzqyvktJXVDLqjtv z?z=WUBnJk}4Nm#U80dNU=}$_BjsgeCN}qNB(Q$`_48ZeqpwSq7*>F_f?Ht&>g-EL` z7klgJMNFk!y_u$2t06bil1wwX3q2cWp*U4%%wYNQ#fukLV_TW@(0u}u zUAy{)x`WW>bM<5ntSFM)sSdjpjSRb6{Y~bN9T#O~-JpRrO#f*4`$J0q_N9&ev$-F_ zGVN2SA1j#7&(H60d?<23Xih-Et7W$Dg{4^P!t-e+n??!S0f%3`q;d6`|5j}z4SNn* zW@=6c78K-~dMyxSXVfc;&NyZ^GKly|kD7vagj(A{#7XD5FNLE;&!2CymZGvW>96&B zNH9?>EzZvVezUmf6frG@Kx~1eoy&-n zV;k)g?Y!{!1uH%HDn=^K^?=_=6u|Hf%H{+1lpt)`!Y(=JZXd+8J8|3o`6n#`Md4?d`Rsg8?|t3f`#YhYJqS-d7Ml(5@+lZDd%kTU92Ex zLqiYO$B!Rta_m!PB5Wd>WFfhkSoU_yeLo@)2fsX=+a85fQaFElGuxaYpbA>6US=LH ze&m70+QFVn_iEsg8cI|bjhrI zC}N4+@;;EPlr&S1525Ty+x?w;SpVG#)2}8YtE5Fc_})ik%dZ9}+bS4|7l?!4#|l-G zu4kGD1C3q-8z)10SCEj%i_jIlejWGLc~t$1=lprVj*^5qVe%V{tgQPMu)(EsD9nkV z5xn{HiV6wK@}Uh|+$`cU?A!t%5_JCE*m6Dt6ujOQrkY8PW&oC2vH?iCT z73Z|D^6N_fE7N^hSeFW5!Gd)6NHe^$E(KRIh(5d;70uhOqqZhi$aaR$V<%SN#(T(KV3n z{(fs=V%d88O%EVhsLRpo73{~si?EsXqHICcTb-LsJMsu;Byv1d*Z!5T~g5; z7Zb%zP0%bTLutDfdIcYY+bS0PwJ?;20|(6vDM52eF3-k3+47enj0zncox}!X@O9g+ z^c)C&CzRu53=-bJzPpqFU;WWodW2vtEbb+qix&f4;koU*4OXH%ZDzm;W*6|f67m)k zt&`t649NEN^&RXT$uMU?(UR?s|7Zx=FD@_dU#rSGh-iTxo0rq%5z0P3KEEJyi~{$; z*=-QY`Klu>K0dmCE(f@{;0vCoq<1Cg*$O8IP#iuef93UAgvsdQ zNY$hc@A?{GrB!ez@|l1p3wWW70N@V(YkwA8L+{U@#ow*UyzV^eX~wA&YhS;9{e7Jt zVe?QA+;1f80|RQgE8!*jFolp=GZ8J6g1X&^qgkKiFby}8r>C`uO~aACo1(;GJ$lrpa) z%)T{_FMDaIs`?cs0oDOWp$P1%I1?!oOG!#P&20aLR=&Hvy*(g8=+{p0sdM4}-5A8_ z2YPyXrjZI+&G3+SNe`LVODa!*0Z80I0c&&rS|H?!F5)P`ANNXBIagsIp6lxAg>(iY z>f(xXd!(MfcySUu=nuXM*eq#kxaOYqNAl8v`HToCX|9g{{L!Fs^kKa-)UU}0Fb5urt{!t=v~I=n=)Uw;3pOu?cB)~Lb?Sp<2i~n$%<&Nx7oXP1(Pm~7u_`lw zJ%YIm5+KgrWPj0jJKW%Tjho15$3ZGd0H-{aVQDa&l=DNbmqs zUK7y;j-(PMbkw{=pBj=z_ITy)X!e`jXCF`biP=PUL$I`O3a5jG%u2+8b1?9sRfJ^!6FFnLg0yQ^&>GJV-1LFHF0um%c+nh?mxFT>{biYCkHjn;e+=n9Y-qH z=YVHRfiPbDd>JG&8M?ZYjWQP(^ z&v}`fQ7pT40PN0$yXrk_x&|>Gz}zHkE^se4RFnHao$Pnmr*pGnbSVI@e-mY&q5uIY z3*an-#3O~}#^#_4h$b#f9uJ88l+i9j5 ztRzvH{7*R+q}KQIP2xLk_?(B|E$0FJs_T&27tmbZ7E z9h{wI6u(ZMLL45bxwjnzx{=N%NjiccxuomZ;Zx+~nvixu0TDbS-m+6!^z3fq{X@t0 z?uWYEKZbbpG~?bcgKkE8k#yyp2N38k%bT`pB4_%K=`m9zD^0NM zT@pl5DJfMbze>zKrH@tREOWH7%Kmu!%_0vEj}}0pz?>sohgVg5U!Q|@0%R4G*T$BI zYv4Si6r8%d15;)}ftQ2Bd@KONk7#;C?CktJ_VB~{sACW7yYQOS4Y%KL0!+AAUS3|S z$N>J0v&4V*chR-;KjWG#PJ|I+USTEqB|qTg*rF+`SK9bYr)OqdNjO#Iq&nHu0EC|g z&#K*Ry!&Y?4DeCJx2X19!$`qsML$wDW`LzYBo$Rv4Y4WpG=FSvmf`T7nACtgO- z`>qFP8~Cfr$zHyXK)f6J`1l-U-=BGj$_E98yBT+q1#zZwp+uE9=VkClSQP_7| z4I~j>dp|>D0I-2J_Nk>pKOjw$CHdB)v#L`3y|ujpeS z$=w%k(FQ@+V$0<}W=q|K_1%c2-)e-hj=fVi|bJD6#Y8oa5ORNBiOioJ5+57o!FgXa~4lvvZlr?M_@C5V+ z3*mBX0R||{#|21eN>R6rjLr$^(x&NCfQwxRjO#p5I{BVNER?;0Z=Z{gk7pK3bp)C* zhnGHLw*aYRNVutw@6$3h1@gzxqy0|NT zcQ5L2pzJwt%EBM_q<}6l5PGaR_}cy!;U3Oi8A**DYdpc5U@z@$;Y z16-^5^2#)z=mtxcCELC>l^2-Ra<@kLYe` zY5A-~Zy7o7c?GSfcT)WqFQz{ph@Lkn_|Sl;4zRA`}h?d z4cyYCQ3nBlV!D5TK3y1$-}IMwoyMj=e@ z8)KN9YRN}F1WJHAo*r}vu#J)AzB1{)P@ewb;ywG;oIzAj@VsDmHI&VxSg-nGNeVxe zdWh$Hw=FF2&&hHinyB%`#k5*=KhcD$d8(12VdZhs9fcteJj2J!I|zjCvuV`oeIW&w zsGHcxez*;hBcvf2B>f5xpi2aO+wb+c zR|$!UwF5s~XzwL|>*g(zPEiEj@L|`hsBCJp40Dsp%Jh=eOa-1sX5sr4?c$k5a^S%w9 z_9iLR>Dz%J`sKiK6iV&%L_^(SdTA+7mD4LIc&G_Hcg`~%5t@jmZ&I*8ulm&-4Xn79 zkGw`Aw-!7D)Ui)2Vj;#KJ9dmjXxCYMf_~eLdb6M9;rW__#pZ`vOL4b&;k8M6!%7}! zfa|Xd3T7Vz$`R)UUl7O*n=@3rw`dBd2x%}6ABkRtx&VoH{S#AaF{lxV@YYD6cL~wa z(HW<;wERFkyLvqz8QMH9F@cqg_;pGr4p`V+KqRE58${Fmr_39saIsnr$!1laa9PN1 z({#C;EQBQ8CXVN5^W2}Y>2fNMI(qQF8i==LxJVCS2bJ)CSQu}`nVz%*K<0x53#XIi z(y4^wKY{O6GeyZ~b>D;o^+x5zFCPw+`kULT#B>o=)Kyi#lGNC8wmf151&d6~%)9a! z%t#=3(k82o#SuuY{H2*FWY{I!9T;d3!&?{O#jcR)2E^sb**@`#*_XcG7V=1c#&$r~LBr01O z?74B%{`Mdx_^noUkcaH}%N%)l9k!C^gjL*}LKo8DDsuWb_N8g9zt+6>VT7ilbjHP4 z^}K!d*jA~Wg4-3-Qd^qnc!ircNgkJ0KA9IPzB&d5O0Y-pd{a-UgV?q=+}7#*Y3?HP zI>dQ$WsdvrO`NU3S&2m}I-E(%`3eXN+?AM0%$nn?em+00s|b-R9mJ z(%?tPmDw;402^2$IhznV)rs&X7oY!57!gp9a_`L$DH#dQoupG~^YK zIYWL6^B9BF0t~_ts|4lr2e)d((C9m8^ul-CK9mT^&p03WUL)wNi&|Xwtp!di3;&Qd zONY0w8*JGN9DCA0vWTrQ)N?Y}Ax^sxs(c$fNBBO06HnbgD|RjDCSD_tw|f<8k4~5hqXP#F#-* z_q`#MIq!8#grmWu`~(>6e*<7!8N~IAj~agLn?zc7SisXAgG`RK2|tWLi1Y(`g6v>f zO+Bv+NPjM2fEXP)|4FBB@ZD9jjPde8S7VHfLmgg)C4AWg! z!n)lC;n7H?%Jxa$iBrzrH}b%4ZQS6Hz9lG^dX0IQkmvRKz&4CdD(>#9NhAQ#WM^g7 z*w@=z#s$+ylg44{S(QfO?VP+8sSAS5Kf$&EGq7mjRU_LcE1{3`vea@TP175ZqX-m3 zq>k`?U?nIjMiyBk2+M}UPM1lAb#T4%H!DCnGy&AXDj;!Mr*wyk=SX}P2dHAp3Hy<@aot2;yqT1dL2#XayG zx50qj%*^VbxQ|GpQQ%E(1jdjT9M&-%G+NHK;?});SAltZg0wFU02VnrEbK&m{$i(1 z8Ro^ym&26+6SQD<vl51~wmdsKd!s~Nd*a12YCufUMl~COC0_*jAN10| z?W6xmpuQ(Kh}}iPqDwbt$Ym_d<|ffM``HG4AfMG;M z=ld0$f3g`U>h$#evxXFZXVOw=l*&&1sv#niN2ggW8+QE0WDdph?yXX?5z* z!Z$uil^StkujeB^&Q>e81;93xzt+~?E$!QZ-awN#S&@XhHn#V_++yPtKyOz1|wm2@Q#5&QF=ci|F4 z;zaD;j*m9_Ql5T6etst89Q89M=ZH91O&N>83irYx6jR0+rt=z!7fl650Xcqy>3=$n z65(hU2E|ZnWn}s(0L+y|MT=?x1e>lz9Y8!&gf3AlSTf%x-70>C5lVFc!CJ$SPt8Rp z)rMX4?WtD~9cPs}Xbj)n?z0AcgLSvWB6yGdygV+VG;~h^4`VEl2xtU)VfIL?zZW)& z^@O!a_5&EKc!-Xppg536bCXnEB<(Ti1h6;>_X^`YsF00*86KA61RqeR-8VeUgekFT z3UIF)*(Rl5AT75vH62xf*Xn#U0Ldq{XMhM-QNipM&?hen3KBvo+>KE5?63M$Z?OTU z0OJP(wwmJCGJsZxXjRU3QMIA*)h%rIeZ-2#@|gTS1mq1sK#(Sopk!%j3>1Wyy<29m zJwS_2+xbA|lLtO814!jco<9#oT=wpfTYCt^f^=-_OWHJBo*vOc5;rIkt+?~7v{V>L z;%HFdKl7o4lL-Q>eSXR2G?*$-_rg>W{WH35LefSZcY6xp$kHj`@pZ8jfqsCpjT96V z{0$v$eIq?gLBZ|YgF_25We`0d^meAVPfAw$ZLKtersfiKIU*3MpmsT~2TjbS?UTPD zAyh&0#uuDpW*!Nc>PRMo@isHh)Dad6X;=! zNvVMIY=1!IcuNIBXooJT1&t*t18DA45UTMQB-oFCd@K<}O{ykicYk}FWnrODa{6T~ z&|eR(<8q;X30^!z{{RAI0U6g5eqg*u)bm!WNR$Nh(|N-5&$q^Mbp~i75Jw(rw~}`L zErp8Ty%?*afZXnGtx8xd1Z??GuhG2c>O~_G6uP_gt@`33lmO`=Y2k#OZJ5CNMqu0i zG?L{iQIqTj=mU2$4uS-5PM?BKo`J@t`zLIh7d%-3DEL9gUnq%lL2Q6A;i8cjfo-yQs@{Tuhj#A4}|rM4)i*V)>OZE{d(;T-hH?bS{OhkuU-O54Lf*stGCqi zCa|E4?n#^Kf%c#%J1~EJV?z(vn@L3>dSyU>5*woeafGxhdJaK7^}vsom%{=gL7l+< z{_gI~LX+y>he$n#v}xA)kZ|7z9M2?k_t*ScQY}3qeXDElKA@5G5`9$eV5woh2h@RR z?Nh`&SS=&{q**UrzI;+u%pC@E7nTpEIf~VkAM|h4a57RKJjlK%eMI4zH0xZaD8rWp z1#J}>a4wnC@H1eQ=`oprLsyG{91kTqS3walrB;y5Y=Dqw?;18?OB1RV5W`v-PdJD{ z1B0)Xl~q?$#2KR#P1JSxU{EdN;4JNTpw4gxF9$0t3-+eP(a6Cf&(F{2{-Vdm27&7_ zK;>gm%vXLvu8Qyi8+BU%SQ9`U)qFXWAbcxQ2=a@DJM_x(;<7W7pbPRU1kYhiqC__X z7Djx$&u!czE-!tbI1+f~ILm1ztPc zBkvZZeZ)qxm3q1CV@A}L9G*CC*vCo{?3okzlkwJCwDLormBNwS>4>9s+0 z?CfkgI6q*%ibEh#1$>9Kx3@O|c`H*`tqfSl4Pz)*#9zAeWlbXO*!0}o3kF6;iK2qr zA-x9l%38DlJtO3@x`)1Kk}B&s(76Cn0O0i@FyGC<|8b^)S?l8l2MtHYm;C_PiGq*> z?-M)aB%Aqke(;To6iZZSXk22Via1jOw3-H+i{lmvZWYELrHF!0KvjOe{osrgV!koUl}c6jp2 z%sj66tXR3g$_9Ir_b`}(qMXngfCddstK_-IR4AJhq7~A@2e0g&Qs!92MGpIk$NR^zF#Be(L z9#oLidJUXx3=$$AKWiMKWgFPft?fw#I2NtpqV z4w^?;bZg)+yQc*OS6i)0;i_9-U)#sNe)DETmm8L_Jg`wj)U@d|aJscna0q~9!_o=& zM%;y}Sqdo0MwuBI?Lp`V{g)B(>Ur|$zJc}*I(aI2tw!Q1R57C=JB=thGnGzVVO#|I z?hSPfjeshsL-LrJ9pQN*L>(GR!YL*g%&u!jSE7q#d!eXWEU56p%^55V5|VOxAUX<5 zoA{|jKQH*5tCA>?CgU;tc~|4=I&Bmc>R3)~ZS5tNsM;h|P9q#eY?Pg-@6gyzf^{oo zh2ce-WI?i{(B4}+JTQ>#K?+cwHn0yq4X_HY(kM!4A=s9snhg6z^7HY;3F^6<$R`;}uIfb`6p}fKo&Ar!eonXBMJm|&9Cr>yI%HP}*XlnP5^GYHmiAv-jED zZwPfLT`87CApN5itq7AuAnHILXJuoPYnJ#-kUR?&+q#13*cXw`bfpgC~^Oq}_#LKeIFu@7zCLqq34Tmh=% zS;M?C)R&2gJMC^T&^F&{^MgBCAp#iv0I~0^!SuMyKKcC&tFcy3O=(gf&OJ zNIU^`?xC+=H&aRHl6RE59YAcTf+>+i+pL?rd)kFk;IyLz^sm`uaF3jGa7}onUQ`FSmduBPTx}c0TO!s7BF8Fay=`nF4G;pD8EF zbEIEp2oPl(~O0n#RS{t=2V`K#-*s;~Esh4F|48aft)B$u*7$Trr4ye{KVj`?%L!mmO@rv~*>T#484fo>jX7q9siRh=FP_=2X#b z{r&p9Z5B|cZQ0y>2ZH*D=JDiRARuA@p32tJ67uW^Ye~-bBvebQOQG`3$r{LZJzr;=QaYFy>%XkKJUhHz08XA*-vDvH0I>~_jqz3|uiJ)&yR;xETW1#0{NDymjjqrDU3ACJ8Hy)|vuVz{v!dfLVOUB8@<2tpoXNES!q9^Ptz@ zJMP=JZyq4Tk^$~82kaYYyPvkCU%m^a?f->EAo6%f;wF%$?mth8$gu$91j@J|V+i)b zrGjPh?uv2_osFH{^R+%b(`v|O>YaT|O-uwp*IEpCW8hWw@7WhCa8T)$q&5@(+I`q} z^S3?_vc2=7ZwlT4oG#~pf|p>ta!?vYnIRgDzDMFKyg){TB{|~on7IMljNPo{UC^oowG?muz|RF(tH9fSnZ zt{h_<+n|6p)|LHX&!7-rP_U$qq;>|6%L-SnV4?^a;Pg1M{iVES@|SOQ6%Z`7P%NFp z5z5X2=?`Tg|4U~d`$J;1-gIe+5;lp@1<(@>u{8!HOM$dAVYrOQq7ReRTRwew9|tc0sU#(|U6uZI5%7LDIb-d; zB)iT5>^cn`FJw1mG%By0clqIoK=LgRTw-zbE=kabCN=i!I{9`^`FpB|(fP`3+H4h@bT7!<^&u~FI_tn(S zy+}M1_q9JZi8GKQ)|73kVmHPmen)J;0`nTRvh zh+nh9y(0{=PIw|4+hS7xr;s-S#Pt>Q4wMv>l>6sn zTjL1(BSGw^kGDW6#)&U3+*KBIq+{cxP=yP9S#wMV@$5YFRo}Hk5Cv2ctP7J!0GK56 zfG*w^sB!$i_OARNs{Q|K(QPlTN}J?!G1*DEmT+rS){rdONhn)`k*v3pEh%BL7a{u^ zjkV<>rl_VuW6d&Gwky?S|2~iV{XQPwAHILWx5uML9+^3F=A8HPdOf%Eu&C&im!*%= z$GyHP0`NO82EbLiB#ud*herItFKjV3DMEj z)r9EsnLuCswKjI-oPXV5f^ zo@gEYz6*%Xq@&$BQ>$35Jbf5WAxB?Q1p{0iQum*(O{feI4dKgL*&`%0;&ql-Q*v{a zVhcU60hhP2m4~R+OS1eM1G6C@FNbb_TcNCV49a3Z)DIaLM@uHma!U5}U`Bqv4p`M7 zR=G9Kc!#yW0rr9k{nGiGx*PBIss8M{mc~m|NZemKO6*Uecy8O~SHc%gCJNcg6ra3Y zG4$eSy-8D`@jfu16BVIXS34ggjfT160#M)-?b8dHD~k12$&y;^^@#P&%2) zC5$0R!>U_)bFjnHhwsGjq^hj~0s@Oxpc03$#3M;c8(6?)6#*XK0uS_wF;K<)o}6EN zAR-1rbrV30?uY^@LqjJjMDaz|$*+2j;x<~RPPMV@N;=f|%pVML=7zvx7+R_N6z8rL zjeP|S=i5B zAX*gax1gf3vgx*v@PNKlOv)iE-=VlH*oD|Y+0pTRH83+fpzJdd3v33q1tZ zK5Y(7CuJeO8KIMI zeCFi?S9lbV)o3a-WY0zS*_^z!-d#D59l&;li~G5xdL%1fQrAaXdO1-Yxp*| z;2hoL**#9R28>^XX?)xu7A5d$>DuoF?jMp{pI89>={=0u;)>Pyf`Qwe_N*DG<>j*V zTZSk^NnsRX2=~QP!i!ZO;=>Oc)t3av)EPoSuF#-b;#FyfAI39vfeF}{N8Ef4EH%^!M?7s+GVWORLCKioh5Q0>A`O~OM z8`hf}MB6;4oJk2s1E;0EH;qEZioFZ}m>n(96Kq<3-vz z<~4dN=xMjUnC&gx<9ukPBSEFfRGpcfzBb^mW29i5lpM<9U2zSqVc*yG1-ldwmEFOzpd zq!Oic8+Mi;yUcBb6QmTrPG=XFSf~>9Ar%)$+we?abDmrOWM5$1Ou!h0;9ovogunFG*3=LnaiV&ggaTNungdSJn z$Pr0A961>{3^pz~85#73T#T0kUR-YfvcxOah2c3jh~-aX5CAinB=(Pb{R?t+@u%Sp)Dcp*wwoM0lFb2aoD*V`OBQ zfI&Ku(t7oI<3rx<61$?ZwrR|G;hgR_4alOq;{(D98^|6-3K{*e2kU)II*I5Mp-H9u z(}!nqIosAQCwJu?mEFQ)<9onmTbPDEMpVy1S=lKQx<%`_7r*lAYkzd&PFYr}J(jpg zoq7g|uY=xlmJ6e4V9FGfYYP5m6Q^-J=XwJrO*LS)-M*xQMxiISjmS`)FIKYpjYMyLBsR3BQUoT&LYn%#wD@lBE_Huk}Jp zBHKsf07YnOYe%BRua4ISBn4q|(Mh2@9)inXp=-B}nC$~nT(OasyZdAt#P#*M*~~L| z?`h!c`R0{+;AJPPQe#k;$;hZCYilXr{x;YtAW-Kj>(0YNV+dog-MMvZ^Dn$arc3Ywzv{E224c^)qZG&n z!SMrX7OCvA4#@y?*tIm{LOsIOL6NIM2vDCwHL4^i@9~3;dCgj%KbXE-kODY$Cif%z-Hj z!h*k-DQ{wNNfCn|m5QOUu?UtENRs}N*Uh%ay1>O%oNwH;#(&p9cFBEvnC!~O#yGKnQ3@4 zWjRJ?{q{`W_y(+rjUNllISg2JMMXtKR5M4b+DCNsK=!<6+Ky5$2YB+dsHUJ02&|9TDi&GaoiNkCjh*&2P`#gnP?+M7mQC!kGbgGXTBNBQ5 zL@llttmc>34amu^M6gL)lId2n#>SKu3GtT*piyZT{WofVkKhbU_;3TN8iR7z;;F!?Oo9j;tC3%2w(foR5G zf32RxUFPC4m9J#E7FgG(8m^m8H@cbuVBEi|aA_W2)&!cqjuDj*_|P-~!E)M>w@Y(7oGQjC~16qop}nd#|v zt%=$`ur3V2Y{gL(B;LIZ7R}Blmj?7O%R@t|4A#Di+O1%P99-Xr&~Aid2&jGV4zS&@ zvA`zYK@;mB#yrGhw^8Z>0D6Z#zW<-@&?;~ta#_n6uKC6~Vh{ShefvhvhII;(Qw7RW z%|Vn7HdCK;bZKHv6gywb2lu#im>6 zf!&-@ASP#@ya z6xgD`esZX+aFjvq^~(rKGe5|h@mESSU+S$43>@fnr0>gc|K?MznBiasp=7);bNA5J zgrv?1Hq)QdeSi`5U6sb%-CgPS+J7L*P`S(B&2zeh-B|yElIpmE5oNfJJ0~OKKb`IE zoOAZ}16z=GS$9A8LNzmCeP?NlB&DO_Bh(!mwDcs6W@9bDWPielQv=l0ApsQW+7T6R@u}fi3=c!}`G~t2CA;Wl< znjoa>@fJXTXjxez=5C{q6jyA^fxS5n3U40Y@h4>+r3%|4H4(}ynLs9klM#iKLc7%) zxj;Mj?tLtL7uy@bHVV|EeoWW?8)eX^AO2?z4EAHRCN6Z1Q>C38S>_P9D?+O3%B=@U zA{Kvb^lL7*lT(+!O1&HpOtr427J!<06}WiZ{h!)9Mq4iv&Qt{5U@_+v@uwS6U0)q;8|JfScZgk{L;4X4NdmIK>UsgF-fm@gk2oh3Q7 z?()><4B#mopp9xy{A)O!e&^^too91Vme)`ls*{qkcaCjcyV|AMdw@G~FSNM_bISQp z-c=Zmd|a2?fIQTA&4k@!EwE|1z8;#o>@uhPsTqSnk>+4lp&J?=hC+k2STN=`=+a_W{}l9L@@+xj}`my!_vu;N2Cbi$Rgi>qe{}D=5djXLn-A- zum5z2TcY%*r&N8fb#W%I3BS#QG3DG9oRj9}tPki?_W`XEkP;X7YZy@xhnJY>C>4Vi zYly;4EKcm@wQx(CuH+BPr`09mG|NhR1!e1>t2hxU4kalZU*$CS^@nIFV=Y79uekcX zHGQ;X95UNw@d&?(E}{0srz}~8+`G7+`OgMG8m^M+DaMc5B-K^dPoG@f_4#{FCR^IN zt_Du9K{eD}q6m0tdwv+vaWN-9y@7fORE0$E%-j2t|-$dnzDnH7-z@pnc=wa{3yB(AkXVlZ*Bi7|7>+uUP z(*zXROYc~$x$)-a=KAQS0giHtv+zM(PwM`Oqp~V0E(|46YU)F=2q746%ZK`u4<58X zmfnu}>HGbIi7Q=FdB`XF;g5D4lc}XNm1)L$Xesl@EZ}R@C$rdyWNx z^?;Bi?UmIUpXm*c{d>DBrb{lv`JRxR9IGxU!)5Qbruw4QmpZy&us zxVgOXxMs`GIrr8~!(b4aW#3c$-Iu$%U#93~yDseBbz?vDv!z4t4kb zu>HGVPe*vW%tpupc*C!|vRxDeA|vVU30VgSuC`s)-6b7aez zN3^mf=#2->uF@{)td6u=+T1Bam_E32(tC}%3_cbJ}Dr;%4y zb`W#8rws(H>!X5#g^STmr}v2KE*G4|EA_=5{0#xJ8`NR}ljaccW&o#Aq%j(PE=MOy z{2@ilkbYY8SLUd5fR6(G`}s-ZaAXRIV7P2E~}%g9e$N0vNOzl}B3J z`7&X>UzWezebcupc86r4XX=V;J{JfF*~}eSvAsoT#E11!Ef#Nt+&6lj>j{LiD*$-* z^MR?FBDnpnU0uf&YOUN`M=Xd}>XRt+J(;}28ada`5LfdtVctSCR1uIb1rW6XFD5ys zrHDe-^!vsa_i?pw-Sey*9UYCy$H{};D3?AUyy75qyEN2RIm}Mts9Yo?`RJ#*=%+2FNBv z0mbe}ep&NP~_Ofzrb~Y7>BTiUB_Pv)MPpK*lO=(?2oCwt(?a;G_yv4t_z~JL< zXJ;2$I2v>h0x@Jhw~go%udxYrlAk5i1zVJf<7x`qT*`+op#VeidYB3<2iB7t8QH-hNt=iSg)3=;gu65$3iy`kq0BfEJh|K7B9hW z6lKe=3y`fIk*|w{@w{kMiThW)EBGq1G)QqW(jSdhCJzKI&aeaeJvx++99cjr#$>y^ z>nZF4>`TN4Jk^A`&#}_Ickx)Z=lx;>u@&6JG9!!x_CKNYo%FH{BBkh=j8?UhmuqW> z4iGN&ypL-gICi@ZAYeG9kf2f*HGrb*zSXF9$67&NyZg*G8Ll0f=(A|fh~Vrr^`pj z#H_M<;@bvawM`VcyL<-4N6m^{$M5+q9K)HQ9^!K0H~A7OWZYfiaEoxVtp} zHC9=SFkLXvA7yUdx8)bUVf%zk?(ZYMPX792jy;$Mu7{5wA7Xy`G.column, +.columns.col-gapless>.column { + padding-left: 0; + padding-right: 0 +} + +.cols.col-oneline, +.columns.col-oneline { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + overflow-x: auto +} + +.column, +[class~=col-] { + -ms-flex: 1; + flex: 1; + max-width: 100%; + padding-left: .4rem; + padding-right: .4rem +} + +.column.col-1, +.column.col-10, +.column.col-11, +.column.col-12, +.column.col-2, +.column.col-3, +.column.col-4, +.column.col-5, +.column.col-6, +.column.col-7, +.column.col-8, +.column.col-9, +.column.col-auto, +[class~=col-].col-1, +[class~=col-].col-10, +[class~=col-].col-11, +[class~=col-].col-12, +[class~=col-].col-2, +[class~=col-].col-3, +[class~=col-].col-4, +[class~=col-].col-5, +[class~=col-].col-6, +[class~=col-].col-7, +[class~=col-].col-8, +[class~=col-].col-9, +[class~=col-].col-auto { + -ms-flex: none; + flex: none +} + +.col-12 { + width: 100% +} + +.col-11 { + width: 91.66666667% +} + +.col-10 { + width: 83.33333333% +} + +.col-9 { + width: 75% +} + +.col-8 { + width: 66.66666667% +} + +.col-7 { + width: 58.33333333% +} + +.col-6 { + width: 50% +} + +.col-5 { + width: 41.66666667% +} + +.col-4 { + width: 33.33333333% +} + +.col-3 { + width: 25% +} + +.col-2 { + width: 16.66666667% +} + +.col-1 { + width: 8.33333333% +} + +.col-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + max-width: none; + width: auto +} + +.col-mx-auto { + margin-left: auto; + margin-right: auto +} + +.col-ml-auto { + margin-left: auto +} + +.col-mr-auto { + margin-right: auto +} + +@media (max-width:1280px) { + + .col-xl-1, + .col-xl-10, + .col-xl-11, + .col-xl-12, + .col-xl-2, + .col-xl-3, + .col-xl-4, + .col-xl-5, + .col-xl-6, + .col-xl-7, + .col-xl-8, + .col-xl-9, + .col-xl-auto { + -ms-flex: none; + flex: none + } + + .col-xl-12 { + width: 100% + } + + .col-xl-11 { + width: 91.66666667% + } + + .col-xl-10 { + width: 83.33333333% + } + + .col-xl-9 { + width: 75% + } + + .col-xl-8 { + width: 66.66666667% + } + + .col-xl-7 { + width: 58.33333333% + } + + .col-xl-6 { + width: 50% + } + + .col-xl-5 { + width: 41.66666667% + } + + .col-xl-4 { + width: 33.33333333% + } + + .col-xl-3 { + width: 25% + } + + .col-xl-2 { + width: 16.66666667% + } + + .col-xl-1 { + width: 8.33333333% + } + + .col-xl-auto { + width: auto + } + + .hide-xl { + display: none !important + } + + .show-xl { + display: block !important + } +} + +@media (max-width:960px) { + + .col-lg-1, + .col-lg-10, + .col-lg-11, + .col-lg-12, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-auto { + -ms-flex: none; + flex: none + } + + .col-lg-12 { + width: 100% + } + + .col-lg-11 { + width: 91.66666667% + } + + .col-lg-10 { + width: 83.33333333% + } + + .col-lg-9 { + width: 75% + } + + .col-lg-8 { + width: 66.66666667% + } + + .col-lg-7 { + width: 58.33333333% + } + + .col-lg-6 { + width: 50% + } + + .col-lg-5 { + width: 41.66666667% + } + + .col-lg-4 { + width: 33.33333333% + } + + .col-lg-3 { + width: 25% + } + + .col-lg-2 { + width: 16.66666667% + } + + .col-lg-1 { + width: 8.33333333% + } + + .col-lg-auto { + width: auto + } + + .hide-lg { + display: none !important + } + + .show-lg { + display: block !important + } +} + +@media (max-width:840px) { + + .col-md-1, + .col-md-10, + .col-md-11, + .col-md-12, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-auto { + -ms-flex: none; + flex: none + } + + .col-md-12 { + width: 100% + } + + .col-md-11 { + width: 91.66666667% + } + + .col-md-10 { + width: 83.33333333% + } + + .col-md-9 { + width: 75% + } + + .col-md-8 { + width: 66.66666667% + } + + .col-md-7 { + width: 58.33333333% + } + + .col-md-6 { + width: 50% + } + + .col-md-5 { + width: 41.66666667% + } + + .col-md-4 { + width: 33.33333333% + } + + .col-md-3 { + width: 25% + } + + .col-md-2 { + width: 16.66666667% + } + + .col-md-1 { + width: 8.33333333% + } + + .col-md-auto { + width: auto + } + + .hide-md { + display: none !important + } + + .show-md { + display: block !important + } +} + +@media (max-width:600px) { + + .col-sm-1, + .col-sm-10, + .col-sm-11, + .col-sm-12, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-auto { + -ms-flex: none; + flex: none + } + + .col-sm-12 { + width: 100% + } + + .col-sm-11 { + width: 91.66666667% + } + + .col-sm-10 { + width: 83.33333333% + } + + .col-sm-9 { + width: 75% + } + + .col-sm-8 { + width: 66.66666667% + } + + .col-sm-7 { + width: 58.33333333% + } + + .col-sm-6 { + width: 50% + } + + .col-sm-5 { + width: 41.66666667% + } + + .col-sm-4 { + width: 33.33333333% + } + + .col-sm-3 { + width: 25% + } + + .col-sm-2 { + width: 16.66666667% + } + + .col-sm-1 { + width: 8.33333333% + } + + .col-sm-auto { + width: auto + } + + .hide-sm { + display: none !important + } + + .show-sm { + display: block !important + } +} + +@media (max-width:480px) { + + .col-xs-1, + .col-xs-10, + .col-xs-11, + .col-xs-12, + .col-xs-2, + .col-xs-3, + .col-xs-4, + .col-xs-5, + .col-xs-6, + .col-xs-7, + .col-xs-8, + .col-xs-9, + .col-xs-auto { + -ms-flex: none; + flex: none + } + + .col-xs-12 { + width: 100% + } + + .col-xs-11 { + width: 91.66666667% + } + + .col-xs-10 { + width: 83.33333333% + } + + .col-xs-9 { + width: 75% + } + + .col-xs-8 { + width: 66.66666667% + } + + .col-xs-7 { + width: 58.33333333% + } + + .col-xs-6 { + width: 50% + } + + .col-xs-5 { + width: 41.66666667% + } + + .col-xs-4 { + width: 33.33333333% + } + + .col-xs-3 { + width: 25% + } + + .col-xs-2 { + width: 16.66666667% + } + + .col-xs-1 { + width: 8.33333333% + } + + .col-xs-auto { + width: auto + } + + .hide-xs { + display: none !important + } + + .show-xs { + display: block !important + } +} + +.hero { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: justify; + justify-content: space-between; + padding-bottom: 4rem; + padding-top: 4rem +} + +.hero.hero-sm { + padding-bottom: 2rem; + padding-top: 2rem +} + +.hero.hero-lg { + padding-bottom: 8rem; + padding-top: 8rem +} + +.hero .hero-body { + padding: .4rem +} + +.navbar { + align-items: stretch; + display: -ms-flexbox; + display: flex; + -ms-flex-align: stretch; + -ms-flex-pack: justify; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + justify-content: space-between +} + +.navbar .navbar-section { + align-items: center; + display: -ms-flexbox; + display: flex; + -ms-flex: 1 0 0; + flex: 1 0 0; + -ms-flex-align: center +} + +.navbar .navbar-section:not(:first-child):last-child { + -ms-flex-pack: end; + justify-content: flex-end +} + +.navbar .navbar-center { + align-items: center; + display: -ms-flexbox; + display: flex; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -ms-flex-align: center +} + +.navbar .navbar-brand { + font-size: .9rem; + text-decoration: none +} + +.accordion input:checked~.accordion-header>.icon:first-child, +.accordion[open] .accordion-header>.icon:first-child { + transform: rotate(90deg) +} + +.accordion input:checked~.accordion-body, +.accordion[open] .accordion-body { + max-height: 50rem +} + +.accordion .accordion-header { + display: block; + padding: .2rem .4rem +} + +.accordion .accordion-header .icon { + transition: transform .25s +} + +.accordion .accordion-body { + margin-bottom: .4rem; + max-height: 0; + overflow: hidden; + transition: max-height .25s +} + +summary.accordion-header::-webkit-details-marker { + display: none +} + +.avatar { + background: #DE7061; + border-radius: 50%; + color: rgba(255, 255, 255, .85); + display: inline-block; + font-size: .8rem; + font-weight: 300; + height: 1.6rem; + line-height: 1.25; + margin: 0; + position: relative; + vertical-align: middle; + width: 1.6rem +} + +.avatar.avatar-xs { + font-size: .4rem; + height: .8rem; + width: .8rem +} + +.avatar.avatar-sm { + font-size: .6rem; + height: 1.2rem; + width: 1.2rem +} + +.avatar.avatar-lg { + font-size: 1.2rem; + height: 2.4rem; + width: 2.4rem +} + +.avatar.avatar-xl { + font-size: 1.6rem; + height: 3.2rem; + width: 3.2rem +} + +.avatar img { + border-radius: 50%; + height: 100%; + position: relative; + width: 100%; + z-index: 1 +} + +.avatar .avatar-icon, +.avatar .avatar-presence { + background: #fff; + bottom: 14.64%; + height: 50%; + padding: .1rem; + position: absolute; + right: 14.64%; + transform: translate(50%, 50%); + width: 50%; + z-index: 2 +} + +.avatar .avatar-presence { + background: #bcc3ce; + border-radius: 50%; + box-shadow: 0 0 0 .1rem #fff; + height: .5em; + width: .5em +} + +.avatar .avatar-presence.online { + background: #32b643 +} + +.avatar .avatar-presence.busy { + background: #e85600 +} + +.avatar .avatar-presence.away { + background: #ffb700 +} + +.avatar[data-initial]::before { + color: currentColor; + content: attr(data-initial); + left: 50%; + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + z-index: 1 +} + +.badge { + position: relative; + white-space: nowrap +} + +.badge:not([data-badge])::after, +.badge[data-badge]::after { + background: #DE7061; + background-clip: padding-box; + border-radius: .5rem; + box-shadow: 0 0 0 .1rem #fff; + color: #fff; + content: attr(data-badge); + display: inline-block; + transform: translate(-.05rem, -.5rem) +} + +.badge[data-badge]::after { + font-size: .7rem; + height: .9rem; + line-height: 1; + min-width: .9rem; + padding: .1rem .2rem; + text-align: center; + white-space: nowrap +} + +.badge:not([data-badge])::after, +.badge[data-badge=""]::after { + height: 6px; + min-width: 6px; + padding: 0; + width: 6px +} + +.badge.btn::after { + position: absolute; + right: 0; + top: 0; + transform: translate(50%, -50%) +} + +.badge.avatar::after { + position: absolute; + right: 14.64%; + top: 14.64%; + transform: translate(50%, -50%); + z-index: 100 +} + +.breadcrumb { + list-style: none; + margin: .2rem 0; + padding: .2rem 0 +} + +.breadcrumb .breadcrumb-item { + color: #66758c; + display: inline-block; + margin: 0; + padding: .2rem 0 +} + +.breadcrumb .breadcrumb-item:not(:last-child) { + margin-right: .2rem +} + +.breadcrumb .breadcrumb-item:not(:last-child) a { + color: #66758c +} + +.breadcrumb .breadcrumb-item:not(:first-child)::before { + color: #66758c; + content: "/"; + padding-right: .4rem +} + +.bar { + background: #eef0f3; + border-radius: .1rem; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + height: .8rem; + width: 100% +} + +.bar.bar-sm { + height: .2rem +} + +.bar .bar-item { + background: #DE7061; + color: #fff; + display: block; + -ms-flex-negative: 0; + flex-shrink: 0; + font-size: .7rem; + height: 100%; + line-height: .8rem; + position: relative; + text-align: center; + width: 0 +} + +.bar .bar-item:first-child { + border-bottom-left-radius: .1rem; + border-top-left-radius: .1rem +} + +.bar .bar-item:last-child { + border-bottom-right-radius: .1rem; + border-top-right-radius: .1rem; + -ms-flex-negative: 1; + flex-shrink: 1 +} + +.bar-slider { + height: .1rem; + margin: .4rem 0; + position: relative +} + +.bar-slider .bar-item { + left: 0; + padding: 0; + position: absolute +} + +.bar-slider .bar-item:not(:last-child):first-child { + background: #eef0f3; + z-index: 1 +} + +.bar-slider .bar-slider-btn { + background: #DE7061; + border: 0; + border-radius: 50%; + height: .6rem; + padding: 0; + position: absolute; + right: 0; + top: 50%; + transform: translate(50%, -50%); + width: .6rem +} + +.bar-slider .bar-slider-btn:active { + box-shadow: 0 0 0 .1rem #DE7061 +} + +.card { + background: #fff; + border: .05rem solid #dadee4; + border-radius: .1rem; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column +} + +.card .card-body, +.card .card-footer, +.card .card-header { + padding: .8rem; + padding-bottom: 0 +} + +.card .card-body:last-child, +.card .card-footer:last-child, +.card .card-header:last-child { + padding-bottom: .8rem +} + +.card .card-body { + -ms-flex: 1 1 auto; + flex: 1 1 auto +} + +.card .card-image { + padding-top: .8rem +} + +.card .card-image:first-child { + padding-top: 0 +} + +.card .card-image:first-child img { + border-top-left-radius: .1rem; + border-top-right-radius: .1rem +} + +.card .card-image:last-child img { + border-bottom-left-radius: .1rem; + border-bottom-right-radius: .1rem +} + +.chip { + align-items: center; + background: #eef0f3; + border-radius: 5rem; + display: -ms-inline-flexbox; + display: inline-flex; + -ms-flex-align: center; + font-size: 90%; + height: 1.2rem; + line-height: .8rem; + margin: .1rem; + max-width: 320px; + overflow: hidden; + padding: .2rem .4rem; + text-decoration: none; + text-overflow: ellipsis; + vertical-align: middle; + white-space: nowrap +} + +.chip.active { + background: #DE7061; + color: #fff +} + +.chip .avatar { + margin-left: -.4rem; + margin-right: .2rem +} + +.chip .btn-clear { + border-radius: 50%; + transform: scale(.75) +} + +.dropdown { + display: inline-block; + position: relative +} + +.dropdown .menu { + animation: slide-down .15s ease 1; + display: none; + left: 0; + max-height: 50vh; + overflow-y: auto; + position: absolute; + top: 100% +} + +.dropdown.dropdown-right .menu { + left: auto; + right: 0 +} + +.dropdown .dropdown-toggle:focus+.menu, +.dropdown .menu:hover, +.dropdown.active .menu { + display: block +} + +.dropdown .btn-group .dropdown-toggle:nth-last-child(2) { + border-bottom-right-radius: .1rem; + border-top-right-radius: .1rem +} + +.empty { + background: #f7f8f9; + border-radius: .1rem; + color: #66758c; + padding: 3.2rem 1.6rem; + text-align: center +} + +.empty .empty-icon { + margin-bottom: .8rem +} + +.empty .empty-subtitle, +.empty .empty-title { + margin: .4rem auto +} + +.empty .empty-action { + margin-top: .8rem +} + +.menu { + background: #fff; + border-radius: .1rem; + box-shadow: 0 .05rem .2rem rgba(48, 55, 66, .3); + list-style: none; + margin: 0; + min-width: 180px; + padding: .4rem; + transform: translateY(.2rem); + z-index: 300 +} + +.menu.menu-nav { + background: 0 0; + box-shadow: none +} + +.menu .menu-item { + margin-top: 0; + padding: 0 .4rem; + position: relative; + text-decoration: none +} + +.menu .menu-item>a { + border-radius: .1rem; + color: inherit; + display: block; + margin: 0 -.4rem; + padding: .2rem .4rem; + text-decoration: none +} + +.menu .menu-item>a:focus, +.menu .menu-item>a:hover { + background: #f1f1fc; + color: #DE7061 +} + +.menu .menu-item>a.active, +.menu .menu-item>a:active { + background: #f1f1fc; + color: #DE7061 +} + +.menu .menu-item .form-checkbox, +.menu .menu-item .form-radio, +.menu .menu-item .form-switch { + margin: .1rem 0 +} + +.menu .menu-item+.menu-item { + margin-top: .2rem +} + +.menu .menu-badge { + align-items: center; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + height: 100%; + position: absolute; + right: 0; + top: 0 +} + +.menu .menu-badge .label { + margin-right: .4rem +} + +.modal { + align-items: center; + bottom: 0; + display: none; + -ms-flex-align: center; + -ms-flex-pack: center; + justify-content: center; + left: 0; + opacity: 0; + overflow: hidden; + padding: .4rem; + position: fixed; + right: 0; + top: 0 +} + +.modal.active, +.modal:target { + display: -ms-flexbox; + display: flex; + opacity: 1; + z-index: 400 +} + +.modal.active .modal-overlay, +.modal:target .modal-overlay { + background: rgba(247, 248, 249, .75); + bottom: 0; + cursor: default; + display: block; + left: 0; + position: absolute; + right: 0; + top: 0 +} + +.modal.active .modal-container, +.modal:target .modal-container { + animation: slide-down .2s ease 1; + z-index: 1 +} + +.modal.modal-sm .modal-container { + max-width: 320px; + padding: 0 .4rem +} + +.modal.modal-lg .modal-overlay { + background: #fff +} + +.modal.modal-lg .modal-container { + box-shadow: none; + max-width: 960px +} + +.modal-container { + background: #fff; + border-radius: .1rem; + box-shadow: 0 .2rem .5rem rgba(48, 55, 66, .3); + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + max-height: 75vh; + max-width: 640px; + padding: 0 .8rem; + width: 100% +} + +.modal-container.modal-fullheight { + max-height: 100vh +} + +.modal-container .modal-header { + color: #303742; + padding: .8rem +} + +.modal-container .modal-body { + overflow-y: auto; + padding: .8rem; + position: relative +} + +.modal-container .modal-footer { + padding: .8rem; + text-align: right +} + +.nav { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + list-style: none; + margin: .2rem 0 +} + +.nav .nav-item a { + color: #66758c; + padding: .2rem .4rem; + text-decoration: none +} + +.nav .nav-item a:focus, +.nav .nav-item a:hover { + color: #DE7061 +} + +.nav .nav-item.active>a { + color: #505c6e; + font-weight: 700 +} + +.nav .nav-item.active>a:focus, +.nav .nav-item.active>a:hover { + color: #DE7061 +} + +.nav .nav { + margin-bottom: .4rem; + margin-left: .8rem +} + +.pagination { + display: -ms-flexbox; + display: flex; + list-style: none; + margin: .2rem 0; + padding: .2rem 0 +} + +.pagination .page-item { + margin: .2rem .05rem +} + +.pagination .page-item span { + display: inline-block; + padding: .2rem .2rem +} + +.pagination .page-item a { + border-radius: .1rem; + display: inline-block; + padding: .2rem .4rem; + text-decoration: none +} + +.pagination .page-item a:focus, +.pagination .page-item a:hover { + color: #DE7061 +} + +.pagination .page-item.disabled a { + cursor: default; + opacity: .5; + pointer-events: none +} + +.pagination .page-item.active a { + background: #DE7061; + color: #fff +} + +.pagination .page-item.page-next, +.pagination .page-item.page-prev { + -ms-flex: 1 0 50%; + flex: 1 0 50% +} + +.pagination .page-item.page-next { + text-align: right +} + +.pagination .page-item .page-item-title { + margin: 0 +} + +.pagination .page-item .page-item-subtitle { + margin: 0; + opacity: .5 +} + +.panel { + border: .05rem solid #dadee4; + border-radius: .1rem; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column +} + +.panel .panel-footer, +.panel .panel-header { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + padding: .8rem +} + +.panel .panel-nav { + -ms-flex: 0 0 auto; + flex: 0 0 auto +} + +.panel .panel-body { + -ms-flex: 1 1 auto; + flex: 1 1 auto; + overflow-y: auto; + padding: 0 .8rem +} + +.popover { + display: inline-block; + position: relative +} + +.popover .popover-container { + left: 50%; + opacity: 0; + padding: .4rem; + position: absolute; + top: 0; + transform: translate(-50%, -50%) scale(0); + transition: transform .2s; + width: 320px; + z-index: 300 +} + +.popover :focus+.popover-container, +.popover:hover .popover-container { + display: block; + opacity: 1; + transform: translate(-50%, -100%) scale(1) +} + +.popover.popover-right .popover-container { + left: 100%; + top: 50% +} + +.popover.popover-right :focus+.popover-container, +.popover.popover-right:hover .popover-container { + transform: translate(0, -50%) scale(1) +} + +.popover.popover-bottom .popover-container { + left: 50%; + top: 100% +} + +.popover.popover-bottom :focus+.popover-container, +.popover.popover-bottom:hover .popover-container { + transform: translate(-50%, 0) scale(1) +} + +.popover.popover-left .popover-container { + left: 0; + top: 50% +} + +.popover.popover-left :focus+.popover-container, +.popover.popover-left:hover .popover-container { + transform: translate(-100%, -50%) scale(1) +} + +.popover .card { + border: 0; + box-shadow: 0 .2rem .5rem rgba(48, 55, 66, .3) +} + +.step { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + list-style: none; + margin: .2rem 0; + width: 100% +} + +.step .step-item { + -ms-flex: 1 1 0; + flex: 1 1 0; + margin-top: 0; + min-height: 1rem; + position: relative; + text-align: center +} + +.step .step-item:not(:first-child)::before { + background: #DE7061; + content: ""; + height: 2px; + left: -50%; + position: absolute; + top: 9px; + width: 100% +} + +.step .step-item a { + color: #DE7061; + display: inline-block; + padding: 20px 10px 0; + text-decoration: none +} + +.step .step-item a::before { + background: #DE7061; + border: .1rem solid #fff; + border-radius: 50%; + content: ""; + display: block; + height: .6rem; + left: 50%; + position: absolute; + top: .2rem; + transform: translateX(-50%); + width: .6rem; + z-index: 1 +} + +.step .step-item.active a::before { + background: #fff; + border: .1rem solid #DE7061 +} + +.step .step-item.active~.step-item::before { + background: #dadee4 +} + +.step .step-item.active~.step-item a { + color: #bcc3ce +} + +.step .step-item.active~.step-item a::before { + background: #dadee4 +} + +.tab { + align-items: center; + border-bottom: .05rem solid #dadee4; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + list-style: none; + margin: .2rem 0 .15rem 0 +} + +.tab .tab-item { + margin-top: 0 +} + +.tab .tab-item a { + border-bottom: .1rem solid transparent; + color: inherit; + display: block; + margin: 0 .4rem 0 0; + padding: .4rem .2rem .3rem .2rem; + text-decoration: none +} + +.tab .tab-item a:focus, +.tab .tab-item a:hover { + color: #DE7061 +} + +.tab .tab-item a.active, +.tab .tab-item.active a { + border-bottom-color: #DE7061; + color: #DE7061 +} + +.tab .tab-item.tab-action { + -ms-flex: 1 0 auto; + flex: 1 0 auto; + text-align: right +} + +.tab .tab-item .btn-clear { + margin-top: -.2rem +} + +.tab.tab-block .tab-item { + -ms-flex: 1 0 0; + flex: 1 0 0; + text-align: center +} + +.tab.tab-block .tab-item a { + margin: 0 +} + +.tab.tab-block .tab-item .badge[data-badge]::after { + position: absolute; + right: .1rem; + top: .1rem; + transform: translate(0, 0) +} + +.tab:not(.tab-block) .badge { + padding-right: 0 +} + +.tile { + align-content: space-between; + align-items: flex-start; + display: -ms-flexbox; + display: flex; + -ms-flex-align: start; + -ms-flex-line-pack: justify +} + +.tile .tile-action, +.tile .tile-icon { + -ms-flex: 0 0 auto; + flex: 0 0 auto +} + +.tile .tile-content { + -ms-flex: 1 1 auto; + flex: 1 1 auto +} + +.tile .tile-content:not(:first-child) { + padding-left: .4rem +} + +.tile .tile-content:not(:last-child) { + padding-right: .4rem +} + +.tile .tile-subtitle, +.tile .tile-title { + line-height: 1.2rem +} + +.tile.tile-centered { + align-items: center; + -ms-flex-align: center +} + +.tile.tile-centered .tile-content { + overflow: hidden +} + +.tile.tile-centered .tile-subtitle, +.tile.tile-centered .tile-title { + margin-bottom: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.toast { + background: rgba(48, 55, 66, .95); + border: .05rem solid #303742; + border-color: #303742; + border-radius: .1rem; + color: #fff; + display: block; + padding: .4rem; + width: 100% +} + +.toast.toast-primary { + background: rgba(87, 85, 217, .95); + border-color: #DE7061 +} + +.toast.toast-success { + background: rgba(50, 182, 67, .95); + border-color: #32b643 +} + +.toast.toast-warning { + background: rgba(255, 183, 0, .95); + border-color: #ffb700 +} + +.toast.toast-error { + background: rgba(232, 86, 0, .95); + border-color: #e85600 +} + +.toast a { + color: #fff; + text-decoration: underline +} + +.toast a.active, +.toast a:active, +.toast a:focus, +.toast a:hover { + opacity: .75 +} + +.toast .btn-clear { + margin: .1rem +} + +.toast p:last-child { + margin-bottom: 0 +} + +.tooltip { + position: relative +} + +.tooltip::after { + background: rgba(48, 55, 66, .95); + border-radius: .1rem; + bottom: 100%; + color: #fff; + content: attr(data-tooltip); + display: block; + font-size: .7rem; + left: 50%; + max-width: 320px; + opacity: 0; + overflow: hidden; + padding: .2rem .4rem; + pointer-events: none; + position: absolute; + text-overflow: ellipsis; + transform: translate(-50%, .4rem); + transition: opacity .2s, transform .2s; + white-space: pre; + z-index: 300 +} + +.tooltip:focus::after, +.tooltip:hover::after { + opacity: 1; + transform: translate(-50%, -.2rem) +} + +.tooltip.disabled, +.tooltip[disabled] { + pointer-events: auto +} + +.tooltip.tooltip-right::after { + bottom: 50%; + left: 100%; + transform: translate(-.2rem, 50%) +} + +.tooltip.tooltip-right:focus::after, +.tooltip.tooltip-right:hover::after { + transform: translate(.2rem, 50%) +} + +.tooltip.tooltip-bottom::after { + bottom: auto; + top: 100%; + transform: translate(-50%, -.4rem) +} + +.tooltip.tooltip-bottom:focus::after, +.tooltip.tooltip-bottom:hover::after { + transform: translate(-50%, .2rem) +} + +.tooltip.tooltip-left::after { + bottom: 50%; + left: auto; + right: 100%; + transform: translate(.4rem, 50%) +} + +.tooltip.tooltip-left:focus::after, +.tooltip.tooltip-left:hover::after { + transform: translate(-.2rem, 50%) +} + +@keyframes loading { + 0% { + transform: rotate(0) + } + + 100% { + transform: rotate(360deg) + } +} + +@keyframes slide-down { + 0% { + opacity: 0; + transform: translateY(-1.6rem) + } + + 100% { + opacity: 1; + transform: translateY(0) + } +} + +.text-primary { + color: #DE7061 !important +} + +a.text-primary:focus, +a.text-primary:hover { + color: #DE7061 +} + +a.text-primary:visited { + color: #6c6ade +} + +.text-secondary { + color: #e5e5f9 !important +} + +a.text-secondary:focus, +a.text-secondary:hover { + color: #d1d0f4 +} + +a.text-secondary:visited { + color: #fafafe +} + +.text-gray { + color: #bcc3ce !important +} + +a.text-gray:focus, +a.text-gray:hover { + color: #adb6c4 +} + +a.text-gray:visited { + color: #cbd0d9 +} + +.text-light { + color: #fff !important +} + +a.text-light:focus, +a.text-light:hover { + color: #f2f2f2 +} + +a.text-light:visited { + color: #fff +} + +.text-dark { + color: #3b4351 !important +} + +a.text-dark:focus, +a.text-dark:hover { + color: #303742 +} + +a.text-dark:visited { + color: #455060 +} + +.text-success { + color: #32b643 !important +} + +a.text-success:focus, +a.text-success:hover { + color: #2da23c +} + +a.text-success:visited { + color: #39c94b +} + +.text-warning { + color: #ffb700 !important +} + +a.text-warning:focus, +a.text-warning:hover { + color: #e6a500 +} + +a.text-warning:visited { + color: #ffbe1a +} + +.text-error { + color: #e85600 !important +} + +a.text-error:focus, +a.text-error:hover { + color: #cf4d00 +} + +a.text-error:visited { + color: #ff6003 +} + +.bg-primary { + background: #DE7061 !important; + color: #fff +} + +.bg-secondary { + background: #e3e3e3 !important +} + +.bg-dark { + background: #303742 !important; + color: #fff +} + +.bg-gray { + background: #f7f8f9 !important +} + +.bg-success { + background: #32b643 !important; + color: #fff +} + +.bg-warning { + background: #ffb700 !important; + color: #fff +} + +.bg-error { + background: #e85600 !important; + color: #fff +} + +.c-hand { + cursor: pointer +} + +.c-move { + cursor: move +} + +.c-zoom-in { + cursor: zoom-in +} + +.c-zoom-out { + cursor: zoom-out +} + +.c-not-allowed { + cursor: not-allowed +} + +.c-auto { + cursor: auto +} + +.d-block { + display: block +} + +.d-inline { + display: inline +} + +.d-inline-block { + display: inline-block +} + +.d-flex { + display: -ms-flexbox; + display: flex +} + +.d-inline-flex { + display: -ms-inline-flexbox; + display: inline-flex +} + +.d-hide, +.d-none { + display: none !important +} + +.d-visible { + visibility: visible +} + +.d-invisible { + visibility: hidden +} + +.text-hide { + background: 0 0; + border: 0; + color: transparent; + font-size: 0; + line-height: 0; + text-shadow: none +} + +.text-assistive { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px +} + +.divider, +.divider-vert { + display: block; + position: relative +} + +.divider-vert[data-content]::after, +.divider[data-content]::after { + background: #fff; + color: #bcc3ce; + content: attr(data-content); + display: inline-block; + font-size: .7rem; + padding: 0 .4rem; + transform: translateY(-.65rem) +} + +.divider { + border-top: .05rem solid #f1f3f5; + height: .05rem; + margin: .4rem 0 +} + +.divider[data-content] { + margin: .8rem 0 +} + +.divider-vert { + display: block; + padding: .8rem +} + +.divider-vert::before { + border-left: .05rem solid #dadee4; + bottom: .4rem; + content: ""; + display: block; + left: 50%; + position: absolute; + top: .4rem; + transform: translateX(-50%) +} + +.divider-vert[data-content]::after { + left: 50%; + padding: .2rem 0; + position: absolute; + top: 50%; + transform: translate(-50%, -50%) +} + +.loading { + color: transparent !important; + min-height: .8rem; + pointer-events: none; + position: relative +} + +.loading::after { + animation: loading .5s infinite linear; + background: 0 0; + border: .1rem solid #DE7061; + border-radius: 50%; + border-right-color: transparent; + border-top-color: transparent; + content: ""; + display: block; + height: .8rem; + left: 50%; + margin-left: -.4rem; + margin-top: -.4rem; + opacity: 1; + padding: 0; + position: absolute; + top: 50%; + width: .8rem; + z-index: 1 +} + +.loading.loading-lg { + min-height: 2rem +} + +.loading.loading-lg::after { + height: 1.6rem; + margin-left: -.8rem; + margin-top: -.8rem; + width: 1.6rem +} + +.clearfix::after { + clear: both; + content: ""; + display: table +} + +.float-left { + float: left !important +} + +.float-right { + float: right !important +} + +.p-relative { + position: relative !important +} + +.p-absolute { + position: absolute !important +} + +.p-fixed { + position: fixed !important +} + +.p-sticky { + position: -webkit-sticky !important; + position: sticky !important +} + +.p-centered { + display: block; + float: none; + margin-left: auto; + margin-right: auto +} + +.flex-centered { + align-items: center; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + -ms-flex-pack: center; + justify-content: center +} + +.m-0 { + margin: 0 !important +} + +.mb-0 { + margin-bottom: 0 !important +} + +.ml-0 { + margin-left: 0 !important +} + +.mr-0 { + margin-right: 0 !important +} + +.mt-0 { + margin-top: 0 !important +} + +.mx-0 { + margin-left: 0 !important; + margin-right: 0 !important +} + +.my-0 { + margin-bottom: 0 !important; + margin-top: 0 !important +} + +.m-1 { + margin: .2rem !important +} + +.mb-1 { + margin-bottom: .2rem !important +} + +.ml-1 { + margin-left: .2rem !important +} + +.mr-1 { + margin-right: .2rem !important +} + +.mt-1 { + margin-top: .2rem !important +} + +.mx-1 { + margin-left: .2rem !important; + margin-right: .2rem !important +} + +.my-1 { + margin-bottom: .2rem !important; + margin-top: .2rem !important +} + +.m-2 { + margin: .4rem !important +} + +.mb-2 { + margin-bottom: .4rem !important +} + +.ml-2 { + margin-left: .4rem !important +} + +.mr-2 { + margin-right: .4rem !important +} + +.mt-2 { + margin-top: .4rem !important +} + +.mx-2 { + margin-left: .4rem !important; + margin-right: .4rem !important +} + +.my-2 { + margin-bottom: .4rem !important; + margin-top: .4rem !important +} + +.p-0 { + padding: 0 !important +} + +.pb-0 { + padding-bottom: 0 !important +} + +.pl-0 { + padding-left: 0 !important +} + +.pr-0 { + padding-right: 0 !important +} + +.pt-0 { + padding-top: 0 !important +} + +.px-0 { + padding-left: 0 !important; + padding-right: 0 !important +} + +.py-0 { + padding-bottom: 0 !important; + padding-top: 0 !important +} + +.p-1 { + padding: .2rem !important +} + +.pb-1 { + padding-bottom: .2rem !important +} + +.pl-1 { + padding-left: .2rem !important +} + +.pr-1 { + padding-right: .2rem !important +} + +.pt-1 { + padding-top: .2rem !important +} + +.px-1 { + padding-left: .2rem !important; + padding-right: .2rem !important +} + +.py-1 { + padding-bottom: .2rem !important; + padding-top: .2rem !important +} + +.p-2 { + padding: .4rem !important +} + +.pb-2 { + padding-bottom: .4rem !important +} + +.pl-2 { + padding-left: .4rem !important +} + +.pr-2 { + padding-right: .4rem !important +} + +.pt-2 { + padding-top: .4rem !important +} + +.px-2 { + padding-left: .4rem !important; + padding-right: .4rem !important +} + +.py-2 { + padding-bottom: .4rem !important; + padding-top: .4rem !important +} + +.s-rounded { + border-radius: .1rem +} + +.s-circle { + border-radius: 50% +} + +.text-left { + text-align: left +} + +.text-right { + text-align: right +} + +.text-center { + text-align: center +} + +.text-justify { + text-align: justify +} + +.text-lowercase { + text-transform: lowercase +} + +.text-uppercase { + text-transform: uppercase +} + +.text-capitalize { + text-transform: capitalize +} + +.text-normal { + font-weight: 400 +} + +.text-bold { + font-weight: 700 +} + +.text-italic { + font-style: italic +} + +.text-large { + font-size: 1.2em +} + +.text-small { + font-size: .9em +} + +.text-tiny { + font-size: .8em +} + +.text-muted { + opacity: .8 +} + +.text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.text-clip { + overflow: hidden; + text-overflow: clip; + white-space: nowrap +} + +.text-break { + -webkit-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto; + word-break: break-word; + word-wrap: break-word +} \ No newline at end of file diff --git a/sledge/sledgeboard/resource/style.css b/sledge/sledgeboard/resource/style.css new file mode 100644 index 0000000..e1c806e --- /dev/null +++ b/sledge/sledgeboard/resource/style.css @@ -0,0 +1,274 @@ +body { + font-family: "Open Sans", sans-serif; +} + +.overlay { + position: fixed; + /* Positioning and size */ + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(128, 128, 128, 0.5); + /* color */ + display: none; + /* making it hidden by default */ + opacity: 0.5; + z-index: 2000; +} + +.ellis-red-text { + color: #de7061; +} + +#main { + background: #f0f2f5; + width: 100%; +} + +#file-header { + margin-top: 10px; +} + +.tab-container { + margin-left: 15rem; +} + +.tab-content { + display: none; + padding: 6px 12px; + height: 100vh; + overflow-x: hidden; + overflow-y: auto; +} + +.full-page { + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + flex: 1; + overflow-x: auto; + overflow-y: hidden; +} + +.navbar { + width: 17rem; + height: 100%; + position: fixed; + margin-left: 0; + z-index: 1200; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + transition: all 0.5s ease; + word-break: break-all; + border-right: 1px solid rgba(191, 191, 191, 0.6); +} + +.nav { + width: 100%; +} + +.nav-last-item { + padding-bottom: 15px; + border-bottom: 1px solid rgba(191, 191, 191, 0.6); +} + +.experiment-file-path-item { + margin-top: 10px; +} + +.experiment-file-path-checkbox-group { + width: 95% !important; + padding: 5px !important; + overflow-y: auto; + overflow-x: hidden; + height: 100% !important; +} + +.experiment-file-path-item .bk-root { + height: 90% !important; +} + +.bk-root .bk-input-group { + word-wrap: break-word; + word-break: break-all; + overflow-wrap: break-word; + white-space: normal; +} + +.flex-1 { + flex: 1; +} + +.section-table-title { + font-size: 1.1em; +} + +.navbar-title { + font-size: 1.4em !important; +} + +.z-index-2 { + z-index: 2; +} + +.header-bar-tab { + display: flex; +} + +.header-bar-tab .sidebar-menu-button { + border: none; + background-color: transparent; +} + +.header-bar-tab .sidebar-menu-icon { + width: 24px; + height: 24px; + color: white; +} + +.sledgeboard-version { + bottom: 5%; + right: 20%; + position: absolute; + font-size: 16px; +} + +.file-header { + display: flex; + align-items: center; +} + +.sidebar-file-path-btn { + width: 4rem; + height: 3.0rem; + border: 0.05rem solid #DE7061; + vertical-align: middle; +} + +.sidebar-file-path-remove>.bk-root { + display: none; +} + +.file-path-input input[type="file"] { + display: none; +} + + +.sidebar-file-path-selection { + padding-left: 2px; + padding-right: 1px; +} + +.file-path-selection { + width: 100% !important; + margin: 0 !important; +} + +.search-section { + font-size: 14px; + height: 80px; + width: 95%; + display: flex; + align-items: flex-start; +} + +.search-grid-item-text { + padding: 30px 0; +} + +.search-grid-item-widget { + padding: 10px; + width: 45%; +} + + +.metric-name-multi-choice { + width: auto !important; + background: #fff; +} + +.metric-name-multi-choice .choices__inner { + overflow: auto; + height: 50px; +} + +.planner-checkbox-group { + width: 80%; + min-width: 65%; + max-width: 80%; +} + +.bk-inline label { + margin-left: 8px !important; +} + +.bk-root .slick-header.ui-state-default { + border: unset !important; +} + +#menu-icon-container { + display: flex; + align-items: center; + height: 100%; +} + +#menu-icon { + margin: 0 0.5rem; +} + +.expandable-arrow-svg { + fill: currentColor; + width: 1em; + height: 1em; + display: inline-block; + font-size: 1.5rem; + transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + flex-shrink: 0; + user-select: none; + vertical-align: middle; + justify-content: center; + align-items: center; + font-size: 18px; +} + +.margin-left-5 { + margin-left: 5px; +} + +.bk-tooltip { + pointer-events: auto !important; +} + +.bk-tooltip:hover { + display: block !important; +} + +.modal-header-panel { + color: #fff; + background-color: #de7061; + border-color: #de7061; +} + +.modal-body { + max-height: calc(100vh - 300px); + overflow-y: auto; +} + +.modal-header button.close { + color: #FFFFFF; +} + +.modal-btn { + height: unset; +} + +.modal-footer-btn-section .bk-root { + display: inline-block; +} + +.white-space-break { + white-space: break-spaces; +} \ No newline at end of file diff --git a/sledge/sledgeboard/sledgeboard.py b/sledge/sledgeboard/sledgeboard.py new file mode 100644 index 0000000..19b4a92 --- /dev/null +++ b/sledge/sledgeboard/sledgeboard.py @@ -0,0 +1,181 @@ +import logging +import os +import signal +from pathlib import Path +from typing import Any, List, Optional + +import jinja2 +from bokeh.application import Application +from bokeh.application.handlers import FunctionHandler +from bokeh.document.document import Document +from bokeh.server.server import Server +from tornado.ioloop import IOLoop +from tornado.web import StaticFileHandler + +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters +from nuplan.planning.scenario_builder.abstract_scenario_builder import AbstractScenarioBuilder +from nuplan.planning.training.callbacks.profile_callback import ProfileCallback + +from sledge.sledgeboard.base.experiment_file_data import ExperimentFileData +from sledge.sledgeboard.tabs.cloud_tab import CloudTab +from sledge.sledgeboard.tabs.configuration_tab import ConfigurationTab +from sledge.sledgeboard.tabs.histogram_tab import HistogramTab +from sledge.sledgeboard.tabs.overview_tab import OverviewTab +from sledge.sledgeboard.tabs.scenario_tab import ScenarioTab +from sledge.sledgeboard.utils.utils import check_sledgeboard_file_paths, read_sledgeboard_file_paths + +logger = logging.getLogger(__name__) + + +class SledgeBoard: + """SledgeBoard application class.""" + + def __init__( + self, + sledgeboard_paths: List[str], + scenario_builder: AbstractScenarioBuilder, + vehicle_parameters: VehicleParameters, + port_number: int = 5006, + profiler_path: Optional[Path] = None, + resource_prefix: Optional[str] = None, + async_scenario_rendering: bool = True, + scenario_rendering_frame_rate_cap_hz: int = 60, + ): + """ + SledgeBoard main class. + :param sledge_paths: A list of paths to sledgeboard files. + :param scenario_builder: Scenario builder instance. + :param vehicle_parameters: vehicle parameters. + :param port_number: Bokeh port number. + :param profiler_path: Path to save the profiler. + :param resource_prefix: Prefix to the resource path in HTML. + :param async_scenario_rendering: Whether to use asynchronous scenario rendering in the scenario tab. + :param scenario_rendering_frame_rate_cap_hz: Maximum frames to render in the scenario tab per second. + Use lower values when running SledgeBoard in the cloud to prevent frame queues due to latency. The rule of thumb + is to match the frame rate with the expected latency, e.g 5Hz for 200ms round-trip latency. + Internally this value is capped at 60. + """ + self._profiler_path = profiler_path + self._sledgeboard_paths = check_sledgeboard_file_paths(sledgeboard_paths) + self._scenario_builder = scenario_builder + self._port_number = port_number + self._vehicle_parameters = vehicle_parameters + self._doc: Optional[Document] = None + self._resource_prefix = resource_prefix if resource_prefix else "" + self._resource_path = Path(__file__).parents[0] / "resource" + self._profiler_file_name = "sledgeboard" + self._profiler: Optional[ProfileCallback] = None + self._async_scenario_rendering = async_scenario_rendering + + # We shouldn't render more frequently than 60Hz to reduce frame lag, even on local instances. + if scenario_rendering_frame_rate_cap_hz < 1 or scenario_rendering_frame_rate_cap_hz > 60: + raise ValueError("scenario_rendering_frame_rate_cap_hz should be between 1 and 60") + + self._scenario_rendering_frame_rate_cap_hz = scenario_rendering_frame_rate_cap_hz + + def stop_handler(self, sig: Any, frame: Any) -> None: + """Helper to handle stop signals.""" + logger.info("Stopping the Bokeh application.") + if self._profiler: + self._profiler.save_profiler(self._profiler_file_name) + IOLoop.current().stop() + + def run(self) -> None: + """Run SledgeBoard WebApp.""" + logger.info(f"Opening Bokeh application on http://localhost:{self._port_number}/") + logger.info(f"Async rendering is set to: {self._async_scenario_rendering}") + + io_loop = IOLoop.current() + + # Add stopping signal + # TODO: Extract profiler to a more general interface + if self._profiler_path is not None: + signal.signal(signal.SIGTERM, self.stop_handler) + signal.signal(signal.SIGINT, self.stop_handler) + + # Add profiler + self._profiler = ProfileCallback(output_dir=self._profiler_path) + self._profiler.start_profiler(self._profiler_file_name) + + bokeh_app = Application(FunctionHandler(self.main_page)) + server = Server( + {"/": bokeh_app}, + io_loop=io_loop, + port=self._port_number, + allow_websocket_origin=["*"], + extra_patterns=[(r"/resource/(.*)", StaticFileHandler, {"path": str(self._resource_path)})], + ) + server.start() + + io_loop.add_callback(server.show, "/") + # Catch RuntimeError in jupyter notebook + try: + io_loop.start() + except RuntimeError as e: + logger.warning(f"{e}") + + def main_page(self, doc: Document) -> None: + """ + Main SledgeBoard page. + :param doc: HTML document. + """ + self._doc = doc + template_path = Path(os.path.dirname(os.path.realpath(__file__))) / "templates" + env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_path)) + self._doc.template = env.get_template("index.html") + self._doc.title = "SledgeBoard" + sledgeboard_files = read_sledgeboard_file_paths(file_paths=self._sledgeboard_paths) + experiment_file_data = ExperimentFileData(file_paths=sledgeboard_files) + overview_tab = OverviewTab(doc=self._doc, experiment_file_data=experiment_file_data) + histogram_tab = HistogramTab(doc=self._doc, experiment_file_data=experiment_file_data) + scenario_tab = ScenarioTab( + experiment_file_data=experiment_file_data, + scenario_builder=self._scenario_builder, + doc=self._doc, + vehicle_parameters=self._vehicle_parameters, + async_rendering=self._async_scenario_rendering, + frame_rate_cap_hz=self._scenario_rendering_frame_rate_cap_hz, + ) + configuration_tab = ConfigurationTab( + experiment_file_data=experiment_file_data, doc=self._doc, tabs=[overview_tab, histogram_tab, scenario_tab] + ) + s3_tab = CloudTab(doc=self._doc, configuration_tab=configuration_tab) + + self._doc.add_root(configuration_tab.file_path_input) + self._doc.add_root(configuration_tab.experiment_file_path_checkbox_group) + + self._doc.add_root(s3_tab.s3_bucket_name) + self._doc.add_root(s3_tab.s3_bucket_text_input) + self._doc.add_root(s3_tab.s3_error_text) + self._doc.add_root(s3_tab.s3_access_key_id_text_input) + self._doc.add_root(s3_tab.s3_secret_access_key_password_input) + self._doc.add_root(s3_tab.s3_bucket_prefix_text_input) + self._doc.add_root(s3_tab.s3_modal_query_btn) + self._doc.add_root(s3_tab.s3_download_text_input) + self._doc.add_root(s3_tab.s3_download_button) + self._doc.add_root(s3_tab.data_table) + + self._doc.add_root(overview_tab.table) + self._doc.add_root(overview_tab.planner_checkbox_group) + + self._doc.add_root(histogram_tab.scenario_type_multi_choice) + self._doc.add_root(histogram_tab.metric_name_multi_choice) + self._doc.add_root(histogram_tab.planner_checkbox_group) + self._doc.add_root(histogram_tab.histogram_plots) + self._doc.add_root(histogram_tab.bin_spinner) + self._doc.add_root(histogram_tab.histogram_modal_query_btn) + + self._doc.add_root(scenario_tab.planner_checkbox_group) + self._doc.add_root(scenario_tab.scenario_title_div) + self._doc.add_root(scenario_tab.object_checkbox_group) + self._doc.add_root(scenario_tab.traj_checkbox_group) + self._doc.add_root(scenario_tab.map_checkbox_group) + self._doc.add_root(scenario_tab.scalar_scenario_type_select) + self._doc.add_root(scenario_tab.scalar_log_name_select) + self._doc.add_root(scenario_tab.scalar_scenario_name_select) + self._doc.add_root(scenario_tab.scenario_token_multi_choice) + self._doc.add_root(scenario_tab.scenario_modal_query_btn) + self._doc.add_root(scenario_tab.time_series_layout) + self._doc.add_root(scenario_tab.ego_expert_states_layout) + self._doc.add_root(scenario_tab.scenario_score_layout) + self._doc.add_root(scenario_tab.simulation_tile_layout) diff --git a/sledge/sledgeboard/style.py b/sledge/sledgeboard/style.py new file mode 100644 index 0000000..78f471f --- /dev/null +++ b/sledge/sledgeboard/style.py @@ -0,0 +1,202 @@ +from typing import Any, Dict + +from nuplan.common.maps.abstract_map import SemanticMapLayer + +PLOT_PALETTE: Dict[str, str] = { + "font_lavender_gray": "#adb9e3", + "font_white": "#c9eaf1", + "background_white": "#fafafa", + "background_black": "#000000", + "chart_green": "#00FF73", + "chart_yellow": "#E2FF1A", +} + +default_data_table: Dict[str, Any] = {"row_height": 80} + +default_multi_choice_style: Dict[str, Any] = {"option_limit": 10} + +default_spinner_style: Dict[str, Any] = {"width": 300, "low": 1} +default_div_style: Dict[str, Any] = {"margin": (5, 5, 5, 30), "width": 800} +base_tab_style: Dict[str, Any] = { + "plot_sizes": (350, 300), + "plot_frame_sizes": (1200, 1200), + "search_criteria_sizes": (80, 80), +} + +simulation_tile_style: Dict[str, Any] = { + "figure_sizes": (500, 500), + "render_figure_sizes": (1500, 1500), + "figure_margins": [5, 40, 0, 30], + "figure_title_text_font_size": "10pt", + "video_button_margins": [5, 40, 5, 35], # Top, right, bottom, left + "frame_control_button_margins": [5, 19, 5, 35], # Top, right, bottom, left + "slider_margins": [5, 40, 0, 30], + "background_color": "#FFFFFF", + "mission_goal_color": "#00FF00", + "mission_goal_alpha": 0.0, + "mission_goal_line_width": 2, + "decimal_points": 2, +} + +render_style: Dict[str, Any] = { + "figure_sizes": (500, 500), + "render_figure_sizes": (500, 500), + "figure_margins": [5, 40, 0, 30], + "figure_title_text_font_size": "10pt", + "video_button_margins": [5, 40, 5, 35], # Top, right, bottom, left + "frame_control_button_margins": [5, 19, 5, 35], # Top, right, bottom, left + "slider_margins": [5, 40, 0, 30], + "background_color": "#FFFFFF", + "mission_goal_color": "#00FF00", + "mission_goal_alpha": 0.0, + "mission_goal_line_width": 2, + "decimal_points": 2, +} + +simulation_tile_trajectory_style: Dict[str, Any] = { + "ego": {"line_color": "#B0E685", "line_alpha": 0.0, "line_width": 3}, + "expert_ego": {"line_color": "#de7b6c", "line_alpha": 0.0, "line_width": 0}, +} + +simulation_tile_agent_style: Dict[str, Any] = { + "ego": { + "fill_color": "#de7b6c", + "fill_alpha": 1.0, + "line_color": "#000000", + "line_width": 3, + }, + "vehicles": { + "fill_color": "#699cdb", + "fill_alpha": 1.0, + "line_color": "#000000", + "line_width": 3, + }, + "pedestrians": { + "fill_color": "#b07aa1", + "fill_alpha": 1.0, + "line_color": "#000000", + "line_width": 3, + }, + "bicycles": { + "fill_color": "#699CDB", + "fill_alpha": 1.0, + "line_color": "#000000", + "line_width": 3, + }, + "genericobjects": { + "fill_color": "#edc948", + "fill_alpha": 1.0, + "line_color": "#000000", + "line_width": 3, + }, + "traffic_cone": { + "fill_color": "#edc948", + "fill_alpha": 0.5, + "line_color": "#000000", + "line_width": 3, + }, + "barrier": { + "fill_color": "#edc948", + "fill_alpha": 0.5, + "line_color": "#000000", + "line_width": 3, + }, + "czone_sign": { + "fill_color": "#edc948", + "fill_alpha": 0.5, + "line_color": "#000000", + "line_width": 3, + }, +} + +simulation_map_layer_color: Dict[SemanticMapLayer, Any] = { + SemanticMapLayer.LANE: { + "fill_color": "#D3D3D3", + "fill_color_alpha": 1.0, + "line_color": "#000000", + "line_width": 0.0, + }, + SemanticMapLayer.WALKWAYS: { + "fill_color": "#699CDB", + "fill_color_alpha": 0.0, + "line_color": "#7e772e", + "line_width": 0.0, + }, + SemanticMapLayer.CARPARK_AREA: { + "fill_color": "#ff7f00", + "fill_color_alpha": 0.0, + "line_color": "#ff7f00", + "line_width": 0.0, + }, + SemanticMapLayer.PUDO: { + "fill_color": "#AF75A7", + "fill_color_alpha": 0.0, + "line_color": "#AF75A7", + "line_width": 0.0, + }, + SemanticMapLayer.INTERSECTION: { + "fill_color": "#D3D3D3", + "fill_color_alpha": 1.0, + "line_color": "#000000", + "line_width": 0.0, + }, + SemanticMapLayer.STOP_LINE: { + "fill_color": "#FF0101", + "fill_color_alpha": 0.0, + "line_color": "#FF0101", + "line_width": 0.0, + }, + SemanticMapLayer.CROSSWALK: { + "fill_color": "#B5B5B5", + "fill_color_alpha": 0.0, + "line_color": "#B5B5B5", + "line_width": 0.0, + }, + SemanticMapLayer.ROADBLOCK: { + "fill_color": "#0000C0", + "fill_color_alpha": 0.0, + "line_color": "#0000C0", + "line_width": 0.0, + }, + SemanticMapLayer.BASELINE_PATHS: { + "line_color": "#666666", + "line_color_alpha": 1.0, + "line_width": 3.0, + }, + SemanticMapLayer.LANE_CONNECTOR: { + "line_color": "#666666", + "line_color_alpha": 1.0, + "line_width": 3.0, + }, +} + +configuration_tab_style: Dict[str, Any] = { + "file_path_input_margin": [15, 0, 50, 30], + "folder_path_selection_margin": [12, 0, 0, 6], + "main_board_layout_height": 600, +} + +overview_tab_style: Dict[str, Any] = { + "table_margins": [20, 0, 0, 50], + "table_width": 800, + "table_height": 300, + "scatter_size": 10, + "statistic_figure_margin": [10, 20, 20, 30], + "plot_legend_background_fill_alpha": 0.3, + "plot_grid_line_color": "white", + "overview_title_div_margin": [10, 20, 20, 30], +} + +scenario_tab_style: Dict[str, Any] = { + "default_div_width": 800, + "col_offset_width": 400, + "ego_expert_state_figure_sizes": [500, 250], + "scenario_metric_score_figure_sizes": [400, 350], + "time_series_figure_margins": [10, 20, 20, 30], + "time_series_figure_title_text_font_size": "0.9em", + "time_series_figure_xaxis_axis_label_text_font_size": "0.8em", + "time_series_figure_xaxis_major_label_text_font_size": "0.8em", + "time_series_figure_yaxis_axis_label_text_font_size": "0.8em", + "time_series_figure_yaxis_major_label_text_font_size": "0.8em", + "plot_legend_label_text_font_size": "0.7em", +} diff --git a/sledge/sledgeboard/tabs/__init__.py b/sledge/sledgeboard/tabs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/sledgeboard/tabs/cloud_tab.py b/sledge/sledgeboard/tabs/cloud_tab.py new file mode 100644 index 0000000..fc5b727 --- /dev/null +++ b/sledge/sledgeboard/tabs/cloud_tab.py @@ -0,0 +1,336 @@ +import hashlib +import logging +import os +import time +from functools import partial +from pathlib import Path +from typing import Any, Dict, List, Optional + +import boto3 +from bokeh.document.document import Document +from bokeh.models import Button, ColumnDataSource, DataTable, Div, PasswordInput, TableColumn, TextInput +from boto3.exceptions import Boto3Error + +from nuplan.common.utils.s3_utils import get_s3_client +from sledge.sledgeboard.tabs.config.cloud_tab_config import ( + S3TabBucketNameConfig, + S3TabBucketTextInputConfig, + S3TabDataTableConfig, + S3TabDownloadButtonConfig, + S3TabDownloadTextInputConfig, + S3TabErrorTextConfig, + S3TabLastModifiedColumnConfig, + S3TabObjectColumnConfig, + S3TabS3AccessKeyIDTextInputConfig, + S3TabS3BucketPrefixTextInputConfig, + S3TabS3ModalQueryButtonConfig, + S3TabS3SecretAccessKeyPasswordTextInputConfig, + S3TabSizeColumnConfig, + S3TabTimeStampColumnConfig, +) +from sledge.sledgeboard.tabs.configuration_tab import ConfigurationTab +from sledge.sledgeboard.tabs.js_code.cloud_tab_js_code import ( + S3TabContentDataSourceOnSelected, + S3TabContentDataSourceOnSelectedLoadingJSCode, + S3TabDataTableUpdateJSCode, + S3TabDownloadUpdateJSCode, + S3TabLoadingJSCode, +) +from sledge.sledgeboard.utils.sledgeboard_cloud_utils import ( + S3FileResultMessage, + S3SledgeBoardFileResultMessage, + check_s3_sledgeboard_files, + download_s3_file, + download_s3_path, + get_s3_file_contents, +) + +logger = logging.getLogger(__name__) + + +class CloudTab: + """Cloud tab in uboard.""" + + def __init__(self, doc: Document, configuration_tab: ConfigurationTab, s3_bucket: Optional[str] = ''): + """ + Cloud tab for remote connection features. + :param doc: Bokeh HTML document. + :param configuration_tab: Configuration tab. + :param s3_bucket: Aws s3 bucket name. + """ + self._doc = doc + self._configuration_tab = configuration_tab + self._nuplan_exp_root = os.getenv('NUPLAN_EXP_ROOT', None) + assert self._nuplan_exp_root is not None, "Please set environment variable: NUPLAN_EXP_ROOT!" + download_path = Path(self._nuplan_exp_root) + download_path.mkdir(parents=True, exist_ok=True) + + # Data source + self._default_datasource_dict = dict( + object=['-'], + last_modified=['-'], + timestamp=['-'], + size=['-'], + ) + self._s3_content_datasource = ColumnDataSource(data=self._default_datasource_dict) + self._selected_column = TextInput() + self._selected_row = TextInput() + + self.s3_bucket_name = Div(**S3TabBucketNameConfig.get_config()) + self.s3_bucket_name.js_on_change('text', S3TabDataTableUpdateJSCode.get_js_code()) + + self.s3_error_text = Div(**S3TabErrorTextConfig.get_config()) + self.s3_download_text_input = TextInput(**S3TabDownloadTextInputConfig.get_config()) + + self.s3_download_button = Button(**S3TabDownloadButtonConfig.get_config()) + self.s3_download_button.on_click(self._s3_download_button_on_click) + self.s3_download_button.js_on_click(S3TabLoadingJSCode.get_js_code()) + self.s3_download_button.js_on_change('disabled', S3TabDownloadUpdateJSCode.get_js_code()) + + self.s3_bucket_text_input = TextInput(**S3TabBucketTextInputConfig.get_config(), value=s3_bucket) + self.s3_access_key_id_text_input = TextInput(**S3TabS3AccessKeyIDTextInputConfig.get_config()) + self.s3_secret_access_key_password_input = PasswordInput( + **S3TabS3SecretAccessKeyPasswordTextInputConfig.get_config() + ) + self.s3_bucket_prefix_text_input = TextInput(**S3TabS3BucketPrefixTextInputConfig.get_config()) + + self.s3_modal_query_btn = Button(**S3TabS3ModalQueryButtonConfig.get_config()) + self.s3_modal_query_btn.on_click(self._s3_modal_query_on_click) + self.s3_modal_query_btn.js_on_click(S3TabLoadingJSCode.get_js_code()) + self._default_columns = [ + TableColumn(**S3TabObjectColumnConfig.get_config()), + TableColumn(**S3TabLastModifiedColumnConfig.get_config()), + TableColumn(**S3TabTimeStampColumnConfig.get_config()), + TableColumn(**S3TabSizeColumnConfig.get_config()), + ] + + self._s3_content_datasource = ColumnDataSource(data=self._default_datasource_dict) + self._s3_content_datasource.js_on_change('data', S3TabDataTableUpdateJSCode.get_js_code()) + self._s3_content_datasource.selected.js_on_change( + 'indices', + S3TabContentDataSourceOnSelected.get_js_code( + selected_column=self._selected_column, selected_row=self._selected_row + ), + ) + self._s3_content_datasource.selected.js_on_change( + 'indices', + S3TabContentDataSourceOnSelectedLoadingJSCode.get_js_code( + source=self._s3_content_datasource, selected_column=self._selected_column + ), + ) + self._s3_content_datasource.selected.on_change('indices', self._s3_data_source_on_selected) + self.data_table = DataTable( + source=self._s3_content_datasource, columns=self._default_columns, **S3TabDataTableConfig.get_config() + ) + + self._s3_client: Optional[boto3.client] = None + if s3_bucket: + self._update_blob_store(s3_bucket=s3_bucket, s3_prefix='') + + def _update_blob_store(self, s3_bucket: str, s3_prefix: str = '') -> None: + """ + :param s3_bucket: + :param s3_prefix: + """ + # Update s3 client + aws_profile_name = bytes( + self.s3_access_key_id_text_input.value + self.s3_secret_access_key_password_input.value, encoding='utf-8' + ) + hash_md5 = hashlib.md5(aws_profile_name) + profile = hash_md5.hexdigest() + self._s3_client = get_s3_client( + aws_access_key_id=self.s3_access_key_id_text_input.value, + aws_secret_access_key=self.s3_secret_access_key_password_input.value, + profile_name=profile, + ) + s3_path = os.path.join(s3_bucket, s3_prefix) + s3_file_result_message = get_s3_file_contents( + s3_path=s3_path, include_previous_folder=True, client=self._s3_client + ) + self._load_s3_contents(s3_file_result_message=s3_file_result_message) + self.s3_error_text.text = s3_file_result_message.s3_connection_status.return_message + if s3_file_result_message.s3_connection_status.success: + self.s3_bucket_name.text = s3_bucket + + def _s3_modal_query_on_click(self) -> None: + """On click function for modal query button.""" + self._update_blob_store( + s3_bucket=self.s3_bucket_text_input.value, s3_prefix=self.s3_bucket_prefix_text_input.value + ) + + def _s3_data_source_on_selected(self, attr: str, old: List[int], new: List[int]) -> None: + """Helper function when select a row in data source.""" + # Cancel history so that it works if users click on the same row again + if not new: + return + row_index = new[0] + self._s3_content_datasource.selected.update(indices=[]) + + # If users click on the object column + column_index = int(self._selected_column.value) + s3_prefix = self.data_table.source.data['object'][row_index] + if column_index == 0: + if not s3_prefix or s3_prefix == '-': + return + # Return to previous folder + if '..' in s3_prefix: + s3_prefix = Path(s3_prefix).parents[1].name + self._update_blob_store(s3_bucket=self.s3_bucket_text_input.value, s3_prefix=s3_prefix) + else: + if '..' in s3_prefix or '-' == s3_prefix: + return + self.s3_download_text_input.value = s3_prefix + + def _update_data_table_source(self, data_sources: Dict[str, List[Any]]) -> None: + """Update data table source.""" + self.data_table.source.data = data_sources + + def _load_s3_contents(self, s3_file_result_message: S3FileResultMessage) -> None: + """ + Load s3 contents into a data table. + :param s3_file_result_message: File content and return messages from s3 connection. + """ + file_contents = s3_file_result_message.file_contents + # If return fail or less than 1 files (only '..' is added to the list, which means no files exist) + if not s3_file_result_message.s3_connection_status.success or len(s3_file_result_message.file_contents) <= 1: + default_data_sources = self._default_datasource_dict + self._doc.add_next_tick_callback(partial(self._update_data_table_source, data_sources=default_data_sources)) + else: + data_sources: Dict[str, List[Any]] = {'object': [], 'last_modified': [], 'timestamp': [], 'size': []} + for file_name, content in file_contents.items(): + data_sources['object'].append(file_name) + data_sources['last_modified'].append( + content.last_modified_day if content.last_modified is not None else '' + ) + data_sources['timestamp'].append(content.date_string if content.date_string is not None else '') + data_sources['size'].append(content.kb_size() if content.kb_size() is not None else '') + self._doc.add_next_tick_callback(partial(self._update_data_table_source, data_sources=data_sources)) + + def _reset_s3_download_button(self) -> None: + """Reset s3 download button.""" + self.s3_download_button.label = 'Download' + self.s3_download_button.disabled = False + self.s3_download_text_input.disabled = False + + def _update_error_text_label(self, text: str) -> None: + """Update error text message in a sequential manner.""" + self.s3_error_text.text = text + + def _s3_download_prefixes(self) -> None: + """Download s3 prefixes and update progress in a sequential manner.""" + try: + start_time = time.perf_counter() + if not self._s3_client: + raise Boto3Error("No s3 connection!") + selected_s3_bucket = str(self.s3_bucket_name.text).strip() + selected_s3_prefix = str(self.s3_download_text_input.value).strip() + selected_s3_path = os.path.join(selected_s3_bucket, selected_s3_prefix) + + s3_result_file_contents = get_s3_file_contents( + s3_path=selected_s3_path, client=self._s3_client, include_previous_folder=False + ) + s3_sledgeboard_file_result = check_s3_sledgeboard_files( + s3_result_file_contents.file_contents, s3_client=self._s3_client, s3_path=selected_s3_path + ) + if not s3_sledgeboard_file_result.s3_connection_status.success: + raise Boto3Error(s3_sledgeboard_file_result.s3_connection_status.return_message) + + if not s3_result_file_contents.file_contents: + raise Boto3Error(f"No objects exist in the path: {selected_s3_path}") + + self._download_s3_file_contents( + s3_result_file_contents=s3_result_file_contents, selected_s3_bucket=selected_s3_bucket + ) + self._update_s3_sledgeboard_file_main_path( + s3_sledgeboard_file_result=s3_sledgeboard_file_result, selected_prefix=selected_s3_prefix + ) + end_time = time.perf_counter() + elapsed_time = end_time - start_time + successful_message = f'Downloaded to {self._nuplan_exp_root} and took {elapsed_time:.4f} seconds' + logger.info('Downloaded to {} and took {:.4f} seconds'.format(self._nuplan_exp_root, elapsed_time)) + self._doc.add_next_tick_callback(partial(self._update_error_text_label, text=successful_message)) + except Exception as e: + logger.info(str(e)) + self.s3_error_text.text = str(e) + self._doc.add_next_tick_callback(self._reset_s3_download_button) + + def _update_s3_sledgeboard_file_main_path( + self, s3_sledgeboard_file_result: S3SledgeBoardFileResultMessage, selected_prefix: str + ) -> None: + """ + Update sledgeboard file simulation and metric main path. + :param s3_sledgeboard_file_result: S3 sledgeboard file result. + :param selected_prefix: Selected prefix on s3. + """ + sledgeboard_file = s3_sledgeboard_file_result.sledgeboard_file + sledgeboard_filename = s3_sledgeboard_file_result.sledgeboard_filename + if not sledgeboard_file or not sledgeboard_filename or not self._nuplan_exp_root: + return + main_path = Path(self._nuplan_exp_root) / selected_prefix + sledgeboard_file.simulation_main_path = str(main_path) + sledgeboard_file.metric_main_path = str(main_path) + metric_path = main_path / sledgeboard_file.metric_folder + if not metric_path.exists(): + metric_path.mkdir(parents=True, exist_ok=True) + + simulation_path = main_path / sledgeboard_file.simulation_folder + if not simulation_path.exists(): + simulation_path.mkdir(parents=True, exist_ok=True) + + aggregator_metric_path = main_path / sledgeboard_file.aggregator_metric_folder + if not aggregator_metric_path.exists(): + aggregator_metric_path.mkdir(parents=True, exist_ok=True) + + # Replace with new local path + save_path = main_path / sledgeboard_filename + sledgeboard_file.save_sledgeboard_file(save_path) + logger.info("Updated nubBard main path in {} to {}".format(save_path, main_path)) + self._configuration_tab.add_sledgeboard_file_to_experiments(sledgeboard_file=s3_sledgeboard_file_result.sledgeboard_file) + + def _download_s3_file_contents(self, s3_result_file_contents: S3FileResultMessage, selected_s3_bucket: str) -> None: + """ + Download s3 file contents. + :param s3_result_file_contents: S3 file result contents. + :param selected_s3_bucket: Selected s3 bucket name. + """ + for index, (file_name, content) in enumerate(s3_result_file_contents.file_contents.items()): + # Skip '..' + if '..' in file_name: + continue + s3_path = os.path.join(selected_s3_bucket, file_name) + if not file_name.endswith('/'): + s3_connection_message = download_s3_file( + s3_path=s3_path, + s3_client=self._s3_client, + file_content=content, + save_path=self._nuplan_exp_root, + ) + else: + s3_connection_message = download_s3_path( + s3_path=s3_path, s3_client=self._s3_client, save_path=self._nuplan_exp_root + ) + if s3_connection_message.success: + text_message = f"Downloaded {file_name} ({index + 1} / {len(s3_result_file_contents.file_contents)})" + logger.info( + "Downloaded {} / ({}/{})".format(file_name, index + 1, len(s3_result_file_contents.file_contents)) + ) + self._doc.add_next_tick_callback(partial(self._update_error_text_label, text=text_message)) + + def _s3_download_button_on_click(self) -> None: + """Function to call when the download button is click.""" + selected_s3_bucket = str(self.s3_bucket_name.text).strip() + self.s3_download_button.label = 'Downloading...' + self.s3_download_button.disabled = True + self.s3_download_text_input.disabled = True + if not selected_s3_bucket: + self.s3_error_text.text = 'Please connect to a s3 bucket' + self._doc.add_next_tick_callback(self._reset_s3_download_button) + return + + selected_s3_prefix = str(self.s3_download_text_input.value).strip() + if not selected_s3_prefix: + self.s3_error_text.text = 'Please input a prefix' + self._doc.add_next_tick_callback(self._reset_s3_download_button) + return + + self._doc.add_next_tick_callback(self._s3_download_prefixes) diff --git a/sledge/sledgeboard/tabs/config/__init__.py b/sledge/sledgeboard/tabs/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/sledgeboard/tabs/config/cloud_tab_config.py b/sledge/sledgeboard/tabs/config/cloud_tab_config.py new file mode 100644 index 0000000..8955d42 --- /dev/null +++ b/sledge/sledgeboard/tabs/config/cloud_tab_config.py @@ -0,0 +1,234 @@ +from dataclasses import dataclass +from typing import Any, ClassVar, Dict, List, Optional + +from bokeh.models import HTMLTemplateFormatter + + +@dataclass(frozen=True) +class S3TabBucketNameConfig: + """Config for s3 tab bucket name div tag.""" + + text: ClassVar[str] = '-' + name: ClassVar[str] = 's3_bucket_name' + css_classes: ClassVar[List[str]] = ['s3-bucket-name', 'h5'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'text': cls.text, 'name': cls.name, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class S3TabDownloadTextInputConfig: + """Config for s3 tab download input text tag.""" + + placeholder: ClassVar[str] = 'Download prefix (without s3://)' + width: ClassVar[int] = 400 + height: ClassVar[int] = 30 + name: ClassVar[str] = 's3_download_text_input' + css_classes: ClassVar[List[str]] = ['s3-download-text-input'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'placeholder': cls.placeholder, + 'width': cls.width, + 'height': cls.height, + 'name': cls.name, + 'css_classes': cls.css_classes, + } + + +@dataclass(frozen=True) +class S3TabDownloadButtonConfig: + """Config for s3 tab download button tag.""" + + label: ClassVar[str] = 'Download' + width: ClassVar[int] = 150 + height: ClassVar[int] = 30 + name: ClassVar[str] = 's3_download_button' + css_classes: ClassVar[List[str]] = ['btn', 'btn-primary', 'modal-btn', 's3-download-btn'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'label': cls.label, + 'width': cls.width, + 'height': cls.height, + 'name': cls.name, + 'css_classes': cls.css_classes, + } + + +@dataclass(frozen=True) +class S3TabErrorTextConfig: + """Config for s3 tab error div tag.""" + + text: ClassVar[str] = '' + name: ClassVar[str] = 's3_error_text' + css_classes: ClassVar[List[str]] = ['s3-error-text', 'h5'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'text': cls.text, 'name': cls.name, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class S3TabBucketTextInputConfig: + """Config for s3 tab bucket text input tag.""" + + placeholder: ClassVar[str] = 'Bucket name' + name: ClassVar[str] = 's3_bucket_text_input' + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'placeholder': cls.placeholder, 'name': cls.name} + + +@dataclass(frozen=True) +class S3TabS3AccessKeyIDTextInputConfig: + """Config for s3 tab access key id text input tag.""" + + value: ClassVar[str] = '' + placeholder: ClassVar[str] = 'Access key ID' + name: ClassVar[str] = 's3_access_key_id_text_input' + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'value': cls.value, 'placeholder': cls.placeholder, 'name': cls.name} + + +@dataclass(frozen=True) +class S3TabS3SecretAccessKeyPasswordTextInputConfig: + """Config for s3 tab secret access key password text input tag.""" + + value: ClassVar[str] = '' + placeholder: ClassVar[str] = 'Secret access key' + name: ClassVar[str] = 's3_secret_access_key_password_input' + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'value': cls.value, 'placeholder': cls.placeholder, 'name': cls.name} + + +@dataclass(frozen=True) +class S3TabS3BucketPrefixTextInputConfig: + """Config for s3 tab bucket prefix text input tag.""" + + value: ClassVar[str] = '' + placeholder: ClassVar[str] = 'prefix e.g. user-name/' + name: ClassVar[str] = 's3_bucket_prefix_text_input' + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'value': cls.value, 'placeholder': cls.placeholder, 'name': cls.name} + + +@dataclass(frozen=True) +class S3TabS3ModalQueryButtonConfig: + """Config for s3 tab modal query button tag.""" + + name: ClassVar[str] = 's3_modal_query_btn' + label: ClassVar[str] = 'Query S3' + css_classes: ClassVar[List[str]] = ['btn', 'btn-primary', 'modal-btn', 's3-tab-modal-query-btn'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'name': cls.name, 'label': cls.label, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class S3TabObjectColumnConfig: + """Config for s3 tab object column tag.""" + + field: ClassVar[str] = 'object' + title: ClassVar[str] = 'Object' + width: ClassVar[int] = 200 + sortable: ClassVar[bool] = False + formatter_template: ClassVar[str] = """<%= value %>""" + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'field': cls.field, + 'title': cls.title, + 'width': cls.width, + 'sortable': cls.sortable, + 'formatter': HTMLTemplateFormatter(template=cls.formatter_template), + } + + +@dataclass(frozen=True) +class S3TabLastModifiedColumnConfig: + """Config for s3 tab last_modified column tag.""" + + field: ClassVar[str] = 'last_modified' + title: ClassVar[str] = 'Last Modified' + width: ClassVar[int] = 150 + sortable: ClassVar[bool] = False + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'field': cls.field, 'title': cls.title, 'width': cls.width, 'sortable': cls.sortable} + + +@dataclass(frozen=True) +class S3TabTimeStampColumnConfig: + """Config for s3 tab timestamp column tag.""" + + field: ClassVar[str] = 'timestamp' + title: ClassVar[str] = 'Timestamp' + width: ClassVar[int] = 150 + sortable: ClassVar[bool] = False + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'field': cls.field, 'title': cls.title, 'width': cls.width, 'sortable': cls.sortable} + + +@dataclass(frozen=True) +class S3TabSizeColumnConfig: + """Config for s3 tab size column tag.""" + + field: ClassVar[str] = 'size' + title: ClassVar[str] = 'Size (KB)' + width: ClassVar[int] = 150 + sortable: ClassVar[bool] = False + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'field': cls.field, 'title': cls.title, 'width': cls.width, 'sortable': cls.sortable} + + +@dataclass(frozen=True) +class S3TabDataTableConfig: + """Config for s3 tab data table column tag.""" + + selectable: ClassVar[bool] = True + row_height: ClassVar[int] = 80 + index_position: ClassVar[Optional[int]] = None + name: ClassVar[str] = 's3_data_table' + css_classes: ClassVar[List[str]] = ["s3-data-table"] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'selectable': cls.selectable, + 'row_height': cls.row_height, + 'index_position': cls.index_position, + 'name': cls.name, + 'css_classes': cls.css_classes, + } diff --git a/sledge/sledgeboard/tabs/config/histogram_tab_config.py b/sledge/sledgeboard/tabs/config/histogram_tab_config.py new file mode 100644 index 0000000..c91b389 --- /dev/null +++ b/sledge/sledgeboard/tabs/config/histogram_tab_config.py @@ -0,0 +1,264 @@ +from dataclasses import dataclass +from typing import Any, ClassVar, Dict, List, Optional, Tuple + +import numpy as np +import numpy.typing as npt +from bokeh.plotting import Figure + +from sledge.sledgeboard.style import PLOT_PALETTE + + +@dataclass +class HistogramStatistics: + """Histogram statistics data.""" + + values: npt.NDArray[np.float64] # An array of values + unit: str # Unit + scenarios: List[str] # Scenario names + + +@dataclass +class HistogramData: + """Histogram data.""" + + experiment_index: int # Experiment index to represent color + planner_name: str # Planner name + statistics: Dict[str, HistogramStatistics] # Aggregated statistic data + histogram_file_name: Optional[str] = None # File name that saves this histogram data + + +@dataclass +class HistogramFigureData: + """Histogram figure data.""" + + figure_plot: Figure # Histogram statistic figure + frequency_array: Optional[npt.NDArray[np.int64]] = None + + +@dataclass +class HistogramEdgeData: + """Histogram edge data.""" + + unit: str # Unit + values: npt.NDArray[np.float64] # An array of values + + +@dataclass(frozen=True) +class HistogramConstantConfig: + """Data class config for constant data in histogram.""" + + # Type for histogram aggregated data: {metric name: A list of histogram aggregated data} + HistogramDataType: ClassVar[Any] = Dict[str, List[HistogramData]] + + # Type for histogram figure data type: {metric name: {metric statistics name: histogram figure data}} + HistogramFigureDataType: ClassVar[Any] = Dict[str, Dict[str, HistogramFigureData]] + + # Type for histogram edge data type: {metric name: {metric statistic name: histogram figure data}} + HistogramEdgesDataType: ClassVar[Any] = Dict[str, Dict[str, Optional[npt.NDArray[np.float64]]]] + + # Type for scenario type score histogram + HistogramScenarioTypeScoreStatisticType: ClassVar[Any] = Dict[str, Dict[str, List[Tuple[float, str]]]] + + PLANNER_CHECKBOX_GROUP_NAME: ClassVar[str] = 'histogram_planner_checkbox_group' + SCENARIO_TYPE_SCORE_HISTOGRAM_NAME: ClassVar[str] = 'scenario_type_scores' + HISTOGRAM_TAB_DEFAULT_NUMBER_COLS: ClassVar[int] = 3 + + +@dataclass(frozen=True) +class HistogramTabMatPlotLibPlotStyleConfig: + """Histogram figure style for matplotlib plot.""" + + main_title_size: int = 8 + axis_title_size: int = 6 + y_axis_label_size: int = 5 + x_axis_label_size: int = 5 + legend_font_size: int = 4 + axis_ticker_size: int = 5 + + +@dataclass(frozen=True) +class HistogramTabHistogramBarStyleConfig: + """Histogram tab bar style configs.""" + + line_color: ClassVar[str] = "white" + fill_alpha: ClassVar[float] = 0.5 + line_alpha: ClassVar[float] = 0.5 + line_width: ClassVar[int] = 3 + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'line_color': cls.line_color, + 'fill_alpha': cls.fill_alpha, + 'line_alpha': cls.line_alpha, + 'line_width': cls.line_width, + } + + @classmethod + def update_histogram_bar_figure_style(cls, histogram_figure: Figure) -> None: + """Update histogram figure bar style.""" + histogram_figure.y_range.start = 0 + histogram_figure.legend.background_fill_alpha = 0.3 + histogram_figure.legend.label_text_font_size = "8pt" + histogram_figure.yaxis.axis_label = "Frequency" + histogram_figure.grid.grid_line_color = "white" + + +@dataclass(frozen=True) +class HistogramTabFigureGridPlotStyleConfig: + """Histogram tab figure grid plot style configs.""" + + toolbar_location: ClassVar[str] = "left" + + @classmethod + def get_config(cls, ncols: int, height: int) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'toolbar_location': cls.toolbar_location, 'ncols': ncols, 'height': height} + + +@dataclass(frozen=True) +class HistogramTabFigureTitleDivStyleConfig: + """Histogram tab figure title div style configs.""" + + style: ClassVar[Dict[str, str]] = {"font-size": "10pt", "width": "100%", "font-weight": "bold"} + + @classmethod + def get_config(cls, title: str) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'text': title, 'style': cls.style} + + +@dataclass(frozen=True) +class HistogramTabFigureStyleConfig: + """Histogram tab figure style configs.""" + + background_fill_color: ClassVar[str] = PLOT_PALETTE["background_white"] + margin: ClassVar[List[int]] = [10, 20, 20, 30] + output_backend: ClassVar[str] = 'webgl' + active_scroll: ClassVar[str] = 'wheel_zoom' + maximum_plot_width: ClassVar[int] = 1200 + decimal_places: ClassVar[int] = 6 + + @classmethod + def get_config( + cls, title: str, x_axis_label: str, width: int, height: int, x_range: Optional[List[str]] = None + ) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'title': title, + 'x_axis_label': x_axis_label, + 'width': width, + 'height': height, + 'x_range': x_range, + 'background_fill_color': cls.background_fill_color, + 'margin': cls.margin, + 'output_backend': cls.output_backend, + 'active_scroll': cls.active_scroll, + } + + @classmethod + def update_histogram_figure_style(cls, histogram_figure: Figure) -> None: + """Update histogram figure style.""" + histogram_figure.title.text_font_size = "8pt" + histogram_figure.xaxis.axis_label_text_font_size = "8pt" + histogram_figure.xaxis.major_label_text_font_size = "7pt" + histogram_figure.yaxis.axis_label_text_font_size = "8pt" + histogram_figure.yaxis.major_label_text_font_size = "8pt" + + # Rotate the x_axis label with 45 (180/4) degrees + histogram_figure.xaxis.major_label_orientation = np.pi / 2 # 90 (180 / 2) degrees + histogram_figure.toolbar.logo = None + + +@dataclass(frozen=True) +class HistogramTabScenarioTypeMultiChoiceConfig: + """Config for the histogram tab scenario type multi choice tag.""" + + name: ClassVar[str] = 'histogram_scenario_type_multi_choice' + css_classes: ClassVar[List[str]] = ['scenario-type-multi-choice'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'name': cls.name, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class HistogramTabMetricNameMultiChoiceConfig: + """Config for the histogram tab metric name multi choice tag.""" + + name: ClassVar[str] = 'histogram_metric_name_multi_choice' + css_classes: ClassVar[List[str]] = ['metric-name-multi-choice'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'name': cls.name, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class HistogramTabBinSpinnerConfig: + """Config for the histogram tab bin spinner tag.""" + + mode: ClassVar[str] = 'int' + placeholder: ClassVar[str] = 'Number of bins (default: 10, max: 100)' + low: ClassVar[int] = 1 + high: ClassVar[int] = 100 + name: ClassVar[str] = 'histogram_bin_spinner' + css_classes: ClassVar[List[str]] = ['histogram-bin-spinner'] + default_bins: ClassVar[int] = 10 + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'mode': cls.mode, + 'placeholder': cls.placeholder, + 'low': cls.low, + 'high': cls.high, + 'name': cls.name, + 'css_classes': cls.css_classes, + } + + +@dataclass(frozen=True) +class HistogramTabDefaultDivConfig: + """Config for the histogram tab default div tag.""" + + text: ClassVar[str] = '

No histogram results, please add more experiments or adjust the search filter.

' + margin: ClassVar[List[int]] = [5, 5, 5, 30] + css_classes: ClassVar[List[str]] = ['histogram-default-div'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'text': cls.text, 'margin': cls.margin, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class HistogramTabPlotConfig: + """Config for the histogram tab plot column tag.""" + + css_classes: ClassVar[List[str]] = ['histogram-plots'] + name: ClassVar[str] = 'histogram_plots' + default_width: ClassVar[int] = 800 + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'name': cls.name, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class HistogramTabModalQueryButtonConfig: + """Config for the histogram tab modal query button tag.""" + + name: ClassVar[str] = 'histogram_modal_query_btn' + label: ClassVar[str] = 'Search Results' + css_classes: ClassVar[List[str]] = ['btn', 'btn-primary', 'modal-btn', 'histogram-tab-modal-query-btn'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'name': cls.name, 'label': cls.label, 'css_classes': cls.css_classes} diff --git a/sledge/sledgeboard/tabs/config/overview_tab_config.py b/sledge/sledgeboard/tabs/config/overview_tab_config.py new file mode 100644 index 0000000..ae33dbc --- /dev/null +++ b/sledge/sledgeboard/tabs/config/overview_tab_config.py @@ -0,0 +1,98 @@ +from dataclasses import dataclass +from typing import Any, ClassVar, Dict, List, Optional + +OVERVIEW_PLANNER_CHECKBOX_GROUP_NAME = 'overview_planner_checkbox_group' + + +@dataclass +class OverviewAggregatorData: + """Aggregator metric data in the overview tab.""" + + aggregator_file_name: str # Aggregator output file name + aggregator_type: str # Aggregator type + planner_name: str # Planner name + scenario_type: str # Scenario type + num_scenarios: int # Number of scenarios in the type + score: float # The aggregator scores for the scenario type + + +@dataclass(frozen=True) +class OverviewTabDefaultDataSourceDictConfig: + """Config for the overview tab default data source tag.""" + + experiment: ClassVar[List[str]] = ['-'] + scenario_type: ClassVar[List[str]] = ['-'] + planner: ClassVar[List[str]] = [ + 'No metric aggregator results, please add more experiments ' 'or adjust the search filter' + ] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'experiment': cls.experiment, 'scenario_type': cls.scenario_type, 'planner': cls.planner} + + +@dataclass(frozen=True) +class OverviewTabExperimentTableColumnConfig: + """Config for the overview tab experiment table column tag.""" + + field: ClassVar[str] = 'experiment' + title: ClassVar[str] = 'Experiment' + width: ClassVar[int] = 150 + sortable: ClassVar[bool] = False + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'field': cls.field, 'title': cls.title, 'width': cls.width, 'sortable': cls.sortable} + + +@dataclass(frozen=True) +class OverviewTabScenarioTypeTableColumnConfig: + """Config for the overview tab scenario type table column tag.""" + + field: ClassVar[str] = 'scenario_type' + title: ClassVar[str] = 'Scenario Type (Number of Scenarios)' + width: ClassVar[int] = 200 + sortable: ClassVar[bool] = False + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'field': cls.field, 'title': cls.title, 'width': cls.width, 'sortable': cls.sortable} + + +@dataclass(frozen=True) +class OverviewTabPlannerTableColumnConfig: + """Config for the overview tab planner table column tag.""" + + field: ClassVar[str] = 'planner' + title: ClassVar[str] = 'Evaluation Score' + sortable: ClassVar[bool] = False + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'field': cls.field, 'title': cls.title, 'sortable': cls.sortable} + + +@dataclass(frozen=True) +class OverviewTabDataTableConfig: + """Config for the overview tab planner data table tag.""" + + selectable: ClassVar[bool] = True + row_height: ClassVar[int] = 80 + index_position: ClassVar[Optional[int]] = None + name: ClassVar[str] = 'overview_table' + css_classes: ClassVar[List[str]] = ['overview-table'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'selectable': cls.selectable, + 'row_height': cls.row_height, + 'index_position': cls.index_position, + 'name': cls.name, + 'css_classes': cls.css_classes, + } diff --git a/sledge/sledgeboard/tabs/config/scenario_tab_config.py b/sledge/sledgeboard/tabs/config/scenario_tab_config.py new file mode 100644 index 0000000..2cbc4cb --- /dev/null +++ b/sledge/sledgeboard/tabs/config/scenario_tab_config.py @@ -0,0 +1,72 @@ +from dataclasses import dataclass, field +from typing import Any, ClassVar, Dict, List, Tuple + + +@dataclass(frozen=True) +class ScenarioTabTitleDivConfig: + """Config for the scenario tab title div tag.""" + + text: ClassVar[str] = "-" + name: ClassVar[str] = 'scenario_title_div' + css_classes: ClassVar[List[str]] = ['scenario-tab-title-div'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'text': cls.text, 'name': cls.name, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class ScenarioTabScenarioTokenMultiChoiceConfig: + """Config for scenario tab scenario token multi choice tag.""" + + max_items: ClassVar[int] = 1 + option_limit: ClassVar[int] = 10 + height: ClassVar[int] = 40 + placeholder: ClassVar[str] = "Scenario token" + name: ClassVar[str] = 'scenario_token_multi_choice' + css_classes: ClassVar[List[str]] = ['scenario-token-multi-choice'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return { + 'max_items': cls.max_items, + 'option_limit': cls.option_limit, + 'height': cls.height, + 'placeholder': cls.placeholder, + 'name': cls.name, + 'css_classes': cls.css_classes, + } + + +@dataclass(frozen=True) +class ScenarioTabModalQueryButtonConfig: + """Config for scenario tab modal query button tag.""" + + name: ClassVar[str] = 'scenario_modal_query_btn' + label: ClassVar[str] = 'Query Scenario' + css_classes: ClassVar[List[str]] = ['btn', 'btn-primary', 'modal-btn', 'scenario-tab-modal-query-btn'] + + @classmethod + def get_config(cls) -> Dict[str, Any]: + """Get configs as a dict.""" + return {'name': cls.name, 'label': cls.label, 'css_classes': cls.css_classes} + + +@dataclass(frozen=True) +class ScenarioTabFrameButtonConfig: + """Config for scenario tab's frame control buttons.""" + + label: str + margin: Tuple[int, int, int, int] = field(default_factory=lambda: (5, 19, 5, 35)) # Top, right, bottom, left + css_classes: List[str] = field(default_factory=lambda: ["frame-control-button"]) + width: int = field(default_factory=lambda: 56) + + +# Global config instances +first_button_config = ScenarioTabFrameButtonConfig(label="first") +prev_button_config = ScenarioTabFrameButtonConfig(label="prev") +play_button_config = ScenarioTabFrameButtonConfig(label="play") +next_button_config = ScenarioTabFrameButtonConfig(label="next") +last_button_config = ScenarioTabFrameButtonConfig(label="last") diff --git a/sledge/sledgeboard/tabs/configuration_tab.py b/sledge/sledgeboard/tabs/configuration_tab.py new file mode 100644 index 0000000..f755db3 --- /dev/null +++ b/sledge/sledgeboard/tabs/configuration_tab.py @@ -0,0 +1,124 @@ +import base64 +import io +import logging +import pathlib +import pickle +from pathlib import Path +from typing import Any, List + +from bokeh.document.document import Document +from bokeh.models import CheckboxGroup, FileInput + +from sledge.sledgeboard.base.base_tab import BaseTab +from sledge.sledgeboard.base.data_class import SledgeBoardFile +from sledge.sledgeboard.base.experiment_file_data import ExperimentFileData +from sledge.sledgeboard.style import configuration_tab_style + +logger = logging.getLogger(__name__) + + +class ConfigurationTab: + """Configuration tab for sledgeboard.""" + + def __init__(self, doc: Document, experiment_file_data: ExperimentFileData, tabs: List[BaseTab]): + """ + Configuration tab about configuring sledgeboard. + :param experiment_file_data: Experiment file data. + :param tabs: A list of tabs to be updated when configuration is changed. + """ + self._doc = doc + self._tabs = tabs + self.experiment_file_data = experiment_file_data + + self._file_path_input = FileInput( + accept=SledgeBoardFile.extension(), + css_classes=["file-path-input"], + margin=configuration_tab_style["file_path_input_margin"], + name="file_path_input", + ) + self._file_path_input.on_change("value", self._add_experiment_file) + self._experiment_file_path_checkbox_group = CheckboxGroup( + labels=self.experiment_file_path_stems, + active=[index for index in range(len(self.experiment_file_data.file_paths))], + name="experiment_file_path_checkbox_group", + css_classes=["experiment-file-path-checkbox-group"], + ) + self._experiment_file_path_checkbox_group.on_click(self._click_experiment_file_path_checkbox) + if self.experiment_file_data.file_paths: + self._file_paths_on_change() + + @property + def experiment_file_path_stems(self) -> List[str]: + """Return a list of file path stems.""" + experiment_paths = [] + for file_path in self.experiment_file_data.file_paths: + metric_path = file_path.current_path / file_path.metric_folder + if metric_path.exists(): + experiment_file_path_stem = file_path.current_path + else: + experiment_file_path_stem = file_path.metric_main_path + + if isinstance(experiment_file_path_stem, str): + experiment_file_path_stem = pathlib.Path(experiment_file_path_stem) + + experiment_file_path_stem = "/".join( + [experiment_file_path_stem.parts[-2], experiment_file_path_stem.parts[-1]] + ) + experiment_paths.append(experiment_file_path_stem) + return experiment_paths + + @property + def file_path_input(self) -> FileInput: + """Return the file path input widget.""" + return self._file_path_input + + @property + def experiment_file_path_checkbox_group(self) -> CheckboxGroup: + """Return experiment file path checkboxgroup.""" + return self._experiment_file_path_checkbox_group + + def _click_experiment_file_path_checkbox(self, attr: Any) -> None: + """ + Click event handler for experiment_file_path_checkbox_group. + :param attr: Clicked attributes. + """ + self._file_paths_on_change() + + def add_sledgeboard_file_to_experiments(self, sledgeboard_file: SledgeBoardFile) -> None: + """ + Add sledgeboard files to experiments. + :param sledgeboard_file: Added sledgeboard file. + """ + sledgeboard_file.current_path = Path(sledgeboard_file.metric_main_path) + if sledgeboard_file not in self.experiment_file_data.file_paths: + self.experiment_file_data.update_data(file_paths=[sledgeboard_file]) + self._experiment_file_path_checkbox_group.labels = self.experiment_file_path_stems + self._experiment_file_path_checkbox_group.active += [len(self.experiment_file_path_stems) - 1] + self._file_paths_on_change() + + def _add_experiment_file(self, attr: str, old: bytes, new: bytes) -> None: + """ + Event responds to file change. + :param attr: Attribute name. + :param old: Old value. + :param new: New value. + """ + if not new: + return + try: + decoded_string = base64.b64decode(new) + file_stream = io.BytesIO(decoded_string) + data = pickle.load(file_stream) + sledgeboard_file = SledgeBoardFile.deserialize(data=data) + self.add_sledgeboard_file_to_experiments(sledgeboard_file=sledgeboard_file) + file_stream.close() + except (OSError, IOError) as e: + logger.info(f"Error loading experiment file. {str(e)}.") + + def _file_paths_on_change(self) -> None: + """Function to call when we change file paths.""" + for tab in self._tabs: + tab.file_paths_on_change( + experiment_file_data=self.experiment_file_data, + experiment_file_active_index=self._experiment_file_path_checkbox_group.active, + ) diff --git a/sledge/sledgeboard/tabs/histogram_tab.py b/sledge/sledgeboard/tabs/histogram_tab.py new file mode 100644 index 0000000..6e3ca30 --- /dev/null +++ b/sledge/sledgeboard/tabs/histogram_tab.py @@ -0,0 +1,746 @@ +import logging +from collections import defaultdict +from copy import deepcopy +from typing import Any, Dict, List, Optional, Union + +import numpy as np +import numpy.typing as npt +from bokeh.document.document import Document +from bokeh.layouts import column, gridplot, layout +from bokeh.models import Button, ColumnDataSource, Div, FactorRange, HoverTool, MultiChoice, Spinner, glyph +from bokeh.plotting import figure + +from sledge.sledgeboard.base.base_tab import BaseTab +from sledge.sledgeboard.base.experiment_file_data import ExperimentFileData +from sledge.sledgeboard.tabs.config.histogram_tab_config import ( + HistogramConstantConfig, + HistogramFigureData, + HistogramTabBinSpinnerConfig, + HistogramTabDefaultDivConfig, + HistogramTabFigureGridPlotStyleConfig, + HistogramTabFigureStyleConfig, + HistogramTabFigureTitleDivStyleConfig, + HistogramTabHistogramBarStyleConfig, + HistogramTabMetricNameMultiChoiceConfig, + HistogramTabModalQueryButtonConfig, + HistogramTabPlotConfig, + HistogramTabScenarioTypeMultiChoiceConfig, +) +from sledge.sledgeboard.tabs.js_code.histogram_tab_js_code import ( + HistogramTabLoadingEndJSCode, + HistogramTabLoadingJSCode, + HistogramTabUpdateWindowsSizeJSCode, +) +from sledge.sledgeboard.utils.sledgeboard_histogram_utils import ( + aggregate_metric_aggregator_dataframe_histogram_data, + aggregate_metric_statistics_dataframe_histogram_data, + compute_histogram_edges, + get_histogram_plot_x_range, +) + +logger = logging.getLogger(__name__) + + +class HistogramTab(BaseTab): + """Histogram tab in SledgeBoard.""" + + def __init__( + self, + doc: Document, + experiment_file_data: ExperimentFileData, + bins: int = HistogramTabBinSpinnerConfig.default_bins, + max_scenario_names: int = 20, + ): + """ + Histogram for metric results about simulation. + :param doc: Bokeh html document. + :param experiment_file_data: Experiment file data. + :param bins: Default number of bins in histograms. + :param max_scenario_names: Show the maximum list of scenario names in each bin, 0 or None to disable + """ + super().__init__(doc=doc, experiment_file_data=experiment_file_data) + self._bins = bins + self._max_scenario_names = max_scenario_names + + # UI. + # Planner selection + self.planner_checkbox_group.name = HistogramConstantConfig.PLANNER_CHECKBOX_GROUP_NAME + self.planner_checkbox_group.js_on_change("active", HistogramTabLoadingJSCode.get_js_code()) + + # Scenario type multi choices + self._scenario_type_multi_choice = MultiChoice(**HistogramTabScenarioTypeMultiChoiceConfig.get_config()) + self._scenario_type_multi_choice.on_change("value", self._scenario_type_multi_choice_on_change) + self._scenario_type_multi_choice.js_on_change("value", HistogramTabUpdateWindowsSizeJSCode.get_js_code()) + + # Metric name multi choices + self._metric_name_multi_choice = MultiChoice(**HistogramTabMetricNameMultiChoiceConfig.get_config()) + self._metric_name_multi_choice.on_change("value", self._metric_name_multi_choice_on_change) + self._metric_name_multi_choice.js_on_change("value", HistogramTabUpdateWindowsSizeJSCode.get_js_code()) + + self._bin_spinner = Spinner(**HistogramTabBinSpinnerConfig.get_config()) + self._histogram_modal_query_btn = Button(**HistogramTabModalQueryButtonConfig.get_config()) + self._histogram_modal_query_btn.js_on_click(HistogramTabLoadingJSCode.get_js_code()) + self._histogram_modal_query_btn.on_click(self._setting_modal_query_button_on_click) + + self._default_div = Div(**HistogramTabDefaultDivConfig.get_config()) + # Histogram plot frame. + self._histogram_plots = column(self._default_div, **HistogramTabPlotConfig.get_config()) + self._histogram_plots.js_on_change("children", HistogramTabLoadingEndJSCode.get_js_code()) + self._histogram_figures: Optional[column] = None + self._aggregated_data: Optional[HistogramConstantConfig.HistogramDataType] = None + self._histogram_edges: Optional[HistogramConstantConfig.HistogramEdgesDataType] = None + self._plot_data: Dict[str, List[glyph]] = defaultdict(list) + self._init_selection() + + @property + def bin_spinner(self) -> Spinner: + """Return a bin spinner.""" + return self._bin_spinner + + @property + def scenario_type_multi_choice(self) -> MultiChoice: + """Return scenario_type_multi_choice.""" + return self._scenario_type_multi_choice + + @property + def metric_name_multi_choice(self) -> MultiChoice: + """Return metric_name_multi_choice.""" + return self._metric_name_multi_choice + + @property + def histogram_plots(self) -> column: + """Return histogram_plots.""" + return self._histogram_plots + + @property + def histogram_modal_query_btn(self) -> Button: + """Return histogram modal query button.""" + return self._histogram_modal_query_btn + + def _click_planner_checkbox_group(self, attr: Any) -> None: + """ + Click event handler for planner_checkbox_group. + :param attr: Clicked attributes. + """ + if not self._aggregated_data and not self._histogram_edges: + return + + # Render histograms. + self._histogram_figures = self._render_histograms() + + # Make sure the histogram upgrades at the last + self._doc.add_next_tick_callback(self._update_histogram_layouts) + + def file_paths_on_change( + self, experiment_file_data: ExperimentFileData, experiment_file_active_index: List[int] + ) -> None: + """ + Interface to update layout when file_paths is changed. + :param experiment_file_data: Experiment file data. + :param experiment_file_active_index: Active indexes for experiment files. + """ + self._experiment_file_data = experiment_file_data + self._experiment_file_active_index = experiment_file_active_index + + self._init_selection() + self._update_histograms() + + def _update_histogram_layouts(self) -> None: + """Update histogram layouts.""" + self._histogram_plots.children[0] = layout(self._histogram_figures) + + def _update_histograms(self) -> None: + """Update histograms.""" + # Aggregate data + self._aggregated_data = self._aggregate_statistics() + + # Aggregate scenario type scores + aggregated_scenario_type_score_data = self._aggregate_scenario_type_score_histogram() + + self._aggregated_data.update(aggregated_scenario_type_score_data) + + # Compute histogram edges + self._histogram_edges = compute_histogram_edges(aggregated_data=self._aggregated_data, bins=self._bins) + + # Render histograms. + self._histogram_figures = self._render_histograms() + + # Make sure the histogram upgrades at the last + self._doc.add_next_tick_callback(self._update_histogram_layouts) + + def _setting_modal_query_button_on_click(self) -> None: + """Setting modal query button on click helper function.""" + if self._metric_name_multi_choice.tags: + self.window_width = self._metric_name_multi_choice.tags[0] + self.window_height = self._metric_name_multi_choice.tags[1] + + if self._bin_spinner.value: + self._bins = self._bin_spinner.value + self._update_histograms() + + def _metric_name_multi_choice_on_change(self, attr: str, old: str, new: str) -> None: + """ + Helper function to change event in histogram metric name. + :param attr: Attribute. + :param old: Old value. + :param new: New value. + """ + # Set up window width and height + if self._metric_name_multi_choice.tags: + self.window_width = self._metric_name_multi_choice.tags[0] + self.window_height = self._metric_name_multi_choice.tags[1] + + def _scenario_type_multi_choice_on_change(self, attr: str, old: str, new: str) -> None: + """ + Helper function to change event in histogram scenario type. + :param attr: Attribute. + :param old: Old value. + :param new: New value. + """ + # Set up window width and height + if self._scenario_type_multi_choice.tags: + self.window_width = self._scenario_type_multi_choice.tags[0] + self.window_height = self.scenario_type_multi_choice.tags[1] + + def _adjust_plot_width_size(self, n_bins: int) -> int: + """ + Adjust plot width size based on number of bins. + :param n_bins: Number of bins. + :return Width size of a histogram plot. + """ + base_plot_width: int = self.plot_sizes[0] + if n_bins < 20: + return base_plot_width + # Increase the width of 50 for every number of bins 20 + width_multiplier_factor: int = (n_bins // 20) * 100 + width_size: int = min( + base_plot_width + width_multiplier_factor, HistogramTabFigureStyleConfig.maximum_plot_width + ) + return width_size + + def _init_selection(self) -> None: + """Init histogram and scalar selection options.""" + # For planner checkbox + planner_name_list: List[str] = [] + # Clean up + self.planner_checkbox_group.labels = [] + self.planner_checkbox_group.active = [] + for index, metric_statistics_dataframes in enumerate(self.experiment_file_data.metric_statistics_dataframes): + if index not in self._experiment_file_active_index: + continue + for metric_statistics_dataframe in metric_statistics_dataframes: + planner_names = metric_statistics_dataframe.planner_names + planner_name_list += planner_names + + sorted_planner_name_list = sorted(list(set(planner_name_list))) + self.planner_checkbox_group.labels = sorted_planner_name_list + self.planner_checkbox_group.active = [index for index in range(len(sorted_planner_name_list))] + + self._init_multi_search_criteria_selection( + scenario_type_multi_choice=self._scenario_type_multi_choice, + metric_name_multi_choice=self._metric_name_multi_choice, + ) + + def plot_vbar( + self, + histogram_figure_data: HistogramFigureData, + counts: npt.NDArray[np.int64], + category: List[str], + planner_name: str, + legend_label: str, + color: str, + scenario_names: List[str], + x_values: List[str], + width: float = 0.4, + histogram_file_name: Optional[str] = None, + ) -> None: + """ + Plot a vertical bar plot. + :param histogram_figure_data: Figure class. + :param counts: An array of counts for each category. + :param category: A list of category (x-axis label). + :param planner_name: Planner name. + :param legend_label: Legend label. + :param color: Legend color. + :param scenario_names: A list of scenario names. + :param x_values: X-axis values. + :param width: Bar width. + :param histogram_file_name: Histogram file name for the histogram data. + """ + y_values = deepcopy(counts) + bottom: npt.NDArray[np.int64] = ( + np.zeros_like(counts) + if histogram_figure_data.frequency_array is None + else histogram_figure_data.frequency_array + ) + count_position = counts > 0 + bottom_arrays: npt.NDArray[np.int64] = bottom * count_position + top = counts + bottom_arrays + histogram_file_names = [histogram_file_name] * len(top) + data_source = ColumnDataSource( + dict( + x=category, + top=top, + bottom=bottom_arrays, + y_values=y_values, + x_values=x_values, + scenario_names=scenario_names, + histogram_file_name=histogram_file_names, + ) + ) + figure_plot = histogram_figure_data.figure_plot + vbar = figure_plot.vbar( + x="x", + top="top", + bottom="bottom", + fill_color=color, + legend_label=legend_label, + width=width, + source=data_source, + **HistogramTabHistogramBarStyleConfig.get_config(), + ) + self._plot_data[planner_name].append(vbar) + HistogramTabHistogramBarStyleConfig.update_histogram_bar_figure_style(histogram_figure=figure_plot) + + def plot_histogram( + self, + histogram_figure_data: HistogramFigureData, + hist: npt.NDArray[np.float64], + edges: npt.NDArray[np.float64], + planner_name: str, + legend_label: str, + color: str, + scenario_names: List[str], + x_values: List[str], + histogram_file_name: Optional[str] = None, + ) -> None: + """ + Plot a histogram. + Reference from https://docs.bokeh.org/en/latest/docs/gallery/histogram.html. + :param histogram_figure_data: Histogram figure data. + :param hist: Histogram data. + :param edges: Histogram bin data. + :param planner_name: Planner name. + :param legend_label: Legend label. + :param color: Legend color. + :param scenario_names: A list of scenario names. + :param x_values: A list of x value names. + :param histogram_file_name: Histogram file name for the histogram data. + """ + bottom: npt.NDArray[np.int64] = ( + np.zeros_like(hist) + if histogram_figure_data.frequency_array is None + else histogram_figure_data.frequency_array + ) + hist_position = hist > 0 + bottom_arrays: npt.NDArray[np.int64] = bottom * hist_position + top = hist + bottom_arrays + histogram_file_names = [histogram_file_name] * len(top) + data_source = ColumnDataSource( + dict( + top=top, + bottom=bottom_arrays, + left=edges[:-1], + right=edges[1:], + y_values=hist, + x_values=x_values, + scenario_names=scenario_names, + histogram_file_name=histogram_file_names, + ) + ) + figure_plot = histogram_figure_data.figure_plot + quad = figure_plot.quad( + top="top", + bottom="bottom", + left="left", + right="right", + fill_color=color, + legend_label=legend_label, + **HistogramTabHistogramBarStyleConfig.get_config(), + source=data_source, + ) + + self._plot_data[planner_name].append(quad) + HistogramTabHistogramBarStyleConfig.update_histogram_bar_figure_style(histogram_figure=figure_plot) + + def _render_histogram_plot( + self, + title: str, + x_axis_label: str, + x_range: Optional[Union[List[str], FactorRange]] = None, + histogram_file_name: Optional[str] = None, + ) -> HistogramFigureData: + """ + Render a histogram plot. + :param title: Title. + :param x_axis_label: x-axis label. + :param x_range: A list of category data if specified. + :param histogram_file_name: Histogram file name for the histogram plot. + :return a figure. + """ + if x_range is None: + len_plot_width = 1 + elif isinstance(x_range, list): + len_plot_width = len(x_range) + else: + len_plot_width = len(x_range.factors) + + plot_width = self._adjust_plot_width_size(n_bins=len_plot_width) + tooltips = [("Frequency", "@y_values"), ("Values", "@x_values{safe}"), ("Scenarios", "@scenario_names{safe}")] + if histogram_file_name: + tooltips.append(("File", "@histogram_file_name")) + + hover_tool = HoverTool(tooltips=tooltips, point_policy="follow_mouse") + statistic_figure = figure( + **HistogramTabFigureStyleConfig.get_config( + title=title, x_axis_label=x_axis_label, width=plot_width, height=self.plot_sizes[1], x_range=x_range + ), + tools=["pan", "wheel_zoom", "save", "reset", hover_tool], + ) + HistogramTabFigureStyleConfig.update_histogram_figure_style(histogram_figure=statistic_figure) + return HistogramFigureData(figure_plot=statistic_figure) + + def _render_histogram_layout(self, histograms: HistogramConstantConfig.HistogramFigureDataType) -> List[column]: + """ + Render histogram layout. + :param histograms: A dictionary of histogram names and their histograms. + :return: A list of lists of figures (a list per row). + """ + layouts = [] + ncols = self.get_plot_cols( + plot_width=self.plot_sizes[0], default_ncols=HistogramConstantConfig.HISTOGRAM_TAB_DEFAULT_NUMBER_COLS + ) + for metric_statistics_name, statistics_data in histograms.items(): + title_div = Div(**HistogramTabFigureTitleDivStyleConfig.get_config(title=metric_statistics_name)) + figures = [histogram_figure.figure_plot for statistic_name, histogram_figure in statistics_data.items()] + grid_plot = gridplot( + figures, + **HistogramTabFigureGridPlotStyleConfig.get_config(ncols=ncols, height=self.plot_sizes[1]), + ) + grid_layout = column(title_div, grid_plot) + layouts.append(grid_layout) + + return layouts + + def _aggregate_scenario_type_score_histogram(self) -> HistogramConstantConfig.HistogramDataType: + """ + Aggregate metric aggregator data. + :return: A dictionary of metric aggregator names and their metric scores. + """ + data: HistogramConstantConfig.HistogramDataType = defaultdict(list) + selected_scenario_types = self._scenario_type_multi_choice.value + + # Loop through all metric aggregators + for index, metric_aggregator_dataframes in enumerate(self.experiment_file_data.metric_aggregator_dataframes): + if index not in self._experiment_file_active_index: + continue + for metric_aggregator_filename, metric_aggregator_dataframe in metric_aggregator_dataframes.items(): + # Aggregate a list of histogram data list + histogram_data_list = aggregate_metric_aggregator_dataframe_histogram_data( + metric_aggregator_dataframe_index=index, + metric_aggregator_dataframe=metric_aggregator_dataframe, + scenario_types=selected_scenario_types, + dataframe_file_name=metric_aggregator_filename, + ) + if histogram_data_list: + data[HistogramConstantConfig.SCENARIO_TYPE_SCORE_HISTOGRAM_NAME] += histogram_data_list + + return data + + def _aggregate_statistics(self) -> HistogramConstantConfig.HistogramDataType: + """ + Aggregate statistics data. + :return A dictionary of metric names and their aggregated data. + """ + data: HistogramConstantConfig.HistogramDataType = defaultdict(list) + scenario_types = self._scenario_type_multi_choice.value + metric_choices = self._metric_name_multi_choice.value + if not len(scenario_types) and not len(metric_choices): + return data + + if 'all' in scenario_types: + scenario_types = None + else: + scenario_types = tuple(scenario_types) + + for index, metric_statistics_dataframes in enumerate(self.experiment_file_data.metric_statistics_dataframes): + if index not in self._experiment_file_active_index: + continue + + for metric_statistics_dataframe in metric_statistics_dataframes: + histogram_data_list = aggregate_metric_statistics_dataframe_histogram_data( + metric_statistics_dataframe=metric_statistics_dataframe, + metric_statistics_dataframe_index=index, + scenario_types=scenario_types, + metric_choices=metric_choices, + ) + + if histogram_data_list: + data[metric_statistics_dataframe.metric_statistic_name] += histogram_data_list + return data + + def _plot_bool_histogram( + self, + histogram_figure_data: HistogramFigureData, + values: npt.NDArray[np.float64], + scenarios: List[str], + planner_name: str, + legend_name: str, + color: str, + histogram_file_name: Optional[str] = None, + ) -> None: + """ + Plot boolean type of histograms. + :param histogram_figure_data: Histogram figure data. + :param values: An array of values. + :param scenarios: A list of scenario names. + :param planner_name: Planner name. + :param legend_name: Legend name. + :param color: Plot color. + :param histogram_file_name: Histogram file name for the histogram data. + """ + # False and True + num_true = np.nansum(values) + num_false = len(values[values == 0]) + scenario_names: List[List[str]] = [[] for _ in range(2)] # False and True bins only + # Get scenario names + for index, scenario in enumerate(scenarios): + scenario_name_index = 1 if values[index] else 0 + if not self._max_scenario_names or len(scenario_names[scenario_name_index]) < self._max_scenario_names: + scenario_names[scenario_name_index].append(scenario) + + scenario_names_flatten = ["
".join(names) if names else "" for names in scenario_names] + counts: npt.NDArray[np.int64] = np.asarray([num_false, num_true]) + x_range = ["False", "True"] + x_values = ["False", "True"] + self.plot_vbar( + histogram_figure_data=histogram_figure_data, + category=x_range, + counts=counts, + planner_name=planner_name, + legend_label=legend_name, + color=color, + scenario_names=scenario_names_flatten, + x_values=x_values, + histogram_file_name=histogram_file_name, + ) + counts = np.asarray(counts) + if histogram_figure_data.frequency_array is None: + histogram_figure_data.frequency_array = deepcopy(counts) + else: + histogram_figure_data.frequency_array += counts + + def _plot_count_histogram( + self, + histogram_figure_data: HistogramFigureData, + values: npt.NDArray[np.float64], + scenarios: List[str], + planner_name: str, + legend_name: str, + color: str, + edges: npt.NDArray[np.float64], + histogram_file_name: Optional[str] = None, + ) -> None: + """ + Plot count type of histograms. + :param histogram_figure_data: Histogram figure data. + :param values: An array of values. + :param scenarios: A list of scenario names. + :param planner_name: Planner name. + :param legend_name: Legend name. + :param color: Plot color. + :param edges: Count edges. + :param histogram_file_name: Histogram file name for the histogram data. + """ + uniques: Any = np.unique(values, return_inverse=True) + unique_values: npt.NDArray[np.float64] = uniques[0] + unique_index: npt.NDArray[np.int64] = uniques[1] + + counts = {value: 0 for value in edges} + bin_count = np.bincount(unique_index) + for index, count_value in enumerate(bin_count): + counts[unique_values[index]] = count_value + # Get scenario names + scenario_names: List[List[str]] = [[] for _ in range(len(counts))] + for index, bin_index in enumerate(unique_index): + if not self._max_scenario_names or len(scenario_names[bin_index]) < self._max_scenario_names: + scenario_names[bin_index].append(scenarios[index]) + scenario_names_flatten = ["
".join(names) if names else "" for names in scenario_names] + category = [str(key) for key in counts.keys()] + count_values: npt.NDArray[np.int64] = np.asarray(list(counts.values())) + + self.plot_vbar( + histogram_figure_data=histogram_figure_data, + category=category, + counts=count_values, + planner_name=planner_name, + legend_label=legend_name, + color=color, + scenario_names=scenario_names_flatten, + width=0.1, + x_values=category, + histogram_file_name=histogram_file_name, + ) + if histogram_figure_data.frequency_array is None: + histogram_figure_data.frequency_array = deepcopy(count_values) + else: + histogram_figure_data.frequency_array += count_values + + def _plot_bin_histogram( + self, + histogram_figure_data: HistogramFigureData, + values: npt.NDArray[np.float64], + scenarios: List[str], + planner_name: str, + legend_name: str, + color: str, + edges: npt.NDArray[np.float64], + histogram_file_name: Optional[str] = None, + ) -> None: + """ + Plot bin type of histograms. + :param histogram_figure_data: Histogram figure data. + :param values: An array of values. + :param scenarios: A list of scenario names. + :param planner_name: Planner name. + :param legend_name: Legend name. + :param color: Plot color. + :param edges: Histogram bin edges. + :param histogram_file_name: Histogram file name for the histogram data. + """ + hist, bins = np.histogram(values, bins=edges) + value_bin_index: npt.NDArray[np.int64] = np.asarray(np.digitize(values, bins=bins[:-1])) + + # Get scenario names + scenario_names: List[List[str]] = [[] for _ in range(len(hist))] + for index, bin_index in enumerate(value_bin_index): + if not self._max_scenario_names or len(scenario_names[bin_index - 1]) < self._max_scenario_names: + scenario_names[bin_index - 1].append(scenarios[index]) + scenario_names_flatten = ["
".join(names) if names else "" for names in scenario_names] + + # Get x_values + bins = np.round(bins, HistogramTabFigureStyleConfig.decimal_places) + x_values = [str(value) + ' - ' + str(bins[index + 1]) for index, value in enumerate(bins[:-1])] + self.plot_histogram( + histogram_figure_data=histogram_figure_data, + planner_name=planner_name, + legend_label=legend_name, + hist=hist, + edges=edges, + color=color, + scenario_names=scenario_names_flatten, + x_values=x_values, + histogram_file_name=histogram_file_name, + ) + if histogram_figure_data.frequency_array is None: + histogram_figure_data.frequency_array = deepcopy(hist) + else: + histogram_figure_data.frequency_array += hist + + def _draw_histogram_data(self) -> HistogramConstantConfig.HistogramFigureDataType: + """ + Draw histogram data based on aggregated data. + :return A dictionary of metric names and theirs histograms. + """ + histograms: HistogramConstantConfig.HistogramFigureDataType = defaultdict() + if self._aggregated_data is None or self._histogram_edges is None: + return histograms + for metric_statistics_name, aggregated_histogram_data in self._aggregated_data.items(): + if metric_statistics_name not in histograms: + histograms[metric_statistics_name] = {} + + for histogram_data in aggregated_histogram_data: + legend_name = ( + histogram_data.planner_name + f" ({self.get_file_path_last_name(histogram_data.experiment_index)})" + ) + if histogram_data.planner_name not in self.enable_planner_names: + continue + + color = self.experiment_file_data.file_path_colors[histogram_data.experiment_index][ + histogram_data.planner_name + ] + for statistic_name, statistic in histogram_data.statistics.items(): + unit = statistic.unit + data: npt.NDArray[np.float64] = np.unique( + self._histogram_edges[metric_statistics_name].get(statistic_name, None) + ) + assert data is not None, f"Count edge data for {statistic_name} cannot be None!" + if statistic_name not in histograms[metric_statistics_name]: + x_range = get_histogram_plot_x_range(unit=unit, data=data) + histograms[metric_statistics_name][statistic_name] = self._render_histogram_plot( + title=statistic_name, + x_axis_label=unit, + x_range=x_range, + histogram_file_name=histogram_data.histogram_file_name, + ) + histogram_figure_data = histograms[metric_statistics_name][statistic_name] + values = statistic.values + # Boolean type of data + if unit in ["bool", "boolean"]: + self._plot_bool_histogram( + histogram_figure_data=histogram_figure_data, + values=values, + scenarios=statistic.scenarios, + planner_name=histogram_data.planner_name, + legend_name=legend_name, + color=color, + histogram_file_name=histogram_data.histogram_file_name, + ) + else: + edges = self._histogram_edges[metric_statistics_name][statistic_name] + if edges is None: + continue + + # Count type + if unit in ["count"]: + self._plot_count_histogram( + histogram_figure_data=histogram_figure_data, + values=values, + scenarios=statistic.scenarios, + planner_name=histogram_data.planner_name, + legend_name=legend_name, + color=color, + edges=edges, + histogram_file_name=histogram_data.histogram_file_name, + ) + else: + self._plot_bin_histogram( + histogram_figure_data=histogram_figure_data, + values=values, + scenarios=statistic.scenarios, + planner_name=histogram_data.planner_name, + legend_name=legend_name, + color=color, + edges=edges, + histogram_file_name=histogram_data.histogram_file_name, + ) + + # Make scenario type score always the first one + sorted_histograms = {} + if HistogramConstantConfig.SCENARIO_TYPE_SCORE_HISTOGRAM_NAME in histograms: + sorted_histograms[HistogramConstantConfig.SCENARIO_TYPE_SCORE_HISTOGRAM_NAME] = histograms[ + HistogramConstantConfig.SCENARIO_TYPE_SCORE_HISTOGRAM_NAME + ] + # Sort + sorted_histogram_keys = sorted( + (key for key in histograms.keys() if key != HistogramConstantConfig.SCENARIO_TYPE_SCORE_HISTOGRAM_NAME), + reverse=False, + ) + sorted_histograms.update({key: histograms[key] for key in sorted_histogram_keys}) + return sorted_histograms + + def _render_histograms(self) -> List[column]: + """ + Render histograms across all scenarios based on a scenario type. + :return: A list of lists of figures (a list per row). + """ + histograms = self._draw_histogram_data() + layouts = self._render_histogram_layout(histograms) + # Empty plot + if not layouts: + layouts = [ + column( + self._default_div, width=HistogramTabPlotConfig.default_width, **HistogramTabPlotConfig.get_config() + ) + ] + return layouts diff --git a/sledge/sledgeboard/tabs/js_code/__init__.py b/sledge/sledgeboard/tabs/js_code/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/sledgeboard/tabs/js_code/cloud_tab_js_code.py b/sledge/sledgeboard/tabs/js_code/cloud_tab_js_code.py new file mode 100644 index 0000000..e5ebf9b --- /dev/null +++ b/sledge/sledgeboard/tabs/js_code/cloud_tab_js_code.py @@ -0,0 +1,111 @@ +from dataclasses import dataclass + +from bokeh.models import ColumnDataSource, CustomJS, TextInput + + +@dataclass(frozen=True) +class S3TabLoadingJSCode: + """JS code when loading.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get a custom js function.""" + return CustomJS( + args={}, + code=""" + document.getElementById('overlay').style.display = 'block'; + document.getElementById('cloud-input-form').style.display = 'none'; + document.getElementById('cloud-modal-loading').style.visibility = 'visible'; + """, + ) + + +@dataclass(frozen=True) +class S3TabDataTableUpdateJSCode: + """JS code when updated data table.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get a custom js function.""" + return CustomJS( + args={}, + code=""" + document.getElementById('cloud-modal-loading').style.visibility = 'hidden'; + document.getElementById('overlay').style.display = 'none'; + """, + ) + + +@dataclass(frozen=True) +class S3TabDownloadUpdateJSCode: + """JS code when downloading finishes.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get a custom js function.""" + return CustomJS( + args={}, + code=""" + if (cb_obj.label == 'Download') { + document.getElementById('cloud-modal-loading').style.visibility = 'hidden'; + document.getElementById('overlay').style.display = 'none'; + } + """, + ) + + +@dataclass(frozen=True) +class S3TabContentDataSourceOnSelected: + """JS code when any data table is selected by users.""" + + @classmethod + def get_js_code(cls, selected_column: TextInput, selected_row: TextInput) -> CustomJS: + """ + Get a custom js function. + :param selected_column: Selected column text input. + :param selected_row: Selected row text input. + """ + return CustomJS( + args=dict(selected_column=selected_column, selected_row=selected_row), + code=""" + var grid = document.getElementsByClassName('s3-data-table')[0].children[3].children[2].children[0].children; + var row, column = ''; + for (var i = 0,max = grid.length; i < max; i++){ + if (grid[i].outerHTML.includes('active')){ + row = i; + for (var j = 0, jmax = grid[i].children.length; j < jmax; j++) + { + if(grid[i].children[j].outerHTML.includes('active')) + { + column = j; + } + } + } + } + selected_column.value = String(column); + selected_row.value = String(row); + """, + ) + + +@dataclass(frozen=True) +class S3TabContentDataSourceOnSelectedLoadingJSCode: + """JS code about loading when data table selection happens.""" + + @classmethod + def get_js_code(cls, source: ColumnDataSource, selected_column: TextInput) -> CustomJS: + """ + Get a custom js function. + :param source: Data source. + :param selected_column: Selected column text input. + """ + return CustomJS( + args=dict(source=source, selected_column=selected_column), + code=""" + if (selected_column.value == 0 && source.data['object'][0] != '-') { + document.getElementById('overlay').style.display = 'block'; + document.getElementById('cloud-input-form').style.display = 'none'; + document.getElementById('cloud-modal-loading').style.visibility = 'visible'; + } + """, + ) diff --git a/sledge/sledgeboard/tabs/js_code/histogram_tab_js_code.py b/sledge/sledgeboard/tabs/js_code/histogram_tab_js_code.py new file mode 100644 index 0000000..3775c63 --- /dev/null +++ b/sledge/sledgeboard/tabs/js_code/histogram_tab_js_code.py @@ -0,0 +1,54 @@ +from dataclasses import dataclass + +from bokeh.models.callbacks import CustomJS + + +@dataclass(frozen=True) +class HistogramTabLoadingJSCode: + """JS when loading in the histogram tab.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get js code.""" + return CustomJS( + args={}, + code=""" + cb_obj.tags = [window.outerWidth, window.outerHeight]; + document.getElementById('histogram-loading').style.visibility = 'visible'; + document.getElementById('histogram-plot-section').style.visibility = 'hidden'; + document.getElementById('histogram-setting-form').style.display = 'none'; + """, + ) + + +@dataclass(frozen=True) +class HistogramTabUpdateWindowsSizeJSCode: + """JS when updating window size in the histogram tab.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get js code.""" + return CustomJS( + args={}, + code=""" + console.log(cb_obj.tags); + cb_obj.tags = [window.outerWidth, window.outerHeight]; + """, + ) + + +@dataclass(frozen=True) +class HistogramTabLoadingEndJSCode: + """JS when loading simulation is done in the histogram tab.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get js code.""" + return CustomJS( + args={}, + code=""" + document.getElementById('histogram-loading').style.visibility = 'hidden'; + document.getElementById('histogram-plot-section').style.visibility = 'visible'; + document.getElementById('overlay').style.display = 'none'; + """, + ) diff --git a/sledge/sledgeboard/tabs/js_code/scenario_tab_js_code.py b/sledge/sledgeboard/tabs/js_code/scenario_tab_js_code.py new file mode 100644 index 0000000..6ea32c9 --- /dev/null +++ b/sledge/sledgeboard/tabs/js_code/scenario_tab_js_code.py @@ -0,0 +1,53 @@ +from dataclasses import dataclass + +from bokeh.models.callbacks import CustomJS + + +@dataclass(frozen=True) +class ScenarioTabLoadingJSCode: + """JS when loading simulation in the scenario tab.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get js code.""" + return CustomJS( + args={}, + code=""" + cb_obj.tags = [window.outerWidth, window.outerHeight]; + document.getElementById('scenario-loading').style.visibility = 'visible'; + document.getElementById('scenario-plot-section').style.visibility = 'hidden'; + document.getElementById('scenario-setting-form').style.display = 'none'; + """, + ) + + +@dataclass(frozen=True) +class ScenarioTabUpdateWindowsSizeJSCode: + """JS when updating window size in the scenario tab.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get js code.""" + return CustomJS( + args={}, + code=""" + cb_obj.tags = [window.outerWidth, window.outerHeight]; + """, + ) + + +@dataclass(frozen=True) +class ScenarioTabLoadingEndJSCode: + """JS when loading simulation is done in the scenario tab.""" + + @classmethod + def get_js_code(cls) -> CustomJS: + """Get js code.""" + return CustomJS( + args={}, + code=""" + document.getElementById('scenario-loading').style.visibility = 'hidden'; + document.getElementById('scenario-plot-section').style.visibility = 'visible'; + document.getElementById('overlay').style.display = 'none'; + """, + ) diff --git a/sledge/sledgeboard/tabs/overview_tab.py b/sledge/sledgeboard/tabs/overview_tab.py new file mode 100644 index 0000000..c5e8d1e --- /dev/null +++ b/sledge/sledgeboard/tabs/overview_tab.py @@ -0,0 +1,207 @@ +import logging +from collections import defaultdict +from typing import Any, Dict, List + +import numpy as np +from bokeh.document.document import Document +from bokeh.models import ColumnDataSource, DataTable, TableColumn + +from sledge.sledgeboard.base.base_tab import BaseTab +from sledge.sledgeboard.base.experiment_file_data import ExperimentFileData +from sledge.sledgeboard.tabs.config.overview_tab_config import ( + OVERVIEW_PLANNER_CHECKBOX_GROUP_NAME, + OverviewAggregatorData, + OverviewTabDataTableConfig, + OverviewTabDefaultDataSourceDictConfig, + OverviewTabExperimentTableColumnConfig, + OverviewTabPlannerTableColumnConfig, + OverviewTabScenarioTypeTableColumnConfig, +) + +logger = logging.getLogger(__name__) + + +class OverviewTab(BaseTab): + """Overview tab in SledgeBoard.""" + + def __init__(self, doc: Document, experiment_file_data: ExperimentFileData): + """ + Overview tab to visualize general metric results about simulation. + :param doc: Bokeh HTML document. + :param experiment_file_data: Experiment file data. + """ + super().__init__(doc=doc, experiment_file_data=experiment_file_data) + + self._aggregator_metric_data: Dict[str, List[OverviewAggregatorData]] = {} + + self._default_datasource_dict = dict(**OverviewTabDefaultDataSourceDictConfig.get_config()) + self._default_datasource = ColumnDataSource(data=self._default_datasource_dict) + + self._default_columns = [ + TableColumn(**OverviewTabExperimentTableColumnConfig.get_config()), + TableColumn(**OverviewTabScenarioTypeTableColumnConfig.get_config()), + TableColumn(**OverviewTabPlannerTableColumnConfig.get_config()), + ] + + self.table = DataTable( + source=self._default_datasource, columns=self._default_columns, **OverviewTabDataTableConfig.get_config() + ) + + self.planner_checkbox_group.name = OVERVIEW_PLANNER_CHECKBOX_GROUP_NAME + + def file_paths_on_change( + self, experiment_file_data: ExperimentFileData, experiment_file_active_index: List[int] + ) -> None: + """ + Interface to update layout when file_paths is changed. + :param experiment_file_data: Experiment file data. + :param experiment_file_active_index: Active indexes for experiment files. + """ + self._experiment_file_data = experiment_file_data + self._experiment_file_active_index = experiment_file_active_index + + self._overview_on_change() + + def _click_planner_checkbox_group(self, attr: Any) -> None: + """ + Click event handler for planner_checkbox_group. + :param attr: Clicked attributes. + """ + self._update_overview_table(self._aggregator_metric_data) + + def _overview_on_change(self) -> None: + """Callback when metric search bar changes.""" + # Aggregator metric aggregator data + self._aggregator_metric_data = self._aggregate_metric_aggregator() + self._update_overview_table(data=self._aggregator_metric_data) + + def _aggregate_metric_aggregator(self) -> Dict[str, List[OverviewAggregatorData]]: + """ + Aggregate metric aggregator data. + :return: A dictionary of metric aggregator names and their metric scores. + """ + data: Dict[str, List[OverviewAggregatorData]] = defaultdict(list) + # For planner checkbox + planner_names: List[str] = [] + # Loop through all metric aggregators + for index, metric_aggregator_dataframes in enumerate(self.experiment_file_data.metric_aggregator_dataframes): + if index not in self._experiment_file_active_index: + continue + for metric_aggregator_filename, metric_aggregator_dataframe in metric_aggregator_dataframes.items(): + # Iterate through rows + for _, row_data in metric_aggregator_dataframe.iterrows(): + num_scenarios = row_data["num_scenarios"] + if not num_scenarios or np.isnan(num_scenarios): + continue + + aggregator_type = row_data["aggregator_type"] + planner_name = row_data["planner_name"] + scenario_type = row_data["scenario_type"] + metric_score = row_data["score"] + # Add aggregator data to the data dictionary, the key is + data[metric_aggregator_filename].append( + OverviewAggregatorData( + aggregator_type=aggregator_type, + planner_name=planner_name, + scenario_type=scenario_type, + num_scenarios=int(num_scenarios), + score=metric_score, + aggregator_file_name=metric_aggregator_filename, + ) + ) + planner_names.append(planner_name) + + sorted_planner_names = sorted(list(set(planner_names))) + self.planner_checkbox_group.labels = sorted_planner_names + self.planner_checkbox_group.active = [index for index in range(len(sorted_planner_names))] + return data + + def _update_overview_table(self, data: Dict[str, List[OverviewAggregatorData]]) -> None: + """ + Update overview table with the new metric aggregator data. + :param data: Metric aggregator data. + """ + # Get all planner names + planner_names = sorted( + list( + { + metric_aggregator_data.planner_name + for _, metric_aggregator_data_list in data.items() + for metric_aggregator_data in metric_aggregator_data_list + if metric_aggregator_data.planner_name in self.enable_planner_names + } + ) + ) + planner_name_columns: Dict[str, List[Any]] = {planner_name: [] for planner_name in planner_names} + metric_aggregator_files: List[str] = [] + scenario_types: List[str] = [] + for metric_file, metric_aggregator_data_list in data.items(): + metric_scores: Dict[str, Dict[str, List[float]]] = defaultdict(lambda: defaultdict(list)) + initial_planner_names: List[str] = [] + for metric_aggregator_data in metric_aggregator_data_list: + initial_planner_names.append(metric_aggregator_data.planner_name) + if metric_aggregator_data.planner_name not in self.enable_planner_names: + continue + + metric_scores[metric_aggregator_data.planner_name][metric_aggregator_data.scenario_type] = [ + np.round(metric_aggregator_data.score, 4), + metric_aggregator_data.num_scenarios, + ] + + initial_planner_names = list(set(initial_planner_names)) + if metric_scores: + metric_aggregator_files += [metric_file] + [''] * ( + (len(metric_aggregator_data_list) // len(initial_planner_names)) - 1 + ) + + # Sorted by scenario type + metric_aggregator_file_scenario_types: List[str] = [] + for planner_name, values in metric_scores.items(): + sorted_metric_scores: Dict[str, List[float]] = dict( + sorted(list(values.items()), key=lambda item: item[0]) + ) + # Make sure final_score is always the first + sorted_final_metric_scores = { + f'all ({sorted_metric_scores["final_score"][1]})': sorted_metric_scores['final_score'][0] + } + sorted_final_metric_scores.update( + { + f'{scenario_type} ({score[1]})': score[0] + for scenario_type, score in sorted_metric_scores.items() + if scenario_type != 'final_score' + } + ) + metric_file_scenario_types = list(sorted_final_metric_scores.keys()) + metric_file_scenario_type_scores = list(sorted_final_metric_scores.values()) + planner_name_columns[planner_name] += metric_file_scenario_type_scores + if not metric_aggregator_file_scenario_types: + metric_aggregator_file_scenario_types += metric_file_scenario_types + scenario_types += metric_aggregator_file_scenario_types + + # Fill in empty planners + for planner_name in planner_names: + if planner_name not in metric_scores: + empty_scenario_scores = ['-'] * len(metric_aggregator_file_scenario_types) + planner_name_columns[planner_name] += empty_scenario_scores + if planner_name_columns: + data_sources: Dict[str, List[Any]] = { + 'experiment': metric_aggregator_files, + 'scenario_type': scenario_types, + } + data_sources.update(planner_name_columns) + + # Make planner columns + planner_table_columns = [ + TableColumn(field=planner_name, title=f'Evaluation Score {index+1}: {planner_name}', sortable=False) + for index, planner_name in enumerate(planner_name_columns.keys()) + ] + columns = [ + TableColumn(**OverviewTabExperimentTableColumnConfig.get_config()), + TableColumn(**OverviewTabScenarioTypeTableColumnConfig.get_config()), + ] + columns += planner_table_columns + self.table.columns = columns + self.table.source.data = data_sources + else: + self.table.columns = self._default_columns + self.table.source.data = self._default_datasource_dict diff --git a/sledge/sledgeboard/tabs/scenario_tab.py b/sledge/sledgeboard/tabs/scenario_tab.py new file mode 100644 index 0000000..a60b322 --- /dev/null +++ b/sledge/sledgeboard/tabs/scenario_tab.py @@ -0,0 +1,1183 @@ +import logging +import time +from collections import defaultdict +from dataclasses import dataclass +from itertools import chain +from math import ceil +from typing import Any, Dict, List, Optional, cast + +import numpy as np +import numpy.typing as npt +import pandas +from bokeh.document.document import Document +from bokeh.layouts import LayoutDOM, column, gridplot, layout +from bokeh.models import ( + BasicTickFormatter, + Button, + CheckboxGroup, + ColumnDataSource, + Div, + HoverTool, + MultiChoice, + Select, +) +from bokeh.plotting.figure import Figure + +from nuplan.common.actor_state.ego_state import EgoState +from nuplan.common.actor_state.vehicle_parameters import VehicleParameters +from sledge.sledgeboard.base.base_tab import BaseTab +from sledge.sledgeboard.base.experiment_file_data import ExperimentFileData +from sledge.sledgeboard.base.plot_data import SimulationData +from sledge.sledgeboard.base.simulation_tile import SimulationTile +from sledge.sledgeboard.style import PLOT_PALETTE, default_div_style, scenario_tab_style +from sledge.sledgeboard.tabs.config.scenario_tab_config import ( + ScenarioTabModalQueryButtonConfig, + ScenarioTabScenarioTokenMultiChoiceConfig, + ScenarioTabTitleDivConfig, +) +from sledge.sledgeboard.tabs.js_code.scenario_tab_js_code import ( + ScenarioTabLoadingEndJSCode, + ScenarioTabLoadingJSCode, + ScenarioTabUpdateWindowsSizeJSCode, +) +from nuplan.planning.scenario_builder.abstract_scenario_builder import AbstractScenarioBuilder + +logger = logging.getLogger(__name__) + + +@dataclass +class ScenarioMetricScoreData: + """Metric final score data for each scenario in the scenario tab.""" + + experiment_index: int # Experiment index to represent color + metric_aggregator_file_name: str # Aggregator file name + metric_aggregator_file_index: int # Index of a metric aggregator file in a folder + planner_name: str # Planner name + metric_statistic_name: str # Metric statistic name + score: float # Metric score, some metrics have not a score (none instead of 0) + + +@dataclass +class ScenarioMetricScoreDataSource: + """Data source for each scenario metric final score figure.""" + + xs: List[str] # A list of x axis values + ys: List[float] # A list of y axis values + planners: List[str] # A list of planner names + aggregators: List[str] # A list of aggregator file names + experiments: List[str] # A list of experiment file names + fill_colors: List[str] # A list of fill colors + marker: str # Marker + legend_label: str # Legend label name + + +@dataclass +class ScenarioTimeSeriesData: + """Time series data in the scenario tab.""" + + experiment_index: int # Experiment index to represent color. + planner_name: str # Planner name + time_series_values: npt.NDArray[np.float64] # A list of time series values + time_series_timestamps: List[int] # A list of time series timestamps + time_series_unit: str # Time series unit + time_series_selected_frames: Optional[List[int]] # Time series selected frames + + +# Type for scenario metric score data type: {log name: {scenario name: metric score data}} +scenario_metric_score_dict_type = Dict[str, Dict[str, List[ScenarioMetricScoreData]]] +# Type for scenario ego expert states: {planner_name: {state_key: [state_values]}} +scenario_ego_expert_state_figure_type = Dict[str, Dict[str, List[float]]] + + +class ScenarioTab(BaseTab): + """Scenario tab in SledgeBoard.""" + + def __init__( + self, + doc: Document, + experiment_file_data: ExperimentFileData, + vehicle_parameters: VehicleParameters, + scenario_builder: AbstractScenarioBuilder, + async_rendering: bool = True, + frame_rate_cap_hz: int = 60, + ): + """ + Scenario tab to render metric results about a scenario. + :param doc: Bokeh HTML document. + :param experiment_file_data: Experiment file data. + :param vehicle_parameters: Vehicle parameters. + :param scenario_builder: nuPlan scenario builder instance. + :param async_rendering: When true, will use threads to render SimulationTiles asynchronously. + :param frame_rate_cap_hz: Maximum frames to render per second. Internally this value is capped at 60. + """ + super().__init__(doc=doc, experiment_file_data=experiment_file_data) + self._number_metrics_per_figure: int = 4 + self.planner_checkbox_group.name = "scenario_planner_checkbox_group" + self._scenario_builder = scenario_builder + + # UI. + self._scenario_title_div = Div(**ScenarioTabTitleDivConfig.get_config()) + self._scalar_scenario_type_select = Select( + name="scenario_scalar_scenario_type_select", + css_classes=["scalar-scenario-type-select"], + ) + self._scalar_scenario_type_select.on_change("value", self._scalar_scenario_type_select_on_change) + self._scalar_log_name_select = Select( + name="scenario_scalar_log_name_select", + css_classes=["scalar-log-name-select"], + ) + self._scalar_log_name_select.on_change("value", self._scalar_log_name_select_on_change) + self._scalar_scenario_name_select = Select( + name="scenario_scalar_name_select", + css_classes=["scalar-scenario-name-select"], + ) + self._scalar_scenario_name_select.js_on_change("value", ScenarioTabUpdateWindowsSizeJSCode.get_js_code()) + self._scalar_scenario_name_select.on_change("value", self._scalar_scenario_name_select_on_change) + self._scenario_token_multi_choice = MultiChoice(**ScenarioTabScenarioTokenMultiChoiceConfig.get_config()) + self._scenario_token_multi_choice.on_change("value", self._scenario_token_multi_choice_on_change) + + self._scenario_modal_query_btn = Button(**ScenarioTabModalQueryButtonConfig.get_config()) + self._scenario_modal_query_btn.js_on_click(ScenarioTabLoadingJSCode.get_js_code()) + self._scenario_modal_query_btn.on_click(self._scenario_modal_query_button_on_click) + + self.planner_checkbox_group.js_on_change("active", ScenarioTabLoadingJSCode.get_js_code()) + self._default_time_series_div = Div( + text="""

No time series results, please add more experiments or + adjust the search filter.

""", + css_classes=['scenario-default-div'], + margin=default_div_style['margin'], + width=default_div_style['width'], + ) + self._time_series_layout = column( + self._default_time_series_div, + css_classes=["scenario-time-series-layout"], + name="time_series_layout", + ) + self._default_ego_expert_states_div = Div( + text="""

No expert and ego states, please add more experiments or + adjust the search filter.

""", + css_classes=['scenario-default-div'], + margin=default_div_style['margin'], + width=default_div_style['width'], + ) + self._ego_expert_states_layout = column( + self._default_ego_expert_states_div, + css_classes=["scenario-ego-expert-states-layout"], + name="ego_expert_states_layout", + ) + self._default_simulation_div = Div( + text="""

No simulation data, please add more experiments or + adjust the search filter.

""", + css_classes=['scenario-default-div'], + margin=default_div_style['margin'], + width=default_div_style['width'], + ) + self._simulation_tile_layout = column( + self._default_simulation_div, + css_classes=["scenario-simulation-layout"], + name="simulation_tile_layout", + ) + + self._simulation_tile_layout.js_on_change("children", ScenarioTabLoadingEndJSCode.get_js_code()) + self.simulation_tile = SimulationTile( + map_factory=self._scenario_builder.get_map_factory(), + doc=self._doc, + vehicle_parameters=vehicle_parameters, + experiment_file_data=experiment_file_data, + async_rendering=async_rendering, + frame_rate_cap_hz=frame_rate_cap_hz, + ) + + self._default_scenario_score_div = Div( + text="""

No scenario score results, please add more experiments or + adjust the search filter.

""", + css_classes=['scenario-default-div'], + margin=default_div_style['margin'], + width=default_div_style['width'], + ) + self._scenario_score_layout = column( + self._default_scenario_score_div, + css_classes=["scenario-score-layout"], + name="scenario_score_layout", + ) + + self._scenario_metric_score_data_figure_sizes = scenario_tab_style['scenario_metric_score_figure_sizes'] + self._scenario_metric_score_data: scenario_metric_score_dict_type = {} + self._time_series_data: Dict[str, List[ScenarioTimeSeriesData]] = {} + self._simulation_figure_data: List[SimulationData] = [] + self._available_scenario_names: List[str] = [] + self._simulation_plots: Optional[column] = None + + object_types = ['Ego', 'Vehicle', 'Pedestrian', 'Bicycle', 'Generic', 'Traffic Cone', 'Barrier', 'Czone Sign'] + self._object_checkbox_group = CheckboxGroup( + labels=object_types, + active=list(range(len(object_types))), + css_classes=["scenario-object-checkbox-group"], + name='scenario_object_checkbox_group', + ) + self._object_checkbox_group.on_change('active', self._object_checkbox_group_active_on_change) + trajectories = ['Expert Trajectory', 'Ego Trajectory', 'Goal', 'Traffic Light', 'RoadBlock'] + self._traj_checkbox_group = CheckboxGroup( + labels=trajectories, + active=list(range(len(trajectories))), + css_classes=["scenario-traj-checkbox-group"], + name='scenario_traj_checkbox_group', + ) + self._traj_checkbox_group.on_change('active', self._traj_checkbox_group_active_on_change) + map_objects = [ + 'Lane', + 'Intersection', + 'Stop Line', + 'Crosswalk', + 'Walkway', + 'Carpark', + 'Lane Connector', + 'Lane Line', + ] + self._map_checkbox_group = CheckboxGroup( + labels=map_objects, + active=list(range(len(map_objects))), + css_classes=["scenario-map-checkbox-group"], + name='scenario_map_checkbox_group', + ) + self._map_checkbox_group.on_change('active', self._map_checkbox_group_active_on_change) + self.plot_state_keys = [ + 'x [m]', + 'y [m]', + 'heading [rad]', + 'velocity_x [m/s]', + 'velocity_y [m/s]', + 'speed [m/s]', + 'acceleration_x [m/s^2]', + 'acceleration_y [m/s^2]', + 'acceleration [m/s^2]', + 'steering_angle [rad]', + 'yaw_rate [rad/s]', + ] + self.expert_planner_key = 'Expert' + self._init_selection() + + @property + def scenario_title_div(self) -> Div: + """Return scenario title div.""" + return self._scenario_title_div + + @property + def scalar_scenario_type_select(self) -> Select: + """Return scalar_scenario_type_select.""" + return self._scalar_scenario_type_select + + @property + def scalar_log_name_select(self) -> Select: + """Return scalar_log_name_select.""" + return self._scalar_log_name_select + + @property + def scalar_scenario_name_select(self) -> Select: + """Return scalar_scenario_name_select.""" + return self._scalar_scenario_name_select + + @property + def scenario_token_multi_choice(self) -> MultiChoice: + """Return scenario_token multi choice.""" + return self._scenario_token_multi_choice + + @property + def scenario_modal_query_btn(self) -> Button: + """Return scenario_modal_query_button.""" + return self._scenario_modal_query_btn + + @property + def object_checkbox_group(self) -> CheckboxGroup: + """Return object checkbox group.""" + return self._object_checkbox_group + + @property + def traj_checkbox_group(self) -> CheckboxGroup: + """Return traj checkbox group.""" + return self._traj_checkbox_group + + @property + def map_checkbox_group(self) -> CheckboxGroup: + """Return map checkbox group.""" + return self._map_checkbox_group + + @property + def time_series_layout(self) -> column: + """Return time_series_layout.""" + return self._time_series_layout + + @property + def scenario_score_layout(self) -> column: + """Return scenario_score_layout.""" + return self._scenario_score_layout + + @property + def simulation_tile_layout(self) -> column: + """Return simulation_tile_layout.""" + return self._simulation_tile_layout + + @property + def ego_expert_states_layout(self) -> column: + """Return time_series_state_layout.""" + return self._ego_expert_states_layout + + def _update_glyph_checkbox_group(self, glyph_names: List[str]) -> None: + """ + Update visibility of glyphs according to checkbox group. + :param glyph_names: A list of updated glyph names. + """ + for simulation_figure in self.simulation_tile.figures: + simulation_figure.update_glyphs_visibility(glyph_names=glyph_names) + + def _traj_checkbox_group_active_on_change(self, attr: str, old: List[int], new: List[int]) -> None: + """ + Helper function for traj checkbox group when the list of actives changes. + :param attr: Attribute name. + :param old: Old active index. + :param new: New active index. + """ + active_indices = list(set(old) - set(new)) + list(set(new) - set(old)) + active_labels = [self._traj_checkbox_group.labels[index] for index in active_indices] + self._update_glyph_checkbox_group(glyph_names=active_labels) + + def _map_checkbox_group_active_on_change(self, attr: str, old: List[int], new: List[int]) -> None: + """ + Helper function for map checkbox group when the list of actives changes. + :param attr: Attribute name. + :param old: Old active index. + :param new: New active index. + """ + active_indices = list(set(old) - set(new)) + list(set(new) - set(old)) + active_labels = [self._map_checkbox_group.labels[index] for index in active_indices] + self._update_glyph_checkbox_group(glyph_names=active_labels) + + def _object_checkbox_group_active_on_change(self, attr: str, old: List[int], new: List[int]) -> None: + """ + Helper function for object checkbox group when the list of actives changes. + :param attr: Attribute name. + :param old: Old active index. + :param new: New active index. + """ + active_indices = list(set(old) - set(new)) + list(set(new) - set(old)) + active_labels = [self._object_checkbox_group.labels[index] for index in active_indices] + self._update_glyph_checkbox_group(glyph_names=active_labels) + + def file_paths_on_change( + self, experiment_file_data: ExperimentFileData, experiment_file_active_index: List[int] + ) -> None: + """ + Interface to update layout when file_paths is changed. + :param experiment_file_data: Experiment file data. + :param experiment_file_active_index: Active indexes for experiment files. + """ + self._experiment_file_data = experiment_file_data + self._experiment_file_active_index = experiment_file_active_index + + self.simulation_tile.init_simulations(figure_sizes=self.simulation_figure_sizes) + self._init_selection() + self._scenario_metric_score_data = self._update_aggregation_metric() + self._update_scenario_plot() + + def _click_planner_checkbox_group(self, attr: Any) -> None: + """ + Click event handler for planner_checkbox_group. + :param attr: Clicked attributes. + """ + # Render scenario metric figures + scenario_metric_score_figure_data = self._render_scenario_metric_score() + + # Render scenario metric score layout + scenario_metric_score_layout = self._render_scenario_metric_layout( + figure_data=scenario_metric_score_figure_data, + default_div=self._default_scenario_score_div, + plot_width=self._scenario_metric_score_data_figure_sizes[0], + legend=False, + ) + self._scenario_score_layout.children[0] = layout(scenario_metric_score_layout) + + # Filter time series data + filtered_time_series_data: Dict[str, List[ScenarioTimeSeriesData]] = defaultdict(list) + for key, time_series_data in self._time_series_data.items(): + for data in time_series_data: + if data.planner_name not in self.enable_planner_names: + continue + filtered_time_series_data[key].append(data) + + # Render time series figure data + time_series_figure_data = self._render_time_series(aggregated_time_series_data=filtered_time_series_data) + + # Render time series layout + time_series_figures = self._render_scenario_metric_layout( + figure_data=time_series_figure_data, + default_div=self._default_time_series_div, + plot_width=self.plot_sizes[0], + legend=True, + ) + self._time_series_layout.children[0] = layout(time_series_figures) + + # Render simulation + filtered_simulation_figures = [ + data for data in self._simulation_figure_data if data.planner_name in self.enable_planner_names + ] + if not filtered_simulation_figures: + simulation_layouts = column(self._default_simulation_div) + ego_expert_state_layouts = column(self._default_ego_expert_states_div) + else: + simulation_layouts = gridplot( + [simulation_figure.plot for simulation_figure in filtered_simulation_figures], + ncols=self.get_plot_cols( + plot_width=self.simulation_figure_sizes[0], offset_width=scenario_tab_style['col_offset_width'] + ), + toolbar_location=None, + ) + ego_expert_state_layouts = self._render_ego_expert_states( + simulation_figure_data=filtered_simulation_figures + ) + self._simulation_tile_layout.children[0] = layout(simulation_layouts) + self._ego_expert_states_layout.children[0] = layout(ego_expert_state_layouts) + + def _update_simulation_layouts(self) -> None: + """Update simulation layouts.""" + self._simulation_tile_layout.children[0] = layout(self._simulation_plots) + + def _update_scenario_plot(self) -> None: + """Update scenario plots when selection is made.""" + start_time = time.perf_counter() + self._simulation_figure_data = [] + + # Render scenario metric score figure data + scenario_metric_score_figure_data = self._render_scenario_metric_score() + + # Render scenario metric score layout + scenario_metric_score_layout = self._render_scenario_metric_layout( + figure_data=scenario_metric_score_figure_data, + default_div=self._default_scenario_score_div, + plot_width=self._scenario_metric_score_data_figure_sizes[0], + legend=False, + ) + self._scenario_score_layout.children[0] = layout(scenario_metric_score_layout) + + # Aggregate time series data + self._time_series_data = self._aggregate_time_series_data() + # Render time series figure data + time_series_figure_data = self._render_time_series(aggregated_time_series_data=self._time_series_data) + # Render time series layout + time_series_figures = self._render_scenario_metric_layout( + figure_data=time_series_figure_data, + default_div=self._default_time_series_div, + plot_width=self.plot_sizes[0], + legend=True, + ) + self._time_series_layout.children[0] = layout(time_series_figures) + + # Render simulations. + self._simulation_plots = self._render_simulations() + + # render ego and expert states, call after rendering simulation + ego_expert_state_layout = self._render_ego_expert_states(simulation_figure_data=self._simulation_figure_data) + self._ego_expert_states_layout.children[0] = layout(ego_expert_state_layout) + + # Make sure the simulation plot upgrades at the last + self._doc.add_next_tick_callback(self._update_simulation_layouts) + end_time = time.perf_counter() + elapsed_time = end_time - start_time + logger.info(f"Rending scenario plot takes {elapsed_time:.4f} seconds.") + + def _update_planner_names(self) -> None: + """Update planner name options in the checkbox widget.""" + self.planner_checkbox_group.labels = [] + self.planner_checkbox_group.active = [] + selected_keys = [ + key + for key in self.experiment_file_data.simulation_scenario_keys + if key.scenario_type == self._scalar_scenario_type_select.value + and key.scenario_name == self._scalar_scenario_name_select.value + ] + sorted_planner_names = sorted(list({key.planner_name for key in selected_keys})) + self.planner_checkbox_group.labels = sorted_planner_names + self.planner_checkbox_group.active = [index for index in range(len(sorted_planner_names))] + + def _scalar_scenario_type_select_on_change(self, attr: str, old: str, new: str) -> None: + """ + Helper function to change event in scalar scenario type. + :param attr: Attribute. + :param old: Old value. + :param new: New value. + """ + if new == "": + return + + available_log_names = self.load_log_name(scenario_type=self._scalar_scenario_type_select.value) + self._scalar_log_name_select.options = [""] + available_log_names + self._scalar_log_name_select.value = "" + self._scalar_scenario_name_select.options = [""] + self._scalar_scenario_name_select.value = "" + + def _scalar_log_name_select_on_change(self, attr: str, old: str, new: str) -> None: + """ + Helper function to change event in scalar log name. + :param attr: Attribute. + :param old: Old value. + :param new: New value. + """ + if new == "": + return + + available_scenario_names = self.load_scenario_names( + scenario_type=self._scalar_scenario_type_select.value, log_name=self._scalar_log_name_select.value + ) + self._scalar_scenario_name_select.options = [""] + available_scenario_names + self._scalar_scenario_name_select.value = "" + + def _scalar_scenario_name_select_on_change(self, attr: str, old: str, new: str) -> None: + """ + Helper function to change event in scalar scenario name. + :param attr: Attribute. + :param old: Old value. + :param new: New value. + """ + if self._scalar_scenario_name_select.tags: + self.window_width = self._scalar_scenario_name_select.tags[0] + self.window_height = self._scalar_scenario_name_select.tags[1] + + def _scenario_token_multi_choice_on_change(self, attr: str, old: List[str], new: List[str]) -> None: + """ + Helper function to change event in scenario token multi choice. + :param attr: Attribute. + :param old: List of old values. + :param new: List of new values. + """ + available_scenario_tokens = self._experiment_file_data.available_scenario_tokens + if not available_scenario_tokens or not new: + return + scenario_token_info = available_scenario_tokens.get(new[0]) + if self._scalar_scenario_type_select.value != scenario_token_info.scenario_type: + self._scalar_scenario_type_select.value = scenario_token_info.scenario_type + if self._scalar_log_name_select.value != scenario_token_info.log_name: + self._scalar_log_name_select.value = scenario_token_info.log_name + if self._scalar_scenario_name_select.value != scenario_token_info.scenario_name: + self.scalar_scenario_name_select.value = scenario_token_info.scenario_name + + def _scenario_modal_query_button_on_click(self) -> None: + """Helper function when click the modal query button.""" + if self._scalar_scenario_name_select.tags: + self.window_width = self._scalar_scenario_name_select.tags[0] + self.window_height = self._scalar_scenario_name_select.tags[1] + self._update_planner_names() + self._update_scenario_plot() + + def _init_selection(self) -> None: + """Init histogram and scalar selection options.""" + self._scalar_scenario_type_select.value = "" + self._scalar_scenario_type_select.options = [] + self._scalar_log_name_select.value = "" + self._scalar_log_name_select.options = [] + self._scalar_scenario_name_select.value = "" + self._scalar_scenario_name_select.options = [] + self._available_scenario_names = [] + self._simulation_figure_data = [] + + if len(self._scalar_scenario_type_select.options) == 0: + self._scalar_scenario_type_select.options = [""] + self.experiment_file_data.available_scenario_types + + if len(self._scalar_scenario_type_select.options) > 0: + self._scalar_scenario_type_select.value = self._scalar_scenario_type_select.options[0] + + available_scenario_tokens = list(self._experiment_file_data.available_scenario_tokens.keys()) + self._scenario_token_multi_choice.options = available_scenario_tokens + self._update_planner_names() + + @staticmethod + def _render_scalar_figure( + title: str, + y_axis_label: str, + hover: HoverTool, + sizes: List[int], + x_axis_label: Optional[str] = None, + x_range: Optional[List[str]] = None, + y_range: Optional[List[str]] = None, + ) -> Figure: + """ + Render a scalar figure. + :param title: Plot title. + :param y_axis_label: Y axis label. + :param hover: Hover tool for the plot. + :param sizes: Width and height in pixels. + :param x_axis_label: Label in x axis. + :param x_range: Labels in x major axis. + :param y_range: Labels in y major axis. + :return A time series plot. + """ + scenario_scalar_figure = Figure( + background_fill_color=PLOT_PALETTE["background_white"], + title=title, + css_classes=["time-series-figure"], + margin=scenario_tab_style["time_series_figure_margins"], + width=sizes[0], + height=sizes[1], + active_scroll="wheel_zoom", + output_backend="webgl", + x_range=x_range, + y_range=y_range, + ) + scenario_scalar_figure.add_tools(hover) + + scenario_scalar_figure.title.text_font_size = scenario_tab_style["time_series_figure_title_text_font_size"] + scenario_scalar_figure.xaxis.axis_label_text_font_size = scenario_tab_style[ + "time_series_figure_xaxis_axis_label_text_font_size" + ] + scenario_scalar_figure.xaxis.major_label_text_font_size = scenario_tab_style[ + "time_series_figure_xaxis_major_label_text_font_size" + ] + scenario_scalar_figure.yaxis.axis_label_text_font_size = scenario_tab_style[ + "time_series_figure_yaxis_axis_label_text_font_size" + ] + scenario_scalar_figure.yaxis.major_label_text_font_size = scenario_tab_style[ + "time_series_figure_yaxis_major_label_text_font_size" + ] + scenario_scalar_figure.toolbar.logo = None + + # Rotate the x_axis label with 45 (180/4) degrees. + scenario_scalar_figure.xaxis.major_label_orientation = np.pi / 4 + + scenario_scalar_figure.yaxis.axis_label = y_axis_label + scenario_scalar_figure.xaxis.axis_label = x_axis_label + + return scenario_scalar_figure + + def _update_aggregation_metric(self) -> scenario_metric_score_dict_type: + """ + Update metric score for each scenario. + :return A dict of log name: {scenario names and their metric scores}. + """ + data: scenario_metric_score_dict_type = defaultdict(lambda: defaultdict(list)) + # Loop through all metric aggregators + for index, metric_aggregator_dataframes in enumerate(self.experiment_file_data.metric_aggregator_dataframes): + if index not in self._experiment_file_active_index: + continue + for file_index, (metric_aggregator_filename, metric_aggregator_dataframe) in enumerate( + metric_aggregator_dataframes.items() + ): + # Get columns + columns = set(list(metric_aggregator_dataframe.columns)) + # List of non-metric columns to be excluded + non_metric_columns = { + 'scenario', + 'log_name', + 'scenario_type', + 'num_scenarios', + 'planner_name', + 'aggregator_type', + } + metric_columns = sorted(list(columns - non_metric_columns)) + # Iterate through rows + for _, row_data in metric_aggregator_dataframe.iterrows(): + num_scenarios = row_data["num_scenarios"] + if not np.isnan(num_scenarios): + continue + + planner_name = row_data["planner_name"] + scenario_name = row_data["scenario"] + log_name = row_data["log_name"] + for metric_column in metric_columns: + score = row_data[metric_column] + # Add scenario metric score data + if score is not None: + data[log_name][scenario_name].append( + ScenarioMetricScoreData( + experiment_index=index, + metric_aggregator_file_name=metric_aggregator_filename, + metric_aggregator_file_index=file_index, + planner_name=planner_name, + metric_statistic_name=metric_column, + score=np.round(score, 4), + ) + ) + + return data + + def _aggregate_time_series_data(self) -> Dict[str, List[ScenarioTimeSeriesData]]: + """ + Aggregate time series data. + :return A dict of metric statistic names and their data. + """ + aggregated_time_series_data: Dict[str, List[ScenarioTimeSeriesData]] = {} + scenario_types = ( + tuple([self._scalar_scenario_type_select.value]) if self._scalar_scenario_type_select.value else None + ) + log_names = tuple([self._scalar_log_name_select.value]) if self._scalar_log_name_select.value else None + if not len(self._scalar_scenario_name_select.value): + return aggregated_time_series_data + for index, metric_statistics_dataframes in enumerate(self.experiment_file_data.metric_statistics_dataframes): + if index not in self._experiment_file_active_index: + continue + + for metric_statistics_dataframe in metric_statistics_dataframes: + planner_names = metric_statistics_dataframe.planner_names + if metric_statistics_dataframe.metric_statistic_name not in aggregated_time_series_data: + aggregated_time_series_data[metric_statistics_dataframe.metric_statistic_name] = [] + for planner_name in planner_names: + data_frame = metric_statistics_dataframe.query_scenarios( + scenario_names=tuple([str(self._scalar_scenario_name_select.value)]), + scenario_types=scenario_types, + planner_names=tuple([planner_name]), + log_names=log_names, + ) + if not len(data_frame): + continue + + time_series_headers = metric_statistics_dataframe.time_series_headers + time_series: pandas.DataFrame = data_frame[time_series_headers] + if time_series[time_series_headers[0]].iloc[0] is None: + continue + + time_series_values: npt.NDArray[np.float64] = np.round( + np.asarray( + list( + chain.from_iterable(time_series[metric_statistics_dataframe.time_series_values_column]) + ) + ), + 4, + ) + + time_series_timestamps = list( + chain.from_iterable(time_series[metric_statistics_dataframe.time_series_timestamp_column]) + ) + time_series_unit = time_series[metric_statistics_dataframe.time_series_unit_column].iloc[0] + time_series_selected_frames = metric_statistics_dataframe.get_time_series_selected_frames + + scenario_time_series_data = ScenarioTimeSeriesData( + experiment_index=index, + planner_name=planner_name, + time_series_values=time_series_values, + time_series_timestamps=time_series_timestamps, + time_series_unit=time_series_unit, + time_series_selected_frames=time_series_selected_frames, + ) + + aggregated_time_series_data[metric_statistics_dataframe.metric_statistic_name].append( + scenario_time_series_data + ) + + return aggregated_time_series_data + + def _render_time_series( + self, aggregated_time_series_data: Dict[str, List[ScenarioTimeSeriesData]] + ) -> Dict[str, Figure]: + """ + Render time series plots. + :param aggregated_time_series_data: Aggregated scenario time series data. + :return A dict of figure name and figures. + """ + time_series_figures: Dict[str, Figure] = {} + for metric_statistic_name, scenario_time_series_data in aggregated_time_series_data.items(): + for data in scenario_time_series_data: + if not len(data.time_series_values): + continue + + if metric_statistic_name not in time_series_figures: + time_series_figures[metric_statistic_name] = self._render_scalar_figure( + title=metric_statistic_name, + y_axis_label=data.time_series_unit, + x_axis_label='frame', + hover=HoverTool( + tooltips=[ + ("Frame", "@x"), + ("Value", "@y{0.0000}"), + ("Time_us", "@time_us"), + ("Planner", "$name"), + ] + ), + sizes=self.plot_sizes, + ) + planner_name = data.planner_name + f" ({self.get_file_path_last_name(data.experiment_index)})" + color = self.experiment_file_data.file_path_colors[data.experiment_index][data.planner_name] + time_series_figure = time_series_figures[metric_statistic_name] + # Get frame numbers based on timestamps + timestamp_frames = ( + data.time_series_selected_frames + if data.time_series_selected_frames is not None + else list(range(len(data.time_series_timestamps))) + ) + data_source = ColumnDataSource( + dict( + x=timestamp_frames, + y=data.time_series_values, + time_us=data.time_series_timestamps, + ) + ) + if data.time_series_selected_frames is not None: + time_series_figure.scatter( + x="x", y="y", name=planner_name, color=color, legend_label=planner_name, source=data_source + ) + else: + time_series_figure.line( + x="x", y="y", name=planner_name, color=color, legend_label=planner_name, source=data_source + ) + + return time_series_figures + + def _render_scenario_metric_score_scatter( + self, scatter_figure: Figure, scenario_metric_score_data: Dict[str, List[ScenarioMetricScoreData]] + ) -> None: + """ + Render scatter plot with scenario metric score data. + :param scatter_figure: A scatter figure. + :param scenario_metric_score_data: Metric score data for a scenario. + """ + # Aggregate data sources + data_sources: Dict[str, ScenarioMetricScoreDataSource] = {} + for metric_name, metric_score_data in scenario_metric_score_data.items(): + for index, score_data in enumerate(metric_score_data): + experiment_name = self.get_file_path_last_name(score_data.experiment_index) + legend_label = f"{score_data.planner_name} ({experiment_name})" + data_source_index = legend_label + f" - {score_data.metric_aggregator_file_index})" + if data_source_index not in data_sources: + data_sources[data_source_index] = ScenarioMetricScoreDataSource( + xs=[], + ys=[], + planners=[], + aggregators=[], + experiments=[], + fill_colors=[], + marker=self.get_scatter_sign(score_data.metric_aggregator_file_index), + legend_label=legend_label, + ) + fill_color = self.experiment_file_data.file_path_colors[score_data.experiment_index][ + score_data.planner_name + ] + data_sources[data_source_index].xs.append(score_data.metric_statistic_name) + data_sources[data_source_index].ys.append(score_data.score) + data_sources[data_source_index].planners.append(score_data.planner_name) + data_sources[data_source_index].aggregators.append(score_data.metric_aggregator_file_name) + data_sources[data_source_index].experiments.append( + self.get_file_path_last_name(score_data.experiment_index) + ) + data_sources[data_source_index].fill_colors.append(fill_color) + + # Plot scatter + for legend_label, data_source in data_sources.items(): + sources = ColumnDataSource( + dict( + xs=data_source.xs, + ys=data_source.ys, + planners=data_source.planners, + experiments=data_source.experiments, + aggregators=data_source.aggregators, + fill_colors=data_source.fill_colors, + line_colors=data_source.fill_colors, + ) + ) + glyph_renderer = self.get_scatter_render_func( + scatter_sign=data_source.marker, scatter_figure=scatter_figure + ) + glyph_renderer(x="xs", y="ys", size=10, fill_color="fill_colors", line_color="fill_colors", source=sources) + + def _render_scenario_metric_score(self) -> Dict[str, Figure]: + """ + Render scenario metric score plot. + :return A dict of figure names and figures. + """ + if ( + not self._scalar_log_name_select.value + or not self._scalar_scenario_name_select.value + or not self._scenario_metric_score_data + ): + return {} + selected_scenario_metric_score: List[ScenarioMetricScoreData] = self._scenario_metric_score_data[ + self._scalar_log_name_select.value + ][self._scalar_scenario_name_select.value] + # Rearranged to {metric_statistic_namae: List[scenario_metric_score_data]} + data: Dict[str, List[ScenarioMetricScoreData]] = defaultdict(list) + for scenario_metric_score_data in selected_scenario_metric_score: + if scenario_metric_score_data.planner_name not in self.enable_planner_names: + continue + + # Rename final score from score to scenario_score + metric_statistic_name = scenario_metric_score_data.metric_statistic_name + data[metric_statistic_name].append(scenario_metric_score_data) + metric_statistic_names = sorted(list(set(data.keys()))) + # Make sure the final score of a scenario is the last element + if 'score' in metric_statistic_names: + metric_statistic_names.remove('score') + metric_statistic_names.append('score') + hover = HoverTool( + tooltips=[ + ("Metric", "@xs"), + ("Score", "@ys"), + ("Planner", "@planners"), + ("Experiment", "@experiments"), + ("Aggregator", "@aggregators"), + ] + ) + number_of_figures = ceil(len(metric_statistic_names) / self._number_metrics_per_figure) + + # Create figures based on the number of metrics per figure + scenario_metric_score_figures: Dict[str, Figure] = defaultdict() + for index in range(number_of_figures): + starting_index = index * self._number_metrics_per_figure + ending_index = starting_index + self._number_metrics_per_figure + selected_metric_names = metric_statistic_names[starting_index:ending_index] + scenario_metric_score_figure = self._render_scalar_figure( + title="", + y_axis_label="score", + hover=hover, + x_range=selected_metric_names, + sizes=self._scenario_metric_score_data_figure_sizes, + ) + + # Plot scatter on the figure + metric_score_data = {metric_name: data[metric_name] for metric_name in selected_metric_names} + self._render_scenario_metric_score_scatter( + scatter_figure=scenario_metric_score_figure, scenario_metric_score_data=metric_score_data + ) + scenario_metric_score_figures[str(index)] = scenario_metric_score_figure + return scenario_metric_score_figures + + def _render_grid_plot(self, figures: Dict[str, Figure], plot_width: int, legend: bool = True) -> LayoutDOM: + """ + Render a grid plot. + :param figures: A dict of figure names and figures. + :param plot_width: Width of each plot. + :param legend: If figures have legends. + :return A grid plot. + """ + figure_plot_list: List[Figure] = [] + for figure_name, figure_plot in figures.items(): + if legend: + figure_plot.legend.label_text_font_size = scenario_tab_style["plot_legend_label_text_font_size"] + figure_plot.legend.background_fill_alpha = 0.0 + figure_plot.legend.click_policy = "hide" + figure_plot_list.append(figure_plot) + + grid_plot = gridplot( + figure_plot_list, + ncols=self.get_plot_cols(plot_width=plot_width), + toolbar_location="left", + ) + return grid_plot + + def _render_scenario_metric_layout( + self, figure_data: Dict[str, Figure], default_div: Div, plot_width: int, legend: bool = True + ) -> column: + """ + Render a layout for scenario metric. + :param figure_data: A dict of figure_data. + :param default_div: Default message when there is no result. + :param plot_width: Figure width. + :param legend: If figures have legends. + :return A bokeh column layout. + """ + if not figure_data: + return column(default_div) + + grid_plot = self._render_grid_plot(figures=figure_data, plot_width=plot_width, legend=legend) + scenario_metric_layout = column(grid_plot) + return scenario_metric_layout + + def _render_simulations(self) -> column: + """ + Render simulation plot. + :return: A list of Bokeh columns or rows. + """ + selected_keys = [ + key + for key in self.experiment_file_data.simulation_scenario_keys + if key.scenario_type == self._scalar_scenario_type_select.value + and key.log_name == self._scalar_log_name_select.value + and key.scenario_name == self._scalar_scenario_name_select.value + and key.sledgeboard_file_index in self._experiment_file_active_index + ] + if not selected_keys: + self._scenario_title_div.text = "-" + simulation_layouts = column(self._default_simulation_div) + else: + hidden_glyph_names = [ + label + for checkbox_group in [self._object_checkbox_group, self._traj_checkbox_group, self._map_checkbox_group] + for index, label in enumerate(checkbox_group.labels) + if index not in checkbox_group.active + ] + self._simulation_figure_data = self.simulation_tile.render_simulation_tiles( + selected_scenario_keys=selected_keys, + figure_sizes=self.simulation_figure_sizes, + hidden_glyph_names=hidden_glyph_names, + ) + simulation_figures = [data.plot for data in self._simulation_figure_data] + simulation_layouts = gridplot( + simulation_figures, + ncols=self.get_plot_cols( + plot_width=self.simulation_figure_sizes[0], offset_width=scenario_tab_style['col_offset_width'] + ), + toolbar_location=None, + ) + self._scenario_title_div.text = ( + f"{self._scalar_scenario_type_select.value} - " + f"{self._scalar_log_name_select.value} - " + f"{self._scalar_scenario_name_select.value}" + ) + + return simulation_layouts + + @staticmethod + def _get_ego_expert_states(state_key: str, ego_state: EgoState) -> float: + """ + Get states based on the state key. + :param state_key: Ego state key. + :param ego_state: Ego state. + :return ego state based on the key. + """ + if state_key == 'x [m]': + return cast(float, ego_state.car_footprint.center.x) + elif state_key == 'y [m]': + return cast(float, ego_state.car_footprint.center.y) + elif state_key == 'velocity_x [m/s]': + return cast(float, ego_state.dynamic_car_state.rear_axle_velocity_2d.x) + elif state_key == 'velocity_y [m/s]': + return cast(float, ego_state.dynamic_car_state.rear_axle_velocity_2d.y) + elif state_key == 'speed [m/s]': + return cast(float, ego_state.dynamic_car_state.speed) + elif state_key == 'acceleration_x [m/s^2]': + return cast(float, ego_state.dynamic_car_state.rear_axle_acceleration_2d.x) + elif state_key == 'acceleration_y [m/s^2]': + return cast(float, ego_state.dynamic_car_state.rear_axle_acceleration_2d.y) + elif state_key == 'acceleration [m/s^2]': + return cast(float, ego_state.dynamic_car_state.acceleration) + elif state_key == 'heading [rad]': + return cast(float, ego_state.car_footprint.center.heading) + elif state_key == 'steering_angle [rad]': + return cast(float, ego_state.dynamic_car_state.tire_steering_rate) + elif state_key == 'yaw_rate [rad/s]': + return cast(float, ego_state.dynamic_car_state.angular_velocity) + else: + raise ValueError(f"{state_key} not available!") + + def _render_ego_expert_state_glyph( + self, + ego_expert_plot_aggregated_states: scenario_ego_expert_state_figure_type, + ego_expert_plot_colors: Dict[str, str], + ) -> column: + """ + Render line and circle glyphs on ego_expert_state figures and get a grid plot. + :param ego_expert_plot_aggregated_states: Aggregated ego and expert states over frames. + :param ego_expert_plot_colors: Colors for different planners. + :return Column layout for ego and expert states. + """ + # Render figures with the state keys + ego_expert_state_figures: Dict[str, Figure] = defaultdict() + for plot_state_key in self.plot_state_keys: + hover = HoverTool( + tooltips=[ + ("Frame", "@x"), + ("Value", "@y{0.0000}"), + ("Planner", "$name"), + ] + ) + ego_expert_state_figure = self._render_scalar_figure( + title='', + y_axis_label=plot_state_key, + x_axis_label='frame', + hover=hover, + sizes=scenario_tab_style["ego_expert_state_figure_sizes"], + ) + # Disable scientific notation + ego_expert_state_figure.yaxis.formatter = BasicTickFormatter(use_scientific=False) + ego_expert_state_figures[plot_state_key] = ego_expert_state_figure + for planner_name, plot_states in ego_expert_plot_aggregated_states.items(): + color = ego_expert_plot_colors.get(planner_name, None) + if not color: + color = None + for plot_state_key, plot_state_values in plot_states.items(): + ego_expert_state_figure = ego_expert_state_figures[plot_state_key] + data_source = ColumnDataSource( + dict( + x=list(range(len(plot_state_values))), + y=np.round(plot_state_values, 2), + ) + ) + if self.expert_planner_key in planner_name: + ego_expert_state_figure.circle( + x="x", + y="y", + name=planner_name, + color=color, + legend_label=planner_name, + source=data_source, + size=2, + ) + else: + ego_expert_state_figure.line( + x="x", + y="y", + name=planner_name, + color=color, + legend_label=planner_name, + source=data_source, + line_width=1, + ) + + # Make layout horizontally + ego_expert_states_layout = self._render_grid_plot( + figures=ego_expert_state_figures, + plot_width=scenario_tab_style["ego_expert_state_figure_sizes"][0], + legend=True, + ) + return ego_expert_states_layout + + def _get_ego_expert_plot_color(self, planner_name: str, file_path_index: int, figure_planer_name: str) -> str: + """ + Get color for ego expert plot states based on the planner name. + :param planner_name: Plot planner name. + :param file_path_index: File path index for the plot. + :param figure_planer_name: Figure original planner name. + """ + return cast( + str, + self.experiment_file_data.expert_color_palettes[file_path_index] + if self.expert_planner_key in planner_name + else self.experiment_file_data.file_path_colors[file_path_index][figure_planer_name], + ) + + def _render_ego_expert_states(self, simulation_figure_data: List[SimulationData]) -> column: + """ + Render expert and ego time series states. Make sure it is called after _render_simulation. + :param simulation_figure_data: Simulation figure data after rendering simulation. + :return Column layout for ego and expert states. + """ + if not simulation_figure_data: + return column(self._default_ego_expert_states_div) + + # Aggregate data, {planner_name: {state_key: A list of values for the state}} + ego_expert_plot_aggregated_states: scenario_ego_expert_state_figure_type = defaultdict( + lambda: defaultdict(list) + ) + ego_expert_plot_colors: Dict[str, str] = defaultdict() + for figure_data in simulation_figure_data: + experiment_file_index = figure_data.simulation_figure.file_path_index + experiment_name = self.get_file_path_last_name(experiment_file_index) + expert_planner_name = f'{self.expert_planner_key} - ({experiment_name})' + ego_planner_name = f'{figure_data.planner_name} - ({experiment_name})' + ego_expert_states = { + expert_planner_name: figure_data.simulation_figure.scenario.get_expert_ego_trajectory(), + ego_planner_name: figure_data.simulation_figure.simulation_history.extract_ego_state, + } + for planner_name, planner_states in ego_expert_states.items(): + # Get expert color + ego_expert_plot_colors[planner_name] = self._get_ego_expert_plot_color( + planner_name=planner_name, + figure_planer_name=figure_data.planner_name, + file_path_index=figure_data.simulation_figure.file_path_index, + ) + if planner_name in ego_expert_plot_aggregated_states: + continue + for planner_state in planner_states: + for plot_state_key in self.plot_state_keys: + state_key_value = self._get_ego_expert_states(state_key=plot_state_key, ego_state=planner_state) + ego_expert_plot_aggregated_states[planner_name][plot_state_key].append(state_key_value) + + ego_expert_states_layout = self._render_ego_expert_state_glyph( + ego_expert_plot_aggregated_states=ego_expert_plot_aggregated_states, + ego_expert_plot_colors=ego_expert_plot_colors, + ) + return ego_expert_states_layout diff --git a/sledge/sledgeboard/templates/index.html b/sledge/sledgeboard/templates/index.html new file mode 100644 index 0000000..95a1677 --- /dev/null +++ b/sledge/sledgeboard/templates/index.html @@ -0,0 +1,79 @@ + + + {% extends base %} {% block head %} + + + {% block inner_head %} + + + + + + + + + + + + + SledgeBoard + {% block preamble %}{% endblock %} {% block resources %} {% block + js_resources %} {{ bokeh_js | indent(8) if bokeh_js }} {% endblock %} {{ + script | safe }} {% endblock %} {% block postamble %}{% endblock %} {% + endblock %} + + + + + {% endblock %} {% block body %} + +
+
+ + +
+
+ {% include 'tabs/overview.html' %} {% include 'tabs/histogram.html' %} + {% include 'tabs/scenario.html' %} {% include 'tabs/cloud.html' %} {% for doc in docs %} {{ embed(doc) + if doc.elementid }} {% for root in doc.roots %} {{ embed(root) | + indent(10) }} {% endfor %} {% endfor %} {{ plot_script | indent(8) }} +
+
+
+ + {% endblock %} + diff --git a/sledge/sledgeboard/templates/tabs/cloud.html b/sledge/sledgeboard/templates/tabs/cloud.html new file mode 100644 index 0000000..3cdbd19 --- /dev/null +++ b/sledge/sledgeboard/templates/tabs/cloud.html @@ -0,0 +1,97 @@ + + +S3 Explorer + + Cloud + +
+
+ +
+
+
+
+
+
+ S3 Explorer   +
+ +
+ + +
+
+
+ {{ embed(roots.s3_download_text_input) }} +   {{ embed(roots.s3_download_button) }} +
+
+
+ {{ embed(roots.s3_error_text) }} +
+
+
+ {{ embed(roots.s3_data_table) }} +
+
+
+
+
+
+ diff --git a/sledge/sledgeboard/templates/tabs/histogram.html b/sledge/sledgeboard/templates/tabs/histogram.html new file mode 100644 index 0000000..f2c56e2 --- /dev/null +++ b/sledge/sledgeboard/templates/tabs/histogram.html @@ -0,0 +1,77 @@ + + +
+
+ +
+
+
+
+
+
+ Histograms   +
+
+ + +
+
+ {{ embed(roots.histogram_planner_checkbox_group) }} +
+
+
+ {{ embed(roots.histogram_plots) }} +
+
+
+
+
+
+ diff --git a/sledge/sledgeboard/templates/tabs/overview.html b/sledge/sledgeboard/templates/tabs/overview.html new file mode 100644 index 0000000..e6d167b --- /dev/null +++ b/sledge/sledgeboard/templates/tabs/overview.html @@ -0,0 +1,26 @@ + + +
+
+
+
+
+
+
+ Overview   +
+
+
+
+ {{ embed(roots.overview_planner_checkbox_group) }} +
+
+
+ {{ embed(roots.overview_table) }} +
+
+
+
+
+
+ diff --git a/sledge/sledgeboard/templates/tabs/scenario.html b/sledge/sledgeboard/templates/tabs/scenario.html new file mode 100644 index 0000000..3fbc453 --- /dev/null +++ b/sledge/sledgeboard/templates/tabs/scenario.html @@ -0,0 +1,162 @@ + + +
+
+ + +
+
+
+
+
+
+ Scenarios   + ({{ embed(roots.scenario_title_div) }}) +
+
+ + +
+
+ {{ embed(roots.scenario_planner_checkbox_group) }} +
+
+
+
+
+ {{ embed(roots.simulation_tile_layout) }} +
+
+
+
+ Trajectory + +
+
+ {{ embed(roots.scenario_traj_checkbox_group) }} +
+
+
+
+ Agent + +
+
+ {{ embed(roots.scenario_object_checkbox_group) }} +
+
+
+
+ Map + +
+
+ {{ embed(roots.scenario_map_checkbox_group) }} +
+
+
+
+
+ +
+ Ego and Expert States + +
+
+ {{ embed(roots.ego_expert_states_layout) }} +
+
+ Time Series Metrics + +
+
+ {{ embed(roots.time_series_layout) }} +
+
+ Scenario Scores + +
+
+ {{ embed(roots.scenario_score_layout) }} +
+
+
+
+
+
+ diff --git a/sledge/sledgeboard/utils/__init__.py b/sledge/sledgeboard/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sledge/sledgeboard/utils/sledgeboard_cloud_utils.py b/sledge/sledgeboard/utils/sledgeboard_cloud_utils.py new file mode 100644 index 0000000..96fb35c --- /dev/null +++ b/sledge/sledgeboard/utils/sledgeboard_cloud_utils.py @@ -0,0 +1,269 @@ +from __future__ import annotations + +import io +import logging +import os +import pickle +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Dict, Optional +from urllib import parse + +import boto3 +import numpy as np +from boto3.exceptions import Boto3Error + +from nuplan.common.utils.s3_utils import get_s3_client +from sledge.sledgeboard.base.data_class import SledgeBoardFile + +logger = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class S3FileContent: + """S3 file contents.""" + + filename: Optional[str] = None # Full file name + last_modified: Optional[datetime] = None # Last modified date time + size: Optional[int] = None # File size + + @property + def date_string(self) -> Optional[str]: + """Return date string format.""" + if not self.last_modified: + return None + return self.last_modified.strftime("%m/%d/%Y %H:%M:%S %Z") + + @property + def last_modified_day(self) -> Optional[str]: + """Return last modified day.""" + if not self.last_modified: + return None + datetime_now = datetime.now(timezone.utc) + difference_day = (datetime_now - self.last_modified).days + if difference_day == 0: + return "Less than 24 hours" + elif difference_day < 30: + return f"{difference_day} days ago" + elif 30 <= difference_day < 60: + return "a month ago" + else: + return f"{difference_day/30} months ago" + + def kb_size(self, decimals: int = 2) -> Optional[float]: + """ + Return file size in KB. + :param decimals: Decimal points. + """ + if not self.size: + return None + return float(np.round(self.size / 1024, decimals)) + + def serialize(self) -> Dict[str, Any]: + """ + Serialize the class. + :return A dict of object variables. + """ + return {"filename": self.filename, "last_modified": str(self.last_modified), "size": self.size} + + @classmethod + def deserialize(cls, data: Dict[str, Any]) -> S3FileContent: + """ + Deserialize data to s3 file content. + :param data: A dictionary of data. + :return S3FileContent after loaded the data. + """ + return S3FileContent( + filename=data["filename"], last_modified=datetime.fromisoformat(data["last_modified"]), size=data["size"] + ) + + +@dataclass(frozen=True) +class S3ConnectionStatus: + """Connection status for s3.""" + + return_message: str + success: bool + + +@dataclass(frozen=True) +class S3FileResultMessage: + """Data class to save aws return messages and file contents.""" + + s3_connection_status: S3ConnectionStatus + file_contents: Dict[str, S3FileContent] + + +@dataclass(frozen=True) +class S3SledgeBoardFileResultMessage: + """Data class to save aws return messages and sledgeboard file.""" + + s3_connection_status: S3ConnectionStatus + sledgeboard_filename: Optional[str] = None + sledgeboard_file: Optional[SledgeBoardFile] = None + + +def check_s3_sledgeboard_files( + s3_file_contents: Dict[str, S3FileContent], s3_path: str, s3_client: boto3.client +) -> S3SledgeBoardFileResultMessage: + """ + Return True in the message if there is a sledgeboard file and can load into SledgeBoard. + :param s3_file_contents: S3 prefix with a dictionary of s3 file name and their contents. + :Param s3_path: S3 Path starts with s3://. + :param s3_client: s3 client session. + :return S3SledgeBoardFileResultMessage to indicate if there is available sledgeboard file in the s3 prefix. + """ + success = False + return_message = "No available sledgeboard files in the prefix" + sledgeboard_file = None + sledgeboard_filename = None + if not s3_path.endswith("/"): + s3_path = s3_path + "/" + url = parse.urlparse(s3_path) + for file_name, file_content in s3_file_contents.items(): + if file_name.endswith(SledgeBoardFile.extension()): + try: + sledgeboard_object = s3_client.get_object(Bucket=url.netloc, Key=file_name) + file_stream = io.BytesIO(sledgeboard_object["Body"].read()) + sledgeboard_data = pickle.load(file_stream) + sledgeboard_file = SledgeBoardFile.deserialize(sledgeboard_data) + file_stream.close() + sledgeboard_filename = Path(file_name).name + return_message = f"Found available sledgeboard file: {sledgeboard_filename}" + success = True + break + except Exception as e: + logger.info(str(e)) + continue + + return S3SledgeBoardFileResultMessage( + s3_connection_status=S3ConnectionStatus(success=success, return_message=return_message), + sledgeboard_filename=sledgeboard_filename, + sledgeboard_file=sledgeboard_file, + ) + + +def get_s3_file_contents( + s3_path: str, client: Optional[boto3.client] = None, delimiter: str = "/", include_previous_folder: bool = False +) -> S3FileResultMessage: + """ + Get folders and files contents in the provided s3 path provided. + :param s3_path: S3 path dir to expand. + :param client: Boto3 client to use, if None create a new one. + :param delimiter: Delimiter for path. + :param include_previous_folder: Set True to include '..' as previous folder. + :return: Dict of file contents. + """ + return_message = "Connect successfully" + file_contents: Dict[str, S3FileContent] = {} + try: + client = get_s3_client() if client is None else client + if not s3_path.endswith("/"): + s3_path = s3_path + "/" + url = parse.urlparse(s3_path) + paginator = client.get_paginator("list_objects_v2") + page_iterator = paginator.paginate(Bucket=url.netloc, Prefix=url.path.lstrip("/"), Delimiter=delimiter) + previous_folder = os.path.join(url.path.lstrip("/"), "..") + if previous_folder != ".." and include_previous_folder: + file_contents[previous_folder] = S3FileContent(filename=previous_folder) + + for page in page_iterator: + # Get folders + for obj in page.get("CommonPrefixes", []): + file_contents[obj["Prefix"]] = S3FileContent(filename=obj["Prefix"]) + + # Get files, start from the second file to skip the current folder + for content in page.get("Contents", []): + file_name = str(content["Key"]) + if file_name == url.path.lstrip("/"): + continue + file_contents[file_name] = S3FileContent( + filename=file_name, last_modified=content["LastModified"], size=content["Size"] + ) + success = True + except Exception as err: + logger.info("Error: {}".format(err)) + return_message = f"{err}" + success = False + s3_connection_status = S3ConnectionStatus(return_message=return_message, success=success) + s3_file_result_message = S3FileResultMessage(s3_connection_status=s3_connection_status, file_contents=file_contents) + return s3_file_result_message + + +def download_s3_file( + s3_path: str, file_content: S3FileContent, s3_client: boto3.client, save_path: str +) -> S3ConnectionStatus: + """ + Download a s3 file given a s3 full path. + :param s3_path: S3 full path. + :param file_content: File content info. + :param s3_client: A connecting S3 client. + :param save_path: Local save path. + :return S3 connection status to indicate status of s3 connection. + """ + return_message = f"Downloaded {s3_path}" + try: + if s3_path.endswith("/"): + return S3ConnectionStatus(success=False, return_message=f"{s3_path} is not a file") + url = parse.urlparse(s3_path) + file_name = file_content.filename if file_content.filename is not None else "" + download_file_name = Path(save_path, file_name) + remote_file_size = file_content.size if file_content.size is not None else 0 + local_file_size = os.path.getsize(str(download_file_name)) if download_file_name.exists() else 0 + # Skip if the file exist and file size is similar + if not (download_file_name.exists()) or local_file_size != float(remote_file_size): + s3_client.download_file(url.netloc, file_name, str(download_file_name)) + success = True + except Exception as e: + raise Boto3Error(e) + + return S3ConnectionStatus(success=success, return_message=return_message) + + +def download_s3_path(s3_path: str, s3_client: boto3.client, save_path: str, delimiter: str = "/") -> S3ConnectionStatus: + """ + Download a s3 path recursively given a s3 full path. + :param s3_path: S3 full path. + :param s3_client: A connecting S3 client. + :param save_path: Local save path. + :param delimiter: Delimiter to split folders. + :return S3 connection status to indicate status of s3 connection. + """ + return_message = f"Downloaded {s3_path}" + try: + if not s3_path.endswith("/"): + s3_path = s3_path + "/" + url = parse.urlparse(s3_path) + + paginator = s3_client.get_paginator("list_objects_v2") + page_iterator = paginator.paginate(Bucket=url.netloc, Prefix=url.path.lstrip("/"), Delimiter=delimiter) + for page in page_iterator: + # Get folders + common_prefixes = page.get("CommonPrefixes", []) + for sub_folder in common_prefixes: + sub_s3_path = os.path.join("s3://", url.netloc, sub_folder["Prefix"]) + local_save_sub_path = Path(save_path, sub_folder["Prefix"]) + local_save_sub_path.mkdir(parents=True, exist_ok=True) + # Recursively download folder + download_s3_path(s3_client=s3_client, s3_path=sub_s3_path, save_path=save_path) + + # Get files + contents = page.get("Contents", []) + for content in contents: + file_name = str(content["Key"]) + file_size = content["Size"] + last_modified = content["LastModified"] + s3_file_path = os.path.join("s3://", url.netloc, file_name) + local_folder = Path(save_path, file_name) + local_folder.parents[0].mkdir(exist_ok=True, parents=True) + file_content = S3FileContent(filename=file_name, size=file_size, last_modified=last_modified) + download_s3_file( + s3_path=s3_file_path, file_content=file_content, s3_client=s3_client, save_path=save_path + ) + success = True + except Exception as e: + raise Boto3Error(e) + + s3_connection_status = S3ConnectionStatus(success=success, return_message=return_message) + return s3_connection_status diff --git a/sledge/sledgeboard/utils/sledgeboard_histogram_utils.py b/sledge/sledgeboard/utils/sledgeboard_histogram_utils.py new file mode 100644 index 0000000..85c85a7 --- /dev/null +++ b/sledge/sledgeboard/utils/sledgeboard_histogram_utils.py @@ -0,0 +1,274 @@ +import logging +from collections import defaultdict +from typing import Dict, List, Optional, Tuple, Union + +import numpy as np +import numpy.typing as npt +import pandas as pd +from bokeh.models import FactorRange + +from nuplan.planning.metrics.metric_dataframe import MetricStatisticsDataFrame +from sledge.sledgeboard.tabs.config.histogram_tab_config import ( + HistogramConstantConfig, + HistogramData, + HistogramEdgeData, + HistogramStatistics, + HistogramTabFigureStyleConfig, +) + +logger = logging.getLogger(__name__) + + +def _extract_metric_statistics_dict( + metric_statistics_dataframe: MetricStatisticsDataFrame, + planner_name: str, + scenario_types: Optional[Tuple[str]] = None, +) -> Optional[Dict[str, HistogramStatistics]]: + """ + Extract metric statistics dataframe and aggregate them into histogram data in a dictionary of + {metric_statistics_name: HistogramStatistics}. + :param metric_statistics_dataframe: Metric statistics dataframe. + :param scenario_types: A tuple of scenario types to search from the metric statistics dataframe. + :param planner_name: A planner name to search from the metric statistics dataframe. + :return A dictionary of {metric_statistic_name: HistogramStatistics}. + """ + histogram_statistics_dict = {} + data_frame: pd.DataFrame = metric_statistics_dataframe.query_scenarios( + scenario_types=scenario_types, planner_names=(planner_name,) + ) + if not len(data_frame): + return None + for statistic_name in metric_statistics_dataframe.statistic_names: + columns = [ + f"{statistic_name}_stat_value", + f"{statistic_name}_stat_unit", + "scenario_name", + "log_name", + ] + statistic_data_frame = data_frame[columns] + + # Value column index is 0, make all inf values to be nan + values: npt.NDArray[np.float64] = np.asarray(statistic_data_frame.iloc[:, 0], dtype=np.float64) + infinite_states = np.isinf(values) + values[infinite_states] = np.nan + + scenarios: List[str] = list(np.asarray(statistic_data_frame.iloc[:, 2])) + log_names: List[str] = list(np.asarray(statistic_data_frame.iloc[:, 3])) + scenario_log_names: List[str] = [] + for scenario, log_name in zip(scenarios, log_names): + scenario_log_names.append(scenario + " (" + log_name + ")") + + if values.dtype != "bool": + values = np.round(values, HistogramTabFigureStyleConfig.decimal_places) + + # Unit column index is 1 + unit = statistic_data_frame.iloc[0, 1] or "value" + histogram_statistics = HistogramStatistics(unit=unit, values=values, scenarios=scenario_log_names) + histogram_statistics_dict[statistic_name] = histogram_statistics + return histogram_statistics_dict + + +def _extract_scenario_score_type_histogram_statistics_dict( + scenario_type_info: Dict[str, List[Tuple[float, str]]] +) -> Dict[str, HistogramStatistics]: + """ + Extract histogram statistics dict from a dictionary of {scenario_type_name: [(scenario_score, scenario_name)]} + :param scenario_type_info: A dict of {scenario_type_name: [(scenario_score, scenario_name)]} + :returns A dictionary of scenario type names and histogram statistics. + """ + histogram_statistics_dict = {} + for scenario_type_name, scenario_type_scores in scenario_type_info.items(): + values = np.round( + np.asarray([score[0] for score in scenario_type_scores], dtype=np.float64), + HistogramTabFigureStyleConfig.decimal_places, + ) + scenario_log_names = [score[1] for score in scenario_type_scores] + histogram_statistics = HistogramStatistics(unit="scores", values=values, scenarios=scenario_log_names) + histogram_statistics_dict[scenario_type_name] = histogram_statistics + return histogram_statistics_dict + + +def _extract_metric_aggregator_scenario_type_score_data( + metric_aggregator_dataframe: pd.DataFrame, scenario_types: List[str] +) -> HistogramConstantConfig.HistogramScenarioTypeScoreStatisticType: + """ + Extract scenario type scores from metric aggregator dataframe and transform it to a histogram form in a + dictionary of {'planner_name': {'scenario_type': [scenario_score, scenario_name]}}. + :param metric_aggregator_dataframe: Metric aggregator dataframe. + :param scenario_types: List of selected scenario types. + :return {'planner_name': {'scenario_type': [scenario_score, scenario_name]}}. + """ + # Planner name: scenario type: List[(score, scenario)] + scenario_type_score_histogram_statistics_dict: HistogramConstantConfig.HistogramScenarioTypeScoreStatisticType = ( + defaultdict(lambda: defaultdict(list)) + ) + + # Iterate through rows + for _, row_data in metric_aggregator_dataframe.iterrows(): + num_scenarios = row_data["num_scenarios"] + if not np.isnan(num_scenarios): + continue + scenario_name = row_data["scenario"] + " (" + row_data["log_name"] + ")" + if "all" in scenario_types: + scenario_type_score_histogram_statistics_dict[row_data["planner_name"]]["all"].append( + (row_data["score"], scenario_name) + ) + if "all" in scenario_types or row_data["scenario_type"] in scenario_types: + scenario_type_score_histogram_statistics_dict[row_data["planner_name"]][row_data["scenario_type"]].append( + (row_data["score"], scenario_name) + ) + return scenario_type_score_histogram_statistics_dict + + +def extract_scenario_score_type_score_histogram_data( + histogram_file_name: str, + metric_aggregator_dataframe_index: int, + scenario_type_score_histogram_statistics_dict: HistogramConstantConfig.HistogramScenarioTypeScoreStatisticType, +) -> List[HistogramData]: + """ + Get histogram data from scenario type score histogram statistics. + :param histogram_file_name: Histogram file name. + :param metric_aggregator_dataframe_index: Metric aggregator dataframe index. + :param scenario_type_score_histogram_statistics_dict: Statistics dictionary with scenario types and their + scores. + :return Scenario type histogram data. + """ + histogram_data_list: List[HistogramData] = [] + for planner_name, scenario_type_info in scenario_type_score_histogram_statistics_dict.items(): + histogram_statistics_dict = _extract_scenario_score_type_histogram_statistics_dict( + scenario_type_info=scenario_type_info + ) + histogram_data = HistogramData( + experiment_index=metric_aggregator_dataframe_index, + planner_name=planner_name, + statistics=histogram_statistics_dict, + histogram_file_name=histogram_file_name, + ) + histogram_data_list.append(histogram_data) + return histogram_data_list + + +def aggregate_metric_aggregator_dataframe_histogram_data( + dataframe_file_name: str, + metric_aggregator_dataframe: pd.DataFrame, + metric_aggregator_dataframe_index: int, + scenario_types: List[str], +) -> List[HistogramData]: + """ + Aggregate metric statistics dataframe data for histograms. + :param dataframe_file_name: Dataframe file name. + :param metric_aggregator_dataframe: Metric aggregator dataframe. + :param metric_aggregator_dataframe_index: Metric aggregator dataframe index. + :param scenario_types: List of selected scenario types. + :return A dictionary of {aggregator planner_name: {aggregator scenario type: a list of (scenario type score, + scenario log name)}}. + """ + scenario_type_score_histogram_statistics_dict = _extract_metric_aggregator_scenario_type_score_data( + metric_aggregator_dataframe=metric_aggregator_dataframe, scenario_types=scenario_types + ) + # Get a list of histogram data + histogram_data_list = extract_scenario_score_type_score_histogram_data( + metric_aggregator_dataframe_index=metric_aggregator_dataframe_index, + scenario_type_score_histogram_statistics_dict=scenario_type_score_histogram_statistics_dict, + histogram_file_name=dataframe_file_name, + ) + return histogram_data_list + + +def aggregate_metric_statistics_dataframe_histogram_data( + metric_statistics_dataframe: MetricStatisticsDataFrame, + metric_statistics_dataframe_index: int, + metric_choices: List[str], + scenario_types: Optional[Tuple[str]] = None, +) -> Optional[List[HistogramData]]: + """ + Aggregate metric statistics dataframe data for histograms. + :param metric_statistics_dataframe: Metric statistics dataframe. + :param metric_statistics_dataframe_index: Metric statistics dataframe index. + :param metric_choices: List of selected metrics. + :param scenario_types: List of selected scenario types. + :return A dictionary of statistics and their statistic data. + """ + histogram_data_list = [] + if len(metric_choices) and metric_statistics_dataframe.metric_statistic_name not in metric_choices: + return None + + planner_names = metric_statistics_dataframe.planner_names + for planner_name in planner_names: + histogram_statistics_dict = _extract_metric_statistics_dict( + metric_statistics_dataframe=metric_statistics_dataframe, + scenario_types=scenario_types, + planner_name=planner_name, + ) + if not histogram_statistics_dict: + continue + histogram_data_list.append( + HistogramData( + experiment_index=metric_statistics_dataframe_index, + planner_name=planner_name, + statistics=histogram_statistics_dict, + ) + ) + return histogram_data_list + + +def compute_histogram_edges( + bins: int, aggregated_data: Optional[HistogramConstantConfig.HistogramDataType] +) -> HistogramConstantConfig.HistogramEdgesDataType: + """ + Compute histogram edges across different planners in the same metric statistics. + :param bins: Number of bins. + :param aggregated_data: Histogram aggregated data. + :return Histogram edge data. + """ + histogram_edge_data: HistogramConstantConfig.HistogramEdgesDataType = {} + if aggregated_data is None: + return histogram_edge_data + + for metric_statistics_name, aggregated_histogram_data in aggregated_data.items(): + if metric_statistics_name not in histogram_edge_data: + histogram_edge_data[metric_statistics_name] = {} + edge_data: Dict[str, HistogramEdgeData] = {} + for histogram_data in aggregated_histogram_data: + for statistic_name, statistic in histogram_data.statistics.items(): + unit = statistic.unit + if unit in ["bool", "boolean"]: + continue + + if statistic_name not in edge_data: + edge_data[statistic_name] = HistogramEdgeData(unit=unit, values=statistic.values) + else: + edge_data[statistic_name].values = np.concatenate( + [edge_data[statistic_name].values, statistic.values] + ) + + for statistic_name, statistic_edge_data in edge_data.items(): + unit = statistic_edge_data.unit + if unit in ["count"]: + unique_values: npt.NDArray[np.float64] = np.unique(statistic_edge_data.values) + histogram_edge_data[metric_statistics_name][statistic_name] = ( + None if not len(unique_values) else unique_values + ) + else: + # Remove nan + finite_values = statistic_edge_data.values[~np.isnan(statistic_edge_data.values)] + _, edges = np.histogram(finite_values, bins=bins) + histogram_edge_data[metric_statistics_name][statistic_name] = None if not len(edges) else edges + + return histogram_edge_data + + +def get_histogram_plot_x_range(unit: str, data: npt.NDArray[np.float64]) -> Union[List[str], FactorRange]: + """ + Get Histogram x_range based on unit and data. + :param unit: Histogram unit. + :param data: Histogram data. + :return x_range in histogram plot. + """ + x_range = None + # Boolean type of data + if unit in ["bool", "boolean"]: + x_range = ["False", "True"] + elif unit in ["count"]: + x_range = [str(count) for count in data] + return x_range diff --git a/sledge/sledgeboard/utils/utils.py b/sledge/sledgeboard/utils/utils.py new file mode 100644 index 0000000..e15db0f --- /dev/null +++ b/sledge/sledgeboard/utils/utils.py @@ -0,0 +1,75 @@ +import logging +from pathlib import Path +from typing import List + +import pandas as pd + +from nuplan.planning.metrics.metric_dataframe import MetricStatisticsDataFrame +from sledge.sledgeboard.base.data_class import SledgeBoardFile + +logger = logging.getLogger(__name__) + + +def metric_statistics_reader(parquet_file: Path) -> MetricStatisticsDataFrame: + """ + Reader for a metric statistic parquet file. + :param parquet_file: Parquet file path to read. + :return MetricStatisticsDataFrame. + """ + data_frame = MetricStatisticsDataFrame.load_parquet(parquet_file) + return data_frame + + +def metric_aggregator_reader(parquet_file: Path) -> pd.DataFrame: + """ + Reader for a metric aggregator parquet file. + :param parquet_file: Parquet file path to read. + :return Pandas data frame. + """ + data_frame = pd.read_parquet(parquet_file) + return data_frame + + +def check_sledgeboard_file_paths(main_paths: List[str]) -> List[Path]: + """ + Check if given file paths are valid SledgeBoard files. + :param main_paths: A list of file paths. + :return A list of available SledgeBoard files. + """ + available_paths = [] + for main_path in main_paths: + main_folder_path: Path = Path(main_path) + if main_folder_path.is_dir(): + # Search for sledgeboard event files. + files = list(main_folder_path.iterdir()) + event_files = [file for file in files if file.name.endswith(SledgeBoardFile.extension())] + + if len(event_files) > 0: + # Descending order. + event_files = sorted(event_files, reverse=True) + # Load the first file only. + available_paths.append(event_files[0]) + elif main_folder_path.is_file() and main_folder_path.name.endswith(SledgeBoardFile.extension()): + available_paths.append(main_folder_path) + else: + raise RuntimeError(f"{str(main_folder_path)} is not a valid SledgeBoard file") + + if len(available_paths) == 0: + logger.info("No available SledgeBoard files are found.") + + return available_paths + + +def read_sledgeboard_file_paths(file_paths: List[Path]) -> List[SledgeBoardFile]: + """ + Read a list of file paths to SledgeBoardFile data class. + :param file_paths: A list of file paths. + :return A list of SledgeBoard files. + """ + sledgeboard_files = [] + for file_path in file_paths: + sledgeboard_file = SledgeBoardFile.load_sledgeboard_file(file_path) + sledgeboard_file.current_path = file_path.parents[0] + sledgeboard_files.append(sledgeboard_file) + + return sledgeboard_files