From b6378be2e3a6afebbaacb66713a151dd0a57381e Mon Sep 17 00:00:00 2001 From: lzx1413 Date: Sat, 2 Sep 2023 17:00:00 +0800 Subject: [PATCH] refactor(*): rm unused code --- CMakeLists.txt | 2 +- README.md | 6 +- README_CN.md | 2 +- conanfile.py | 134 +- examples/desktop/autoflip/README.md | 25 - .../desktop/autoflip/autoflip_graph.pbtxt | 202 - .../autoflip/autoflip_graph_development.pbtxt | 252 -- .../desktop/autoflip/autoflip_messages.proto | 203 - .../border_detection_calculator.cc | 301 -- .../border_detection_calculator.proto | 44 - .../border_detection_calculator_test.cc | 400 -- .../calculators/content_zooming_calculator.cc | 890 ---- .../content_zooming_calculator.proto | 90 - .../content_zooming_calculator_state.h | 38 - .../content_zooming_calculator_test.cc | 1022 ----- .../face_box_adjuster_calculator.proto | 80 - .../calculators/face_to_region_calculator.cc | 291 -- .../face_to_region_calculator.proto | 50 - .../face_to_region_calculator_test.cc | 323 -- .../localization_to_region_calculator.cc | 129 - .../localization_to_region_calculator.proto | 33 - .../localization_to_region_calculator_test.cc | 167 - .../calculators/scene_cropping_calculator.cc | 822 ---- .../calculators/scene_cropping_calculator.h | 293 -- .../scene_cropping_calculator.proto | 106 - .../scene_cropping_calculator_test.cc | 925 ---- .../calculators/shot_boundary_calculator.cc | 188 - .../shot_boundary_calculator.proto | 48 - .../shot_boundary_calculator_test.cc | 170 - .../calculators/signal_fusing_calculator.cc | 317 -- .../signal_fusing_calculator.proto | 64 - .../signal_fusing_calculator_test.cc | 569 --- .../autoflip/calculators/testdata/dino.jpg | Bin 467082 -> 0 bytes .../calculators/video_filtering_calculator.cc | 120 - .../video_filtering_calculator.proto | 56 - .../video_filtering_calculator_test.cc | 180 - .../desktop/autoflip/quality/cropping.proto | 241 - .../autoflip/quality/focus_point.proto | 79 - .../quality/frame_crop_region_computer.cc | 261 -- .../quality/frame_crop_region_computer.h | 110 - .../frame_crop_region_computer_test.cc | 579 --- .../autoflip/quality/kinematic_path_solver.cc | 325 -- .../autoflip/quality/kinematic_path_solver.h | 103 - .../quality/kinematic_path_solver.proto | 45 - .../quality/kinematic_path_solver_test.cc | 464 -- .../desktop/autoflip/quality/math_utils.h | 40 - .../quality/padding_effect_generator.cc | 200 - .../quality/padding_effect_generator.h | 73 - .../quality/padding_effect_generator_test.cc | 200 - .../quality/piecewise_linear_function.cc | 69 - .../quality/piecewise_linear_function.h | 82 - .../quality/piecewise_linear_function_test.cc | 73 - .../polynomial_regression_path_solver.cc | 170 - .../polynomial_regression_path_solver.h | 69 - .../polynomial_regression_path_solver_test.cc | 321 -- .../quality/scene_camera_motion_analyzer.cc | 443 -- .../quality/scene_camera_motion_analyzer.h | 157 - .../scene_camera_motion_analyzer_test.cc | 805 ---- .../desktop/autoflip/quality/scene_cropper.cc | 171 - .../desktop/autoflip/quality/scene_cropper.h | 92 - .../autoflip/quality/scene_cropper_test.cc | 229 - .../autoflip/quality/scene_cropping_viz.cc | 222 - .../autoflip/quality/scene_cropping_viz.h | 69 - ...ra_motion_tracking_scene_frame_results.csv | 30 - .../autoflip/quality/testdata/google.jpg | Bin 26189 -> 0 bytes .../autoflip/quality/testdata/result_0.3.jpg | Bin 3290 -> 0 bytes .../testdata/result_0.3_solid_background.jpg | Bin 2666 -> 0 bytes .../autoflip/quality/testdata/result_0.6.jpg | Bin 6203 -> 0 bytes .../testdata/result_0.6_solid_background.jpg | Bin 5730 -> 0 bytes .../autoflip/quality/testdata/result_1.6.jpg | Bin 14217 -> 0 bytes .../testdata/result_1.6_solid_background.jpg | Bin 15160 -> 0 bytes .../autoflip/quality/testdata/result_1.jpg | Bin 8386 -> 0 bytes .../testdata/result_1_solid_background.jpg | Bin 8495 -> 0 bytes .../autoflip/quality/testdata/result_2.5.jpg | Bin 10416 -> 0 bytes .../testdata/result_2.5_solid_background.jpg | Bin 10955 -> 0 bytes .../autoflip/quality/testdata/result_3.4.jpg | Bin 7803 -> 0 bytes .../testdata/result_3.4_solid_background.jpg | Bin 8001 -> 0 bytes examples/desktop/autoflip/quality/utils.cc | 447 -- examples/desktop/autoflip/quality/utils.h | 116 - .../desktop/autoflip/quality/utils_test.cc | 1039 ----- .../desktop/autoflip/quality/visual_scorer.cc | 182 - .../desktop/autoflip/quality/visual_scorer.h | 46 - .../autoflip/quality/visual_scorer.proto | 28 - .../autoflip/quality/visual_scorer_test.cc | 80 - .../autoflip_object_detection_subgraph.pbtxt | 126 - .../subgraph/face_detection_subgraph.pbtxt | 121 - .../front_face_detection_subgraph.pbtxt | 135 - examples/desktop/hand_tracking/CMakeLists.txt | 48 - .../iris_depth_from_image_desktop.cc | 161 - examples/desktop/media_sequence/README.md | 79 - examples/desktop/media_sequence/__init__.py | 14 - .../media_sequence/charades_dataset.py | 519 --- .../desktop/media_sequence/demo_dataset.py | 318 -- .../media_sequence/kinetics_dataset.py | 478 -- .../media_sequence/read_demo_dataset.py | 46 - .../media_sequence/run_graph_file_io_main.cc | 97 - .../object_detection_desktop_live.pbtxt | 4 +- .../ssdlite_object_detection_labelmap.txt | 0 .../desktop/object_detection/test_video.mp4 | Bin 4367585 -> 0 bytes examples/desktop/youtube8m/README.md | 157 - examples/desktop/youtube8m/__init__.py | 14 - .../youtube8m/extract_yt8m_features.cc | 138 - .../generate_input_sequence_example.py | 66 - .../youtube8m/generate_vggish_frozen_graph.py | 70 - examples/desktop/youtube8m/viewer/server.py | 262 -- .../desktop/youtube8m/viewer/static/main.js | 217 - .../edge_detection_mobile_gpu.pbtxt | 22 - .../face_detection_desktop_live.pbtxt | 58 - ...ce_detection_full_range_desktop_live.pbtxt | 60 - ...face_detection_full_range_mobile_gpu.pbtxt | 60 - .../face_detection_mobile_cpu.pbtxt | 76 - .../face_detection_mobile_gpu.pbtxt | 58 - .../graphs/face_effect/face_effect_gpu.pbtxt | 130 - .../subgraphs/face_landmarks_smoothing.pbtxt | 24 - ...gle_face_geometry_from_detection_gpu.pbtxt | 91 - ...gle_face_geometry_from_landmarks_gpu.pbtxt | 89 - ...ace_landmarks_to_render_data_calculator.cc | 104 - .../graphs/face_mesh/face_mesh_desktop.pbtxt | 70 - .../face_mesh/face_mesh_desktop_live.pbtxt | 66 - .../face_mesh_desktop_live_gpu.pbtxt | 66 - .../graphs/face_mesh/face_mesh_mobile.pbtxt | 67 - .../subgraphs/face_renderer_cpu.pbtxt | 96 - .../subgraphs/face_renderer_gpu.pbtxt | 96 - .../hair_segmentation_desktop_live.pbtxt | 159 - .../hair_segmentation_mobile_gpu.pbtxt | 152 - .../hand_detection_desktop.pbtxt | 61 - .../hand_detection_desktop_live.pbtxt | 39 - .../hand_tracking/hand_detection_mobile.pbtxt | 59 - .../hand_tracking/hand_tracking_desktop.pbtxt | 68 - .../hand_tracking_desktop_live.pbtxt | 46 - .../hand_tracking_desktop_live_gpu.pbtxt | 48 - .../hand_tracking/hand_tracking_mobile.pbtxt | 65 - .../subgraphs/hand_renderer_cpu.pbtxt | 209 - .../subgraphs/hand_renderer_gpu.pbtxt | 209 - .../holistic_tracking_cpu.pbtxt | 75 - .../holistic_tracking_gpu.pbtxt | 75 - .../holistic_tracking_to_render_data.pbtxt | 757 ---- .../matrices_manager_calculator.cc | 393 -- .../calculators/sticker_buffer.proto | 33 - .../calculators/sticker_manager_calculator.cc | 150 - .../tracked_anchor_manager_calculator.cc | 210 - .../calculators/transformations.h | 42 - .../instant_motion_tracking.pbtxt | 80 - .../subgraphs/region_tracking.pbtxt | 47 - .../calculators/iris_to_depth_calculator.cc | 245 -- .../iris_to_depth_calculator.proto | 39 - .../iris_to_render_data_calculator.cc | 318 -- .../iris_to_render_data_calculator.proto | 62 - .../update_face_landmarks_calculator.cc | 268 -- .../graphs/iris_tracking/iris_depth_cpu.pbtxt | 159 - .../iris_tracking/iris_tracking_cpu.pbtxt | 142 - .../iris_tracking_cpu_video_input.pbtxt | 153 - .../iris_tracking/iris_tracking_gpu.pbtxt | 163 - .../iris_and_depth_renderer_cpu.pbtxt | 267 -- .../iris_and_depth_renderer_gpu.pbtxt | 270 -- .../subgraphs/iris_renderer_cpu.pbtxt | 254 -- .../clipped_images_from_file_at_24fps.pbtxt | 78 - .../tvl1_flow_and_rgb_from_file.pbtxt | 153 - ...t_detection_desktop_tensorflow_graph.pbtxt | 130 - ...bject_detection_desktop_tflite_graph.pbtxt | 180 - .../object_detection_mobile_cpu.pbtxt | 193 - .../object_detection_mobile_gpu.pbtxt | 175 - ...nnotations_to_model_matrices_calculator.cc | 215 - ...tations_to_model_matrices_calculator.proto | 33 - .../annotations_to_render_data_calculator.cc | 271 -- ...nnotations_to_render_data_calculator.proto | 43 - .../gl_animation_overlay_calculator.cc | 947 ---- .../gl_animation_overlay_calculator.proto | 6 - .../calculators/model_matrix.proto | 48 - .../obj_parser/ObjParserMain.java | 205 - .../obj_parser/SimpleObjParser.java | 386 -- .../obj_parser/obj_cleanup.sh | 44 - .../object_occlusion_tracking.pbtxt | 122 - .../object_occlusion_tracking_1stage.pbtxt | 133 - .../objectron_desktop_cpu.pbtxt | 60 - .../subgraphs/renderer_cpu.pbtxt | 75 - .../pose_tracking/pose_tracking_cpu.pbtxt | 63 - .../pose_tracking/pose_tracking_gpu.pbtxt | 63 - .../pose_landmarks_to_render_data.pbtxt | 236 - .../subgraphs/pose_renderer_cpu.pbtxt | 85 - .../subgraphs/pose_renderer_gpu.pbtxt | 85 - .../selfie_segmentation_cpu.pbtxt | 52 - .../selfie_segmentation_gpu.pbtxt | 52 - .../template_matching/index_building.pbtxt | 92 - .../template_matching_desktop.pbtxt | 141 - .../template_matching_mobile_cpu.pbtxt | 137 - ...ject_detection_tracking_desktop_live.pbtxt | 45 - ...object_detection_tracking_mobile_gpu.pbtxt | 46 - .../tracking/subgraphs/box_tracking_cpu.pbtxt | 119 - .../tracking/subgraphs/box_tracking_gpu.pbtxt | 126 - .../subgraphs/object_detection_cpu.pbtxt | 128 - .../subgraphs/object_detection_gpu.pbtxt | 128 - .../subgraphs/object_tracking_cpu.pbtxt | 56 - .../subgraphs/object_tracking_gpu.pbtxt | 56 - .../tracking/subgraphs/renderer_cpu.pbtxt | 29 - .../tracking/subgraphs/renderer_gpu.pbtxt | 29 - .../graphs/youtube8m/feature_extraction.pbtxt | 295 -- mediapipe/graphs/youtube8m/label_map.txt | 3862 ----------------- .../local_video_model_inference.pbtxt | 178 - .../yt8m_dataset_model_inference.pbtxt | 139 - test_package/CMakeLists.txt | 10 +- test_package/conanfile.py | 6 +- test_package/default_input_stream_handler.cc | 69 - .../in_order_output_stream_handler.cc | 133 - test_package/in_order_output_stream_handler.h | 55 - 205 files changed, 100 insertions(+), 35734 deletions(-) delete mode 100644 examples/desktop/autoflip/README.md delete mode 100644 examples/desktop/autoflip/autoflip_graph.pbtxt delete mode 100644 examples/desktop/autoflip/autoflip_graph_development.pbtxt delete mode 100644 examples/desktop/autoflip/autoflip_messages.proto delete mode 100644 examples/desktop/autoflip/calculators/border_detection_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/border_detection_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/border_detection_calculator_test.cc delete mode 100644 examples/desktop/autoflip/calculators/content_zooming_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/content_zooming_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/content_zooming_calculator_state.h delete mode 100644 examples/desktop/autoflip/calculators/content_zooming_calculator_test.cc delete mode 100644 examples/desktop/autoflip/calculators/face_box_adjuster_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/face_to_region_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/face_to_region_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/face_to_region_calculator_test.cc delete mode 100644 examples/desktop/autoflip/calculators/localization_to_region_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/localization_to_region_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/localization_to_region_calculator_test.cc delete mode 100644 examples/desktop/autoflip/calculators/scene_cropping_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/scene_cropping_calculator.h delete mode 100644 examples/desktop/autoflip/calculators/scene_cropping_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/scene_cropping_calculator_test.cc delete mode 100644 examples/desktop/autoflip/calculators/shot_boundary_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/shot_boundary_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/shot_boundary_calculator_test.cc delete mode 100644 examples/desktop/autoflip/calculators/signal_fusing_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/signal_fusing_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/signal_fusing_calculator_test.cc delete mode 100644 examples/desktop/autoflip/calculators/testdata/dino.jpg delete mode 100644 examples/desktop/autoflip/calculators/video_filtering_calculator.cc delete mode 100644 examples/desktop/autoflip/calculators/video_filtering_calculator.proto delete mode 100644 examples/desktop/autoflip/calculators/video_filtering_calculator_test.cc delete mode 100644 examples/desktop/autoflip/quality/cropping.proto delete mode 100644 examples/desktop/autoflip/quality/focus_point.proto delete mode 100644 examples/desktop/autoflip/quality/frame_crop_region_computer.cc delete mode 100644 examples/desktop/autoflip/quality/frame_crop_region_computer.h delete mode 100644 examples/desktop/autoflip/quality/frame_crop_region_computer_test.cc delete mode 100644 examples/desktop/autoflip/quality/kinematic_path_solver.cc delete mode 100644 examples/desktop/autoflip/quality/kinematic_path_solver.h delete mode 100644 examples/desktop/autoflip/quality/kinematic_path_solver.proto delete mode 100644 examples/desktop/autoflip/quality/kinematic_path_solver_test.cc delete mode 100644 examples/desktop/autoflip/quality/math_utils.h delete mode 100644 examples/desktop/autoflip/quality/padding_effect_generator.cc delete mode 100644 examples/desktop/autoflip/quality/padding_effect_generator.h delete mode 100644 examples/desktop/autoflip/quality/padding_effect_generator_test.cc delete mode 100644 examples/desktop/autoflip/quality/piecewise_linear_function.cc delete mode 100644 examples/desktop/autoflip/quality/piecewise_linear_function.h delete mode 100644 examples/desktop/autoflip/quality/piecewise_linear_function_test.cc delete mode 100644 examples/desktop/autoflip/quality/polynomial_regression_path_solver.cc delete mode 100644 examples/desktop/autoflip/quality/polynomial_regression_path_solver.h delete mode 100644 examples/desktop/autoflip/quality/polynomial_regression_path_solver_test.cc delete mode 100644 examples/desktop/autoflip/quality/scene_camera_motion_analyzer.cc delete mode 100644 examples/desktop/autoflip/quality/scene_camera_motion_analyzer.h delete mode 100644 examples/desktop/autoflip/quality/scene_camera_motion_analyzer_test.cc delete mode 100644 examples/desktop/autoflip/quality/scene_cropper.cc delete mode 100644 examples/desktop/autoflip/quality/scene_cropper.h delete mode 100644 examples/desktop/autoflip/quality/scene_cropper_test.cc delete mode 100644 examples/desktop/autoflip/quality/scene_cropping_viz.cc delete mode 100644 examples/desktop/autoflip/quality/scene_cropping_viz.h delete mode 100644 examples/desktop/autoflip/quality/testdata/camera_motion_tracking_scene_frame_results.csv delete mode 100644 examples/desktop/autoflip/quality/testdata/google.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_0.3.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_0.3_solid_background.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_0.6.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_0.6_solid_background.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_1.6.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_1.6_solid_background.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_1.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_1_solid_background.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_2.5.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_2.5_solid_background.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_3.4.jpg delete mode 100644 examples/desktop/autoflip/quality/testdata/result_3.4_solid_background.jpg delete mode 100644 examples/desktop/autoflip/quality/utils.cc delete mode 100644 examples/desktop/autoflip/quality/utils.h delete mode 100644 examples/desktop/autoflip/quality/utils_test.cc delete mode 100644 examples/desktop/autoflip/quality/visual_scorer.cc delete mode 100644 examples/desktop/autoflip/quality/visual_scorer.h delete mode 100644 examples/desktop/autoflip/quality/visual_scorer.proto delete mode 100644 examples/desktop/autoflip/quality/visual_scorer_test.cc delete mode 100644 examples/desktop/autoflip/subgraph/autoflip_object_detection_subgraph.pbtxt delete mode 100644 examples/desktop/autoflip/subgraph/face_detection_subgraph.pbtxt delete mode 100644 examples/desktop/autoflip/subgraph/front_face_detection_subgraph.pbtxt delete mode 100644 examples/desktop/hand_tracking/CMakeLists.txt delete mode 100644 examples/desktop/iris_tracking/iris_depth_from_image_desktop.cc delete mode 100644 examples/desktop/media_sequence/README.md delete mode 100644 examples/desktop/media_sequence/__init__.py delete mode 100644 examples/desktop/media_sequence/charades_dataset.py delete mode 100644 examples/desktop/media_sequence/demo_dataset.py delete mode 100644 examples/desktop/media_sequence/kinetics_dataset.py delete mode 100644 examples/desktop/media_sequence/read_demo_dataset.py delete mode 100644 examples/desktop/media_sequence/run_graph_file_io_main.cc rename {mediapipe/graphs => examples/desktop}/object_detection/object_detection_desktop_live.pbtxt (96%) rename {mediapipe/models => examples/desktop/object_detection}/ssdlite_object_detection_labelmap.txt (100%) delete mode 100644 examples/desktop/object_detection/test_video.mp4 delete mode 100644 examples/desktop/youtube8m/README.md delete mode 100644 examples/desktop/youtube8m/__init__.py delete mode 100644 examples/desktop/youtube8m/extract_yt8m_features.cc delete mode 100644 examples/desktop/youtube8m/generate_input_sequence_example.py delete mode 100644 examples/desktop/youtube8m/generate_vggish_frozen_graph.py delete mode 100644 examples/desktop/youtube8m/viewer/server.py delete mode 100644 examples/desktop/youtube8m/viewer/static/main.js delete mode 100644 mediapipe/graphs/edge_detection/edge_detection_mobile_gpu.pbtxt delete mode 100644 mediapipe/graphs/face_detection/face_detection_desktop_live.pbtxt delete mode 100644 mediapipe/graphs/face_detection/face_detection_full_range_desktop_live.pbtxt delete mode 100644 mediapipe/graphs/face_detection/face_detection_full_range_mobile_gpu.pbtxt delete mode 100644 mediapipe/graphs/face_detection/face_detection_mobile_cpu.pbtxt delete mode 100644 mediapipe/graphs/face_detection/face_detection_mobile_gpu.pbtxt delete mode 100644 mediapipe/graphs/face_effect/face_effect_gpu.pbtxt delete mode 100644 mediapipe/graphs/face_effect/subgraphs/face_landmarks_smoothing.pbtxt delete mode 100644 mediapipe/graphs/face_effect/subgraphs/single_face_geometry_from_detection_gpu.pbtxt delete mode 100644 mediapipe/graphs/face_effect/subgraphs/single_face_geometry_from_landmarks_gpu.pbtxt delete mode 100644 mediapipe/graphs/face_mesh/calculators/face_landmarks_to_render_data_calculator.cc delete mode 100644 mediapipe/graphs/face_mesh/face_mesh_desktop.pbtxt delete mode 100644 mediapipe/graphs/face_mesh/face_mesh_desktop_live.pbtxt delete mode 100644 mediapipe/graphs/face_mesh/face_mesh_desktop_live_gpu.pbtxt delete mode 100644 mediapipe/graphs/face_mesh/face_mesh_mobile.pbtxt delete mode 100644 mediapipe/graphs/face_mesh/subgraphs/face_renderer_cpu.pbtxt delete mode 100644 mediapipe/graphs/face_mesh/subgraphs/face_renderer_gpu.pbtxt delete mode 100644 mediapipe/graphs/hair_segmentation/hair_segmentation_desktop_live.pbtxt delete mode 100644 mediapipe/graphs/hair_segmentation/hair_segmentation_mobile_gpu.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/hand_detection_desktop.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/hand_detection_desktop_live.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/hand_detection_mobile.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/hand_tracking_desktop.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/hand_tracking_desktop_live.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/hand_tracking_desktop_live_gpu.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/hand_tracking_mobile.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/subgraphs/hand_renderer_cpu.pbtxt delete mode 100644 mediapipe/graphs/hand_tracking/subgraphs/hand_renderer_gpu.pbtxt delete mode 100644 mediapipe/graphs/holistic_tracking/holistic_tracking_cpu.pbtxt delete mode 100644 mediapipe/graphs/holistic_tracking/holistic_tracking_gpu.pbtxt delete mode 100644 mediapipe/graphs/holistic_tracking/holistic_tracking_to_render_data.pbtxt delete mode 100644 mediapipe/graphs/instant_motion_tracking/calculators/matrices_manager_calculator.cc delete mode 100644 mediapipe/graphs/instant_motion_tracking/calculators/sticker_buffer.proto delete mode 100644 mediapipe/graphs/instant_motion_tracking/calculators/sticker_manager_calculator.cc delete mode 100644 mediapipe/graphs/instant_motion_tracking/calculators/tracked_anchor_manager_calculator.cc delete mode 100644 mediapipe/graphs/instant_motion_tracking/calculators/transformations.h delete mode 100644 mediapipe/graphs/instant_motion_tracking/instant_motion_tracking.pbtxt delete mode 100644 mediapipe/graphs/instant_motion_tracking/subgraphs/region_tracking.pbtxt delete mode 100644 mediapipe/graphs/iris_tracking/calculators/iris_to_depth_calculator.cc delete mode 100644 mediapipe/graphs/iris_tracking/calculators/iris_to_depth_calculator.proto delete mode 100644 mediapipe/graphs/iris_tracking/calculators/iris_to_render_data_calculator.cc delete mode 100644 mediapipe/graphs/iris_tracking/calculators/iris_to_render_data_calculator.proto delete mode 100644 mediapipe/graphs/iris_tracking/calculators/update_face_landmarks_calculator.cc delete mode 100644 mediapipe/graphs/iris_tracking/iris_depth_cpu.pbtxt delete mode 100644 mediapipe/graphs/iris_tracking/iris_tracking_cpu.pbtxt delete mode 100644 mediapipe/graphs/iris_tracking/iris_tracking_cpu_video_input.pbtxt delete mode 100644 mediapipe/graphs/iris_tracking/iris_tracking_gpu.pbtxt delete mode 100644 mediapipe/graphs/iris_tracking/subgraphs/iris_and_depth_renderer_cpu.pbtxt delete mode 100644 mediapipe/graphs/iris_tracking/subgraphs/iris_and_depth_renderer_gpu.pbtxt delete mode 100644 mediapipe/graphs/iris_tracking/subgraphs/iris_renderer_cpu.pbtxt delete mode 100644 mediapipe/graphs/media_sequence/clipped_images_from_file_at_24fps.pbtxt delete mode 100644 mediapipe/graphs/media_sequence/tvl1_flow_and_rgb_from_file.pbtxt delete mode 100644 mediapipe/graphs/object_detection/object_detection_desktop_tensorflow_graph.pbtxt delete mode 100644 mediapipe/graphs/object_detection/object_detection_desktop_tflite_graph.pbtxt delete mode 100644 mediapipe/graphs/object_detection/object_detection_mobile_cpu.pbtxt delete mode 100644 mediapipe/graphs/object_detection/object_detection_mobile_gpu.pbtxt delete mode 100644 mediapipe/graphs/object_detection_3d/calculators/annotations_to_model_matrices_calculator.cc delete mode 100644 mediapipe/graphs/object_detection_3d/calculators/annotations_to_model_matrices_calculator.proto delete mode 100644 mediapipe/graphs/object_detection_3d/calculators/annotations_to_render_data_calculator.cc delete mode 100644 mediapipe/graphs/object_detection_3d/calculators/annotations_to_render_data_calculator.proto delete mode 100644 mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.cc delete mode 100644 mediapipe/graphs/object_detection_3d/calculators/gl_animation_overlay_calculator.proto delete mode 100644 mediapipe/graphs/object_detection_3d/calculators/model_matrix.proto delete mode 100644 mediapipe/graphs/object_detection_3d/obj_parser/ObjParserMain.java delete mode 100644 mediapipe/graphs/object_detection_3d/obj_parser/SimpleObjParser.java delete mode 100755 mediapipe/graphs/object_detection_3d/obj_parser/obj_cleanup.sh delete mode 100644 mediapipe/graphs/object_detection_3d/object_occlusion_tracking.pbtxt delete mode 100644 mediapipe/graphs/object_detection_3d/object_occlusion_tracking_1stage.pbtxt delete mode 100644 mediapipe/graphs/object_detection_3d/objectron_desktop_cpu.pbtxt delete mode 100644 mediapipe/graphs/object_detection_3d/subgraphs/renderer_cpu.pbtxt delete mode 100644 mediapipe/graphs/pose_tracking/pose_tracking_cpu.pbtxt delete mode 100644 mediapipe/graphs/pose_tracking/pose_tracking_gpu.pbtxt delete mode 100644 mediapipe/graphs/pose_tracking/subgraphs/pose_landmarks_to_render_data.pbtxt delete mode 100644 mediapipe/graphs/pose_tracking/subgraphs/pose_renderer_cpu.pbtxt delete mode 100644 mediapipe/graphs/pose_tracking/subgraphs/pose_renderer_gpu.pbtxt delete mode 100644 mediapipe/graphs/selfie_segmentation/selfie_segmentation_cpu.pbtxt delete mode 100644 mediapipe/graphs/selfie_segmentation/selfie_segmentation_gpu.pbtxt delete mode 100644 mediapipe/graphs/template_matching/index_building.pbtxt delete mode 100644 mediapipe/graphs/template_matching/template_matching_desktop.pbtxt delete mode 100644 mediapipe/graphs/template_matching/template_matching_mobile_cpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/object_detection_tracking_desktop_live.pbtxt delete mode 100644 mediapipe/graphs/tracking/object_detection_tracking_mobile_gpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/box_tracking_cpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/box_tracking_gpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/object_detection_cpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/object_detection_gpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/object_tracking_cpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/object_tracking_gpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/renderer_cpu.pbtxt delete mode 100644 mediapipe/graphs/tracking/subgraphs/renderer_gpu.pbtxt delete mode 100644 mediapipe/graphs/youtube8m/feature_extraction.pbtxt delete mode 100644 mediapipe/graphs/youtube8m/label_map.txt delete mode 100644 mediapipe/graphs/youtube8m/local_video_model_inference.pbtxt delete mode 100644 mediapipe/graphs/youtube8m/yt8m_dataset_model_inference.pbtxt delete mode 100644 test_package/default_input_stream_handler.cc delete mode 100644 test_package/in_order_output_stream_handler.cc delete mode 100644 test_package/in_order_output_stream_handler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index de09810..511ad4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) find_package(absl REQUIRED) find_package(glog REQUIRED) -find_package(protobuf REQUIRED) +find_package(protobuf CONFIG REQUIRED) if(NOT BUILD_GRAPH_ONLY) find_package(OpenCV REQUIRED) find_package(Eigen3 REQUIRED) diff --git a/README.md b/README.md index 30c05b2..13e787e 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,16 @@ Builds the mediapipe graph module using cmake, conan, and allows conan packaging # The model in the config file is at https://drive.google.com/file/d/1U9cm5qfOxnGwyB6ypJjYvB6OeOjLZqpC/view?usp=drive_link # Download and place in mediapipe/models folder # The test image package can be downloaded from https://drive.google.com/file/d/1IjP8aT_iQ8fV_FCuUJk8TH3_e1X2Y_3Q/view?usp=drive_link - bin/object_detection --calculator_graph_config_file=../mediapipe/graphs/object_detection/object_detection_desktop_live.pbtxt --input_video_path=$IMAGE_DIR --output_video_path=$OUTPUT_DIR + bin/object_detection --calculator_graph_config_file=../examples/desktop/object_detection/object_detection_desktop_live.pbtxt --input_video_path=$IMAGE_DIR --output_video_path=$OUTPUT_DIR # run object detection example with all verbose logs - GLOG_v=5 bin/object_detection --calculator_graph_config_file=../mediapipe/graphs/object_detection/object_detection_desktop_live.pbtxt --input_video_path=$IMAGE_DIR --output_video_path=$OUTPUT_DIR + GLOG_v=5 bin/object_detection --calculator_graph_config_file=../examples/desktop/object_detection/object_detection_desktop_live.pbtxt --input_video_path=$IMAGE_DIR --output_video_path=$OUTPUT_DIR ``` 4. Project Architecture * libgraph depends only on protobuf, abseil and glog, and is 1.7M in size for x86 environments. * libframework contains libgraph and other auxiliary projects. 5. conan package ```bash - conan create . --build=missing -s build_type=Release -pr:h=docker/x86_gcc_profile + conan create . --build=missing -s build_type=Release -pr:h=docker/x86_gcc_profile -o 'export_package=True' ``` 6. Examples of using Graph diff --git a/README_CN.md b/README_CN.md index 9d682dc..beed4ef 100644 --- a/README_CN.md +++ b/README_CN.md @@ -36,7 +36,7 @@ * libframework 包含libgraph和其他辅助工程的整合包 5. conan 打包 ```bash - conan create . --build=missing -s build_type=Release + conan create . --build=missing -s build_type=Release -pr:h=docker/x86_gcc_profile -o 'export_package=True' ``` 6. Graph使用示例 diff --git a/conanfile.py b/conanfile.py index a58ceff..d6fda97 100644 --- a/conanfile.py +++ b/conanfile.py @@ -20,19 +20,31 @@ class MediapieliteRecipe(ConanFile): topics = ("compute graph", "cpp", "pipeline") # Binary configuration settings = "os", "compiler", "build_type", "arch" - options = {"shared": [True, False], "fPIC": [True, False], "enable_profiler": [True, False], 'enable_rtti': [True, False]} - default_options = {"shared": False, "fPIC": True, "enable_profiler": False, "enable_rtti": True} + options = { + "shared": [True, False], + "fPIC": [True, False], + "enable_profiler": [True, False], + "enable_rtti": [True, False], + 'export_package': [True, False] + } + default_options = { + "shared": False, + "fPIC": True, + "enable_profiler": False, + "enable_rtti": True, + 'export_package': False + } exports_sources = "CMakeLists.txt", "mediapipe/*", "cmake/*" @property def _compilers_minimum_version(self): return { - "gcc": "6", - "clang": "5", - "apple-clang": "10", - "Visual Studio": "15", - "msvc": "191", - } + "gcc": "6", + "clang": "5", + "apple-clang": "10", + "Visual Studio": "15", + "msvc": "191", + } def config_options(self): if self.settings.os == "Windows": @@ -41,16 +53,7 @@ def config_options(self): def configure(self): if self.options.shared: self.options.rm_safe("fPIC") - self.options['glog'].with_unwind = False - self.options["opencv/*"].with_gtk = False - self.options["opencv/*"].with_vulkan = False - self.options["opencv/*"].with_ffmpeg = False - self.options["opencv/*"].gapi = False - self.options["opencv/*"].objdetect = False - self.options["opencv/*"].photo = False - self.options["opencv/*"].dnn = False - self.options["opencv/*"].optflow = True - self.options["opencv/*"].ximgproc = True + @property def _min_cppstd(self): return "14" @@ -58,53 +61,75 @@ def _min_cppstd(self): def validate(self): if self.settings.compiler.cppstd: check_min_cppstd(self, self._min_cppstd) - minimum_version = self._compilers_minimum_version.get(str(self.settings.compiler), False) - if minimum_version and Version(self.settings.compiler.version) < minimum_version: + minimum_version = self._compilers_minimum_version.get( + str(self.settings.compiler), False + ) + if ( + minimum_version + and Version(self.settings.compiler.version) < minimum_version + ): raise ConanInvalidConfiguration( f"{self.ref} requires C++{self._min_cppstd}, which your compiler does not support." ) if self.options.shared and is_msvc(self): # upstream tries its best to export symbols, but it's broken for the moment - raise ConanInvalidConfiguration(f"{self.ref} shared not availabe for Visual Studio (yet)") - + raise ConanInvalidConfiguration( + f"{self.ref} shared not availabe for Visual Studio (yet)" + ) def layout(self): cmake_layout(self) - self.folders.generators = f'build_{self.settings.os}' + self.folders.generators = f"build_{self.settings.os}" def requirements(self): self.requires("abseil/20230125.1", visible=True) self.requires("protobuf/3.21.12", visible=True) - self.requires("glog/0.5.0", visible=True) - self.test_requires("gtest/1.13.0") - self.test_requires("zlib/1.2.13") - self.test_requires("opencv/4.5.5") - self.test_requires("tensorflow-lite/2.10.0") - self.test_requires("cpuinfo/cci.20220228") - self.test_requires("pybind11/2.10.1") - self.test_requires("stb/cci.20220909") - self.test_requires("xz_utils/5.4.2") - self.test_requires("benchmark/1.7.1") - self.test_requires("libjpeg/9e") - self.test_requires("eigen/3.4.0") + self.requires("glog/0.6.0", visible=True, options={"with_unwind": False}) + + def build_requirements(self): + if not self.options.export_package: + self.test_requires("gtest/1.13.0") + self.test_requires("zlib/1.2.13") + self.test_requires( + "opencv/4.5.5", + options={ + "with_gtk": False, + "with_vulkan": False, + "with_ffmpeg": False, + "gapi": False, + "objdetect": False, + "photo": False, + "dnn": False, + "optflow": True, + "ximgproc": True, + }, + ) + self.test_requires("tensorflow-lite/2.10.0") + self.test_requires("cpuinfo/cci.20220228") + self.test_requires("pybind11/2.10.1") + self.test_requires("stb/cci.20220909") + self.test_requires("xz_utils/5.4.2") + self.test_requires("benchmark/1.7.1") + self.test_requires("libjpeg/9e") + self.test_requires("eigen/3.4.0") def generate(self): tc = CMakeToolchain(self) tc.variables["BUILD_TESTS"] = False tc.variables["BUILD_PYTHON"] = False tc.variables["BUILD_EXAMPLES"] = False - tc.variables['BUILD_GRAPH_ONLY'] = True - if self.settings.os == 'Android': - tc.variables['BUILD_PROTO_FILES'] = False + tc.variables["BUILD_GRAPH_ONLY"] = True + if self.settings.os == "Android": + tc.variables["BUILD_PROTO_FILES"] = False if self.options.enable_rtti: - tc.variables['ENABLE_RTTI'] = True + tc.variables["ENABLE_RTTI"] = True else: - tc.variables['ENABLE_RTTI'] = False + tc.variables["ENABLE_RTTI"] = False if self.options.enable_profiler: - tc.variables['ENABLE_PROFILER'] = True + tc.variables["ENABLE_PROFILER"] = True else: - tc.variables['ENABLE_PROFILER'] = False + tc.variables["ENABLE_PROFILER"] = False tc.generate() deps = CMakeDeps(self) deps.generate() @@ -122,14 +147,25 @@ def package(self): def package_info(self): self.cpp_info.set_property("cmake_file_name", "mediapipelite") - self.cpp_info.components["graph"].set_property("cmake_target_name", "mediapipelite::graph") - self.cpp_info.components["graph"].set_property("cmake_target_aliases", ["mediapipelite::graph"]) - self.cpp_info.components["graph"].set_property("pkg_config_name", "libmediapipelite_graph") + self.cpp_info.components["graph"].set_property( + "cmake_target_name", "mediapipelite::graph" + ) + self.cpp_info.components["graph"].set_property( + "cmake_target_aliases", ["mediapipelite::graph"] + ) + self.cpp_info.components["graph"].set_property( + "pkg_config_name", "libmediapipelite_graph" + ) self.cpp_info.components["graph"].libs = ["graph"] - self.cpp_info.components["stream_handler"].set_property("cmake_target_name", "mediapipelite::stream_handler") - self.cpp_info.components["stream_handler"].set_property("cmake_target_aliases", ["mediapipelite::stream_handler"]) - self.cpp_info.components["stream_handler"].set_property("pkg_config_name", "libmediapipelite_stream_handler") + self.cpp_info.components["stream_handler"].set_property( + "cmake_target_name", "mediapipelite::stream_handler" + ) + self.cpp_info.components["stream_handler"].set_property( + "cmake_target_aliases", ["mediapipelite::stream_handler"] + ) + self.cpp_info.components["stream_handler"].set_property( + "pkg_config_name", "libmediapipelite_stream_handler" + ) self.cpp_info.components["stream_handler"].libs = ["stream_handler"] self.cpp_info.names["cmake_find_package"] = "mediapipelite" self.cpp_info.names["cmake_find_package_multi"] = "mediapipelite" - diff --git a/examples/desktop/autoflip/README.md b/examples/desktop/autoflip/README.md deleted file mode 100644 index 98004a7..0000000 --- a/examples/desktop/autoflip/README.md +++ /dev/null @@ -1,25 +0,0 @@ -### Steps to run the AutoFlip video cropping graph - -1. Checkout the repository and follow - [the installation instructions](https://github.com/google/mediapipe/blob/master/mediapipe/docs/install.md) - to set up MediaPipe. - - ```bash - git clone https://github.com/google/mediapipe.git - cd mediapipe - ``` - -2. Build and run the run_autoflip binary to process a local video. - -Note: AutoFlip currently only works with OpenCV 3 . Please verify your OpenCV version beforehand. - - ```bash - bazel build -c opt --define MEDIAPIPE_DISABLE_GPU=1 \ - mediapipe/examples/desktop/autoflip:run_autoflip - - GLOG_logtostderr=1 bazel-bin/mediapipe/examples/desktop/autoflip/run_autoflip \ - --calculator_graph_config_file=mediapipe/examples/desktop/autoflip/autoflip_graph.pbtxt \ - --input_side_packets=input_video_path=/absolute/path/to/the/local/video/file,output_video_path=/absolute/path/to/save/the/output/video/file,aspect_ratio=width:height - ``` - -3. View the cropped video. diff --git a/examples/desktop/autoflip/autoflip_graph.pbtxt b/examples/desktop/autoflip/autoflip_graph.pbtxt deleted file mode 100644 index 95af188..0000000 --- a/examples/desktop/autoflip/autoflip_graph.pbtxt +++ /dev/null @@ -1,202 +0,0 @@ -# Autoflip graph that only renders the final cropped video. For use with -# end user applications. -max_queue_size: -1 - -# VIDEO_PREP: Decodes an input video file into images and a video header. -node { - calculator: "OpenCvVideoDecoderCalculator" - input_side_packet: "INPUT_FILE_PATH:input_video_path" - output_stream: "VIDEO:video_raw" - output_stream: "VIDEO_PRESTREAM:video_header" - output_side_packet: "SAVED_AUDIO_PATH:audio_path" -} - -# VIDEO_PREP: Scale the input video before feature extraction. -node { - calculator: "ScaleImageCalculator" - input_stream: "FRAMES:video_raw" - input_stream: "VIDEO_HEADER:video_header" - output_stream: "FRAMES:video_frames_scaled" - options: { - [mediapipe.ScaleImageCalculatorOptions.ext]: { - preserve_aspect_ratio: true - output_format: SRGB - target_width: 480 - algorithm: DEFAULT_WITHOUT_UPSCALE - } - } -} - -# VIDEO_PREP: Create a low frame rate stream for feature extraction. -node { - calculator: "PacketThinnerCalculator" - input_stream: "video_frames_scaled" - output_stream: "video_frames_scaled_downsampled" - options: { - [mediapipe.PacketThinnerCalculatorOptions.ext]: { - thinner_type: ASYNC - period: 200000 - } - } -} - -# DETECTION: find borders around the video and major background color. -node { - calculator: "BorderDetectionCalculator" - input_stream: "VIDEO:video_raw" - output_stream: "DETECTED_BORDERS:borders" -} - -# DETECTION: find shot/scene boundaries on the full frame rate stream. -node { - calculator: "ShotBoundaryCalculator" - input_stream: "VIDEO:video_frames_scaled" - output_stream: "IS_SHOT_CHANGE:shot_change" - options { - [mediapipe.autoflip.ShotBoundaryCalculatorOptions.ext] { - min_shot_span: 0.2 - min_motion: 0.3 - window_size: 15 - min_shot_measure: 10 - min_motion_with_shot_measure: 0.05 - } - } -} - -# DETECTION: find faces on the down sampled stream -node { - calculator: "AutoFlipFaceDetectionSubgraph" - input_stream: "VIDEO:video_frames_scaled_downsampled" - output_stream: "DETECTIONS:face_detections" -} -node { - calculator: "FaceToRegionCalculator" - input_stream: "VIDEO:video_frames_scaled_downsampled" - input_stream: "FACES:face_detections" - output_stream: "REGIONS:face_regions" -} - -# DETECTION: find objects on the down sampled stream -node { - calculator: "AutoFlipObjectDetectionSubgraph" - input_stream: "VIDEO:video_frames_scaled_downsampled" - output_stream: "DETECTIONS:object_detections" -} -node { - calculator: "LocalizationToRegionCalculator" - input_stream: "DETECTIONS:object_detections" - output_stream: "REGIONS:object_regions" - options { - [mediapipe.autoflip.LocalizationToRegionCalculatorOptions.ext] { - output_all_signals: true - } - } -} - -# SIGNAL FUSION: Combine detections (with weights) on each frame -node { - calculator: "SignalFusingCalculator" - input_stream: "shot_change" - input_stream: "face_regions" - input_stream: "object_regions" - output_stream: "salient_regions" - options { - [mediapipe.autoflip.SignalFusingCalculatorOptions.ext] { - signal_settings { - type { standard: FACE_CORE_LANDMARKS } - min_score: 0.85 - max_score: 0.9 - is_required: false - } - signal_settings { - type { standard: FACE_ALL_LANDMARKS } - min_score: 0.8 - max_score: 0.85 - is_required: false - } - signal_settings { - type { standard: FACE_FULL } - min_score: 0.8 - max_score: 0.85 - is_required: false - } - signal_settings { - type: { standard: HUMAN } - min_score: 0.75 - max_score: 0.8 - is_required: false - } - signal_settings { - type: { standard: PET } - min_score: 0.7 - max_score: 0.75 - is_required: false - } - signal_settings { - type: { standard: CAR } - min_score: 0.7 - max_score: 0.75 - is_required: false - } - signal_settings { - type: { standard: OBJECT } - min_score: 0.1 - max_score: 0.2 - is_required: false - } - } - } -} - -# CROPPING: make decisions about how to crop each frame. -node { - calculator: "SceneCroppingCalculator" - input_side_packet: "EXTERNAL_ASPECT_RATIO:aspect_ratio" - input_stream: "VIDEO_FRAMES:video_raw" - input_stream: "KEY_FRAMES:video_frames_scaled_downsampled" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:borders" - input_stream: "SHOT_BOUNDARIES:shot_change" - output_stream: "CROPPED_FRAMES:cropped_frames" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - max_scene_size: 600 - key_frame_crop_options: { - score_aggregation_type: CONSTANT - } - scene_camera_motion_analyzer_options: { - motion_stabilization_threshold_percent: 0.5 - salient_point_bound: 0.499 - } - padding_parameters: { - blur_cv_size: 200 - overlay_opacity: 0.6 - } - target_size_type: MAXIMIZE_TARGET_DIMENSION - } - } -} - -# ENCODING(required): encode the video stream for the final cropped output. -node { - calculator: "VideoPreStreamCalculator" - # Fetch frame format and dimension from input frames. - input_stream: "FRAME:cropped_frames" - # Copying frame rate and duration from original video. - input_stream: "VIDEO_PRESTREAM:video_header" - output_stream: "output_frames_video_header" -} - -node { - calculator: "OpenCvVideoEncoderCalculator" - input_stream: "VIDEO:cropped_frames" - input_stream: "VIDEO_PRESTREAM:output_frames_video_header" - input_side_packet: "OUTPUT_FILE_PATH:output_video_path" - input_side_packet: "AUDIO_FILE_PATH:audio_path" - options: { - [mediapipe.OpenCvVideoEncoderCalculatorOptions.ext]: { - codec: "avc1" - video_format: "mp4" - } - } -} diff --git a/examples/desktop/autoflip/autoflip_graph_development.pbtxt b/examples/desktop/autoflip/autoflip_graph_development.pbtxt deleted file mode 100644 index 0087cd6..0000000 --- a/examples/desktop/autoflip/autoflip_graph_development.pbtxt +++ /dev/null @@ -1,252 +0,0 @@ -# Autoflip graph that renders the final cropped video and debugging videos. -# For use by developers who may be adding signals and adjusting weights. -max_queue_size: -1 - -# VIDEO_PREP: Decodes an input video file into images and a video header. -node { - calculator: "OpenCvVideoDecoderCalculator" - input_side_packet: "INPUT_FILE_PATH:input_video_path" - output_stream: "VIDEO:video_raw" - output_stream: "VIDEO_PRESTREAM:video_header" - output_side_packet: "SAVED_AUDIO_PATH:audio_path" -} - -# VIDEO_PREP: Scale the input video before feature extraction. -node { - calculator: "ScaleImageCalculator" - input_stream: "FRAMES:video_raw" - input_stream: "VIDEO_HEADER:video_header" - output_stream: "FRAMES:video_frames_scaled" - options: { - [mediapipe.ScaleImageCalculatorOptions.ext]: { - preserve_aspect_ratio: true - output_format: SRGB - target_width: 480 - algorithm: DEFAULT_WITHOUT_UPSCALE - } - } -} - -# VIDEO_PREP: Create a low frame rate stream for feature extraction. -node { - calculator: "PacketThinnerCalculator" - input_stream: "video_frames_scaled" - output_stream: "video_frames_scaled_downsampled" - options: { - [mediapipe.PacketThinnerCalculatorOptions.ext]: { - thinner_type: ASYNC - period: 200000 - } - } -} - -# DETECTION: find borders around the video and major background color. -node { - calculator: "BorderDetectionCalculator" - input_stream: "VIDEO:video_raw" - output_stream: "DETECTED_BORDERS:borders" -} - -# DETECTION: find shot/scene boundaries on the full frame rate stream. -node { - calculator: "ShotBoundaryCalculator" - input_stream: "VIDEO:video_frames_scaled" - output_stream: "IS_SHOT_CHANGE:shot_change" - options { - [mediapipe.autoflip.ShotBoundaryCalculatorOptions.ext] { - min_shot_span: 0.2 - min_motion: 0.3 - window_size: 15 - min_shot_measure: 10 - min_motion_with_shot_measure: 0.05 - } - } -} - -# DETECTION: find faces on the down sampled stream -node { - calculator: "AutoFlipFaceDetectionSubgraph" - input_stream: "VIDEO:video_frames_scaled_downsampled" - output_stream: "DETECTIONS:face_detections" -} -node { - calculator: "FaceToRegionCalculator" - input_stream: "VIDEO:video_frames_scaled_downsampled" - input_stream: "FACES:face_detections" - output_stream: "REGIONS:face_regions" -} - -# DETECTION: find objects on the down sampled stream -node { - calculator: "AutoFlipObjectDetectionSubgraph" - input_stream: "VIDEO:video_frames_scaled_downsampled" - output_stream: "DETECTIONS:object_detections" -} -node { - calculator: "LocalizationToRegionCalculator" - input_stream: "DETECTIONS:object_detections" - output_stream: "REGIONS:object_regions" - options { - [mediapipe.autoflip.LocalizationToRegionCalculatorOptions.ext] { - output_all_signals: true - } - } -} - -# SIGNAL FUSION: Combine detections (with weights) on each frame -node { - calculator: "SignalFusingCalculator" - input_stream: "shot_change" - input_stream: "face_regions" - input_stream: "object_regions" - output_stream: "salient_regions" - options { - [mediapipe.autoflip.SignalFusingCalculatorOptions.ext] { - signal_settings { - type { standard: FACE_CORE_LANDMARKS } - min_score: 0.85 - max_score: 0.9 - is_required: false - } - signal_settings { - type { standard: FACE_ALL_LANDMARKS } - min_score: 0.8 - max_score: 0.85 - is_required: false - } - signal_settings { - type { standard: FACE_FULL } - min_score: 0.8 - max_score: 0.85 - is_required: false - } - signal_settings { - type: { standard: HUMAN } - min_score: 0.75 - max_score: 0.8 - is_required: false - } - signal_settings { - type: { standard: PET } - min_score: 0.7 - max_score: 0.75 - is_required: false - } - signal_settings { - type: { standard: CAR } - min_score: 0.7 - max_score: 0.75 - is_required: false - } - signal_settings { - type: { standard: OBJECT } - min_score: 0.1 - max_score: 0.2 - is_required: false - } - } - } -} - -# CROPPING: make decisions about how to crop each frame. -node { - calculator: "SceneCroppingCalculator" - input_side_packet: "EXTERNAL_ASPECT_RATIO:aspect_ratio" - input_stream: "VIDEO_FRAMES:video_raw" - input_stream: "KEY_FRAMES:video_frames_scaled_downsampled" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:borders" - input_stream: "SHOT_BOUNDARIES:shot_change" - output_stream: "CROPPED_FRAMES:cropped_frames" - output_stream: "KEY_FRAME_CROP_REGION_VIZ_FRAMES:key_frame_crop_viz_frames" - output_stream: "SALIENT_POINT_FRAME_VIZ_FRAMES:salient_point_viz_frames" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - max_scene_size: 600 - key_frame_crop_options: { - score_aggregation_type: CONSTANT - } - scene_camera_motion_analyzer_options: { - motion_stabilization_threshold_percent: 0.5 - salient_point_bound: 0.499 - } - padding_parameters: { - blur_cv_size: 200 - overlay_opacity: 0.6 - } - target_size_type: MAXIMIZE_TARGET_DIMENSION - } - } -} - -# ENCODING(required): encode the video stream for the final cropped output. -node { - calculator: "VideoPreStreamCalculator" - # Fetch frame format and dimension from input frames. - input_stream: "FRAME:cropped_frames" - # Copying frame rate and duration from original video. - input_stream: "VIDEO_PRESTREAM:video_header" - output_stream: "output_frames_video_header" -} - -node { - calculator: "OpenCvVideoEncoderCalculator" - input_stream: "VIDEO:cropped_frames" - input_stream: "VIDEO_PRESTREAM:output_frames_video_header" - input_side_packet: "OUTPUT_FILE_PATH:output_video_path" - input_side_packet: "AUDIO_FILE_PATH:audio_path" - options: { - [mediapipe.OpenCvVideoEncoderCalculatorOptions.ext]: { - codec: "avc1" - video_format: "mp4" - } - } -} - -# ENCODING(optional): encode the video stream for the key_frame_crop_viz_frames -# output. Draws boxes around required and non-required objects. -node { - calculator: "VideoPreStreamCalculator" - # Fetch frame format and dimension from input frames. - input_stream: "FRAME:key_frame_crop_viz_frames" - # Copying frame rate and duration from original video. - input_stream: "VIDEO_PRESTREAM:video_header" - output_stream: "key_frame_crop_viz_frames_header" -} - -node { - calculator: "OpenCvVideoEncoderCalculator" - input_stream: "VIDEO:key_frame_crop_viz_frames" - input_stream: "VIDEO_PRESTREAM:key_frame_crop_viz_frames_header" - input_side_packet: "OUTPUT_FILE_PATH:key_frame_crop_viz_frames_path" - options: { - [mediapipe.OpenCvVideoEncoderCalculatorOptions.ext]: { - codec: "avc1" - video_format: "mp4" - } - } -} - -# ENCODING(optional): encode the video stream for the salient_point_viz_frames -# output. Draws the focus points and the scene crop window (red). -node { - calculator: "VideoPreStreamCalculator" - # Fetch frame format and dimension from input frames. - input_stream: "FRAME:salient_point_viz_frames" - # Copying frame rate and duration from original video. - input_stream: "VIDEO_PRESTREAM:video_header" - output_stream: "salient_point_viz_frames_header" -} - -node { - calculator: "OpenCvVideoEncoderCalculator" - input_stream: "VIDEO:salient_point_viz_frames" - input_stream: "VIDEO_PRESTREAM:salient_point_viz_frames_header" - input_side_packet: "OUTPUT_FILE_PATH:salient_point_viz_frames_path" - options: { - [mediapipe.OpenCvVideoEncoderCalculatorOptions.ext]: { - codec: "avc1" - video_format: "mp4" - } - } -} diff --git a/examples/desktop/autoflip/autoflip_messages.proto b/examples/desktop/autoflip/autoflip_messages.proto deleted file mode 100644 index 8507c9a..0000000 --- a/examples/desktop/autoflip/autoflip_messages.proto +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -// Proto messages used for the AutoFlip Pipeline. -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/framework/calculator_options.proto"; - -option java_multiple_files = true; - -// Borders detected on the frame as well as non-border color (if present). -// Next tag: 4 -message StaticFeatures { - // A list of the static parts for a frame. - repeated Border border = 1; - // The background color (only set if solid color). - optional Color solid_background = 2; - // Area of the image that is not a border. - optional Rect non_static_area = 3; -} - -// A static border area within the video. -// Next tag: 3 -message Border { - // Original location within the input frame. - optional Rect border_position = 1; - // Position for static area. - // Next tag: 3 - enum RelativePosition { - TOP = 1; - BOTTOM = 2; - } - // Top or bottom position. - optional RelativePosition relative_position = 2; -} - -// Rectangle (opencv format). -// Next tag: 5 -message Rect { - optional int32 x = 1; - optional int32 y = 2; - optional int32 width = 3; - optional int32 height = 4; -} - -// Color (RGB 8bit) -// Next tag: 4 -message Color { - optional int32 r = 1; - optional int32 g = 2; - optional int32 b = 3; -} - -// Rectangle (opencv format). -// Next tag: 5 -message RectF { - optional float x = 1; - optional float y = 2; - optional float width = 3; - optional float height = 4; -} - -// An image region of interest (eg a detected face or object), accompanied by an -// importance score. -// Next tag: 10 -message SalientRegion { - reserved 3; - // The bounding box for this region in the image. - optional Rect location = 1; - - // The bounding box for this region in the image normalized. - optional RectF location_normalized = 8; - - // A score indicating the importance of this region. - optional float score = 2; - - // A tracking id used to identify this region across video frames. Not always - // set. - optional int64 tracking_id = 4; - - // If true, this region is required to be present in the final video (eg it - // contains text that cannot be cropped). - optional bool is_required = 5 [default = false]; - - // Type of signal carried in this message. - optional SignalType signal_type = 6; - - // If true, object cannot move in the output window (e.g. text would look - // strange moving around). - // TODO: this feature is not implemented, remove proto message. - optional bool requires_static_location = 7 [default = false]; - - // When used with ContentZoomingCalculator, this flag can be set in the - // SignalFusingCalculator indicating that areas outside of these detections - // can be cropped from the frame. When no salient regions have this flag set - // true, no zooming is performed. When one or more salient regions have this - // flag set true, the max zoom value will be used that keeps all - // “only_required” detections within view. The ContentZoomingCalculator - // currently supports zooming by finding the size of non-salient top/bottom - // borders regions and provides this information to the - // SceneCroppingCalculator for reframing. - optional bool only_required = 9 [default = false]; -} - -// Stores the message type, including standard types (face, object) and custom -// types defined by a string id. -// Next tag: 3 -message SignalType { - enum StandardType { - UNSET = 0; - // Full face bounding boxed detected. - FACE_FULL = 1; - // Face landmarks for eyes, nose, chin only. - FACE_CORE_LANDMARKS = 2; - // All face landmarks (eyes, ears, nose, chin). - FACE_ALL_LANDMARKS = 3; - // A specific face landmark. - FACE_LANDMARK = 4; - HUMAN = 5; - CAR = 6; - PET = 7; - OBJECT = 8; - MOTION = 9; - TEXT = 10; - LOGO = 11; - USER_HINT = 12; - } - oneof Signal { - StandardType standard = 1; - string custom = 2; - } -} - -// Features extracted from a image. -// Next tag: 3 -message DetectionSet { - // Mask image showing pixel-wise values at a given location. - optional string encoded_mask = 1; - // List of rectangle detections. - repeated SalientRegion detections = 2; -} - -// General settings needed for multiple calculators. -message ConversionOptions { - extend mediapipe.CalculatorOptions { - optional ConversionOptions ext = 284806832; - } - // Target output width of the conversion. - optional int32 target_width = 1; - // Target output height of the conversion. - optional int32 target_height = 2; -} - -// Self-contained message that provides all needed information to render -// autoflip with an external renderer. One of these messages is required for -// each frame of the video. -message ExternalRenderFrame { - // Rectangle using opencv standard. - message Rect { - optional float x = 1; - optional float y = 2; - optional float width = 3; - optional float height = 4; - } - // RGB color [0...255] - message Color { - optional int32 r = 1; - optional int32 g = 2; - optional int32 b = 3; - } - // Rect that must be cropped out of the input frame. It is in the - // original dimensions of the input video. The first step to render this - // frame is to crop this rect from the input frame. - optional Rect crop_from_location = 1; - // The placement location where the above rect is placed on the output frame. - // This will always have the same aspect ratio as the above rect but scaling - // may be required. - optional Rect render_to_location = 2; - // If render_to_location is smaller than the output dimensions of the frame, - // fill the rest of the frame with this color. - optional Color padding_color = 3; - // Timestamp in microseconds of this frame. - optional uint64 timestamp_us = 4; - // Target width of the cropped video in pixels. |render_to_location| is - // relative to this dimension. - optional int32 target_width = 5; - // Target height of the cropped video in pixels. |render_to_location| is - // relative to this dimension. - optional int32 target_height = 6; -} diff --git a/examples/desktop/autoflip/calculators/border_detection_calculator.cc b/examples/desktop/autoflip/calculators/border_detection_calculator.cc deleted file mode 100644 index caaa368..0000000 --- a/examples/desktop/autoflip/calculators/border_detection_calculator.cc +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. -// -// This Calculator takes an ImageFrame and scales it appropriately. - -#include -#include -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/border_detection_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -using mediapipe::Adopt; -using mediapipe::CalculatorBase; -using mediapipe::ImageFrame; -using mediapipe::PacketTypeSet; -using mediapipe::autoflip::Border; - -constexpr char kDetectedBorders[] = "DETECTED_BORDERS"; -constexpr int kMinBorderDistance = 5; -constexpr int kKMeansClusterCount = 4; -constexpr int kMaxPixelsToProcess = 300000; -constexpr char kVideoInputTag[] = "VIDEO"; - -namespace mediapipe { -namespace autoflip { - -namespace { - -// Sets rect values into a proto. -void SetRect(const cv::Rect& region, - const Border::RelativePosition& relative_position, Border* part) { - part->mutable_border_position()->set_x(region.x); - part->mutable_border_position()->set_y(region.y); - part->mutable_border_position()->set_width(region.width); - part->mutable_border_position()->set_height(region.height); - part->set_relative_position(relative_position); -} - -} // namespace - -// This calculator takes a sequence of images (video) and detects solid color -// borders as well as the dominant color of the non-border area. This per-frame -// information is passed to downstream calculators. -class BorderDetectionCalculator : public CalculatorBase { - public: - BorderDetectionCalculator() : frame_width_(-1), frame_height_(-1) {} - ~BorderDetectionCalculator() override {} - BorderDetectionCalculator(const BorderDetectionCalculator&) = delete; - BorderDetectionCalculator& operator=(const BorderDetectionCalculator&) = - delete; - - static absl::Status GetContract(mediapipe::CalculatorContract* cc); - absl::Status Open(mediapipe::CalculatorContext* cc) override; - absl::Status Process(mediapipe::CalculatorContext* cc) override; - - private: - // Given a color and image direction, check to see if a border of that color - // exists. - void DetectBorder(const cv::Mat& frame, const Color& color, - const Border::RelativePosition& direction, - StaticFeatures* features); - - // Provide the percent this color shows up in a given image. - double ColorCount(const Color& mask_color, const cv::Mat& image) const; - - // Set member vars (image size) and confirm no changes frame-to-frame. - absl::Status SetAndCheckInputs(const cv::Mat& frame); - - // Find the dominant color for a input image. - double FindDominantColor(const cv::Mat& image, Color* dominant_color); - - // Frame width and height. - int frame_width_; - int frame_height_; - - // Options for processing. - BorderDetectionCalculatorOptions options_; -}; -REGISTER_CALCULATOR(BorderDetectionCalculator); - -absl::Status BorderDetectionCalculator::Open(mediapipe::CalculatorContext* cc) { - options_ = cc->Options(); - RET_CHECK_LT(options_.vertical_search_distance(), 0.5) - << "Search distance must be less than half the full image."; - return absl::OkStatus(); -} - -absl::Status BorderDetectionCalculator::SetAndCheckInputs( - const cv::Mat& frame) { - if (frame_width_ < 0) { - frame_width_ = frame.cols; - } - if (frame_height_ < 0) { - frame_height_ = frame.rows; - } - RET_CHECK_EQ(frame.cols, frame_width_) - << "Input frame dimensions must remain constant throughout the video."; - RET_CHECK_EQ(frame.rows, frame_height_) - << "Input frame dimensions must remain constant throughout the video."; - RET_CHECK_EQ(frame.channels(), 3) << "Input video type must be 3-channel"; - return absl::OkStatus(); -} - -absl::Status BorderDetectionCalculator::Process( - mediapipe::CalculatorContext* cc) { - if (!cc->Inputs().HasTag(kVideoInputTag) || - cc->Inputs().Tag(kVideoInputTag).Value().IsEmpty()) { - return mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC) - << "Input tag VIDEO not set or empty at timestamp: " - << cc->InputTimestamp().Value(); - } - cv::Mat frame = mediapipe::formats::MatView( - &cc->Inputs().Tag(kVideoInputTag).Get()); - MP_RETURN_IF_ERROR(SetAndCheckInputs(frame)); - - // Initialize output and set default values. - std::unique_ptr features = - absl::make_unique(); - features->mutable_non_static_area()->set_x(0); - features->mutable_non_static_area()->set_width(frame_width_); - features->mutable_non_static_area()->set_y(options_.default_padding_px()); - features->mutable_non_static_area()->set_height( - std::max(0, frame_height_ - options_.default_padding_px() * 2)); - - // Check for border at the top of the frame. - Color seed_color_top; - FindDominantColor(frame(cv::Rect(0, 0, frame_width_, 1)), &seed_color_top); - DetectBorder(frame, seed_color_top, Border::TOP, features.get()); - - // Check for border at the bottom of the frame. - Color seed_color_bottom; - FindDominantColor(frame(cv::Rect(0, frame_height_ - 1, frame_width_, 1)), - &seed_color_bottom); - DetectBorder(frame, seed_color_bottom, Border::BOTTOM, features.get()); - - // Check the non-border area for a dominant color. - cv::Mat non_static_frame = frame( - cv::Rect(features->non_static_area().x(), features->non_static_area().y(), - features->non_static_area().width(), - features->non_static_area().height())); - Color dominant_color_nonborder; - double dominant_color_percent = - FindDominantColor(non_static_frame, &dominant_color_nonborder); - if (dominant_color_percent > options_.solid_background_tol_perc()) { - auto* bg_color = features->mutable_solid_background(); - bg_color->set_r(dominant_color_nonborder.r()); - bg_color->set_g(dominant_color_nonborder.g()); - bg_color->set_b(dominant_color_nonborder.b()); - } - - // Output result. - cc->Outputs() - .Tag(kDetectedBorders) - .AddPacket(Adopt(features.release()).At(cc->InputTimestamp())); - - return absl::OkStatus(); -} - -// Find the dominant color within an image. -double BorderDetectionCalculator::FindDominantColor(const cv::Mat& image_raw, - Color* dominant_color) { - cv::Mat image; - if (image_raw.total() > kMaxPixelsToProcess) { - float resize = kMaxPixelsToProcess / static_cast(image_raw.total()); - cv::resize(image_raw, image, cv::Size(), resize, resize); - } else { - image = image_raw; - } - - cv::Mat float_data, cluster, cluster_center; - image.convertTo(float_data, CV_32F); - cv::Mat reshaped = float_data.reshape(1, float_data.total()); - - cv::kmeans(reshaped, kKMeansClusterCount, cluster, - cv::TermCriteria(CV_TERMCRIT_ITER, 5, 1.0), 1, - cv::KMEANS_PP_CENTERS, cluster_center); - - std::vector count(kKMeansClusterCount, 0); - for (int i = 0; i < cluster.rows; i++) { - count[cluster.at(i, 0)]++; - } - auto max_cluster_ptr = std::max_element(count.begin(), count.end()); - double max_cluster_perc = - *max_cluster_ptr / static_cast(cluster.rows); - int max_cluster_idx = std::distance(count.begin(), max_cluster_ptr); - - dominant_color->set_r(cluster_center.at(max_cluster_idx, 2)); - dominant_color->set_g(cluster_center.at(max_cluster_idx, 1)); - dominant_color->set_b(cluster_center.at(max_cluster_idx, 0)); - - return max_cluster_perc; -} - -double BorderDetectionCalculator::ColorCount(const Color& mask_color, - const cv::Mat& image) const { - int background_count = 0; - for (int i = 0; i < image.rows; i++) { - const uint8* row_ptr = image.ptr(i); - for (int j = 0; j < image.cols * 3; j += 3) { - if (std::abs(mask_color.r() - static_cast(row_ptr[j + 2])) <= - options_.color_tolerance() && - std::abs(mask_color.g() - static_cast(row_ptr[j + 1])) <= - options_.color_tolerance() && - std::abs(mask_color.b() - static_cast(row_ptr[j])) <= - options_.color_tolerance()) { - background_count++; - } - } - } - return background_count / static_cast(image.rows * image.cols); -} - -void BorderDetectionCalculator::DetectBorder( - const cv::Mat& frame, const Color& color, - const Border::RelativePosition& direction, StaticFeatures* features) { - // Search the entire image until we find an object, or hit the max search - // distance. - int search_distance = - (direction == Border::TOP || direction == Border::BOTTOM) ? frame.rows - : frame.cols; - search_distance *= options_.vertical_search_distance(); - - // Check if each next line has a dominant color that matches the given - // border color. - int last_border = -1; - for (int i = 0; i < search_distance; i++) { - cv::Rect current_row; - switch (direction) { - case Border::TOP: - current_row = cv::Rect(0, i, frame.cols, 1); - break; - case Border::BOTTOM: - current_row = cv::Rect(0, frame.rows - i - 1, frame.cols, 1); - break; - } - if (ColorCount(color, frame(current_row)) < - options_.border_color_pixel_perc()) { - break; - } - last_border = i; - } - - // Reject results that are not borders (or too small). - if (last_border <= kMinBorderDistance || last_border == search_distance - 1) { - return; - } - - // Apply defined padding. - last_border += options_.border_object_padding_px(); - - switch (direction) { - case Border::TOP: - SetRect(cv::Rect(0, 0, frame.cols, last_border), Border::TOP, - features->add_border()); - features->mutable_non_static_area()->set_y( - last_border + features->non_static_area().y()); - features->mutable_non_static_area()->set_height( - std::max(0, frame_height_ - (features->non_static_area().y() + - options_.default_padding_px()))); - break; - case Border::BOTTOM: - SetRect( - cv::Rect(0, frame.rows - last_border - 1, frame.cols, last_border), - Border::BOTTOM, features->add_border()); - - features->mutable_non_static_area()->set_height(std::max( - 0, frame.rows - (features->non_static_area().y() + last_border + - options_.default_padding_px()))); - - break; - } -} - -absl::Status BorderDetectionCalculator::GetContract( - mediapipe::CalculatorContract* cc) { - cc->Inputs().Tag(kVideoInputTag).Set(); - cc->Outputs().Tag(kDetectedBorders).Set(); - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/border_detection_calculator.proto b/examples/desktop/autoflip/calculators/border_detection_calculator.proto deleted file mode 100644 index 9be74a3..0000000 --- a/examples/desktop/autoflip/calculators/border_detection_calculator.proto +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/framework/calculator.proto"; - -// Next tag: 7 -message BorderDetectionCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional BorderDetectionCalculatorOptions ext = 276599815; - } - // Max difference in color to be considered the same (per rgb channel). - optional int32 color_tolerance = 1 [default = 6]; - - // Amount of padding to add around any object within the border that is - // resized to fit into the new border. - optional int32 border_object_padding_px = 2 [default = 5]; - - // Distance (as a percent of height) to search for a border. - optional float vertical_search_distance = 3 [default = .20]; - - // Percent of pixels matching border color to be a border - optional float border_color_pixel_perc = 4 [default = .995]; - - // Percent of pixels matching background to be a solid background frame - optional float solid_background_tol_perc = 5 [default = .5]; - - // Force a border of this size in pixels on top and bottom. - optional int32 default_padding_px = 6 [default = 0]; -} diff --git a/examples/desktop/autoflip/calculators/border_detection_calculator_test.cc b/examples/desktop/autoflip/calculators/border_detection_calculator_test.cc deleted file mode 100644 index e72d54e..0000000 --- a/examples/desktop/autoflip/calculators/border_detection_calculator_test.cc +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "absl/strings/string_view.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/border_detection_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/benchmark.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -using mediapipe::Adopt; -using mediapipe::CalculatorGraphConfig; -using mediapipe::CalculatorRunner; -using mediapipe::ImageFormat; -using mediapipe::ImageFrame; -using mediapipe::Packet; -using mediapipe::PacketTypeSet; -using mediapipe::ParseTextProtoOrDie; -using mediapipe::Timestamp; -using mediapipe::autoflip::Border; - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kDetectedBordersTag[] = "DETECTED_BORDERS"; -constexpr char kVideoTag[] = "VIDEO"; - -const char kConfig[] = R"( - calculator: "BorderDetectionCalculator" - input_stream: "VIDEO:camera_frames" - output_stream: "DETECTED_BORDERS:regions" - options:{ - [mediapipe.autoflip.BorderDetectionCalculatorOptions.ext]:{ - border_object_padding_px: 0 - } - })"; - -const char kConfigPad[] = R"( - calculator: "BorderDetectionCalculator" - input_stream: "VIDEO:camera_frames" - output_stream: "DETECTED_BORDERS:regions" - options:{ - [mediapipe.autoflip.BorderDetectionCalculatorOptions.ext]:{ - default_padding_px: 10 - border_object_padding_px: 0 - } - })"; - -const int kTestFrameWidth = 640; -const int kTestFrameHeight = 480; - -const int kTestFrameLargeWidth = 1920; -const int kTestFrameLargeHeight = 1080; - -const int kTestFrameWidthTall = 1200; -const int kTestFrameHeightTall = 2001; - -TEST(BorderDetectionCalculatorTest, NoBorderTest) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfig)); - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kDetectedBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - ASSERT_EQ(0, static_features.border().size()); - EXPECT_EQ(0, static_features.non_static_area().x()); - EXPECT_EQ(0, static_features.non_static_area().y()); - EXPECT_EQ(kTestFrameWidth, static_features.non_static_area().width()); - EXPECT_EQ(kTestFrameHeight, static_features.non_static_area().height()); - EXPECT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(0, static_features.solid_background().b()); -} - -TEST(BorderDetectionCalculatorTest, TopBorderTest) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfig)); - - const int kTopBorderHeight = 50; - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - cv::Mat sub_image = - input_mat(cv::Rect(0, 0, kTestFrameWidth, kTopBorderHeight)); - sub_image.setTo(cv::Scalar(255, 0, 0)); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kDetectedBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - ASSERT_EQ(1, static_features.border().size()); - const auto& part = static_features.border(0); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), 0); - EXPECT_EQ(part.border_position().width(), kTestFrameWidth); - EXPECT_LT(std::abs(part.border_position().height() - kTopBorderHeight), 2); - EXPECT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(0, static_features.solid_background().b()); - EXPECT_EQ(0, static_features.non_static_area().x()); - EXPECT_EQ(kTopBorderHeight - 1, static_features.non_static_area().y()); - EXPECT_EQ(kTestFrameWidth, static_features.non_static_area().width()); - EXPECT_EQ(kTestFrameHeight - kTopBorderHeight + 1, - static_features.non_static_area().height()); -} - -TEST(BorderDetectionCalculatorTest, TopBorderPadTest) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfigPad)); - - const int kTopBorderHeight = 50; - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - cv::Mat sub_image = - input_mat(cv::Rect(0, 0, kTestFrameWidth, kTopBorderHeight)); - sub_image.setTo(cv::Scalar(255, 0, 0)); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kDetectedBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - ASSERT_EQ(1, static_features.border().size()); - const auto& part = static_features.border(0); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), 0); - EXPECT_EQ(part.border_position().width(), kTestFrameWidth); - EXPECT_LT(std::abs(part.border_position().height() - kTopBorderHeight), 2); - EXPECT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(0, static_features.solid_background().b()); - EXPECT_EQ(Border::TOP, part.relative_position()); - EXPECT_EQ(0, static_features.non_static_area().x()); - EXPECT_EQ(9 + kTopBorderHeight, static_features.non_static_area().y()); - EXPECT_EQ(kTestFrameWidth, static_features.non_static_area().width()); - EXPECT_EQ(kTestFrameHeight - 19 - kTopBorderHeight, - static_features.non_static_area().height()); -} - -TEST(BorderDetectionCalculatorTest, BottomBorderTest) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfig)); - - const int kBottomBorderHeight = 50; - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - cv::Mat bottom_image = - input_mat(cv::Rect(0, kTestFrameHeight - kBottomBorderHeight, - kTestFrameWidth, kBottomBorderHeight)); - bottom_image.setTo(cv::Scalar(255, 0, 0)); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kDetectedBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - ASSERT_EQ(1, static_features.border().size()); - const auto& part = static_features.border(0); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), kTestFrameHeight - kBottomBorderHeight); - EXPECT_EQ(part.border_position().width(), kTestFrameWidth); - EXPECT_LT(std::abs(part.border_position().height() - kBottomBorderHeight), 2); - EXPECT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(0, static_features.solid_background().b()); - EXPECT_EQ(Border::BOTTOM, part.relative_position()); -} - -TEST(BorderDetectionCalculatorTest, TopBottomBorderTest) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfig)); - - const int kBottomBorderHeight = 50; - const int kTopBorderHeight = 25; - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - cv::Mat top_image = - input_mat(cv::Rect(0, 0, kTestFrameWidth, kTopBorderHeight)); - top_image.setTo(cv::Scalar(0, 255, 0)); - cv::Mat bottom_image = - input_mat(cv::Rect(0, kTestFrameHeight - kBottomBorderHeight, - kTestFrameWidth, kBottomBorderHeight)); - bottom_image.setTo(cv::Scalar(255, 0, 0)); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kDetectedBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - ASSERT_EQ(2, static_features.border().size()); - auto part = static_features.border(0); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), 0); - EXPECT_EQ(part.border_position().width(), kTestFrameWidth); - EXPECT_LT(std::abs(part.border_position().height() - kTopBorderHeight), 2); - EXPECT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(0, static_features.solid_background().b()); - EXPECT_EQ(0, static_features.non_static_area().x()); - EXPECT_EQ(kTopBorderHeight - 1, static_features.non_static_area().y()); - EXPECT_EQ(kTestFrameWidth, static_features.non_static_area().width()); - EXPECT_EQ(kTestFrameHeight - kTopBorderHeight - kBottomBorderHeight + 2, - static_features.non_static_area().height()); - EXPECT_EQ(Border::TOP, part.relative_position()); - - part = static_features.border(1); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), kTestFrameHeight - kBottomBorderHeight); - EXPECT_EQ(part.border_position().width(), kTestFrameWidth); - EXPECT_LT(std::abs(part.border_position().height() - kBottomBorderHeight), 2); - EXPECT_EQ(Border::BOTTOM, part.relative_position()); -} - -TEST(BorderDetectionCalculatorTest, TopBottomBorderTestAspect2) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfig)); - - const int kBottomBorderHeight = 50; - const int kTopBorderHeight = 25; - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidthTall, kTestFrameHeightTall); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - cv::Mat top_image = - input_mat(cv::Rect(0, 0, kTestFrameWidthTall, kTopBorderHeight)); - top_image.setTo(cv::Scalar(0, 255, 0)); - cv::Mat bottom_image = - input_mat(cv::Rect(0, kTestFrameHeightTall - kBottomBorderHeight, - kTestFrameWidthTall, kBottomBorderHeight)); - bottom_image.setTo(cv::Scalar(255, 0, 0)); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kDetectedBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - ASSERT_EQ(2, static_features.border().size()); - auto part = static_features.border(0); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), 0); - EXPECT_EQ(part.border_position().width(), kTestFrameWidthTall); - EXPECT_LT(std::abs(part.border_position().height() - kTopBorderHeight), 2); - EXPECT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(0, static_features.solid_background().b()); - EXPECT_EQ(Border::TOP, part.relative_position()); - - part = static_features.border(1); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), - kTestFrameHeightTall - kBottomBorderHeight); - EXPECT_EQ(part.border_position().width(), kTestFrameWidthTall); - EXPECT_LT(std::abs(part.border_position().height() - kBottomBorderHeight), 2); - EXPECT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(0, static_features.solid_background().b()); - EXPECT_EQ(Border::BOTTOM, part.relative_position()); -} - -TEST(BorderDetectionCalculatorTest, DominantColor) { - CalculatorGraphConfig::Node node = - ParseTextProtoOrDie(kConfigPad); - node.mutable_options() - ->MutableExtension(BorderDetectionCalculatorOptions::ext) - ->set_solid_background_tol_perc(.25); - - auto runner = ::absl::make_unique(node); - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - - cv::Mat sub_image = input_mat(cv::Rect( - kTestFrameWidth / 2, 0, kTestFrameWidth / 2, kTestFrameHeight / 2)); - sub_image.setTo(cv::Scalar(0, 255, 0)); - - sub_image = input_mat(cv::Rect(0, kTestFrameHeight / 2, kTestFrameWidth / 2, - kTestFrameHeight / 2)); - sub_image.setTo(cv::Scalar(0, 0, 255)); - - sub_image = - input_mat(cv::Rect(0, 0, kTestFrameWidth / 2 + 50, kTestFrameHeight / 2)); - sub_image.setTo(cv::Scalar(255, 0, 0)); - - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kDetectedBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - ASSERT_EQ(0, static_features.border().size()); - ASSERT_TRUE(static_features.has_solid_background()); - EXPECT_EQ(0, static_features.solid_background().r()); - EXPECT_EQ(0, static_features.solid_background().g()); - EXPECT_EQ(255, static_features.solid_background().b()); -} - -void BM_Large(benchmark::State& state) { - for (auto _ : state) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfig)); - - const int kTopBorderHeight = 50; - - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameLargeWidth, kTestFrameLargeHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - cv::Mat sub_image = - input_mat(cv::Rect(0, 0, kTestFrameLargeWidth, kTopBorderHeight)); - sub_image.setTo(cv::Scalar(255, 0, 0)); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - } -} -BENCHMARK(BM_Large); - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/content_zooming_calculator.cc b/examples/desktop/autoflip/calculators/content_zooming_calculator.cc deleted file mode 100644 index 8230807..0000000 --- a/examples/desktop/autoflip/calculators/content_zooming_calculator.cc +++ /dev/null @@ -1,890 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include -#include - -#include "absl/status/status.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/content_zooming_calculator.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/content_zooming_calculator_state.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/detection.pb.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/location_data.pb.h" -#include "mediapipe/framework/packet.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_builder.h" - -constexpr char kVideoFrame[] = "VIDEO"; -constexpr char kVideoSize[] = "VIDEO_SIZE"; -constexpr char kSalientRegions[] = "SALIENT_REGIONS"; -constexpr char kDetections[] = "DETECTIONS"; -constexpr char kDetectedBorders[] = "BORDERS"; -// Crop location as abs rect discretized. -constexpr char kCropRect[] = "CROP_RECT"; -// Crop location as normalized rect. -constexpr char kNormalizedCropRect[] = "NORMALIZED_CROP_RECT"; -// Crop location without position smoothing. -constexpr char kFirstCropRect[] = "FIRST_CROP_RECT"; -// Can be used to control whether an animated zoom should actually performed -// (configured through option us_to_first_rect). If provided, a non-zero integer -// will allow the animated zoom to be used when the first detections arrive. -// Applies to first detection only. -constexpr char kAnimateZoom[] = "ANIMATE_ZOOM"; -// Can be used to control the maximum zoom; note that it is re-evaluated only -// upon change of input resolution. A value of 100 disables zooming and is the -// smallest allowed value. A value of 200 allows zooming such that a pixel of -// the input may cover up to four times its original area. Note that -// max_zoom_value_deg from options is always respected; MAX_ZOOM_PCT can only be -// used to limit zooming further. -constexpr char kMaxZoomFactorPercent[] = "MAX_ZOOM_FACTOR_PCT"; -// Field-of-view (degrees) of the camera's x-axis (width). -// TODO: Parameterize FOV based on camera specs. -constexpr float kFieldOfView = 60; -// A pointer to a ContentZoomingCalculatorStateCacheType in a side packet. -// Used to save state on Close and load state on Open in a new graph. -// Can be used to preserve state between graphs. -constexpr char kStateCache[] = "STATE_CACHE"; -// Tolerance for zooming out recentering. -constexpr float kPixelTolerance = 3; -// Returns 'true' when camera is moving (pan/tilt/zoom) & 'false' for no motion. -constexpr char kCameraActive[] = "CAMERA_ACTIVE"; - -namespace mediapipe { -namespace autoflip { -using StateCacheType = ContentZoomingCalculatorStateCacheType; - -// Content zooming calculator zooms in on content when a detection has -// "only_required" set true or any raw detection input. It does this by -// computing the value of top/bottom borders to remove from the output and sends -// these to the SceneCroppingCalculator using BORDERS output or a full rect crop -// using CROP_RECT output. When more than one detections are received the -// zoom box is calculated as the union of the detections. Typical applications -// include mobile makeover and autofliplive face reframing. -class ContentZoomingCalculator : public CalculatorBase { - public: - ContentZoomingCalculator() : initialized_(false) {} - ~ContentZoomingCalculator() override {} - ContentZoomingCalculator(const ContentZoomingCalculator&) = delete; - ContentZoomingCalculator& operator=(const ContentZoomingCalculator&) = delete; - - static absl::Status GetContract(mediapipe::CalculatorContract* cc); - absl::Status Open(mediapipe::CalculatorContext* cc) override; - absl::Status Process(mediapipe::CalculatorContext* cc) override; - absl::Status Close(mediapipe::CalculatorContext* cc) override; - - private: - // Tries to load state from a state-cache, if provided. Fallsback to - // initializing state if no cache or no value in the cache are available. - absl::Status MaybeLoadState(mediapipe::CalculatorContext* cc, int frame_width, - int frame_height); - // Saves state to a state-cache, if provided. - absl::Status SaveState(mediapipe::CalculatorContext* cc) const; - // Returns the factor for maximum zoom based on options and the - // kMaxZoomFactorPercent input (if present). - double GetMaxZoomFactor(mediapipe::CalculatorContext* cc) const; - // Initializes the calculator for the given frame size, creating path solvers - // and resetting history like last measured values. - absl::Status InitializeState(mediapipe::CalculatorContext* cc, - int frame_width, int frame_height); - // Adjusts state to work with an updated frame size. - absl::Status UpdateForResolutionChange(mediapipe::CalculatorContext* cc, - int frame_width, int frame_height); - // Returns true if we are animating to the first rect. - bool IsAnimatingToFirstRect(const Timestamp& timestamp) const; - // Builds the output rectangle when animating to the first rect. - absl::StatusOr GetAnimationRect( - int frame_width, int frame_height, const Timestamp& timestamp) const; - // Converts bounds to tilt offset, pan offset and height. - absl::Status ConvertToPanTiltZoom(float xmin, float xmax, float ymin, - float ymax, int* tilt_offset, - int* pan_offset, int* height); - // Sets max_frame_value_ and target_aspect_ - absl::Status UpdateAspectAndMax(); - // Smooth camera path - absl::Status SmoothAndClampPath(int target_width, int target_height, - float path_width, float path_height, - float* path_offset_x, float* path_offset_y); - // Compute box containing all detections. - absl::Status GetDetectionsBox(mediapipe::CalculatorContext* cc, float* xmin, - float* xmax, float* ymin, float* ymax, - bool* only_required_found, - bool* has_detections); - - ContentZoomingCalculatorOptions options_; - // Detection frame width/height. - int frame_height_; - int frame_width_; - // Path solver used to smooth top/bottom border crop values. - std::unique_ptr path_solver_zoom_; - std::unique_ptr path_solver_pan_; - std::unique_ptr path_solver_tilt_; - // Are parameters initialized. - bool initialized_; - // Stores the time of the first crop rectangle. This is used to control - // animating to it. Until a first crop rectangle was computed, it has - // the value Timestamp::Unset(). If animating is not requested, it receives - // the value Timestamp::Done() instead of the time. - Timestamp first_rect_timestamp_; - // Stores the first crop rectangle. - mediapipe::NormalizedRect first_rect_; - // Stores the time of the last "only_required" input. - int64 last_only_required_detection_; - // Rect values of last message with detection(s). - int last_measured_height_; - int last_measured_x_offset_; - int last_measured_y_offset_; - // Target aspect ratio. - float target_aspect_; - // Max size of bounding box. If input/output aspect ratios are the same, - // will be 1.0. Else, will be less than 1.0 to prevent exceeding the size of - // the image in either dimension. - float max_frame_value_; -}; -REGISTER_CALCULATOR(ContentZoomingCalculator); - -absl::Status ContentZoomingCalculator::GetContract( - mediapipe::CalculatorContract* cc) { - RET_CHECK( - !(cc->Inputs().HasTag(kVideoFrame) && cc->Inputs().HasTag(kVideoSize))) - << "Provide only VIDEO or VIDEO_SIZE, not both."; - if (cc->Inputs().HasTag(kVideoFrame)) { - cc->Inputs().Tag(kVideoFrame).Set(); - } else if (cc->Inputs().HasTag(kVideoSize)) { - cc->Inputs().Tag(kVideoSize).Set>(); - } else { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "Input VIDEO or VIDEO_SIZE must be provided."; - } - if (cc->Inputs().HasTag(kMaxZoomFactorPercent)) { - cc->Inputs().Tag(kMaxZoomFactorPercent).Set(); - } - if (cc->Inputs().HasTag(kSalientRegions)) { - cc->Inputs().Tag(kSalientRegions).Set(); - } - if (cc->Inputs().HasTag(kDetections)) { - cc->Inputs().Tag(kDetections).Set>(); - } - if (cc->Inputs().HasTag(kAnimateZoom)) { - cc->Inputs().Tag(kAnimateZoom).Set(); - } - if (cc->Outputs().HasTag(kDetectedBorders)) { - cc->Outputs().Tag(kDetectedBorders).Set(); - } - if (cc->Outputs().HasTag(kCropRect)) { - cc->Outputs().Tag(kCropRect).Set(); - } - if (cc->Outputs().HasTag(kNormalizedCropRect)) { - cc->Outputs().Tag(kNormalizedCropRect).Set(); - } - if (cc->Outputs().HasTag(kFirstCropRect)) { - cc->Outputs().Tag(kFirstCropRect).Set(); - } - if (cc->InputSidePackets().HasTag(kStateCache)) { - cc->InputSidePackets().Tag(kStateCache).Set(); - } - if (cc->Outputs().HasTag(kCameraActive)) { - cc->Outputs().Tag(kCameraActive).Set(); - } - return absl::OkStatus(); -} - -absl::Status ContentZoomingCalculator::Open(mediapipe::CalculatorContext* cc) { - cc->SetOffset(mediapipe::TimestampDiff(0)); - options_ = cc->Options(); - if (options_.has_kinematic_options()) { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "Deprecated kinematic_options was set, please set " - "kinematic_options_zoom and kinematic_options_tilt."; - } - if (options_.has_min_motion_to_reframe()) { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "Deprecated min_motion_to_reframe was set, please set " - "in kinematic_options_zoom and kinematic_options_tilt " - "directly."; - } - return absl::OkStatus(); -} - -absl::Status ContentZoomingCalculator::Close(mediapipe::CalculatorContext* cc) { - if (initialized_) { - MP_RETURN_IF_ERROR(SaveState(cc)); - } - return absl::OkStatus(); -} - -absl::Status ContentZoomingCalculator::ConvertToPanTiltZoom( - float xmin, float xmax, float ymin, float ymax, int* tilt_offset, - int* pan_offset, int* height) { - // Find center of the y-axis offset (for tilt control). - float y_center = ymin + (ymax - ymin) / 2; - // Find center of the x-axis offset (for pan control). - float x_center = xmin + (xmax - xmin) / 2; - // Find size and apply scale factor to y-axis. - float fit_size_raw = - fmax((ymax - ymin) / options_.scale_factor(), xmax - xmin); - // Apply max frame for cases where the target size is different than input - // frame size. - float fit_size = fmin(max_frame_value_, fit_size_raw); - // Prevent box from extending beyond the image. - if (y_center - fit_size / 2 < 0) { - y_center = fit_size / 2; - } else if (y_center + fit_size / 2 > 1) { - y_center = 1 - fit_size / 2; - } - if (x_center - fit_size / 2 < 0) { - x_center = fit_size / 2; - } else if (x_center + fit_size / 2 > 1) { - x_center = 1 - fit_size / 2; - } - // Scale to pixel coordinates. - *tilt_offset = frame_height_ * y_center; - *pan_offset = frame_width_ * x_center; - *height = frame_height_ * fit_size_raw; - return absl::OkStatus(); -} - -namespace { -mediapipe::LocationData::RelativeBoundingBox ShiftDetection( - const mediapipe::LocationData::RelativeBoundingBox& relative_bounding_box, - const float y_offset_percent, const float x_offset_percent) { - auto shifted_bb = relative_bounding_box; - shifted_bb.set_ymin(relative_bounding_box.ymin() + - relative_bounding_box.height() * y_offset_percent); - shifted_bb.set_xmin(relative_bounding_box.xmin() + - relative_bounding_box.width() * x_offset_percent); - return shifted_bb; -} -mediapipe::autoflip::RectF ShiftDetection( - const mediapipe::autoflip::RectF& relative_bounding_box, - const float y_offset_percent, const float x_offset_percent) { - auto shifted_bb = relative_bounding_box; - shifted_bb.set_y(relative_bounding_box.y() + - relative_bounding_box.height() * y_offset_percent); - shifted_bb.set_x(relative_bounding_box.x() + - relative_bounding_box.width() * x_offset_percent); - return shifted_bb; -} -absl::Status UpdateRanges(const SalientRegion& region, - const float shift_vertical, - const float shift_horizontal, - const float pad_vertical, const float pad_horizontal, - float* xmin, float* xmax, float* ymin, float* ymax) { - if (!region.has_location_normalized()) { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "SalientRegion did not have location normalized set."; - } - auto location = ShiftDetection(region.location_normalized(), shift_vertical, - shift_horizontal); - - const float x_padding = pad_horizontal * location.width(); - const float y_padding = pad_vertical * location.height(); - - *xmin = fmin(*xmin, location.x() - x_padding); - *xmax = fmax(*xmax, location.x() + location.width() + x_padding); - *ymin = fmin(*ymin, location.y() - y_padding); - *ymax = fmax(*ymax, location.y() + location.height() + y_padding); - - return absl::OkStatus(); -} -absl::Status UpdateRanges(const mediapipe::Detection& detection, - const float shift_vertical, - const float shift_horizontal, - const float pad_vertical, const float pad_horizontal, - float* xmin, float* xmax, float* ymin, float* ymax) { - RET_CHECK(detection.location_data().format() == - mediapipe::LocationData::RELATIVE_BOUNDING_BOX) - << "Face detection input is lacking required relative_bounding_box()"; - const auto& location = - ShiftDetection(detection.location_data().relative_bounding_box(), - shift_vertical, shift_horizontal); - - const float x_padding = pad_horizontal * location.width(); - const float y_padding = pad_vertical * location.height(); - - *xmin = fmin(*xmin, location.xmin() - x_padding); - *xmax = fmax(*xmax, location.xmin() + location.width() + x_padding); - *ymin = fmin(*ymin, location.ymin() - y_padding); - *ymax = fmax(*ymax, location.ymin() + location.height() + y_padding); - - return absl::OkStatus(); -} -void MakeStaticFeatures(const int top_border, const int bottom_border, - const int frame_width, const int frame_height, - StaticFeatures* static_feature) { - auto border_top = static_feature->add_border(); - border_top->set_relative_position(Border::TOP); - border_top->mutable_border_position()->set_x(0); - border_top->mutable_border_position()->set_y(0); - border_top->mutable_border_position()->set_width(frame_width); - border_top->mutable_border_position()->set_height(top_border); - - auto border_bottom = static_feature->add_border(); - border_bottom->set_relative_position(Border::BOTTOM); - border_bottom->mutable_border_position()->set_x(0); - border_bottom->mutable_border_position()->set_y(frame_height - bottom_border); - border_bottom->mutable_border_position()->set_width(frame_width); - border_bottom->mutable_border_position()->set_height(bottom_border); -} -absl::Status GetVideoResolution(mediapipe::CalculatorContext* cc, - int* frame_width, int* frame_height) { - if (cc->Inputs().HasTag(kVideoFrame)) { - *frame_width = cc->Inputs().Tag(kVideoFrame).Get().Width(); - *frame_height = cc->Inputs().Tag(kVideoFrame).Get().Height(); - } else if (cc->Inputs().HasTag(kVideoSize)) { - *frame_width = - cc->Inputs().Tag(kVideoSize).Get>().first; - *frame_height = - cc->Inputs().Tag(kVideoSize).Get>().second; - } else { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "Input VIDEO or VIDEO_SIZE must be provided."; - } - return absl::OkStatus(); -} -} // namespace - -absl::Status ContentZoomingCalculator::UpdateAspectAndMax() { - max_frame_value_ = 1.0; - target_aspect_ = frame_width_ / static_cast(frame_height_); - // If target size is set and wider than input aspect, make sure to always - // crop the min required amount. - if (options_.has_target_size()) { - RET_CHECK_GT(options_.target_size().width(), 0) - << "Provided target width not valid."; - RET_CHECK_GT(options_.target_size().height(), 0) - << "Provided target height not valid."; - float input_aspect = frame_width_ / static_cast(frame_height_); - target_aspect_ = options_.target_size().width() / - static_cast(options_.target_size().height()); - max_frame_value_ = - std::min(input_aspect / target_aspect_, target_aspect_ / input_aspect); - } - return absl::OkStatus(); -} - -absl::Status ContentZoomingCalculator::MaybeLoadState( - mediapipe::CalculatorContext* cc, int frame_width, int frame_height) { - const auto* state_cache = - cc->InputSidePackets().HasTag(kStateCache) - ? cc->InputSidePackets().Tag(kStateCache).Get() - : nullptr; - if (!state_cache || !state_cache->has_value()) { - return InitializeState(cc, frame_width, frame_height); - } - - const ContentZoomingCalculatorState& state = state_cache->value(); - frame_width_ = state.frame_width; - frame_height_ = state.frame_height; - path_solver_pan_ = - std::make_unique(state.path_solver_pan); - path_solver_tilt_ = - std::make_unique(state.path_solver_tilt); - path_solver_zoom_ = - std::make_unique(state.path_solver_zoom); - first_rect_timestamp_ = state.first_rect_timestamp; - first_rect_ = state.first_rect; - last_only_required_detection_ = state.last_only_required_detection; - last_measured_height_ = state.last_measured_height; - last_measured_x_offset_ = state.last_measured_x_offset; - last_measured_y_offset_ = state.last_measured_y_offset; - MP_RETURN_IF_ERROR(UpdateAspectAndMax()); - - return UpdateForResolutionChange(cc, frame_width, frame_height); -} - -absl::Status ContentZoomingCalculator::SaveState( - mediapipe::CalculatorContext* cc) const { - auto* state_cache = - cc->InputSidePackets().HasTag(kStateCache) - ? cc->InputSidePackets().Tag(kStateCache).Get() - : nullptr; - if (!state_cache) { - return absl::OkStatus(); - } - - *state_cache = ContentZoomingCalculatorState{ - .frame_height = frame_height_, - .frame_width = frame_width_, - .path_solver_zoom = *path_solver_zoom_, - .path_solver_pan = *path_solver_pan_, - .path_solver_tilt = *path_solver_tilt_, - .first_rect_timestamp = first_rect_timestamp_, - .first_rect = first_rect_, - .last_only_required_detection = last_only_required_detection_, - .last_measured_height = last_measured_height_, - .last_measured_x_offset = last_measured_x_offset_, - .last_measured_y_offset = last_measured_y_offset_, - }; - return absl::OkStatus(); -} - -double ContentZoomingCalculator::GetMaxZoomFactor( - mediapipe::CalculatorContext* cc) const { - double max_zoom_value = - options_.max_zoom_value_deg() / static_cast(kFieldOfView); - if (cc->Inputs().HasTag(kMaxZoomFactorPercent)) { - const double factor = std::max( - 1.0, cc->Inputs().Tag(kMaxZoomFactorPercent).Get() / 100.0); - max_zoom_value = std::max(max_zoom_value, 1.0 / factor); - } - return max_zoom_value; -} - -absl::Status ContentZoomingCalculator::InitializeState( - mediapipe::CalculatorContext* cc, int frame_width, int frame_height) { - frame_width_ = frame_width; - frame_height_ = frame_height; - path_solver_pan_ = std::make_unique( - options_.kinematic_options_pan(), 0, frame_width_, - static_cast(frame_width_) / kFieldOfView); - path_solver_tilt_ = std::make_unique( - options_.kinematic_options_tilt(), 0, frame_height_, - static_cast(frame_height_) / kFieldOfView); - MP_RETURN_IF_ERROR(UpdateAspectAndMax()); - int min_zoom_size = frame_height_ * GetMaxZoomFactor(cc); - path_solver_zoom_ = std::make_unique( - options_.kinematic_options_zoom(), min_zoom_size, - max_frame_value_ * frame_height_, - static_cast(frame_height_) / kFieldOfView); - first_rect_timestamp_ = Timestamp::Unset(); - last_only_required_detection_ = 0; - last_measured_height_ = max_frame_value_ * frame_height_; - last_measured_x_offset_ = target_aspect_ * frame_width_; - last_measured_y_offset_ = frame_width_ / 2; - return absl::OkStatus(); -} - -absl::Status ContentZoomingCalculator::UpdateForResolutionChange( - mediapipe::CalculatorContext* cc, int frame_width, int frame_height) { - // Update state for change in input resolution. - if (frame_width_ != frame_width || frame_height_ != frame_height) { - double width_scale = frame_width / static_cast(frame_width_); - double height_scale = frame_height / static_cast(frame_height_); - last_measured_height_ = last_measured_height_ * height_scale; - last_measured_y_offset_ = last_measured_y_offset_ * height_scale; - last_measured_x_offset_ = last_measured_x_offset_ * width_scale; - frame_width_ = frame_width; - frame_height_ = frame_height; - MP_RETURN_IF_ERROR(UpdateAspectAndMax()); - MP_RETURN_IF_ERROR(path_solver_pan_->UpdateMinMaxLocation(0, frame_width_)); - MP_RETURN_IF_ERROR( - path_solver_tilt_->UpdateMinMaxLocation(0, frame_height_)); - int min_zoom_size = frame_height_ * GetMaxZoomFactor(cc); - MP_RETURN_IF_ERROR(path_solver_zoom_->UpdateMinMaxLocation( - min_zoom_size, max_frame_value_ * frame_height_)); - MP_RETURN_IF_ERROR(path_solver_zoom_->UpdatePixelsPerDegree( - static_cast(frame_height_) / kFieldOfView)); - } - return absl::OkStatus(); -} - -bool ContentZoomingCalculator::IsAnimatingToFirstRect( - const Timestamp& timestamp) const { - if (options_.us_to_first_rect() == 0 || - first_rect_timestamp_ == Timestamp::Unset() || - first_rect_timestamp_ == Timestamp::Done()) { - return false; - } - - const int64 delta_us = (timestamp - first_rect_timestamp_).Value(); - return (0 <= delta_us && delta_us <= options_.us_to_first_rect()); -} - -namespace { -double easeInQuad(double t) { return t * t; } -double easeOutQuad(double t) { return -1 * t * (t - 2); } -double easeInOutQuad(double t) { - if (t < 0.5) { - return easeInQuad(t * 2) * 0.5; - } else { - return easeOutQuad(t * 2 - 1) * 0.5 + 0.5; - } -} -double lerp(double a, double b, double i) { return a * (1 - i) + b * i; } -} // namespace - -absl::StatusOr ContentZoomingCalculator::GetAnimationRect( - int frame_width, int frame_height, const Timestamp& timestamp) const { - RET_CHECK(IsAnimatingToFirstRect(timestamp)) - << "Must only be called if animating to first rect."; - - const int64 delta_us = (timestamp - first_rect_timestamp_).Value(); - const int64 delay = options_.us_to_first_rect_delay(); - const double interpolation = easeInOutQuad(std::max( - 0.0, (delta_us - delay) / - static_cast(options_.us_to_first_rect() - delay))); - - const double x_center = lerp(0.5, first_rect_.x_center(), interpolation); - const double y_center = lerp(0.5, first_rect_.y_center(), interpolation); - const double width = lerp(1.0, first_rect_.width(), interpolation); - const double height = lerp(1.0, first_rect_.height(), interpolation); - - mediapipe::Rect gpu_rect; - gpu_rect.set_x_center(x_center * frame_width); - gpu_rect.set_width(width * frame_width); - gpu_rect.set_y_center(y_center * frame_height); - gpu_rect.set_height(height * frame_height); - return gpu_rect; -} - -absl::Status ContentZoomingCalculator::Process( - mediapipe::CalculatorContext* cc) { - // For async subgraph support, return on empty video size packets. - if (cc->Inputs().HasTag(kVideoSize) && - cc->Inputs().Tag(kVideoSize).IsEmpty()) { - return absl::OkStatus(); - } - int frame_width, frame_height; - MP_RETURN_IF_ERROR(GetVideoResolution(cc, &frame_width, &frame_height)); - - // Init on first call or re-init always if configured to be stateless. - if (!initialized_) { - MP_RETURN_IF_ERROR(MaybeLoadState(cc, frame_width, frame_height)); - initialized_ = !options_.is_stateless(); - } else { - MP_RETURN_IF_ERROR( - UpdateForResolutionChange(cc, frame_width, frame_height)); - } - - // Compute the box that contains all "is_required" detections. - float xmin = 1, ymin = 1, xmax = 0, ymax = 0; - bool only_required_found = false; - bool has_detections = true; - MP_RETURN_IF_ERROR(GetDetectionsBox(cc, &xmin, &xmax, &ymin, &ymax, - &only_required_found, &has_detections)); - if (!has_detections) return absl::OkStatus(); - - const bool may_start_animation = (options_.us_to_first_rect() != 0) && - (!cc->Inputs().HasTag(kAnimateZoom) || - cc->Inputs().Tag(kAnimateZoom).Get()); - bool is_animating = IsAnimatingToFirstRect(cc->InputTimestamp()); - - int offset_y, height, offset_x; - if (!is_animating && options_.start_zoomed_out() && !may_start_animation && - first_rect_timestamp_ == Timestamp::Unset()) { - // If we should start zoomed out and won't be doing an animation, - // initialize the path solvers using the full frame, ignoring detections. - height = max_frame_value_ * frame_height_; - offset_x = (target_aspect_ * height) / 2; - offset_y = frame_height_ / 2; - } else if (!is_animating && only_required_found) { - // Convert bounds to tilt/zoom and in pixel coordinates. - MP_RETURN_IF_ERROR(ConvertToPanTiltZoom(xmin, xmax, ymin, ymax, &offset_y, - &offset_x, &height)); - // A only required detection was found. - last_only_required_detection_ = cc->InputTimestamp().Microseconds(); - last_measured_height_ = height; - last_measured_x_offset_ = offset_x; - last_measured_y_offset_ = offset_y; - } else if (!is_animating && cc->InputTimestamp().Microseconds() - - last_only_required_detection_ >= - options_.us_before_zoomout()) { - // No only_require detections found within salient regions packets - // arriving since us_before_zoomout duration. - height = max_frame_value_ * frame_height_ + - (options_.kinematic_options_zoom().min_motion_to_reframe() * - (static_cast(frame_height_) / kFieldOfView)); - offset_x = (target_aspect_ * height) / 2; - offset_y = frame_height_ / 2; - } else { - // Either animating to the first rectangle, or - // no only detection found but using last detection due to - // duration_before_zoomout_us setting. - height = last_measured_height_; - offset_x = last_measured_x_offset_; - offset_y = last_measured_y_offset_; - } - - // Check if the camera is changing in pan, tilt or zoom. If the camera is in - // motion disable temporal filtering. - bool pan_state, tilt_state, zoom_state; - MP_RETURN_IF_ERROR(path_solver_pan_->PredictMotionState( - offset_x, cc->InputTimestamp().Microseconds(), &pan_state)); - MP_RETURN_IF_ERROR(path_solver_tilt_->PredictMotionState( - offset_y, cc->InputTimestamp().Microseconds(), &tilt_state)); - MP_RETURN_IF_ERROR(path_solver_zoom_->PredictMotionState( - height, cc->InputTimestamp().Microseconds(), &zoom_state)); - if (pan_state || tilt_state || zoom_state) { - path_solver_pan_->ClearHistory(); - path_solver_tilt_->ClearHistory(); - path_solver_zoom_->ClearHistory(); - } - const bool camera_active = - is_animating || ((pan_state || tilt_state || zoom_state) && - !options_.disable_animations()); - // Waiting for first rect before setting any value of the camera active flag - // so we avoid setting it to false during initialization. - if (cc->Outputs().HasTag(kCameraActive) && - first_rect_timestamp_ != Timestamp::Unset()) { - cc->Outputs() - .Tag(kCameraActive) - .AddPacket(MakePacket(camera_active).At(cc->InputTimestamp())); - } - - // Skip the path solvers to the final destination if not animating. - const bool disable_animations = - options_.disable_animations() && path_solver_zoom_->IsInitialized(); - if (disable_animations) { - MP_RETURN_IF_ERROR(path_solver_zoom_->SetState(height)); - MP_RETURN_IF_ERROR(path_solver_tilt_->SetState(offset_y)); - MP_RETURN_IF_ERROR(path_solver_pan_->SetState(offset_x)); - } - - // Compute smoothed zoom camera path. - MP_RETURN_IF_ERROR(path_solver_zoom_->AddObservation( - height, cc->InputTimestamp().Microseconds())); - float path_height; - MP_RETURN_IF_ERROR(path_solver_zoom_->GetState(&path_height)); - const float path_width = path_height * target_aspect_; - - // Update pixel-per-degree value for pan/tilt. - int target_height; - MP_RETURN_IF_ERROR(path_solver_zoom_->GetTargetPosition(&target_height)); - const int target_width = target_height * target_aspect_; - MP_RETURN_IF_ERROR(path_solver_pan_->UpdatePixelsPerDegree( - static_cast(target_width) / kFieldOfView)); - MP_RETURN_IF_ERROR(path_solver_tilt_->UpdatePixelsPerDegree( - static_cast(target_height) / kFieldOfView)); - - // Compute smoothed pan/tilt paths. - MP_RETURN_IF_ERROR(path_solver_pan_->AddObservation( - offset_x, cc->InputTimestamp().Microseconds())); - MP_RETURN_IF_ERROR(path_solver_tilt_->AddObservation( - offset_y, cc->InputTimestamp().Microseconds())); - float path_offset_x; - MP_RETURN_IF_ERROR(path_solver_pan_->GetState(&path_offset_x)); - float path_offset_y; - MP_RETURN_IF_ERROR(path_solver_tilt_->GetState(&path_offset_y)); - - // Update path. - MP_RETURN_IF_ERROR(SmoothAndClampPath(target_width, target_height, path_width, - path_height, &path_offset_x, - &path_offset_y)); - - // Transmit result downstream to scenecroppingcalculator. - if (cc->Outputs().HasTag(kDetectedBorders)) { - // Convert to top/bottom borders to remove. - const int path_top = path_offset_y - path_height / 2; - const int path_bottom = frame_height_ - (path_offset_y + path_height / 2); - std::unique_ptr features = - absl::make_unique(); - MakeStaticFeatures(path_top, path_bottom, frame_width_, frame_height_, - features.get()); - cc->Outputs() - .Tag(kDetectedBorders) - .AddPacket(Adopt(features.release()).At(cc->InputTimestamp())); - } - - // Record the first crop rectangle - if (first_rect_timestamp_ == Timestamp::Unset()) { - first_rect_.set_x_center(path_offset_x / static_cast(frame_width_)); - first_rect_.set_width(path_height * target_aspect_ / - static_cast(frame_width_)); - first_rect_.set_y_center(path_offset_y / static_cast(frame_height_)); - first_rect_.set_height(path_height / static_cast(frame_height_)); - - // Record the time to serve as departure point for the animation. - // If we are not allowed to start the animation, set Timestamp::Done. - first_rect_timestamp_ = - may_start_animation ? cc->InputTimestamp() : Timestamp::Done(); - // After setting the first rectangle, check whether we should animate to it. - is_animating = IsAnimatingToFirstRect(cc->InputTimestamp()); - } - - // Transmit downstream to glcroppingcalculator in discrete int values. - if (cc->Outputs().HasTag(kCropRect)) { - std::unique_ptr gpu_rect; - if (is_animating) { - auto rect = - GetAnimationRect(frame_width, frame_height, cc->InputTimestamp()); - MP_RETURN_IF_ERROR(rect.status()); - gpu_rect = absl::make_unique(*rect); - } else { - gpu_rect = absl::make_unique(); - gpu_rect->set_x_center(path_offset_x); - gpu_rect->set_width(path_width); - gpu_rect->set_y_center(path_offset_y); - gpu_rect->set_height(path_height); - } - cc->Outputs().Tag(kCropRect).Add(gpu_rect.release(), - Timestamp(cc->InputTimestamp())); - } - if (cc->Outputs().HasTag(kNormalizedCropRect)) { - std::unique_ptr gpu_rect = - absl::make_unique(); - const float float_frame_width = static_cast(frame_width_); - const float float_frame_height = static_cast(frame_height_); - if (is_animating) { - auto rect = - GetAnimationRect(frame_width, frame_height, cc->InputTimestamp()); - MP_RETURN_IF_ERROR(rect.status()); - gpu_rect->set_x_center(rect->x_center() / float_frame_width); - gpu_rect->set_width(rect->width() / float_frame_width); - gpu_rect->set_y_center(rect->y_center() / float_frame_height); - gpu_rect->set_height(rect->height() / float_frame_height); - } else { - gpu_rect->set_x_center(path_offset_x / float_frame_width); - gpu_rect->set_width(path_width / float_frame_width); - gpu_rect->set_y_center(path_offset_y / float_frame_height); - gpu_rect->set_height(path_height / float_frame_height); - } - cc->Outputs() - .Tag(kNormalizedCropRect) - .Add(gpu_rect.release(), Timestamp(cc->InputTimestamp())); - } - - if (cc->Outputs().HasTag(kFirstCropRect)) { - cc->Outputs() - .Tag(kFirstCropRect) - .Add(new mediapipe::NormalizedRect(first_rect_), - Timestamp(cc->InputTimestamp())); - } - - return absl::OkStatus(); -} - -absl::Status ContentZoomingCalculator::SmoothAndClampPath( - int target_width, int target_height, float path_width, float path_height, - float* path_offset_x, float* path_offset_y) { - float delta_height; - MP_RETURN_IF_ERROR(path_solver_zoom_->GetDeltaState(&delta_height)); - const int delta_width = delta_height * target_aspect_; - - // Smooth centering when zooming out. - const float remaining_width = target_width - path_width; - const int width_space = frame_width_ - target_width; - if (abs(*path_offset_x - frame_width_ / 2) > - width_space / 2 + kPixelTolerance && - remaining_width > kPixelTolerance) { - const float required_width = - abs(*path_offset_x - frame_width_ / 2) - width_space / 2; - if (*path_offset_x < frame_width_ / 2) { - *path_offset_x += delta_width * (required_width / remaining_width); - MP_RETURN_IF_ERROR(path_solver_pan_->SetState(*path_offset_x)); - } else { - *path_offset_x -= delta_width * (required_width / remaining_width); - MP_RETURN_IF_ERROR(path_solver_pan_->SetState(*path_offset_x)); - } - } - - const float remaining_height = target_height - path_height; - const int height_space = frame_height_ - target_height; - if (abs(*path_offset_y - frame_height_ / 2) > - height_space / 2 + kPixelTolerance && - remaining_height > kPixelTolerance) { - const float required_height = - abs(*path_offset_y - frame_height_ / 2) - height_space / 2; - if (*path_offset_y < frame_height_ / 2) { - *path_offset_y += delta_height * (required_height / remaining_height); - MP_RETURN_IF_ERROR(path_solver_tilt_->SetState(*path_offset_y)); - } else { - *path_offset_y -= delta_height * (required_height / remaining_height); - MP_RETURN_IF_ERROR(path_solver_tilt_->SetState(*path_offset_y)); - } - } - - // Prevent box from extending beyond the image after camera smoothing. - if (*path_offset_y - ceil(path_height / 2.0) < 0) { - *path_offset_y = ceil(path_height / 2.0); - MP_RETURN_IF_ERROR(path_solver_tilt_->SetState(*path_offset_y)); - } else if (*path_offset_y + ceil(path_height / 2.0) > frame_height_) { - *path_offset_y = frame_height_ - ceil(path_height / 2.0); - MP_RETURN_IF_ERROR(path_solver_tilt_->SetState(*path_offset_y)); - } - - if (*path_offset_x - ceil(path_width / 2.0) < 0) { - *path_offset_x = ceil(path_width / 2.0); - MP_RETURN_IF_ERROR(path_solver_pan_->SetState(*path_offset_x)); - } else if (*path_offset_x + ceil(path_width / 2.0) > frame_width_) { - *path_offset_x = frame_width_ - ceil(path_width / 2.0); - MP_RETURN_IF_ERROR(path_solver_pan_->SetState(*path_offset_x)); - } - - return absl::OkStatus(); -} - -absl::Status ContentZoomingCalculator::GetDetectionsBox( - mediapipe::CalculatorContext* cc, float* xmin, float* xmax, float* ymin, - float* ymax, bool* only_required_found, bool* has_detections) { - if (cc->Inputs().HasTag(kSalientRegions)) { - auto detection_set = cc->Inputs().Tag(kSalientRegions).Get(); - for (const auto& region : detection_set.detections()) { - if (!region.only_required()) { - continue; - } - *only_required_found = true; - MP_RETURN_IF_ERROR(UpdateRanges( - region, options_.detection_shift_vertical(), - options_.detection_shift_horizontal(), - options_.extra_vertical_padding(), - options_.extra_horizontal_padding(), xmin, xmax, ymin, ymax)); - } - } - - if (cc->Inputs().HasTag(kDetections)) { - if (cc->Inputs().Tag(kDetections).IsEmpty()) { - if (last_only_required_detection_ == 0) { - // If no detections are available and we never had any, - // simply return the full-image rectangle as crop-rect. - if (cc->Outputs().HasTag(kCropRect)) { - auto default_rect = absl::make_unique(); - default_rect->set_x_center(frame_width_ / 2); - default_rect->set_y_center(frame_height_ / 2); - default_rect->set_width(frame_width_); - default_rect->set_height(frame_height_); - cc->Outputs().Tag(kCropRect).Add(default_rect.release(), - Timestamp(cc->InputTimestamp())); - } - if (cc->Outputs().HasTag(kNormalizedCropRect)) { - auto default_rect = absl::make_unique(); - default_rect->set_x_center(0.5); - default_rect->set_y_center(0.5); - default_rect->set_width(1.0); - default_rect->set_height(1.0); - cc->Outputs() - .Tag(kNormalizedCropRect) - .Add(default_rect.release(), Timestamp(cc->InputTimestamp())); - } - // Also provide a first crop rect: in this case a zero-sized one. - if (cc->Outputs().HasTag(kFirstCropRect)) { - cc->Outputs() - .Tag(kFirstCropRect) - .Add(new mediapipe::NormalizedRect(), - Timestamp(cc->InputTimestamp())); - } - *has_detections = false; - return absl::OkStatus(); - } - } else { - auto raw_detections = cc->Inputs() - .Tag(kDetections) - .Get>(); - for (const auto& detection : raw_detections) { - *only_required_found = true; - MP_RETURN_IF_ERROR(UpdateRanges( - detection, options_.detection_shift_vertical(), - options_.detection_shift_horizontal(), - options_.extra_vertical_padding(), - options_.extra_horizontal_padding(), xmin, xmax, ymin, ymax)); - } - } - } - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/content_zooming_calculator.proto b/examples/desktop/autoflip/calculators/content_zooming_calculator.proto deleted file mode 100644 index 1d08fe8..0000000 --- a/examples/desktop/autoflip/calculators/content_zooming_calculator.proto +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.proto"; -import "mediapipe/framework/calculator.proto"; - -// NextTag: 21 -message ContentZoomingCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional ContentZoomingCalculatorOptions ext = 313091992; - } - // Amount the only required area should fill the image. When set to 1, - // reframing is done to remove content to the very edge of the salient region - // bounding box. When a smaller value is used, zooming will be done to fill - // this ratio of the frame. - optional float scale_factor = 1 [default = .9]; - // Kinematic options for zooming. - optional KinematicOptions kinematic_options_zoom = 6; - // Kinematic options for tilt (y-axis reframing.) - optional KinematicOptions kinematic_options_tilt = 7; - // Kinematic options for pan (x-axis reframing.) - optional KinematicOptions kinematic_options_pan = 10; - // Duration (in MicroSeconds) before returning to fully zoomed out position - // when no "only_required" frames are received. - optional int64 us_before_zoomout = 9 [default = 1000000]; - // Value of target output size, required to be set if different than input. - // Should match target_width and target_height in croppingcalculator. - message Size { - optional int64 width = 1; - optional int64 height = 2; - } - optional Size target_size = 8; - - // Amount to shift an input detection, as a ratio of its size (positive: - // down/right, negative: up/left). Use a negative value to increase padding - // above/left of an object, positive to increase padding below/right of an - // object. (Applies to one side only) - optional float detection_shift_vertical = 11 [default = 0.0]; - optional float detection_shift_horizontal = 12 [default = 0.0]; - // Amount to pad around an input detection, as a ratio of its size. - // (Applies to both sides) - optional float extra_vertical_padding = 19 [default = 0.0]; - optional float extra_horizontal_padding = 20 [default = 0.0]; - - // Defines the smallest value in degrees the camera is permitted to zoom. - optional float max_zoom_value_deg = 13 [default = 35]; - - // Whether to keep state between frames or to compute the final crop rect. - optional bool is_stateless = 14 [default = false]; - - // If true, on the first packet start with the camera zoomed out and then zoom - // in on the subject. If false, the camera will start zoomed in on the - // subject. - optional bool start_zoomed_out = 17 [default = false]; - - // Duration (in MicroSeconds) for animating to the first crop rect. - // Note that if set, takes precedence over start_zoomed_out. - optional int64 us_to_first_rect = 15 [default = 0]; - // Duration (in MicroSeconds) to delay animating to the first crop rect. - // Used only if us_to_first_rect is set and is interpreted as part of the - // us_to_first_rect time budget. - optional int64 us_to_first_rect_delay = 16 [default = 0]; - - // When true, this flag disables animating camera motions, - // and cuts directly to final target position. - // Does not apply to the first instance (first detection will still animate). - // Use "ANIMATE_ZOOM" input stream to control the first animation. - optional bool disable_animations = 18; - - // Deprecated parameters - optional KinematicOptions kinematic_options = 2 [deprecated = true]; - optional int64 min_motion_to_reframe = 4 [deprecated = true]; - optional float min_vertical_zoom = 5 [default = 1, deprecated = true]; - optional int64 frames_before_zoomout = 3 [default = 30, deprecated = true]; -} diff --git a/examples/desktop/autoflip/calculators/content_zooming_calculator_state.h b/examples/desktop/autoflip/calculators/content_zooming_calculator_state.h deleted file mode 100644 index c01a6fb..0000000 --- a/examples/desktop/autoflip/calculators/content_zooming_calculator_state.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_CALCULATORS_CONTENT_ZOOMING_CALCULATOR_STATE_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_CALCULATORS_CONTENT_ZOOMING_CALCULATOR_STATE_H_ - -#include - -#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h" -#include "mediapipe/framework/formats/rect.pb.h" -#include "mediapipe/framework/timestamp.h" - -namespace mediapipe { -namespace autoflip { - -struct ContentZoomingCalculatorState { - int frame_height = -1; - int frame_width = -1; - // Path solver used to smooth top/bottom border crop values. - KinematicPathSolver path_solver_zoom; - KinematicPathSolver path_solver_pan; - KinematicPathSolver path_solver_tilt; - // Stores the time of the first crop rectangle. - Timestamp first_rect_timestamp; - // Stores the first crop rectangle. - mediapipe::NormalizedRect first_rect; - // Stores the time of the last "only_required" input. - int64 last_only_required_detection = 0; - // Rect values of last message with detection(s). - int last_measured_height = 0; - int last_measured_x_offset = 0; - int last_measured_y_offset = 0; -}; - -using ContentZoomingCalculatorStateCacheType = - std::optional; - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_CALCULATORS_CONTENT_ZOOMING_CALCULATOR_STATE_H_ diff --git a/examples/desktop/autoflip/calculators/content_zooming_calculator_test.cc b/examples/desktop/autoflip/calculators/content_zooming_calculator_test.cc deleted file mode 100644 index 48e4a28..0000000 --- a/examples/desktop/autoflip/calculators/content_zooming_calculator_test.cc +++ /dev/null @@ -1,1022 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/content_zooming_calculator.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/content_zooming_calculator_state.h" -#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/formats/detection.pb.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/formats/location_data.pb.h" -#include "mediapipe/framework/formats/rect.pb.h" -#include "mediapipe/framework/port/benchmark.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kFirstCropRectTag[] = "FIRST_CROP_RECT"; -constexpr char kStateCacheTag[] = "STATE_CACHE"; -constexpr char kCropRectTag[] = "CROP_RECT"; -constexpr char kBordersTag[] = "BORDERS"; -constexpr char kSalientRegionsTag[] = "SALIENT_REGIONS"; -constexpr char kVideoTag[] = "VIDEO"; -constexpr char kMaxZoomFactorPctTag[] = "MAX_ZOOM_FACTOR_PCT"; -constexpr char kAnimateZoomTag[] = "ANIMATE_ZOOM"; -constexpr char kVideoSizeTag[] = "VIDEO_SIZE"; -constexpr char kDetectionsTag[] = "DETECTIONS"; - -const char kConfigA[] = R"( - calculator: "ContentZoomingCalculator" - input_stream: "VIDEO:camera_frames" - input_stream: "SALIENT_REGIONS:detection_set" - output_stream: "BORDERS:borders" - options: { - [mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: { - max_zoom_value_deg: 0 - kinematic_options_zoom { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_tilt { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_pan { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - } - } - )"; - -const char kConfigB[] = R"( - calculator: "ContentZoomingCalculator" - input_stream: "VIDEO:camera_frames" - input_stream: "SALIENT_REGIONS:detection_set" - output_stream: "BORDERS:borders" - options: { - [mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: { - target_size { - width: 1000 - height: 500 - } - max_zoom_value_deg: 0 - kinematic_options_zoom { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_tilt { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_pan { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - } - } - )"; - -const char kConfigC[] = R"( - calculator: "ContentZoomingCalculator" - input_stream: "VIDEO_SIZE:size" - input_stream: "SALIENT_REGIONS:detection_set" - output_stream: "BORDERS:borders" - options: { - [mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: { - max_zoom_value_deg: 0 - kinematic_options_zoom { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_tilt { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_pan { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - } - } - )"; - -const char kConfigD[] = R"( - calculator: "ContentZoomingCalculator" - input_stream: "VIDEO_SIZE:size" - input_stream: "DETECTIONS:detections" - output_stream: "CROP_RECT:rect" - output_stream: "FIRST_CROP_RECT:first_rect" - output_stream: "NORMALIZED_CROP_RECT:float_rect" - options: { - [mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: { - max_zoom_value_deg: 0 - kinematic_options_zoom { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_tilt { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_pan { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - } - } - )"; - -const char kConfigE[] = R"( - calculator: "ContentZoomingCalculator" - input_stream: "VIDEO_SIZE:size" - input_stream: "DETECTIONS:detections" - input_stream: "ANIMATE_ZOOM:animate_zoom" - output_stream: "CROP_RECT:rect" - output_stream: "FIRST_CROP_RECT:first_rect" - options: { - [mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: { - max_zoom_value_deg: 0 - kinematic_options_zoom { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_tilt { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_pan { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - } - } - )"; - -const char kConfigF[] = R"( - calculator: "ContentZoomingCalculator" - input_stream: "VIDEO_SIZE:size" - input_stream: "DETECTIONS:detections" - input_stream: "MAX_ZOOM_FACTOR_PCT:max_zoom_factor_pct" - output_stream: "CROP_RECT:rect" - output_stream: "FIRST_CROP_RECT:first_rect" - options: { - [mediapipe.autoflip.ContentZoomingCalculatorOptions.ext]: { - max_zoom_value_deg: 0 - kinematic_options_zoom { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_tilt { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - kinematic_options_pan { - min_motion_to_reframe: 1.2 - max_velocity: 18 - } - } - } - )"; - -void CheckBorder(const StaticFeatures& static_features, int width, int height, - int top_border, int bottom_border) { - ASSERT_EQ(2, static_features.border().size()); - auto part = static_features.border(0); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), 0); - EXPECT_EQ(part.border_position().width(), width); - EXPECT_EQ(part.border_position().height(), top_border); - EXPECT_EQ(Border::TOP, part.relative_position()); - - part = static_features.border(1); - EXPECT_EQ(part.border_position().x(), 0); - EXPECT_EQ(part.border_position().y(), height - bottom_border); - EXPECT_EQ(part.border_position().width(), width); - EXPECT_EQ(part.border_position().height(), bottom_border); - EXPECT_EQ(Border::BOTTOM, part.relative_position()); -} - -struct AddDetectionFlags { - std::optional animated_zoom; - std::optional max_zoom_factor_percent; -}; - -void AddDetectionFrameSize(const cv::Rect_& position, const int64 time, - const int width, const int height, - CalculatorRunner* runner, - const AddDetectionFlags& flags = {}) { - auto detections = std::make_unique>(); - if (position.width > 0 && position.height > 0) { - mediapipe::Detection detection; - detection.mutable_location_data()->set_format( - mediapipe::LocationData::RELATIVE_BOUNDING_BOX); - detection.mutable_location_data() - ->mutable_relative_bounding_box() - ->set_height(position.height); - detection.mutable_location_data() - ->mutable_relative_bounding_box() - ->set_width(position.width); - detection.mutable_location_data() - ->mutable_relative_bounding_box() - ->set_xmin(position.x); - detection.mutable_location_data() - ->mutable_relative_bounding_box() - ->set_ymin(position.y); - detections->push_back(detection); - } - runner->MutableInputs() - ->Tag(kDetectionsTag) - .packets.push_back(Adopt(detections.release()).At(Timestamp(time))); - - auto input_size = ::absl::make_unique>(width, height); - runner->MutableInputs() - ->Tag(kVideoSizeTag) - .packets.push_back(Adopt(input_size.release()).At(Timestamp(time))); - - if (flags.animated_zoom.has_value()) { - runner->MutableInputs() - ->Tag(kAnimateZoomTag) - .packets.push_back( - mediapipe::MakePacket(flags.animated_zoom.value()) - .At(Timestamp(time))); - } - - if (flags.max_zoom_factor_percent.has_value()) { - runner->MutableInputs() - ->Tag(kMaxZoomFactorPctTag) - .packets.push_back( - mediapipe::MakePacket(flags.max_zoom_factor_percent.value()) - .At(Timestamp(time))); - } -} - -void AddDetection(const cv::Rect_& position, const int64 time, - CalculatorRunner* runner) { - AddDetectionFrameSize(position, time, 1000, 1000, runner); -} - -void CheckCropRectFloats(const float x_center, const float y_center, - const float width, const float height, - const int frame_number, - const CalculatorRunner::StreamContentsSet& output) { - ASSERT_GT(output.Tag("NORMALIZED_CROP_RECT").packets.size(), frame_number); - auto float_rect = output.Tag("NORMALIZED_CROP_RECT") - .packets[frame_number] - .Get(); - - EXPECT_FLOAT_EQ(float_rect.x_center(), x_center); - EXPECT_FLOAT_EQ(float_rect.y_center(), y_center); - EXPECT_FLOAT_EQ(float_rect.width(), width); - EXPECT_FLOAT_EQ(float_rect.height(), height); -} - -void CheckCropRect(const int x_center, const int y_center, const int width, - const int height, const int frame_number, - const std::vector& output_packets) { - ASSERT_GT(output_packets.size(), frame_number); - const auto& rect = output_packets[frame_number].Get(); - EXPECT_EQ(rect.x_center(), x_center); - EXPECT_EQ(rect.y_center(), y_center); - EXPECT_EQ(rect.width(), width); - EXPECT_EQ(rect.height(), height); -} -TEST(ContentZoomingCalculatorTest, ZoomTest) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfigA)); - auto detection_set = std::make_unique(); - auto* detection = detection_set->add_detections(); - detection->set_only_required(true); - auto* location = detection->mutable_location_normalized(); - location->set_height(.1); - location->set_width(.1); - location->set_x(.4); - location->set_y(.5); - - auto input_frame = - ::absl::make_unique(ImageFormat::SRGB, 1000, 1000); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp(0))); - - runner->MutableInputs() - ->Tag(kSalientRegionsTag) - .packets.push_back(Adopt(detection_set.release()).At(Timestamp(0))); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - CheckBorder(static_features, 1000, 1000, 494, 394); -} - -TEST(ContentZoomingCalculatorTest, ZoomTestFullPTZ) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfigD)); - AddDetection(cv::Rect_(.4, .5, .1, .1), 0, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(450, 550, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, PanConfig) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->mutable_kinematic_options_pan()->set_min_motion_to_reframe(0.0); - options->mutable_kinematic_options_pan()->set_update_rate_seconds(2); - options->mutable_kinematic_options_tilt()->set_min_motion_to_reframe(50.0); - options->mutable_kinematic_options_zoom()->set_min_motion_to_reframe(50.0); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(.4, .5, .1, .1), 0, runner.get()); - AddDetection(cv::Rect_(.45, .55, .15, .15), 1000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(450, 550, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(483, 550, 111, 111, 1, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, PanConfigWithCache) { - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType cache; - auto config = ParseTextProtoOrDie(kConfigD); - config.add_input_side_packet("STATE_CACHE:state_cache"); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->mutable_kinematic_options_pan()->set_min_motion_to_reframe(0.0); - options->mutable_kinematic_options_pan()->set_update_rate_seconds(2); - options->mutable_kinematic_options_tilt()->set_min_motion_to_reframe(50.0); - options->mutable_kinematic_options_zoom()->set_min_motion_to_reframe(50.0); - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetection(cv::Rect_(.4, .5, .1, .1), 0, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(450, 550, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - } - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetection(cv::Rect_(.45, .55, .15, .15), 1000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(483, 550, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - } - // Now repeat the last frame for a new runner without the cache to see a reset - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(nullptr); - AddDetection(cv::Rect_(.45, .55, .15, .15), 2000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(525, 625, 166, 166, 0, // Without a cache, state was lost. - runner->Outputs().Tag(kCropRectTag).packets); - } -} - -TEST(ContentZoomingCalculatorTest, TiltConfig) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->mutable_kinematic_options_pan()->set_min_motion_to_reframe(50.0); - options->mutable_kinematic_options_tilt()->set_min_motion_to_reframe(0.0); - options->mutable_kinematic_options_tilt()->set_update_rate_seconds(2); - options->mutable_kinematic_options_zoom()->set_min_motion_to_reframe(50.0); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(.4, .5, .1, .1), 0, runner.get()); - AddDetection(cv::Rect_(.45, .55, .15, .15), 1000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(450, 550, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(450, 583, 111, 111, 1, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, ZoomConfig) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->mutable_kinematic_options_pan()->set_min_motion_to_reframe(50.0); - options->mutable_kinematic_options_tilt()->set_min_motion_to_reframe(50.0); - options->mutable_kinematic_options_zoom()->set_min_motion_to_reframe(0.0); - options->mutable_kinematic_options_zoom()->set_update_rate_seconds(2); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(.4, .5, .1, .1), 0, runner.get()); - AddDetection(cv::Rect_(.45, .55, .15, .15), 1000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(450, 550, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(450, 550, 138, 138, 1, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, ZoomConfigWithCache) { - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType cache; - auto config = ParseTextProtoOrDie(kConfigD); - config.add_input_side_packet("STATE_CACHE:state_cache"); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->mutable_kinematic_options_pan()->set_min_motion_to_reframe(50.0); - options->mutable_kinematic_options_tilt()->set_min_motion_to_reframe(50.0); - options->mutable_kinematic_options_zoom()->set_min_motion_to_reframe(0.0); - options->mutable_kinematic_options_zoom()->set_update_rate_seconds(2); - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetection(cv::Rect_(.4, .5, .1, .1), 0, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(450, 550, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - } - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetection(cv::Rect_(.45, .55, .15, .15), 1000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(450, 550, 138, 138, 0, - runner->Outputs().Tag(kCropRectTag).packets); - } - // Now repeat the last frame for a new runner without the cache to see a reset - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(nullptr); - AddDetection(cv::Rect_(.45, .55, .15, .15), 2000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(525, 625, 166, 166, 0, // Without a cache, state was lost. - runner->Outputs().Tag(kCropRectTag).packets); - } -} - -TEST(ContentZoomingCalculatorTest, MinAspectBorderValues) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfigB)); - auto detection_set = std::make_unique(); - auto* detection = detection_set->add_detections(); - detection->set_only_required(true); - auto* location = detection->mutable_location_normalized(); - location->set_height(1); - location->set_width(1); - location->set_x(0); - location->set_y(0); - - auto input_frame = - ::absl::make_unique(ImageFormat::SRGB, 1000, 1000); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp(0))); - - runner->MutableInputs() - ->Tag(kSalientRegionsTag) - .packets.push_back(Adopt(detection_set.release()).At(Timestamp(0))); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - CheckBorder(static_features, 1000, 1000, 250, 250); -} - -TEST(ContentZoomingCalculatorTest, TwoFacesWide) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfigA)); - auto detection_set = std::make_unique(); - auto* detection = detection_set->add_detections(); - detection->set_only_required(true); - auto* location = detection->mutable_location_normalized(); - location->set_height(.2); - location->set_width(.2); - location->set_x(.2); - location->set_y(.4); - - location = detection->mutable_location_normalized(); - location->set_height(.2); - location->set_width(.2); - location->set_x(.6); - location->set_y(.4); - - auto input_frame = - ::absl::make_unique(ImageFormat::SRGB, 1000, 1000); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp(0))); - - runner->MutableInputs() - ->Tag(kSalientRegionsTag) - .packets.push_back(Adopt(detection_set.release()).At(Timestamp(0))); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - - CheckBorder(static_features, 1000, 1000, 389, 389); -} - -TEST(ContentZoomingCalculatorTest, NoDetectionOnInit) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfigA)); - auto detection_set = std::make_unique(); - - auto input_frame = - ::absl::make_unique(ImageFormat::SRGB, 1000, 1000); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp(0))); - - runner->MutableInputs() - ->Tag(kSalientRegionsTag) - .packets.push_back(Adopt(detection_set.release()).At(Timestamp(0))); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - - CheckBorder(static_features, 1000, 1000, 0, 0); -} - -TEST(ContentZoomingCalculatorTest, ZoomTestPairSize) { - auto runner = ::absl::make_unique( - ParseTextProtoOrDie(kConfigC)); - auto detection_set = std::make_unique(); - auto* detection = detection_set->add_detections(); - detection->set_only_required(true); - auto* location = detection->mutable_location_normalized(); - location->set_height(.1); - location->set_width(.1); - location->set_x(.4); - location->set_y(.5); - - auto input_size = ::absl::make_unique>(1000, 1000); - runner->MutableInputs() - ->Tag(kVideoSizeTag) - .packets.push_back(Adopt(input_size.release()).At(Timestamp(0))); - - runner->MutableInputs() - ->Tag(kSalientRegionsTag) - .packets.push_back(Adopt(detection_set.release()).At(Timestamp(0))); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kBordersTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& static_features = output_packets[0].Get(); - CheckBorder(static_features, 1000, 1000, 494, 394); -} - -TEST(ContentZoomingCalculatorTest, ZoomTestNearOutsideBorder) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->mutable_kinematic_options_pan()->set_update_rate_seconds(2); - options->mutable_kinematic_options_tilt()->set_update_rate_seconds(2); - options->mutable_kinematic_options_zoom()->set_update_rate_seconds(2); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(.95, .95, .05, .05), 0, runner.get()); - AddDetection(cv::Rect_(.9, .9, .1, .1), 1000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(972, 972, 55, 55, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(944, 944, 83, 83, 1, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, ZoomTestNearInsideBorder) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->mutable_kinematic_options_pan()->set_update_rate_seconds(2); - options->mutable_kinematic_options_tilt()->set_update_rate_seconds(2); - options->mutable_kinematic_options_zoom()->set_update_rate_seconds(2); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(0, 0, .05, .05), 0, runner.get()); - AddDetection(cv::Rect_(0, 0, .1, .1), 1000000, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(28, 28, 55, 55, 0, runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(56, 56, 83, 83, 1, runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, VerticalShift) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_detection_shift_vertical(0.2); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(.1, .1, .1, .1), 0, runner.get()); - MP_ASSERT_OK(runner->Run()); - // 1000px * .1 offset + 1000*.1*.1 shift = 170 - CheckCropRect(150, 170, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRectFloats(150 / 1000.0, 170 / 1000.0, 111 / 1000.0, 111 / 1000.0, 0, - runner->Outputs()); -} - -TEST(ContentZoomingCalculatorTest, HorizontalShift) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_detection_shift_horizontal(0.2); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(.1, .1, .1, .1), 0, runner.get()); - MP_ASSERT_OK(runner->Run()); - // 1000px * .1 offset + 1000*.1*.1 shift = 170 - CheckCropRect(170, 150, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRectFloats(170 / 1000.0, 150 / 1000.0, 111 / 1000.0, 111 / 1000.0, 0, - runner->Outputs()); -} - -TEST(ContentZoomingCalculatorTest, ShiftOutsideBounds) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_detection_shift_vertical(-0.2); - options->set_detection_shift_horizontal(0.2); - auto runner = ::absl::make_unique(config); - AddDetection(cv::Rect_(.9, 0, .1, .1), 0, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(944, 56, 111, 111, 0, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, EmptySize) { - auto config = ParseTextProtoOrDie(kConfigD); - auto runner = ::absl::make_unique(config); - MP_ASSERT_OK(runner->Run()); - ASSERT_EQ(runner->Outputs().Tag(kCropRectTag).packets.size(), 0); -} - -TEST(ContentZoomingCalculatorTest, EmptyDetections) { - auto config = ParseTextProtoOrDie(kConfigD); - auto runner = ::absl::make_unique(config); - auto input_size = ::absl::make_unique>(1000, 1000); - runner->MutableInputs() - ->Tag(kVideoSizeTag) - .packets.push_back(Adopt(input_size.release()).At(Timestamp(0))); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 1000, 1000, 0, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, ResolutionChangeStationary) { - auto config = ParseTextProtoOrDie(kConfigD); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1, 500, 500, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 222, 222, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500 * 0.5, 500 * 0.5, 222 * 0.5, 222 * 0.5, 1, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, ResolutionChangeStationaryWithCache) { - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType cache; - auto config = ParseTextProtoOrDie(kConfigD); - config.add_input_side_packet("STATE_CACHE:state_cache"); - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 222, 222, 0, - runner->Outputs().Tag(kCropRectTag).packets); - } - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1, 500, 500, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500 * 0.5, 500 * 0.5, 222 * 0.5, 222 * 0.5, 0, - runner->Outputs().Tag(kCropRectTag).packets); - } -} - -TEST(ContentZoomingCalculatorTest, ResolutionChangeZooming) { - auto config = ParseTextProtoOrDie(kConfigD); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.1, .1, .8, .8), 0, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1000000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 2000000, 500, 500, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 888, 888, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 588, 588, 1, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500 * 0.5, 500 * 0.5, 288 * 0.5, 288 * 0.5, 2, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, ResolutionChangeZoomingWithCache) { - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType cache; - auto config = ParseTextProtoOrDie(kConfigD); - config.add_input_side_packet("STATE_CACHE:state_cache"); - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetectionFrameSize(cv::Rect_(.1, .1, .8, .8), 0, 1000, 1000, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 888, 888, 0, - runner->Outputs().Tag(kCropRectTag).packets); - } - // The second runner should just resume based on state from the first runner. - { - auto runner = ::absl::make_unique(config); - runner->MutableSidePackets()->Tag(kStateCacheTag) = MakePacket< - mediapipe::autoflip::ContentZoomingCalculatorStateCacheType*>(&cache); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1000000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 2000000, 500, 500, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 588, 588, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500 * 0.5, 500 * 0.5, 288 * 0.5, 288 * 0.5, 1, - runner->Outputs().Tag(kCropRectTag).packets); - } -} - -TEST(ContentZoomingCalculatorTest, MaxZoomValue) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_max_zoom_value_deg(55); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get()); - MP_ASSERT_OK(runner->Run()); - // 55/60 * 1000 = 916 - CheckCropRect(500, 500, 916, 916, 0, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, MaxZoomValueOverride) { - auto config = ParseTextProtoOrDie(kConfigF); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_max_zoom_value_deg(30); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 640, 480, - runner.get(), {.max_zoom_factor_percent = 133}); - // Change resolution and allow more zoom, and give time to use the new limit - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1000000, 1280, 720, - runner.get(), {.max_zoom_factor_percent = 166}); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 2000000, 1280, 720, - runner.get(), {.max_zoom_factor_percent = 166}); - // Switch back to a smaller resolution with a more limited zoom - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 3000000, 640, 480, - runner.get(), {.max_zoom_factor_percent = 133}); - MP_ASSERT_OK(runner->Run()); - // Max. 133% zoomed in means min. (100/133) ~ 75% of height left: ~360 - // Max. 166% zoomed in means min. (100/166) ~ 60% of height left: ~430 - CheckCropRect(320, 240, 480, 360, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(640, 360, 769, 433, 2, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(320, 240, 480, 360, 3, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, MaxZoomOutValue) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_scale_factor(1.0); - options->mutable_kinematic_options_zoom()->set_min_motion_to_reframe(5.0); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.025, .025, .95, .95), 0, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(0, 0, -1, -1), 1000000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(0, 0, -1, -1), 2000000, 1000, 1000, - runner.get()); - MP_ASSERT_OK(runner->Run()); - // 55/60 * 1000 = 916 - CheckCropRect(500, 500, 950, 950, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 1000, 1000, 2, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, StartZoomedOut) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_start_zoomed_out(true); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 400000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 800000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1000000, 1000, 1000, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 1000, 1000, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 880, 880, 1, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 760, 760, 2, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 655, 655, 3, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, AnimateToFirstRect) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_us_to_first_rect(1000000); - options->set_us_to_first_rect_delay(500000); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 400000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 800000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1000000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1500000, 1000, 1000, - runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 1000, 1000, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 1000, 1000, 1, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 470, 470, 2, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 222, 222, 3, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 222, 222, 4, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, CanControlAnimation) { - auto config = ParseTextProtoOrDie(kConfigE); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_start_zoomed_out(true); - options->set_us_to_first_rect(1000000); - options->set_us_to_first_rect_delay(500000); - auto runner = ::absl::make_unique(config); - // Request the animation for the first frame. - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get(), {.animated_zoom = true}); - // We now stop requesting animated zoom and expect the already started - // animation run to completion. This tests that the zoom in continues in the - // call when it was started in the Meet greenroom. - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 400000, 1000, 1000, - runner.get(), {.animated_zoom = false}); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 800000, 1000, 1000, - runner.get(), {.animated_zoom = false}); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1000000, 1000, 1000, - runner.get(), {.animated_zoom = false}); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1500000, 1000, 1000, - runner.get(), {.animated_zoom = false}); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 1000, 1000, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 1000, 1000, 1, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 470, 470, 2, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 222, 222, 3, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 222, 222, 4, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, DoesNotAnimateIfDisabledViaInput) { - auto config = ParseTextProtoOrDie(kConfigE); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_start_zoomed_out(true); - options->set_us_to_first_rect(1000000); - options->set_us_to_first_rect_delay(500000); - auto runner = ::absl::make_unique(config); - // Disable the animation already for the first frame. - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get(), {.animated_zoom = false}); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 400000, 1000, 1000, - runner.get(), {.animated_zoom = false}); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 800000, 1000, 1000, - runner.get(), {.animated_zoom = false}); - MP_ASSERT_OK(runner->Run()); - CheckCropRect(500, 500, 1000, 1000, 0, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 880, 880, 1, - runner->Outputs().Tag(kCropRectTag).packets); - CheckCropRect(500, 500, 760, 760, 2, - runner->Outputs().Tag(kCropRectTag).packets); -} - -TEST(ContentZoomingCalculatorTest, ProvidesZeroSizeFirstRectWithoutDetections) { - auto config = ParseTextProtoOrDie(kConfigD); - auto runner = ::absl::make_unique(config); - - auto input_size = ::absl::make_unique>(1000, 1000); - runner->MutableInputs() - ->Tag(kVideoSizeTag) - .packets.push_back(Adopt(input_size.release()).At(Timestamp(0))); - - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kFirstCropRectTag).packets; - ASSERT_EQ(output_packets.size(), 1); - const auto& rect = output_packets[0].Get(); - EXPECT_EQ(rect.x_center(), 0); - EXPECT_EQ(rect.y_center(), 0); - EXPECT_EQ(rect.width(), 0); - EXPECT_EQ(rect.height(), 0); -} - -TEST(ContentZoomingCalculatorTest, ProvidesConstantFirstRect) { - auto config = ParseTextProtoOrDie(kConfigD); - auto* options = config.mutable_options()->MutableExtension( - ContentZoomingCalculatorOptions::ext); - options->set_us_to_first_rect(500000); - auto runner = ::absl::make_unique(config); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 0, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 500000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1000000, 1000, 1000, - runner.get()); - AddDetectionFrameSize(cv::Rect_(.4, .4, .2, .2), 1500000, 1000, 1000, - runner.get()); - MP_ASSERT_OK(runner->Run()); - const std::vector& output_packets = - runner->Outputs().Tag(kFirstCropRectTag).packets; - ASSERT_EQ(output_packets.size(), 4); - const auto& first_rect = output_packets[0].Get(); - EXPECT_NEAR(first_rect.x_center(), 0.5, 0.05); - EXPECT_NEAR(first_rect.y_center(), 0.5, 0.05); - EXPECT_NEAR(first_rect.width(), 0.222, 0.05); - EXPECT_NEAR(first_rect.height(), 0.222, 0.05); - for (int i = 1; i < 4; ++i) { - const auto& rect = output_packets[i].Get(); - EXPECT_EQ(first_rect.x_center(), rect.x_center()); - EXPECT_EQ(first_rect.y_center(), rect.y_center()); - EXPECT_EQ(first_rect.width(), rect.width()); - EXPECT_EQ(first_rect.height(), rect.height()); - } -} - -} // namespace -} // namespace autoflip - -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/face_box_adjuster_calculator.proto b/examples/desktop/autoflip/calculators/face_box_adjuster_calculator.proto deleted file mode 100644 index 0912d1b..0000000 --- a/examples/desktop/autoflip/calculators/face_box_adjuster_calculator.proto +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/framework/calculator.proto"; - -message FaceBoxAdjusterCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional FaceBoxAdjusterCalculatorOptions ext = 347462240; - } - - // When faces are detected in a given frame, we check these number of frames - // in the past. We include only those faces in auto framing that have been - // seen in this past history. This helps reduce False Positives and also - // handles some of the edge cases. Setting the value to 0 disables the - // feature. - optional int32 num_frame_history = 1 [default = 0]; - - // IOU threshold for matching detected faces with the faces in the frame - // history buffer. - optional float iou_threshold = 2 [default = 0.2]; - - // If true, the face boxes are adjusted based on their face pose. This is done - // to correct for extreme poses that can cause the detected face boxes to be - // either too big or too small. - optional bool adjust_for_pose = 3 [default = true]; - - // There are DEPRECATED fields. Do not use. - optional float box_area_change_per_up_tilt_degree = 4 [deprecated = true]; - optional float box_area_change_per_down_tilt_degree = 5 [deprecated = true]; - - // The ratios of the face-pose corrected IPD to the face bounding box's width - // and height respectively. - optional float ipd_face_box_width_ratio = 6 [default = 0.5566]; - optional float ipd_face_box_height_ratio = 7 [default = 0.3131]; - - // The max look up angle before considering the eye distance unstable. - optional float max_head_tilt_angle_deg = 8 [default = 5.0]; - // The min look up angle (i.e. looking down) before considering the eye - // distance unstable. - optional float min_head_tilt_angle_deg = 10 [default = -18.0]; - // The max look right angle before considering the eye distance unstable. - optional float max_head_pan_angle_deg = 11 [default = 25.0]; - // The min look right angle (i.e. looking left) before considering the eye - // distance unstable. - optional float min_head_pan_angle_deg = 12 [default = -25.0]; - - // Update rate for motion history, valid values [0.0, 1.0]. - optional float motion_history_alpha = 13 [default = 0.5]; - - // Max value of head motion (max of current or history) to be considered still - // stable. - optional float head_motion_threshold = 14 [default = 360.0]; - - // The max amount of time to use an old eye distance when the face look angle - // is unstable. - optional int32 max_facesize_history_us = 9 [default = 300000000]; - - // Scale factor of face width to shift based on pan look angle. - optional float pan_position_shift_scale = 15 - [default = 1.0, deprecated = true]; - // Scale factor of face height to shift based on tilt look angle. - optional float tilt_position_shift_scale = 16 [default = 0.25]; - // Scale factor of face width to shift based on roll look angle. - optional float roll_position_shift_scale = 17 [default = 0.7]; -} diff --git a/examples/desktop/autoflip/calculators/face_to_region_calculator.cc b/examples/desktop/autoflip/calculators/face_to_region_calculator.cc deleted file mode 100644 index 494dc8c..0000000 --- a/examples/desktop/autoflip/calculators/face_to_region_calculator.cc +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/face_to_region_calculator.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/visual_scorer.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/detection.pb.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/formats/location_data.pb.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_builder.h" - -namespace mediapipe { -namespace autoflip { - -constexpr char kRegionsTag[] = "REGIONS"; -constexpr char kFacesTag[] = "FACES"; -constexpr char kVideoTag[] = "VIDEO"; - -// This calculator converts detected faces to SalientRegion protos that can be -// used for downstream processing. Each SalientRegion is scored using image -// cues. Scoring can be controlled through -// FaceToRegionCalculator::scorer_options. -// Example: -// calculator: "FaceToRegionCalculator" -// input_stream: "VIDEO:frames" -// input_stream: "FACES:faces" -// output_stream: "REGIONS:regions" -// options:{ -// [mediapipe.autoflip.FaceToRegionCalculatorOptions.ext]:{ -// export_individual_face_landmarks: false -// export_whole_face: true -// } -// } -// -class FaceToRegionCalculator : public CalculatorBase { - public: - FaceToRegionCalculator(); - ~FaceToRegionCalculator() override {} - FaceToRegionCalculator(const FaceToRegionCalculator&) = delete; - FaceToRegionCalculator& operator=(const FaceToRegionCalculator&) = delete; - - static absl::Status GetContract(mediapipe::CalculatorContract* cc); - absl::Status Open(mediapipe::CalculatorContext* cc) override; - absl::Status Process(mediapipe::CalculatorContext* cc) override; - - private: - double NormalizeX(const int pixel); - double NormalizeY(const int pixel); - // Extend the given SalientRegion to include the given point. - void ExtendSalientRegionWithPoint(const float x, const float y, - SalientRegion* region); - // Calculator options. - FaceToRegionCalculatorOptions options_; - - // A scorer used to assign weights to faces. - std::unique_ptr scorer_; - // Dimensions of video frame - int frame_width_; - int frame_height_; -}; -REGISTER_CALCULATOR(FaceToRegionCalculator); - -FaceToRegionCalculator::FaceToRegionCalculator() {} - -absl::Status FaceToRegionCalculator::GetContract( - mediapipe::CalculatorContract* cc) { - if (cc->Inputs().HasTag(kVideoTag)) { - cc->Inputs().Tag(kVideoTag).Set(); - } - cc->Inputs().Tag(kFacesTag).Set>(); - cc->Outputs().Tag(kRegionsTag).Set(); - return absl::OkStatus(); -} - -absl::Status FaceToRegionCalculator::Open(mediapipe::CalculatorContext* cc) { - options_ = cc->Options(); - if (!cc->Inputs().HasTag(kVideoTag)) { - RET_CHECK(!options_.use_visual_scorer()) - << "VIDEO input must be provided when using visual_scorer."; - RET_CHECK(!options_.export_individual_face_landmarks()) - << "VIDEO input must be provided when export_individual_face_landmarks " - "is set true."; - RET_CHECK(!options_.export_bbox_from_landmarks()) - << "VIDEO input must be provided when export_bbox_from_landmarks " - "is set true."; - } - - scorer_ = absl::make_unique(options_.scorer_options()); - frame_width_ = -1; - frame_height_ = -1; - return absl::OkStatus(); -} - -inline double FaceToRegionCalculator::NormalizeX(const int pixel) { - return pixel / static_cast(frame_width_); -} - -inline double FaceToRegionCalculator::NormalizeY(const int pixel) { - return pixel / static_cast(frame_height_); -} - -void FaceToRegionCalculator::ExtendSalientRegionWithPoint( - const float x, const float y, SalientRegion* region) { - auto* location = region->mutable_location_normalized(); - if (!location->has_width()) { - location->set_width(NormalizeX(1)); - } else if (x < location->x()) { - location->set_width(location->width() + location->x() - x); - } else if (x > location->x() + location->width()) { - location->set_width(x - location->x()); - } - if (!location->has_height()) { - location->set_height(NormalizeY(1)); - } else if (y < location->y()) { - location->set_height(location->height() + location->y() - y); - } else if (y > location->y() + location->height()) { - location->set_height(y - location->y()); - } - - if (!location->has_x()) { - location->set_x(x); - } else { - location->set_x(std::min(location->x(), x)); - } - if (!location->has_y()) { - location->set_y(y); - } else { - location->set_y(std::min(location->y(), y)); - } -} - -absl::Status FaceToRegionCalculator::Process(mediapipe::CalculatorContext* cc) { - if (cc->Inputs().HasTag(kVideoTag) && - cc->Inputs().Tag(kVideoTag).Value().IsEmpty()) { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "No VIDEO input at time " << cc->InputTimestamp().Seconds(); - } - - cv::Mat frame; - if (cc->Inputs().HasTag(kVideoTag)) { - frame = mediapipe::formats::MatView( - &cc->Inputs().Tag(kVideoTag).Get()); - frame_width_ = frame.cols; - frame_height_ = frame.rows; - } - - auto region_set = ::absl::make_unique(); - if (!cc->Inputs().Tag(kFacesTag).Value().IsEmpty()) { - const auto& input_faces = - cc->Inputs().Tag(kFacesTag).Get>(); - - for (const auto& input_face : input_faces) { - RET_CHECK(input_face.location_data().format() == - mediapipe::LocationData::RELATIVE_BOUNDING_BOX) - << "Face detection input is lacking required relative_bounding_box()"; - // 6 landmarks should be provided, ordered as: - // Left eye, Right eye, Nose tip, Mouth center, Left ear tragion, Right - // ear tragion. - RET_CHECK(input_face.location_data().relative_keypoints().size() == 6) - << "Face detection input expected 6 keypoints, has " - << input_face.location_data().relative_keypoints().size(); - - const auto& location = input_face.location_data().relative_bounding_box(); - - // Reduce region size to only contain parts of the image in frame. - float x = std::max(0.0f, location.xmin()); - float y = std::max(0.0f, location.ymin()); - float width = - std::min(location.width() - abs(x - location.xmin()), 1 - x); - float height = - std::min(location.height() - abs(y - location.ymin()), 1 - y); - - // Convert the face to a region. - if (options_.export_whole_face()) { - SalientRegion* region = region_set->add_detections(); - region->mutable_location_normalized()->set_x(x); - region->mutable_location_normalized()->set_y(y); - region->mutable_location_normalized()->set_width(width); - region->mutable_location_normalized()->set_height(height); - region->mutable_signal_type()->set_standard(SignalType::FACE_FULL); - - // Score the face based on image cues. - float visual_score = 1.0f; - if (options_.use_visual_scorer()) { - MP_RETURN_IF_ERROR( - scorer_->CalculateScore(frame, *region, &visual_score)); - } - region->set_score(visual_score); - } - - // Generate two more output regions from important face landmarks. One - // includes all exterior landmarks, such as ears and chin, and the - // other includes only interior landmarks, such as the eye edges and the - // mouth. - SalientRegion core_landmark_region, all_landmark_region; - // Keypoints are ordered: Left Eye, Right Eye, Nose Tip, Mouth Center, - // Left Ear Tragion, Right Ear Tragion. - - // Set 'core' landmarks (Left Eye, Right Eye, Nose Tip, Mouth Center) - for (int i = 0; i < 4; i++) { - const auto& keypoint = input_face.location_data().relative_keypoints(i); - if (options_.export_individual_face_landmarks()) { - SalientRegion* region = region_set->add_detections(); - region->mutable_location_normalized()->set_x(keypoint.x()); - region->mutable_location_normalized()->set_y(keypoint.y()); - region->mutable_location_normalized()->set_width(NormalizeX(1)); - region->mutable_location_normalized()->set_height(NormalizeY(1)); - region->mutable_signal_type()->set_standard( - SignalType::FACE_LANDMARK); - } - - // Extend the core/full landmark regions to include the new - ExtendSalientRegionWithPoint(keypoint.x(), keypoint.y(), - &core_landmark_region); - ExtendSalientRegionWithPoint(keypoint.x(), keypoint.y(), - &all_landmark_region); - } - // Set 'all' landmarks (Left Ear Tragion, Right Ear Tragion + core) - for (int i = 4; i < 6; i++) { - const auto& keypoint = input_face.location_data().relative_keypoints(i); - if (options_.export_individual_face_landmarks()) { - SalientRegion* region = region_set->add_detections(); - region->mutable_location()->set_x(keypoint.x()); - region->mutable_location()->set_y(keypoint.y()); - region->mutable_location()->set_width(NormalizeX(1)); - region->mutable_location()->set_height(NormalizeY(1)); - region->mutable_signal_type()->set_standard( - SignalType::FACE_LANDMARK); - } - - // Extend the full landmark region to include the new landmark. - ExtendSalientRegionWithPoint(keypoint.x(), keypoint.y(), - &all_landmark_region); - } - - // Generate scores for the landmark bboxes and export them. - if (options_.export_bbox_from_landmarks() && - core_landmark_region.has_location_normalized()) { // Not empty. - float visual_score = 1.0f; - if (options_.use_visual_scorer()) { - MP_RETURN_IF_ERROR(scorer_->CalculateScore( - frame, core_landmark_region, &visual_score)); - } - core_landmark_region.set_score(visual_score); - core_landmark_region.mutable_signal_type()->set_standard( - SignalType::FACE_CORE_LANDMARKS); - *region_set->add_detections() = core_landmark_region; - } - if (options_.export_bbox_from_landmarks() && - all_landmark_region.has_location_normalized()) { // Not empty. - float visual_score = 1.0f; - if (options_.use_visual_scorer()) { - MP_RETURN_IF_ERROR(scorer_->CalculateScore(frame, all_landmark_region, - &visual_score)); - } - all_landmark_region.set_score(visual_score); - all_landmark_region.mutable_signal_type()->set_standard( - SignalType::FACE_ALL_LANDMARKS); - *region_set->add_detections() = all_landmark_region; - } - } - } - cc->Outputs() - .Tag(kRegionsTag) - .Add(region_set.release(), cc->InputTimestamp()); - - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/face_to_region_calculator.proto b/examples/desktop/autoflip/calculators/face_to_region_calculator.proto deleted file mode 100644 index 86e4c31..0000000 --- a/examples/desktop/autoflip/calculators/face_to_region_calculator.proto +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/examples/desktop/autoflip/quality/visual_scorer.proto"; -import "mediapipe/framework/calculator.proto"; - -// Next tag: 6 -message FaceToRegionCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional FaceToRegionCalculatorOptions ext = 282401234; - } - - // Options for generating a score for the entire face from its visual - // appearance. The generated score is used to modulate the detection scores - // for whole face and/or landmark bbox region types. - optional VisualScorerOptions scorer_options = 1; - - // If true, export the large face bounding box generated by the face tracker. - // This bounding box is generally larger than the actual face and relatively - // inaccurate. - optional bool export_whole_face = 2 [default = false]; - - // If true, export a number of individual face landmarks (eyes, nose, mouth, - // ears etc) as separate SalientRegion protos. - optional bool export_individual_face_landmarks = 3 [default = false]; - - // If true, export two bounding boxes from landmarks (one for the core face - // landmarks like eyes and nose, and one for extended landmarks including ears - // and chin). - optional bool export_bbox_from_landmarks = 4 [default = true]; - - // If true, generate a score from the appearance of the face and use it to - // modulate the detection scores for whole face and/or landmark bboxes. - optional bool use_visual_scorer = 5 [default = true]; -} diff --git a/examples/desktop/autoflip/calculators/face_to_region_calculator_test.cc b/examples/desktop/autoflip/calculators/face_to_region_calculator_test.cc deleted file mode 100644 index fb7403c..0000000 --- a/examples/desktop/autoflip/calculators/face_to_region_calculator_test.cc +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "absl/strings/string_view.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/face_to_region_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/formats/detection.pb.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -using mediapipe::Detection; - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kRegionsTag[] = "REGIONS"; -constexpr char kFacesTag[] = "FACES"; -constexpr char kVideoTag[] = "VIDEO"; - -const char kConfig[] = R"( - calculator: "FaceToRegionCalculator" - input_stream: "VIDEO:frames" - input_stream: "FACES:faces" - output_stream: "REGIONS:regions" - )"; - -const char kConfigNoVideo[] = R"( - calculator: "FaceToRegionCalculator" - input_stream: "FACES:faces" - output_stream: "REGIONS:regions" - )"; - -const char kFace1[] = R"(location_data { - format: RELATIVE_BOUNDING_BOX - relative_bounding_box { - xmin: -0.00375 - ymin: 0.003333 - width: 0.125 - height: 0.33333 - } - relative_keypoints { x: 0.03125 y: 0.05 } - relative_keypoints { x: 0.0875 y: 0.0666666 } - relative_keypoints { x: 0.03125 y: 0.05 } - relative_keypoints { x: 0.0875 y: 0.0666666 } - relative_keypoints { x: 0.0250 y: 0.0666666 } - relative_keypoints { x: 0.0950 y: 0.0666666 } - })"; - -const char kFace2[] = R"(location_data { - format: RELATIVE_BOUNDING_BOX - relative_bounding_box { - xmin: 0.0025 - ymin: 0.005 - width: 0.25 - height: 0.5 - } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - })"; - -const char kFace3[] = R"(location_data { - format: RELATIVE_BOUNDING_BOX - relative_bounding_box { - xmin: 0.0 - ymin: 0.0 - width: 0.5 - height: 0.5 - } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - relative_keypoints { x: 0 y: 0 } - })"; - -void SetInputs(const std::vector& faces, const bool include_video, - CalculatorRunner* runner) { - // Setup an input video frame. - if (include_video) { - auto input_frame = - ::absl::make_unique(ImageFormat::SRGB, 800, 600); - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp::PostStream())); - } - // Setup two faces as input. - auto input_faces = ::absl::make_unique>(); - // A face with landmarks. - for (const auto& face : faces) { - input_faces->push_back(ParseTextProtoOrDie(face)); - } - runner->MutableInputs()->Tag(kFacesTag).packets.push_back( - Adopt(input_faces.release()).At(Timestamp::PostStream())); -} - -CalculatorGraphConfig::Node MakeConfig(std::string base_config, bool whole_face, - bool landmarks, bool bb_from_landmarks, - bool visual_scoring) { - auto config = ParseTextProtoOrDie(base_config); - config.mutable_options() - ->MutableExtension(FaceToRegionCalculatorOptions::ext) - ->set_export_whole_face(whole_face); - config.mutable_options() - ->MutableExtension(FaceToRegionCalculatorOptions::ext) - ->set_export_individual_face_landmarks(landmarks); - config.mutable_options() - ->MutableExtension(FaceToRegionCalculatorOptions::ext) - ->set_export_bbox_from_landmarks(bb_from_landmarks); - config.mutable_options() - ->MutableExtension(FaceToRegionCalculatorOptions::ext) - ->set_use_visual_scorer(visual_scoring); - - return config; -} - -TEST(FaceToRegionCalculatorTest, FaceFullTypeSize) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfig, true, false, false, true)); - SetInputs({kFace1, kFace2}, true, runner.get()); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(2, regions.detections().size()); - auto face_1 = regions.detections(0); - EXPECT_EQ(face_1.signal_type().standard(), SignalType::FACE_FULL); - EXPECT_FLOAT_EQ(face_1.location_normalized().x(), 0); - EXPECT_FLOAT_EQ(face_1.location_normalized().y(), 0.003333); - EXPECT_FLOAT_EQ(face_1.location_normalized().width(), 0.12125); - EXPECT_FLOAT_EQ(face_1.location_normalized().height(), 0.33333); - EXPECT_FLOAT_EQ(face_1.score(), 0.040214583); - - auto face_2 = regions.detections(1); - EXPECT_EQ(face_2.signal_type().standard(), SignalType::FACE_FULL); - EXPECT_FLOAT_EQ(face_2.location_normalized().x(), 0.0025); - EXPECT_FLOAT_EQ(face_2.location_normalized().y(), 0.005); - EXPECT_FLOAT_EQ(face_2.location_normalized().width(), 0.25); - EXPECT_FLOAT_EQ(face_2.location_normalized().height(), 0.5); - EXPECT_FLOAT_EQ(face_2.score(), 0.125); -} - -TEST(FaceToRegionCalculatorTest, FaceLandmarksTypeSize) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfig, false, true, false, true)); - SetInputs({kFace1}, true, runner.get()); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(6, regions.detections().size()); - auto landmark_1 = regions.detections(0); - EXPECT_EQ(landmark_1.signal_type().standard(), SignalType::FACE_LANDMARK); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().x(), 0.03125); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().y(), 0.05); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().width(), 0.00125); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().height(), 0.0016666667); - - auto landmark_2 = regions.detections(1); - EXPECT_EQ(landmark_2.signal_type().standard(), SignalType::FACE_LANDMARK); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().x(), 0.0875); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().y(), 0.0666666); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().width(), 0.00125); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().height(), 0.0016666667); -} - -TEST(FaceToRegionCalculatorTest, FaceLandmarksBox) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfig, false, false, true, true)); - SetInputs({kFace1}, true, runner.get()); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(2, regions.detections().size()); - auto landmark_1 = regions.detections(0); - EXPECT_EQ(landmark_1.signal_type().standard(), - SignalType::FACE_CORE_LANDMARKS); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().x(), 0.03125); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().y(), 0.05); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().width(), 0.056249999); - EXPECT_FLOAT_EQ(landmark_1.location_normalized().height(), 0.016666602); - EXPECT_FLOAT_EQ(landmark_1.score(), 0.00084375002); - - auto landmark_2 = regions.detections(1); - EXPECT_EQ(landmark_2.signal_type().standard(), - SignalType::FACE_ALL_LANDMARKS); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().x(), 0.025); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().y(), 0.050000001); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().width(), 0.07); - EXPECT_FLOAT_EQ(landmark_2.location_normalized().height(), 0.016666602); - EXPECT_FLOAT_EQ(landmark_2.score(), 0.00105); -} - -TEST(FaceToRegionCalculatorTest, FaceScore) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfig, true, false, false, true)); - SetInputs({kFace3}, true, runner.get()); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(1, regions.detections().size()); - auto landmark_1 = regions.detections(0); - EXPECT_FLOAT_EQ(landmark_1.score(), 0.25); -} - -TEST(FaceToRegionCalculatorTest, FaceNoVideoVisualScoreFail) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfigNoVideo, true, false, false, true)); - SetInputs({kFace3}, false, runner.get()); - - // Run the calculator. - ASSERT_FALSE(runner->Run().ok()); -} - -TEST(FaceToRegionCalculatorTest, FaceNoVideoLandmarksFail) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfigNoVideo, false, true, false, false)); - SetInputs({kFace3}, false, runner.get()); - - // Run the calculator. - ASSERT_FALSE(runner->Run().ok()); -} - -TEST(FaceToRegionCalculatorTest, FaceNoVideoBBLandmarksFail) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfigNoVideo, false, false, true, false)); - SetInputs({kFace3}, false, runner.get()); - - // Run the calculator. - ASSERT_FALSE(runner->Run().ok()); -} - -TEST(FaceToRegionCalculatorTest, FaceNoVideoPass) { - // Setup test - auto runner = ::absl::make_unique( - MakeConfig(kConfigNoVideo, true, false, false, false)); - SetInputs({kFace1, kFace2}, false, runner.get()); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(2, regions.detections().size()); - auto face_1 = regions.detections(0); - EXPECT_EQ(face_1.signal_type().standard(), SignalType::FACE_FULL); - EXPECT_FLOAT_EQ(face_1.location_normalized().x(), 0); - EXPECT_FLOAT_EQ(face_1.location_normalized().y(), 0.003333); - EXPECT_FLOAT_EQ(face_1.location_normalized().width(), 0.12125); - EXPECT_FLOAT_EQ(face_1.location_normalized().height(), 0.33333); - EXPECT_FLOAT_EQ(face_1.score(), 1); - - auto face_2 = regions.detections(1); - EXPECT_EQ(face_2.signal_type().standard(), SignalType::FACE_FULL); - EXPECT_FLOAT_EQ(face_2.location_normalized().x(), 0.0025); - EXPECT_FLOAT_EQ(face_2.location_normalized().y(), 0.005); - EXPECT_FLOAT_EQ(face_2.location_normalized().width(), 0.25); - EXPECT_FLOAT_EQ(face_2.location_normalized().height(), 0.5); - EXPECT_FLOAT_EQ(face_2.score(), 1); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/localization_to_region_calculator.cc b/examples/desktop/autoflip/calculators/localization_to_region_calculator.cc deleted file mode 100644 index dea1273..0000000 --- a/examples/desktop/autoflip/calculators/localization_to_region_calculator.cc +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include -#include - -#include "absl/memory/memory.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/localization_to_region_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/detection.pb.h" -#include "mediapipe/framework/formats/location_data.pb.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// This calculator converts detections from ObjectLocalizationCalculator to -// SalientRegion protos that can be used for downstream processing. -class LocalizationToRegionCalculator : public mediapipe::CalculatorBase { - public: - LocalizationToRegionCalculator(); - ~LocalizationToRegionCalculator() override {} - LocalizationToRegionCalculator(const LocalizationToRegionCalculator&) = - delete; - LocalizationToRegionCalculator& operator=( - const LocalizationToRegionCalculator&) = delete; - - static absl::Status GetContract(mediapipe::CalculatorContract* cc); - absl::Status Open(mediapipe::CalculatorContext* cc) override; - absl::Status Process(mediapipe::CalculatorContext* cc) override; - - private: - // Calculator options. - LocalizationToRegionCalculatorOptions options_; -}; -REGISTER_CALCULATOR(LocalizationToRegionCalculator); - -LocalizationToRegionCalculator::LocalizationToRegionCalculator() {} - -namespace { - -constexpr char kRegionsTag[] = "REGIONS"; -constexpr char kDetectionsTag[] = "DETECTIONS"; - -// Converts an object detection to a autoflip SignalType. Returns true if the -// string label has a autoflip label. -bool MatchType(const std::string& label, SignalType* type) { - if (label == "person") { - type->set_standard(SignalType::HUMAN); - return true; - } - if (label == "car" || label == "truck") { - type->set_standard(SignalType::CAR); - return true; - } - if (label == "dog" || label == "cat" || label == "bird" || label == "horse") { - type->set_standard(SignalType::PET); - return true; - } - return false; -} - -// Converts a detection to a SalientRegion with a given label. -void FillSalientRegion(const mediapipe::Detection& detection, - const SignalType& label, SalientRegion* region) { - const auto& location = detection.location_data().relative_bounding_box(); - region->mutable_location_normalized()->set_x(location.xmin()); - region->mutable_location_normalized()->set_y(location.ymin()); - region->mutable_location_normalized()->set_width(location.width()); - region->mutable_location_normalized()->set_height(location.height()); - region->set_score(1.0); - *region->mutable_signal_type() = label; -} - -} // namespace - -absl::Status LocalizationToRegionCalculator::GetContract( - mediapipe::CalculatorContract* cc) { - cc->Inputs().Tag(kDetectionsTag).Set>(); - cc->Outputs().Tag(kRegionsTag).Set(); - return absl::OkStatus(); -} - -absl::Status LocalizationToRegionCalculator::Open( - mediapipe::CalculatorContext* cc) { - options_ = cc->Options(); - - return absl::OkStatus(); -} - -absl::Status LocalizationToRegionCalculator::Process( - mediapipe::CalculatorContext* cc) { - const auto& annotations = - cc->Inputs().Tag(kDetectionsTag).Get>(); - auto regions = ::absl::make_unique(); - for (const auto& detection : annotations) { - RET_CHECK_EQ(detection.label().size(), 1) - << "Number of labels not equal to one."; - SignalType autoflip_label; - if (MatchType(detection.label(0), &autoflip_label) && - options_.output_standard_signals()) { - FillSalientRegion(detection, autoflip_label, regions->add_detections()); - } - if (options_.output_all_signals()) { - SignalType object; - object.set_standard(SignalType::OBJECT); - FillSalientRegion(detection, object, regions->add_detections()); - } - } - - cc->Outputs().Tag(kRegionsTag).Add(regions.release(), cc->InputTimestamp()); - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/localization_to_region_calculator.proto b/examples/desktop/autoflip/calculators/localization_to_region_calculator.proto deleted file mode 100644 index 1dd00ac..0000000 --- a/examples/desktop/autoflip/calculators/localization_to_region_calculator.proto +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/framework/calculator.proto"; - -message LocalizationToRegionCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional LocalizationToRegionCalculatorOptions ext = 284226721; - } - - // Output standard autoflip signals only (Human, Pet, Car, etc) and apply - // standard autoflip labels. - optional bool output_standard_signals = 1 [default = true]; - // Output all signals (regardless of label) and set autoflip label as - // 'Object'. Can be combined with output_standard_signals giving each - // detection a 'object' label and a autoflip sepcific label. - optional bool output_all_signals = 2 [default = false]; -} diff --git a/examples/desktop/autoflip/calculators/localization_to_region_calculator_test.cc b/examples/desktop/autoflip/calculators/localization_to_region_calculator_test.cc deleted file mode 100644 index 0456b86..0000000 --- a/examples/desktop/autoflip/calculators/localization_to_region_calculator_test.cc +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "absl/strings/string_view.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/localization_to_region_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/formats/detection.pb.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -using mediapipe::Detection; - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kRegionsTag[] = "REGIONS"; -constexpr char kDetectionsTag[] = "DETECTIONS"; - -const char kConfig[] = R"( - calculator: "LocalizationToRegionCalculator" - input_stream: "DETECTIONS:detections" - output_stream: "REGIONS:regions" - )"; - -const char kCar[] = R"( - label: "car" - location_data { - format: RELATIVE_BOUNDING_BOX - relative_bounding_box { - xmin: -0.00375 - ymin: 0.003333 - width: 0.125 - height: 0.33333 - } - })"; - -const char kDog[] = R"( - label: "dog" - location_data { - format: RELATIVE_BOUNDING_BOX - relative_bounding_box { - xmin: 0.0025 - ymin: 0.005 - width: 0.25 - height: 0.5 - } - })"; - -const char kZebra[] = R"( - label: "zebra" - location_data { - format: RELATIVE_BOUNDING_BOX - relative_bounding_box { - xmin: 0.0 - ymin: 0.0 - width: 0.5 - height: 0.5 - } - })"; - -void SetInputs(CalculatorRunner* runner, - const std::vector& detections) { - auto inputs = ::absl::make_unique>(); - // A face with landmarks. - for (const auto& detection : detections) { - inputs->push_back(ParseTextProtoOrDie(detection)); - } - runner->MutableInputs() - ->Tag(kDetectionsTag) - .packets.push_back(Adopt(inputs.release()).At(Timestamp::PostStream())); -} - -CalculatorGraphConfig::Node MakeConfig(bool output_standard, bool output_all) { - auto config = ParseTextProtoOrDie(kConfig); - - config.mutable_options() - ->MutableExtension(LocalizationToRegionCalculatorOptions::ext) - ->set_output_standard_signals(output_standard); - - config.mutable_options() - ->MutableExtension(LocalizationToRegionCalculatorOptions::ext) - ->set_output_all_signals(output_all); - - return config; -} - -TEST(LocalizationToRegionCalculatorTest, StandardTypes) { - // Setup test - auto runner = ::absl::make_unique(MakeConfig(true, false)); - SetInputs(runner.get(), {kCar, kDog, kZebra}); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(2, regions.detections().size()); - const auto& detection = regions.detections(0); - EXPECT_EQ(detection.signal_type().standard(), SignalType::CAR); - EXPECT_FLOAT_EQ(detection.location_normalized().x(), -0.00375); - EXPECT_FLOAT_EQ(detection.location_normalized().y(), 0.003333); - EXPECT_FLOAT_EQ(detection.location_normalized().width(), 0.125); - EXPECT_FLOAT_EQ(detection.location_normalized().height(), 0.33333); - const auto& detection_1 = regions.detections(1); - EXPECT_EQ(detection_1.signal_type().standard(), SignalType::PET); - EXPECT_FLOAT_EQ(detection_1.location_normalized().x(), 0.0025); - EXPECT_FLOAT_EQ(detection_1.location_normalized().y(), 0.005); - EXPECT_FLOAT_EQ(detection_1.location_normalized().width(), 0.25); - EXPECT_FLOAT_EQ(detection_1.location_normalized().height(), 0.5); -} - -TEST(LocalizationToRegionCalculatorTest, AllTypes) { - // Setup test - auto runner = ::absl::make_unique(MakeConfig(false, true)); - SetInputs(runner.get(), {kCar, kDog, kZebra}); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(3, regions.detections().size()); -} - -TEST(LocalizationToRegionCalculatorTest, BothTypes) { - // Setup test - auto runner = ::absl::make_unique(MakeConfig(true, true)); - SetInputs(runner.get(), {kCar, kDog, kZebra}); - - // Run the calculator. - MP_ASSERT_OK(runner->Run()); - - // Check the output regions. - const std::vector& output_packets = - runner->Outputs().Tag(kRegionsTag).packets; - ASSERT_EQ(1, output_packets.size()); - const auto& regions = output_packets[0].Get(); - ASSERT_EQ(5, regions.detections().size()); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/scene_cropping_calculator.cc b/examples/desktop/autoflip/calculators/scene_cropping_calculator.cc deleted file mode 100644 index 89170dc..0000000 --- a/examples/desktop/autoflip/calculators/scene_cropping_calculator.cc +++ /dev/null @@ -1,822 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/calculators/scene_cropping_calculator.h" - -#include - -#include "absl/memory/memory.h" -#include "absl/strings/str_format.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/scene_cropping_viz.h" -#include "mediapipe/examples/desktop/autoflip/quality/utils.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/canonical_errors.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/timestamp.h" - -namespace mediapipe { -namespace autoflip { - -constexpr char kInputVideoFrames[] = "VIDEO_FRAMES"; -constexpr char kInputVideoSize[] = "VIDEO_SIZE"; -constexpr char kInputKeyFrames[] = "KEY_FRAMES"; -constexpr char kInputDetections[] = "DETECTION_FEATURES"; -constexpr char kInputStaticFeatures[] = "STATIC_FEATURES"; -constexpr char kInputShotBoundaries[] = "SHOT_BOUNDARIES"; -constexpr char kInputExternalSettings[] = "EXTERNAL_SETTINGS"; -// This side packet must be used in conjunction with -// TargetSizeType::MAXIMIZE_TARGET_DIMENSION -constexpr char kAspectRatio[] = "EXTERNAL_ASPECT_RATIO"; - -// Output the cropped frames, as well as visualization of crop regions and focus -// points. Note that, KEY_FRAME_CROP_REGION_VIZ_FRAMES and -// SALIENT_POINT_FRAME_VIZ_FRAMES can only be enabled when CROPPED_FRAMES is -// enabled. -constexpr char kOutputCroppedFrames[] = "CROPPED_FRAMES"; -// Shows detections on key frames. Any static borders will be removed from the -// output frame. -constexpr char kOutputKeyFrameCropViz[] = "KEY_FRAME_CROP_REGION_VIZ_FRAMES"; -// Shows x/y (raw unsmoothed) cropping and focus points. Any static borders -// will be removed from the output frame. -constexpr char kOutputFocusPointFrameViz[] = "SALIENT_POINT_FRAME_VIZ_FRAMES"; -// Shows final smoothed cropping and a focused area of the camera. Any static -// borders will remain and be shown in grey. Output frame will match input -// frame size. -constexpr char kOutputFramingAndDetections[] = "FRAMING_DETECTIONS_VIZ_FRAMES"; -// Final summary of cropping. -constexpr char kOutputSummary[] = "CROPPING_SUMMARY"; - -// External rendering outputs -constexpr char kExternalRenderingPerFrame[] = "EXTERNAL_RENDERING_PER_FRAME"; -constexpr char kExternalRenderingFullVid[] = "EXTERNAL_RENDERING_FULL_VID"; - -absl::Status SceneCroppingCalculator::GetContract( - mediapipe::CalculatorContract* cc) { - if (cc->InputSidePackets().HasTag(kInputExternalSettings)) { - cc->InputSidePackets().Tag(kInputExternalSettings).Set(); - } - if (cc->InputSidePackets().HasTag(kAspectRatio)) { - cc->InputSidePackets().Tag(kAspectRatio).Set(); - } - if (cc->Inputs().HasTag(kInputVideoFrames)) { - cc->Inputs().Tag(kInputVideoFrames).Set(); - } - if (cc->Inputs().HasTag(kInputVideoSize)) { - cc->Inputs().Tag(kInputVideoSize).Set>(); - } - if (cc->Inputs().HasTag(kInputKeyFrames)) { - cc->Inputs().Tag(kInputKeyFrames).Set(); - } - cc->Inputs().Tag(kInputDetections).Set(); - if (cc->Inputs().HasTag(kInputStaticFeatures)) { - cc->Inputs().Tag(kInputStaticFeatures).Set(); - } - if (cc->Inputs().HasTag(kInputShotBoundaries)) { - cc->Inputs().Tag(kInputShotBoundaries).Set(); - } - - if (cc->Outputs().HasTag(kOutputCroppedFrames)) { - cc->Outputs().Tag(kOutputCroppedFrames).Set(); - } - if (cc->Outputs().HasTag(kOutputKeyFrameCropViz)) { - RET_CHECK(cc->Outputs().HasTag(kOutputCroppedFrames)) - << "KEY_FRAME_CROP_REGION_VIZ_FRAMES can only be used when " - "CROPPED_FRAMES is specified."; - cc->Outputs().Tag(kOutputKeyFrameCropViz).Set(); - } - if (cc->Outputs().HasTag(kOutputFramingAndDetections)) { - RET_CHECK(cc->Outputs().HasTag(kOutputCroppedFrames)) - << "FRAMING_DETECTIONS_VIZ_FRAMES can only be used when " - "CROPPED_FRAMES is specified."; - cc->Outputs().Tag(kOutputFramingAndDetections).Set(); - } - if (cc->Outputs().HasTag(kOutputFocusPointFrameViz)) { - RET_CHECK(cc->Outputs().HasTag(kOutputCroppedFrames)) - << "SALIENT_POINT_FRAME_VIZ_FRAMES can only be used when " - "CROPPED_FRAMES is specified."; - cc->Outputs().Tag(kOutputFocusPointFrameViz).Set(); - } - if (cc->Outputs().HasTag(kOutputSummary)) { - cc->Outputs().Tag(kOutputSummary).Set(); - } - if (cc->Outputs().HasTag(kExternalRenderingPerFrame)) { - cc->Outputs().Tag(kExternalRenderingPerFrame).Set(); - } - if (cc->Outputs().HasTag(kExternalRenderingFullVid)) { - cc->Outputs() - .Tag(kExternalRenderingFullVid) - .Set>(); - } - RET_CHECK(cc->Inputs().HasTag(kInputVideoFrames) ^ - cc->Inputs().HasTag(kInputVideoSize)) - << "VIDEO_FRAMES or VIDEO_SIZE must be set and not both."; - RET_CHECK(!(cc->Inputs().HasTag(kInputVideoSize) && - cc->Inputs().HasTag(kOutputCroppedFrames))) - << "CROPPED_FRAMES (internal cropping) has been set as an output without " - "VIDEO_FRAMES (video data) input."; - RET_CHECK(cc->Outputs().HasTag(kExternalRenderingPerFrame) || - cc->Outputs().HasTag(kExternalRenderingFullVid) || - cc->Outputs().HasTag(kOutputCroppedFrames)) - << "At leaset one output stream must be specified"; - return absl::OkStatus(); -} - -absl::Status SceneCroppingCalculator::Open(CalculatorContext* cc) { - options_ = cc->Options(); - RET_CHECK_GT(options_.max_scene_size(), 0) - << "Maximum scene size is non-positive."; - RET_CHECK_GE(options_.prior_frame_buffer_size(), 0) - << "Prior frame buffer size is negative."; - - RET_CHECK(options_.solid_background_frames_padding_fraction() >= 0.0 && - options_.solid_background_frames_padding_fraction() <= 1.0) - << "Solid background frames padding fraction is not in [0, 1]."; - const auto& padding_params = options_.padding_parameters(); - background_contrast_ = padding_params.background_contrast(); - RET_CHECK(background_contrast_ >= 0.0 && background_contrast_ <= 1.0) - << "Background contrast " << background_contrast_ << " is not in [0, 1]."; - blur_cv_size_ = padding_params.blur_cv_size(); - RET_CHECK_GT(blur_cv_size_, 0) << "Blur cv size is non-positive."; - overlay_opacity_ = padding_params.overlay_opacity(); - RET_CHECK(overlay_opacity_ >= 0.0 && overlay_opacity_ <= 1.0) - << "Overlay opacity " << overlay_opacity_ << " is not in [0, 1]."; - - // Set default camera model to polynomial_path_solver. - if (!options_.camera_motion_options().has_kinematic_options()) { - options_.mutable_camera_motion_options() - ->mutable_polynomial_path_solver() - ->set_prior_frame_buffer_size(options_.prior_frame_buffer_size()); - } - if (cc->Outputs().HasTag(kOutputSummary)) { - summary_ = absl::make_unique(); - } - if (cc->Outputs().HasTag(kExternalRenderingFullVid)) { - external_render_list_ = - absl::make_unique>(); - } - should_perform_frame_cropping_ = cc->Outputs().HasTag(kOutputCroppedFrames); - scene_camera_motion_analyzer_ = absl::make_unique( - options_.scene_camera_motion_analyzer_options()); - return absl::OkStatus(); -} - -namespace { -absl::Status ParseAspectRatioString(const std::string& aspect_ratio_string, - double* aspect_ratio) { - std::string error_msg = - "Aspect ratio string must be in the format of 'width:height', e.g. " - "'1:1' or '5:4', your input was " + - aspect_ratio_string; - auto pos = aspect_ratio_string.find(':'); - RET_CHECK(pos != std::string::npos) << error_msg; - double width_ratio; - RET_CHECK(absl::SimpleAtod(aspect_ratio_string.substr(0, pos), &width_ratio)) - << error_msg; - double height_ratio; - RET_CHECK(absl::SimpleAtod( - aspect_ratio_string.substr(pos + 1, aspect_ratio_string.size()), - &height_ratio)) - << error_msg; - *aspect_ratio = width_ratio / height_ratio; - return absl::OkStatus(); -} -void ConstructExternalRenderMessage( - const cv::Rect& crop_from_location, const cv::Rect& render_to_location, - const cv::Scalar& padding_color, const uint64 timestamp_us, - ExternalRenderFrame* external_render_message) { - auto crop_from_message = - external_render_message->mutable_crop_from_location(); - crop_from_message->set_x(crop_from_location.x); - crop_from_message->set_y(crop_from_location.y); - crop_from_message->set_width(crop_from_location.width); - crop_from_message->set_height(crop_from_location.height); - auto render_to_message = - external_render_message->mutable_render_to_location(); - render_to_message->set_x(render_to_location.x); - render_to_message->set_y(render_to_location.y); - render_to_message->set_width(render_to_location.width); - render_to_message->set_height(render_to_location.height); - auto padding_color_message = external_render_message->mutable_padding_color(); - padding_color_message->set_r(padding_color[0]); - padding_color_message->set_g(padding_color[1]); - padding_color_message->set_b(padding_color[2]); - external_render_message->set_timestamp_us(timestamp_us); -} - -double GetRatio(int width, int height) { - return static_cast(width) / height; -} - -int RoundToEven(float value) { - int rounded_value = std::round(value); - if (rounded_value % 2 == 1) { - rounded_value = std::max(2, rounded_value - 1); - } - return rounded_value; -} - -} // namespace - -absl::Status SceneCroppingCalculator::InitializeSceneCroppingCalculator( - mediapipe::CalculatorContext* cc) { - if (cc->Inputs().HasTag(kInputVideoFrames)) { - const auto& frame = cc->Inputs().Tag(kInputVideoFrames).Get(); - frame_width_ = frame.Width(); - frame_height_ = frame.Height(); - frame_format_ = frame.Format(); - } else if (cc->Inputs().HasTag(kInputVideoSize)) { - frame_width_ = - cc->Inputs().Tag(kInputVideoSize).Get>().first; - frame_height_ = - cc->Inputs().Tag(kInputVideoSize).Get>().second; - } else { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "Input VIDEO or VIDEO_SIZE must be provided."; - } - RET_CHECK_GT(frame_height_, 0) << "Input frame height is non-positive."; - RET_CHECK_GT(frame_width_, 0) << "Input frame width is non-positive."; - - // Calculate target width and height. - switch (options_.target_size_type()) { - case SceneCroppingCalculatorOptions::KEEP_ORIGINAL_HEIGHT: - RET_CHECK(options_.has_target_width() && options_.has_target_height()) - << "Target width and height have to be specified."; - target_height_ = RoundToEven(frame_height_); - target_width_ = - RoundToEven(target_height_ * GetRatio(options_.target_width(), - options_.target_height())); - break; - case SceneCroppingCalculatorOptions::KEEP_ORIGINAL_WIDTH: - RET_CHECK(options_.has_target_width() && options_.has_target_height()) - << "Target width and height have to be specified."; - target_width_ = RoundToEven(frame_width_); - target_height_ = - RoundToEven(target_width_ / GetRatio(options_.target_width(), - options_.target_height())); - break; - case SceneCroppingCalculatorOptions::MAXIMIZE_TARGET_DIMENSION: { - RET_CHECK(cc->InputSidePackets().HasTag(kAspectRatio)) - << "MAXIMIZE_TARGET_DIMENSION is set without an " - "external_aspect_ratio"; - double requested_aspect_ratio; - MP_RETURN_IF_ERROR(ParseAspectRatioString( - cc->InputSidePackets().Tag(kAspectRatio).Get(), - &requested_aspect_ratio)); - const double original_aspect_ratio = - GetRatio(frame_width_, frame_height_); - if (original_aspect_ratio > requested_aspect_ratio) { - target_height_ = RoundToEven(frame_height_); - target_width_ = RoundToEven(target_height_ * requested_aspect_ratio); - } else { - target_width_ = RoundToEven(frame_width_); - target_height_ = RoundToEven(target_width_ / requested_aspect_ratio); - } - break; - } - case SceneCroppingCalculatorOptions::USE_TARGET_DIMENSION: - RET_CHECK(options_.has_target_width() && options_.has_target_height()) - << "Target width and height have to be specified."; - target_width_ = options_.target_width(); - target_height_ = options_.target_height(); - break; - case SceneCroppingCalculatorOptions::KEEP_ORIGINAL_DIMENSION: - target_width_ = frame_width_; - target_height_ = frame_height_; - break; - case SceneCroppingCalculatorOptions::UNKNOWN: - return absl::InvalidArgumentError("target_size_type not set properly."); - } - target_aspect_ratio_ = GetRatio(target_width_, target_height_); - - // Set keyframe width/height for feature upscaling. - RET_CHECK(!(cc->Inputs().HasTag(kInputKeyFrames) && - (options_.has_video_features_width() || - options_.has_video_features_height()))) - << "Key frame size must be defined by either providing the input stream " - "KEY_FRAMES or setting video_features_width/video_features_height as " - "calculator options. Both methods cannot be used together."; - if (options_.has_video_features_width() && - options_.has_video_features_height()) { - key_frame_width_ = options_.video_features_width(); - key_frame_height_ = options_.video_features_height(); - } else if (!cc->Inputs().HasTag(kInputKeyFrames)) { - key_frame_width_ = frame_width_; - key_frame_height_ = frame_height_; - } - // Check provided dimensions. - RET_CHECK_GT(target_width_, 0) << "Target width is non-positive."; - // TODO: it seems this check is too strict and maybe limiting, - // considering the receiver of frames can be something other than encoder. - RET_CHECK_NE(target_width_ % 2, 1) - << "Target width cannot be odd, because encoder expects dimension " - "values to be even."; - RET_CHECK_GT(target_height_, 0) << "Target height is non-positive."; - RET_CHECK_NE(target_height_ % 2, 1) - << "Target height cannot be odd, because encoder expects dimension " - "values to be even."; - - scene_cropper_ = absl::make_unique( - options_.camera_motion_options(), frame_width_, frame_height_); - - return absl::OkStatus(); -} - -bool HasFrameSignal(mediapipe::CalculatorContext* cc) { - if (cc->Inputs().HasTag(kInputVideoFrames)) { - return !cc->Inputs().Tag(kInputVideoFrames).Value().IsEmpty(); - } - return !cc->Inputs().Tag(kInputVideoSize).Value().IsEmpty(); -} - -absl::Status SceneCroppingCalculator::Process( - mediapipe::CalculatorContext* cc) { - // Sets frame dimension and initializes scenecroppingcalculator on first video - // frame. - if (frame_width_ < 0) { - MP_RETURN_IF_ERROR(InitializeSceneCroppingCalculator(cc)); - } - - // Sets key frame dimension on first keyframe. - if (cc->Inputs().HasTag(kInputKeyFrames) && - !cc->Inputs().Tag(kInputKeyFrames).Value().IsEmpty() && - key_frame_width_ < 0) { - const auto& key_frame = cc->Inputs().Tag(kInputKeyFrames).Get(); - key_frame_width_ = key_frame.Width(); - key_frame_height_ = key_frame.Height(); - } - - // Processes a scene when shot boundary or buffer is full. - bool is_end_of_scene = false; - if (cc->Inputs().HasTag(kInputShotBoundaries) && - !cc->Inputs().Tag(kInputShotBoundaries).Value().IsEmpty()) { - is_end_of_scene = cc->Inputs().Tag(kInputShotBoundaries).Get(); - } - - if (!scene_frame_timestamps_.empty() && (is_end_of_scene)) { - continue_last_scene_ = false; - MP_RETURN_IF_ERROR(ProcessScene(is_end_of_scene, cc)); - } - - // Saves frame and timestamp and whether it is a key frame. - if (HasFrameSignal(cc)) { - // Only buffer frames if |should_perform_frame_cropping_| is true. - if (should_perform_frame_cropping_) { - const auto& frame = cc->Inputs().Tag(kInputVideoFrames).Get(); - const cv::Mat frame_mat = formats::MatView(&frame); - cv::Mat copy_mat; - frame_mat.copyTo(copy_mat); - scene_frames_or_empty_.push_back(copy_mat); - } - scene_frame_timestamps_.push_back(cc->InputTimestamp().Value()); - is_key_frames_.push_back( - !cc->Inputs().Tag(kInputDetections).Value().IsEmpty()); - } - - // Packs key frame info. - if (!cc->Inputs().Tag(kInputDetections).Value().IsEmpty()) { - const auto& detections = - cc->Inputs().Tag(kInputDetections).Get(); - KeyFrameInfo key_frame_info; - MP_RETURN_IF_ERROR(PackKeyFrameInfo( - cc->InputTimestamp().Value(), detections, frame_width_, frame_height_, - key_frame_width_, key_frame_height_, &key_frame_info)); - key_frame_infos_.push_back(key_frame_info); - } - - // Buffers static features. - if (cc->Inputs().HasTag(kInputStaticFeatures) && - !cc->Inputs().Tag(kInputStaticFeatures).Value().IsEmpty()) { - static_features_.push_back( - cc->Inputs().Tag(kInputStaticFeatures).Get()); - static_features_timestamps_.push_back(cc->InputTimestamp().Value()); - } - - const bool force_buffer_flush = - scene_frame_timestamps_.size() >= options_.max_scene_size(); - if (!scene_frame_timestamps_.empty() && force_buffer_flush) { - MP_RETURN_IF_ERROR(ProcessScene(is_end_of_scene, cc)); - continue_last_scene_ = true; - } - - return absl::OkStatus(); -} - -absl::Status SceneCroppingCalculator::Close(mediapipe::CalculatorContext* cc) { - if (!scene_frame_timestamps_.empty()) { - MP_RETURN_IF_ERROR(ProcessScene(/* is_end_of_scene = */ true, cc)); - } - if (cc->Outputs().HasTag(kOutputSummary)) { - cc->Outputs() - .Tag(kOutputSummary) - .Add(summary_.release(), Timestamp::PostStream()); - } - if (cc->Outputs().HasTag(kExternalRenderingFullVid)) { - cc->Outputs() - .Tag(kExternalRenderingFullVid) - .Add(external_render_list_.release(), Timestamp::PostStream()); - } - return absl::OkStatus(); -} - -// TODO: split this function into two, one for calculating the border -// sizes, the other for the actual removal of borders from the frames. -absl::Status SceneCroppingCalculator::RemoveStaticBorders( - CalculatorContext* cc, int* top_border_size, int* bottom_border_size) { - *top_border_size = 0; - *bottom_border_size = 0; - MP_RETURN_IF_ERROR(ComputeSceneStaticBordersSize( - static_features_, top_border_size, bottom_border_size)); - const double scale = static_cast(frame_height_) / key_frame_height_; - top_border_distance_ = std::round(scale * *top_border_size); - const int bottom_border_distance = std::round(scale * *bottom_border_size); - effective_frame_height_ = - frame_height_ - top_border_distance_ - bottom_border_distance; - - // Store shallow copy of the original frames for debug display if required - // before static areas are removed. - if (cc->Outputs().HasTag(kOutputFramingAndDetections)) { - raw_scene_frames_or_empty_ = {scene_frames_or_empty_.begin(), - scene_frames_or_empty_.end()}; - } - - if (top_border_distance_ > 0 || bottom_border_distance > 0) { - VLOG(1) << "Remove top border " << top_border_distance_ << " bottom border " - << bottom_border_distance; - // Remove borders from frames. - cv::Rect roi(0, top_border_distance_, frame_width_, - effective_frame_height_); - for (int i = 0; i < scene_frames_or_empty_.size(); ++i) { - cv::Mat tmp; - scene_frames_or_empty_[i](roi).copyTo(tmp); - scene_frames_or_empty_[i] = tmp; - } - // Adjust detection bounding boxes. - for (int i = 0; i < key_frame_infos_.size(); ++i) { - DetectionSet adjusted_detections; - const auto& detections = key_frame_infos_[i].detections(); - for (int j = 0; j < detections.detections_size(); ++j) { - const auto& detection = detections.detections(j); - SalientRegion adjusted_detection = detection; - // Clamp the box to be within the de-bordered frame. - if (!ClampRect(0, top_border_distance_, frame_width_, - top_border_distance_ + effective_frame_height_, - adjusted_detection.mutable_location()) - .ok()) { - continue; - } - // Offset the y position. - adjusted_detection.mutable_location()->set_y( - adjusted_detection.location().y() - top_border_distance_); - *adjusted_detections.add_detections() = adjusted_detection; - } - *key_frame_infos_[i].mutable_detections() = adjusted_detections; - } - } - return absl::OkStatus(); -} - -absl::Status SceneCroppingCalculator::InitializeFrameCropRegionComputer() { - key_frame_crop_options_ = options_.key_frame_crop_options(); - MP_RETURN_IF_ERROR( - SetKeyFrameCropTarget(frame_width_, effective_frame_height_, - target_aspect_ratio_, &key_frame_crop_options_)); - VLOG(1) << "Target width " << key_frame_crop_options_.target_width(); - VLOG(1) << "Target height " << key_frame_crop_options_.target_height(); - frame_crop_region_computer_ = - absl::make_unique(key_frame_crop_options_); - return absl::OkStatus(); -} - -void SceneCroppingCalculator::FilterKeyFrameInfo() { - if (!options_.user_hint_override()) { - return; - } - std::vector user_hints_only; - bool has_user_hints = false; - for (auto key_frame : key_frame_infos_) { - DetectionSet user_hint_only_set; - for (const auto& detection : key_frame.detections().detections()) { - if (detection.signal_type().has_standard() && - detection.signal_type().standard() == SignalType::USER_HINT) { - *user_hint_only_set.add_detections() = detection; - has_user_hints = true; - } - } - *key_frame.mutable_detections() = user_hint_only_set; - user_hints_only.push_back(key_frame); - } - if (has_user_hints) { - key_frame_infos_ = user_hints_only; - } -} - -absl::Status SceneCroppingCalculator::ProcessScene(const bool is_end_of_scene, - CalculatorContext* cc) { - // Removes detections under special circumstances. - FilterKeyFrameInfo(); - - // Removes any static borders. - int top_static_border_size, bottom_static_border_size; - MP_RETURN_IF_ERROR(RemoveStaticBorders(cc, &top_static_border_size, - &bottom_static_border_size)); - - // Decides if solid background color padding is possible and sets up color - // interpolation functions in CIELAB. Uses linear interpolation by default. - MP_RETURN_IF_ERROR(FindSolidBackgroundColor( - static_features_, static_features_timestamps_, - options_.solid_background_frames_padding_fraction(), - &has_solid_background_, &background_color_l_function_, - &background_color_a_function_, &background_color_b_function_)); - - // Computes key frame crop regions and moves information from raw - // key_frame_infos_ to key_frame_crop_results. - MP_RETURN_IF_ERROR(InitializeFrameCropRegionComputer()); - const int num_key_frames = key_frame_infos_.size(); - std::vector key_frame_crop_results(num_key_frames); - for (int i = 0; i < num_key_frames; ++i) { - MP_RETURN_IF_ERROR(frame_crop_region_computer_->ComputeFrameCropRegion( - key_frame_infos_[i], &key_frame_crop_results[i])); - } - - SceneKeyFrameCropSummary scene_summary; - std::vector focus_point_frames; - SceneCameraMotion scene_camera_motion; - MP_RETURN_IF_ERROR( - scene_camera_motion_analyzer_->AnalyzeSceneAndPopulateFocusPointFrames( - key_frame_crop_options_, key_frame_crop_results, frame_width_, - effective_frame_height_, scene_frame_timestamps_, - has_solid_background_, &scene_summary, &focus_point_frames, - &scene_camera_motion)); - - // Crops scene frames. - std::vector cropped_frames; - std::vector crop_from_locations; - - auto* cropped_frames_ptr = - should_perform_frame_cropping_ ? &cropped_frames : nullptr; - - MP_RETURN_IF_ERROR(scene_cropper_->CropFrames( - scene_summary, scene_frame_timestamps_, is_key_frames_, - scene_frames_or_empty_, focus_point_frames, prior_focus_point_frames_, - top_static_border_size, bottom_static_border_size, continue_last_scene_, - &crop_from_locations, cropped_frames_ptr)); - - // Formats and outputs cropped frames. - bool apply_padding = false; - float vertical_fill_percent; - std::vector render_to_locations; - std::vector padding_colors; - MP_RETURN_IF_ERROR(FormatAndOutputCroppedFrames( - scene_summary.crop_window_width(), scene_summary.crop_window_height(), - scene_frame_timestamps_.size(), &render_to_locations, &apply_padding, - &padding_colors, &vertical_fill_percent, cropped_frames_ptr, cc)); - // Caches prior FocusPointFrames if this was not the end of a scene. - prior_focus_point_frames_.clear(); - if (!is_end_of_scene) { - const int start = - std::max(0, static_cast(scene_frame_timestamps_.size()) - - options_.camera_motion_options() - .polynomial_path_solver() - .prior_frame_buffer_size()); - for (int i = start; i < num_key_frames; ++i) { - prior_focus_point_frames_.push_back(focus_point_frames[i]); - } - } - - // Optionally outputs visualization frames. - MP_RETURN_IF_ERROR(OutputVizFrames(key_frame_crop_results, focus_point_frames, - crop_from_locations, - scene_summary.crop_window_width(), - scene_summary.crop_window_height(), cc)); - - const double start_sec = Timestamp(scene_frame_timestamps_.front()).Seconds(); - const double end_sec = Timestamp(scene_frame_timestamps_.back()).Seconds(); - VLOG(1) << absl::StrFormat("Processed a scene from %.2f sec to %.2f sec", - start_sec, end_sec); - - // Optionally makes summary. - if (cc->Outputs().HasTag(kOutputSummary)) { - auto* scene_summary = summary_->add_scene_summaries(); - scene_summary->set_start_sec(start_sec); - scene_summary->set_end_sec(end_sec); - *(scene_summary->mutable_camera_motion()) = scene_camera_motion; - scene_summary->set_is_end_of_scene(is_end_of_scene); - scene_summary->set_is_padded(apply_padding); - } - - if (cc->Outputs().HasTag(kExternalRenderingPerFrame)) { - for (int i = 0; i < scene_frame_timestamps_.size(); i++) { - auto external_render_message = absl::make_unique(); - ConstructExternalRenderMessage( - crop_from_locations[i], render_to_locations[i], padding_colors[i], - scene_frame_timestamps_[i], external_render_message.get()); - cc->Outputs() - .Tag(kExternalRenderingPerFrame) - .Add(external_render_message.release(), - Timestamp(scene_frame_timestamps_[i])); - } - } - - if (cc->Outputs().HasTag(kExternalRenderingFullVid)) { - for (int i = 0; i < scene_frame_timestamps_.size(); i++) { - ExternalRenderFrame render_frame; - ConstructExternalRenderMessage(crop_from_locations[i], - render_to_locations[i], padding_colors[i], - scene_frame_timestamps_[i], &render_frame); - external_render_list_->push_back(render_frame); - } - } - - key_frame_infos_.clear(); - scene_frames_or_empty_.clear(); - scene_frame_timestamps_.clear(); - is_key_frames_.clear(); - static_features_.clear(); - static_features_timestamps_.clear(); - return absl::OkStatus(); -} - -absl::Status SceneCroppingCalculator::FormatAndOutputCroppedFrames( - const int crop_width, const int crop_height, const int num_frames, - std::vector* render_to_locations, bool* apply_padding, - std::vector* padding_colors, float* vertical_fill_percent, - const std::vector* cropped_frames_ptr, CalculatorContext* cc) { - RET_CHECK(apply_padding) << "Has padding boolean is null."; - - // Computes scaling factor and decides if padding is needed. - VLOG(1) << "crop_width = " << crop_width << " crop_height = " << crop_height; - const double scaling = - std::max(static_cast(target_width_) / crop_width, - static_cast(target_height_) / crop_height); - int scaled_width = std::round(scaling * crop_width); - int scaled_height = std::round(scaling * crop_height); - RET_CHECK_GE(scaled_width, target_width_) - << "Scaled width is less than target width - something is wrong."; - RET_CHECK_GE(scaled_height, target_height_) - << "Scaled height is less than target height - something is wrong."; - if (scaled_width - target_width_ <= 1) scaled_width = target_width_; - if (scaled_height - target_height_ <= 1) scaled_height = target_height_; - *apply_padding = - scaled_width != target_width_ || scaled_height != target_height_; - *vertical_fill_percent = scaled_height / static_cast(target_height_); - if (*apply_padding) { - padder_ = absl::make_unique( - scaled_width, scaled_height, target_aspect_ratio_); - VLOG(1) << "Scene is padded: scaled width = " << scaled_width - << " target width = " << target_width_ - << " scaled height = " << scaled_height - << " target height = " << target_height_; - } - - // Compute the "render to" location. This is where the rect taken from the - // input video gets pasted on the output frame. For use with external - // rendering solutions. - for (int i = 0; i < num_frames; i++) { - if (*apply_padding) { - render_to_locations->push_back(padder_->ComputeOutputLocation()); - } else { - render_to_locations->push_back( - cv::Rect(0, 0, target_width_, target_height_)); - } - } - - // Compute padding colors. - for (int i = 0; i < num_frames; ++i) { - // Set default padding color to white. - cv::Scalar padding_color_to_add = cv::Scalar(255, 255, 255); - const int64 time_ms = scene_frame_timestamps_[i]; - if (*apply_padding) { - if (has_solid_background_) { - double lab[3]; - lab[0] = background_color_l_function_.Evaluate(time_ms); - lab[1] = background_color_a_function_.Evaluate(time_ms); - lab[2] = background_color_b_function_.Evaluate(time_ms); - cv::Mat3f lab_mat(1, 1, cv::Vec3f(lab[0], lab[1], lab[2])); - cv::Mat3f rgb_mat(1, 1); - // Necessary scaling of the RGB values from [0, 1] to [0, 255] based on: - // https://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#cvtcolor - cv::cvtColor(lab_mat, rgb_mat, cv::COLOR_Lab2RGB); - rgb_mat *= 255.0; - auto k = rgb_mat.at(0, 0); - k[0] = k[0] < 0.0 ? 0.0 : k[0] > 255.0 ? 255.0 : k[0]; - k[1] = k[1] < 0.0 ? 0.0 : k[1] > 255.0 ? 255.0 : k[1]; - k[2] = k[2] < 0.0 ? 0.0 : k[2] > 255.0 ? 255.0 : k[2]; - cv::Scalar interpolated_color = - cv::Scalar(std::round(k[0]), std::round(k[1]), std::round(k[2])); - padding_color_to_add = interpolated_color; - } - } - padding_colors->push_back(padding_color_to_add); - } - if (!cropped_frames_ptr) { - return absl::OkStatus(); - } - - // Resizes cropped frames, pads frames, and output frames. - for (int i = 0; i < num_frames; ++i) { - const int64 time_ms = scene_frame_timestamps_[i]; - const Timestamp timestamp(time_ms); - auto scaled_frame = absl::make_unique( - frame_format_, scaled_width, scaled_height); - auto destination = formats::MatView(scaled_frame.get()); - if (scaled_width == crop_width && scaled_height == crop_height) { - cropped_frames_ptr->at(i).copyTo(destination); - } else { - // cubic is better quality for upscaling and area is good for - // downscaling - const int interpolation_method = - scaling > 1 ? cv::INTER_CUBIC : cv::INTER_AREA; - cv::resize(cropped_frames_ptr->at(i), destination, destination.size(), 0, - 0, interpolation_method); - } - if (*apply_padding) { - cv::Scalar* background_color = nullptr; - if (has_solid_background_) { - background_color = &padding_colors->at(i); - } - auto padded_frame = absl::make_unique(); - MP_RETURN_IF_ERROR(padder_->Process( - *scaled_frame, background_contrast_, - std::min({blur_cv_size_, scaled_width, scaled_height}), - overlay_opacity_, padded_frame.get(), background_color)); - RET_CHECK_EQ(padded_frame->Width(), target_width_) - << "Padded frame width is off."; - RET_CHECK_EQ(padded_frame->Height(), target_height_) - << "Padded frame height is off."; - cc->Outputs() - .Tag(kOutputCroppedFrames) - .Add(padded_frame.release(), timestamp); - } else { - cc->Outputs() - .Tag(kOutputCroppedFrames) - .Add(scaled_frame.release(), timestamp); - } - } - return absl::OkStatus(); -} - -absl::Status SceneCroppingCalculator::OutputVizFrames( - const std::vector& key_frame_crop_results, - const std::vector& focus_point_frames, - const std::vector& crop_from_locations, - const int crop_window_width, const int crop_window_height, - CalculatorContext* cc) const { - if (cc->Outputs().HasTag(kOutputKeyFrameCropViz)) { - std::vector> viz_frames; - MP_RETURN_IF_ERROR(DrawDetectionsAndCropRegions( - scene_frames_or_empty_, is_key_frames_, key_frame_infos_, - key_frame_crop_results, frame_format_, &viz_frames)); - for (int i = 0; i < scene_frames_or_empty_.size(); ++i) { - cc->Outputs() - .Tag(kOutputKeyFrameCropViz) - .Add(viz_frames[i].release(), Timestamp(scene_frame_timestamps_[i])); - } - } - if (cc->Outputs().HasTag(kOutputFocusPointFrameViz)) { - std::vector> viz_frames; - MP_RETURN_IF_ERROR(DrawFocusPointAndCropWindow( - scene_frames_or_empty_, focus_point_frames, - options_.viz_overlay_opacity(), crop_window_width, crop_window_height, - frame_format_, &viz_frames)); - for (int i = 0; i < scene_frames_or_empty_.size(); ++i) { - cc->Outputs() - .Tag(kOutputFocusPointFrameViz) - .Add(viz_frames[i].release(), Timestamp(scene_frame_timestamps_[i])); - } - } - if (cc->Outputs().HasTag(kOutputFramingAndDetections)) { - std::vector> viz_frames; - MP_RETURN_IF_ERROR(DrawDetectionAndFramingWindow( - raw_scene_frames_or_empty_, crop_from_locations, frame_format_, - options_.viz_overlay_opacity(), &viz_frames)); - for (int i = 0; i < raw_scene_frames_or_empty_.size(); ++i) { - cc->Outputs() - .Tag(kOutputFramingAndDetections) - .Add(viz_frames[i].release(), Timestamp(scene_frame_timestamps_[i])); - } - } - return absl::OkStatus(); -} - -REGISTER_CALCULATOR(SceneCroppingCalculator); - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/scene_cropping_calculator.h b/examples/desktop/autoflip/calculators/scene_cropping_calculator.h deleted file mode 100644 index 61b7b53..0000000 --- a/examples/desktop/autoflip/calculators/scene_cropping_calculator.h +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_CALCULATORS_SCENE_CROPPING_CALCULATOR_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_CALCULATORS_SCENE_CROPPING_CALCULATOR_H_ - -#include -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/scene_cropping_calculator.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/frame_crop_region_computer.h" -#include "mediapipe/examples/desktop/autoflip/quality/padding_effect_generator.h" -#include "mediapipe/examples/desktop/autoflip/quality/piecewise_linear_function.h" -#include "mediapipe/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h" -#include "mediapipe/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.h" -#include "mediapipe/examples/desktop/autoflip/quality/scene_cropper.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { -// This calculator crops video scenes to target size, which can be of any aspect -// ratio. The calculator supports both "landscape -> portrait", and "portrait -> -// landscape" use cases. The two use cases are automatically determined by -// comparing the input and output frame's aspect ratios internally. -// -// The target (i.e. output) frame's dimension can be specified through the -// target_width(height) fields in the options. Both this target dimension and -// the input dimension should be even. If either keep_original_height or -// keep_original_width is set to true, the corresponding target dimension will -// only be used to compute the aspect ratio (as opposed to setting the actual -// dimension) of the output. If the output frame thus computed has an odd -// size, it will be rounded down to an even number. -// -// The calculator takes shot boundary signals to identify shot boundaries, and -// crops each scene independently. The cropping decisions are made based on -// detection features, which are a collection of focus regions detected from -// different signals, and then fused together by a SignalFusingCalculator. To -// add a new type of focus signals, it should be added in the input of the -// SignalFusingCalculator, which can take an arbitrary number of input streams. -// -// If after attempting to cover focus regions based on the cropping decisions -// made, the retained frame region's aspect ratio is still different from the -// target aspect ratio, padding will be applied. In this case, a seamless -// padding with a solid color would be preferred wherever possible, given -// information from the input static features; otherwise, a simple padding with -// centered foreground on blurred background will be applied. -// -// The main complexity of this calculator lies in stabilizing crop regions over -// the scene using a Retargeter, which solves linear programming problems -// through a L1 path solver (default) or least squares problems through a L2 -// path solver. - -// Input streams: -// - required tag VIDEO_FRAMES (type ImageFrame): -// Original scene frames to be cropped. -// - required tag DETECTION_FEATURES (type DetectionSet): -// Detected features on the key frames. -// - optional tag STATIC_FEATURES (type StaticFeatures): -// Detected features on the key frames. -// - required tag SHOT_BOUNDARIES (type bool): -// Indicators for shot boundaries (output of shot boundary detection). -// - optional tag KEY_FRAMES (type ImageFrame): -// Key frames on which features are detected. This is only used to set the -// detection features frame size. Alternatively, set -// video_feature_width/video_features_height within the options proto to -// define this value. When neither is set, the features frame size is -// assumed to be the original scene frame size. -// -// Output streams: -// - required tag CROPPED_FRAMES (type ImageFrame): -// Cropped frames at target size and original frame rate. -// - optional tag KEY_FRAME_CROP_REGION_VIZ_FRAMES (type ImageFrame): -// Debug visualization frames at original frame size and frame rate. Draws -// the required (yellow) and non-required (cyan) detection features and the -// key frame crop regions (green). -// - optional tag SALIENT_POINT_FRAME_VIZ_FRAMES (type ImageFrame): -// Debug visualization frames at original frame size and frame rate. Draws -// the focus points and the scene crop window (red). -// - optional tag CROPPING_SUMMARY (type VideoCroppingSummary): -// Debug summary information for the video. Only generates one packet when -// calculator closes. -// - optional tag EXTERNAL_RENDERING_PER_FRAME (type ExternalRenderFrame) -// Provides a per-frame message that can be used to render autoflip using an -// external renderer. -// - optional tag EXTERNAL_RENDERING_FULL_VID (type Vector) -// Provides an end-stream message that can be used to render autoflip using -// an external renderer. -// -// Example config: -// node { -// calculator: "SceneCroppingCalculator" -// input_stream: "VIDEO_FRAMES:camera_frames_org" -// input_stream: "KEY_FRAMES:down_sampled_frames" -// input_stream: "DETECTION_FEATURES:focus_regions" -// input_stream: "STATIC_FEATURES:border_features" -// input_stream: "SHOT_BOUNDARIES:shot_boundary_frames" -// output_stream: "CROPPED_FRAMES:cropped_frames" -// options: { -// [mediapipe.SceneCroppingCalculatorOptions.ext]: { -// target_width: 720 -// target_height: 1124 -// target_size_type: USE_TARGET_DIMENSION -// } -// } -// } -// Note that only the target size is required in the options, and all other -// fields are optional with default settings. -class SceneCroppingCalculator : public CalculatorBase { - public: - static absl::Status GetContract(CalculatorContract* cc); - - // Validates calculator options and initializes SceneCameraMotionAnalyzer and - // SceneCropper. - absl::Status Open(CalculatorContext* cc) override; - - // Buffers each scene frame and its timestamp. Packs and stores KeyFrameInfo - // for key frames (a.k.a. frames with detection features). When a shot - // boundary is encountered or when the buffer is full, calls ProcessScene() - // to process the scene at once, and clears buffers. - absl::Status Process(CalculatorContext* cc) override; - - // Calls ProcessScene() on remaining buffered frames. Optionally outputs a - // VideoCroppingSummary if the output stream CROPPING_SUMMARY is present. - absl::Status Close(mediapipe::CalculatorContext* cc) override; - - private: - // Removes any static borders from the scene frames before cropping. The - // arguments |top_border_size| and |bottom_border_size| report the size of the - // removed borders. - absl::Status RemoveStaticBorders(CalculatorContext* cc, int* top_border_size, - int* bottom_border_size); - - // Sets up autoflip after first frame is received and input size is known. - absl::Status InitializeSceneCroppingCalculator( - mediapipe::CalculatorContext* cc); - // Initializes a FrameCropRegionComputer given input and target frame sizes. - absl::Status InitializeFrameCropRegionComputer(); - - // Processes a scene using buffered scene frames and KeyFrameInfos: - // 1. Computes key frame crop regions using a FrameCropRegionComputer. - // 2. Analyzes scene camera motion and generates FocusPointFrames using a - // SceneCameraMotionAnalyzer. - // 3. Crops scene frames using a SceneCropper (wrapper around Retargeter). - // 4. Formats and outputs cropped frames . - // 5. Caches prior FocusPointFrames if this is not the end of a scene (due - // to force flush). - // 6. Optionally outputs visualization frames. - // 7. Optionally updates cropping summary. - absl::Status ProcessScene(const bool is_end_of_scene, CalculatorContext* cc); - - // Formats and outputs the cropped frames passed in through - // |cropped_frames_ptr|. Scales them to be at least as big as the target - // size. If the aspect ratio is different, applies padding. Uses solid - // background from static features if possible, otherwise uses blurred - // background. Sets |apply_padding| to true if the scene is padded. Set - // |cropped_frames_ptr| to nullptr, to bypass the actual output of the - // cropped frames. This is useful when the calculator is only used for - // computing the cropping metadata rather than doing the actual cropping - // operation. - absl::Status FormatAndOutputCroppedFrames( - const int crop_width, const int crop_height, const int num_frames, - std::vector* render_to_locations, bool* apply_padding, - std::vector* padding_colors, float* vertical_fill_percent, - const std::vector* cropped_frames_ptr, CalculatorContext* cc); - - // Draws and outputs visualization frames if those streams are present. - absl::Status OutputVizFrames( - const std::vector& key_frame_crop_results, - const std::vector& focus_point_frames, - const std::vector& crop_from_locations, - const int crop_window_width, const int crop_window_height, - CalculatorContext* cc) const; - - // Filters detections based on USER_HINT under specific flag conditions. - void FilterKeyFrameInfo(); - - // Target frame size and aspect ratio passed in or computed from options. - int target_width_ = -1; - int target_height_ = -1; - double target_aspect_ratio_ = -1.0; - - // Input video frame size and format. - int frame_width_ = -1; - int frame_height_ = -1; - ImageFormat::Format frame_format_ = ImageFormat::UNKNOWN; - - // Key frame size (frame size for detections and border detections). - int key_frame_width_ = -1; - int key_frame_height_ = -1; - - // Calculator options. - SceneCroppingCalculatorOptions options_; - - // Buffered KeyFrameInfos for the current scene (size = number of key - // frames). - std::vector key_frame_infos_; - - // Buffered frames, timestamps, and indicators for key frames in the current - // scene (size = number of input video frames). - // Note: scene_frames_or_empty_ may be empty if the actual cropping - // operation of frames is turned off, e.g. when - // |should_perform_frame_cropping_| is false, so rely on - // scene_frame_timestamps_.size() to query the number of accumulated - // timestamps rather than scene_frames_or_empty_.size(). - // TODO: all of the following vectors are expected to be the same - // size. Add to struct and store together in one vector. - std::vector scene_frames_or_empty_; - std::vector raw_scene_frames_or_empty_; - std::vector scene_frame_timestamps_; - std::vector is_key_frames_; - - // Static border information for the scene. - int top_border_distance_ = -1; - int effective_frame_height_ = -1; - - // Stored FocusPointFrames from prior scene when there was no actual scene - // change (due to forced flush when buffer is full). - std::vector prior_focus_point_frames_; - // Indicates if this scene is a continuation of the last scene (due to - // forced flush when buffer is full). - bool continue_last_scene_ = false; - - // KeyFrameCropOptions used by the FrameCropRegionComputer. - KeyFrameCropOptions key_frame_crop_options_; - - // Object for computing key frame crop regions from detection features. - std::unique_ptr frame_crop_region_computer_ = - nullptr; - - // Object for analyzing scene camera motion from key frame crop regions and - // generating FocusPointFrames. - std::unique_ptr scene_camera_motion_analyzer_ = - nullptr; - - // Object for cropping a scene given FocusPointFrames. - std::unique_ptr scene_cropper_ = nullptr; - - // Buffered static features and their timestamps used in padding with solid - // background color (size = number of frames with static features). - std::vector static_features_; - std::vector static_features_timestamps_; - bool has_solid_background_ = false; - // CIELAB yields more natural color transitions than RGB and HSV: RGB tends - // to produce darker in-between colors and HSV can introduce new hues. See - // https://howaboutanorange.com/blog/2011/08/10/color_interpolation/ for - // visual comparisons of color transition in different spaces. - PiecewiseLinearFunction background_color_l_function_; // CIELAB - l - PiecewiseLinearFunction background_color_a_function_; // CIELAB - a - PiecewiseLinearFunction background_color_b_function_; // CIELAB - b - - // Parameters for padding with blurred background passed in from options. - float background_contrast_ = -1.0; - int blur_cv_size_ = -1; - float overlay_opacity_ = -1.0; - // Object for padding an image to a target aspect ratio. - std::unique_ptr padder_ = nullptr; - - // Optional diagnostic summary output emitted in Close(). - std::unique_ptr summary_ = nullptr; - - // Optional list of external rendering messages for each processed frame. - std::unique_ptr> external_render_list_; - - // Determines whether to perform real cropping on input frames. This flag is - // useful when the user only needs to compute cropping windows, in which - // case setting this flag to false can avoid buffering as well as cropping - // frames. This can significantly reduce memory usage and speed up - // processing. Some debugging visualization inevitably will be disabled - // because of this flag too. - bool should_perform_frame_cropping_ = false; -}; -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_CALCULATORS_SCENE_CROPPING_CALCULATOR_H_ diff --git a/examples/desktop/autoflip/calculators/scene_cropping_calculator.proto b/examples/desktop/autoflip/calculators/scene_cropping_calculator.proto deleted file mode 100644 index f9ba9cb..0000000 --- a/examples/desktop/autoflip/calculators/scene_cropping_calculator.proto +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/examples/desktop/autoflip/quality/cropping.proto"; -import "mediapipe/framework/calculator.proto"; - -// Options for the SceneCroppingCalculator. -message SceneCroppingCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional SceneCroppingCalculatorOptions ext = 284806831; - } - - // Target frame size - this has to be even (for ffmpeg encoding). - optional int32 target_width = 1; - optional int32 target_height = 2; - - // Choices for target size specification. - enum TargetSizeType { - // Unknown type (needed by ProtoBestPractices to ensure consistent behavior - // across proto2 and proto3). This type should not be used. - UNKNOWN = 0; - // Directly uses the target dimension given above. - USE_TARGET_DIMENSION = 1; - // Uses the target dimension to compute the target aspect ratio, but keeps - // original height/width. If the resulting size for the other dimension is - // odd, it is rounded down to an even size. - KEEP_ORIGINAL_HEIGHT = 2; - KEEP_ORIGINAL_WIDTH = 3; - // Used on conjuntion with external_aspect_ratio, create the largest sized - // output without upscaling the video. - MAXIMIZE_TARGET_DIMENSION = 4; - // Uses original dimensions to calculate aspect ratio. - KEEP_ORIGINAL_DIMENSION = 5; - } - optional TargetSizeType target_size_type = 3 [default = USE_TARGET_DIMENSION]; - - // Forces a flush of the frame buffer after this number of frames even if - // there is not a shot boundary. - optional int32 max_scene_size = 4 [default = 600]; - - // Number of frames from prior buffer to be used to smooth out camera - // trajectory when it was a forced flush. - optional int32 prior_frame_buffer_size = 5 [default = 30, deprecated = true]; - // Set camera motion type along with parameters. Must select between the two - // provided options. - optional CameraMotionOptions camera_motion_options = 14; - - // Options for computing key frame crop regions using the - // FrameCropRegionComputer. - // **** Note: You shall NOT manually set the target width and height fields - // inside this field as they will be overridden internally in the calculator - // (i.e. automatically computed from target aspect ratio). - optional KeyFrameCropOptions key_frame_crop_options = 6; - - // Options for analyzing scene camera motion and populating SalientPointFrames - // using the SceneCameraMotionAnalyzer. - optional SceneCameraMotionAnalyzerOptions - scene_camera_motion_analyzer_options = 7; - - // If the fraction of frames with solid background in one shot exceeds this - // threshold, use a solid color for background in padding for this shot. - optional float solid_background_frames_padding_fraction = 8 [default = 0.6]; - - // Options for padding using the PaddingEffectGenerator (copied from - // ad_creation/calculators/universal_padding_calculator.proto). - message PaddingEffectParameters { - // Contrast adjustment for padding background. This value should between 0 - // and 1. The smaller the value, the darker the background. 1 means no - // contrast change. - optional float background_contrast = 1 [default = 1.0]; - // The cv::Size() parameter used in creating blurry effects for padding - // backgrounds. - optional int32 blur_cv_size = 2 [default = 200]; - // The opacity of the black layer overlaied on top of the background. The - // value should be within [0, 1], in which 0 means totally transparent, and - // 1 means totally opaque. - optional float overlay_opacity = 3 [default = 0.6]; - } - optional PaddingEffectParameters padding_parameters = 9; - - // If set and input "KEY_FRAMES" not provided, uses these keyframe values. - optional int32 video_features_width = 10; - optional int32 video_features_height = 11; - - // If a user hint is provided on a scene, use only this signal for cropping - // and camera motion. - optional bool user_hint_override = 12; - - // An opacity used to render cropping windows for visualization purposes. - optional float viz_overlay_opacity = 13 [default = 0.7]; -} diff --git a/examples/desktop/autoflip/calculators/scene_cropping_calculator_test.cc b/examples/desktop/autoflip/calculators/scene_cropping_calculator_test.cc deleted file mode 100644 index 8872886..0000000 --- a/examples/desktop/autoflip/calculators/scene_cropping_calculator_test.cc +++ /dev/null @@ -1,925 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/calculators/scene_cropping_calculator.h" - -#include -#include -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kFramingDetectionsVizFramesTag[] = - "FRAMING_DETECTIONS_VIZ_FRAMES"; -constexpr char kExternalRenderingFullVidTag[] = "EXTERNAL_RENDERING_FULL_VID"; -constexpr char kExternalRenderingPerFrameTag[] = "EXTERNAL_RENDERING_PER_FRAME"; -constexpr char kCroppingSummaryTag[] = "CROPPING_SUMMARY"; -constexpr char kSalientPointFrameVizFramesTag[] = - "SALIENT_POINT_FRAME_VIZ_FRAMES"; -constexpr char kKeyFrameCropRegionVizFramesTag[] = - "KEY_FRAME_CROP_REGION_VIZ_FRAMES"; -constexpr char kCroppedFramesTag[] = "CROPPED_FRAMES"; -constexpr char kShotBoundariesTag[] = "SHOT_BOUNDARIES"; -constexpr char kStaticFeaturesTag[] = "STATIC_FEATURES"; -constexpr char kVideoSizeTag[] = "VIDEO_SIZE"; -constexpr char kVideoFramesTag[] = "VIDEO_FRAMES"; -constexpr char kDetectionFeaturesTag[] = "DETECTION_FEATURES"; -constexpr char kKeyFramesTag[] = "KEY_FRAMES"; - -using ::testing::HasSubstr; - -constexpr char kConfig[] = R"( - calculator: "SceneCroppingCalculator" - input_stream: "VIDEO_FRAMES:camera_frames_org" - input_stream: "KEY_FRAMES:down_sampled_frames" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:border_features" - input_stream: "SHOT_BOUNDARIES:shot_boundary_frames" - output_stream: "CROPPED_FRAMES:cropped_frames" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - target_width: $0 - target_height: $1 - target_size_type: $2 - max_scene_size: $3 - prior_frame_buffer_size: $4 - } - })"; - -constexpr char kNoKeyFrameConfig[] = R"( - calculator: "SceneCroppingCalculator" - input_stream: "VIDEO_FRAMES:camera_frames_org" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:border_features" - input_stream: "SHOT_BOUNDARIES:shot_boundary_frames" - output_stream: "CROPPED_FRAMES:cropped_frames" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - target_width: $0 - target_height: $1 - } - })"; - -constexpr char kDebugConfigNoCroppedFrame[] = R"( - calculator: "SceneCroppingCalculator" - input_stream: "VIDEO_FRAMES:camera_frames_org" - input_stream: "KEY_FRAMES:down_sampled_frames" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:border_features" - input_stream: "SHOT_BOUNDARIES:shot_boundary_frames" - output_stream: "KEY_FRAME_CROP_REGION_VIZ_FRAMES:key_frame_crop_viz_frames" - output_stream: "SALIENT_POINT_FRAME_VIZ_FRAMES:salient_point_viz_frames" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - target_width: $0 - target_height: $1 - } - })"; - -constexpr char kDebugConfig[] = R"( - calculator: "SceneCroppingCalculator" - input_stream: "VIDEO_FRAMES:camera_frames_org" - input_stream: "KEY_FRAMES:down_sampled_frames" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:border_features" - input_stream: "SHOT_BOUNDARIES:shot_boundary_frames" - output_stream: "CROPPED_FRAMES:cropped_frames" - output_stream: "KEY_FRAME_CROP_REGION_VIZ_FRAMES:key_frame_crop_viz_frames" - output_stream: "SALIENT_POINT_FRAME_VIZ_FRAMES:salient_point_viz_frames" - output_stream: "FRAMING_DETECTIONS_VIZ_FRAMES:framing_viz_frames" - output_stream: "CROPPING_SUMMARY:cropping_summaries" - output_stream: "EXTERNAL_RENDERING_PER_FRAME:external_rendering_per_frame" - output_stream: "EXTERNAL_RENDERING_FULL_VID:external_rendering_full_vid" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - target_width: $0 - target_height: $1 - } - })"; - -constexpr char kExternalRenderConfig[] = R"( - calculator: "SceneCroppingCalculator" - input_stream: "VIDEO_FRAMES:camera_frames_org" - input_stream: "KEY_FRAMES:down_sampled_frames" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:border_features" - input_stream: "SHOT_BOUNDARIES:shot_boundary_frames" - output_stream: "EXTERNAL_RENDERING_PER_FRAME:external_rendering_per_frame" - output_stream: "EXTERNAL_RENDERING_FULL_VID:external_rendering_full_vid" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - target_width: $0 - target_height: $1 - } - })"; - -constexpr char kExternalRenderConfigNoVideo[] = R"( - calculator: "SceneCroppingCalculator" - input_stream: "VIDEO_SIZE:camera_size" - input_stream: "DETECTION_FEATURES:salient_regions" - input_stream: "STATIC_FEATURES:border_features" - input_stream: "SHOT_BOUNDARIES:shot_boundary_frames" - output_stream: "EXTERNAL_RENDERING_PER_FRAME:external_rendering_per_frame" - output_stream: "EXTERNAL_RENDERING_FULL_VID:external_rendering_full_vid" - options: { - [mediapipe.autoflip.SceneCroppingCalculatorOptions.ext]: { - target_width: $0 - target_height: $1 - video_features_width: $2 - video_features_height: $3 - } - })"; - -constexpr int kInputFrameWidth = 1280; -constexpr int kInputFrameHeight = 720; - -constexpr int kKeyFrameWidth = 640; -constexpr int kKeyFrameHeight = 360; - -constexpr int kTargetWidth = 720; -constexpr int kTargetHeight = 1124; -constexpr SceneCroppingCalculatorOptions::TargetSizeType kTargetSizeType = - SceneCroppingCalculatorOptions::USE_TARGET_DIMENSION; - -constexpr int kNumScenes = 3; -constexpr int kSceneSize = 8; -constexpr int kMaxSceneSize = 10; -constexpr int kPriorFrameBufferSize = 5; - -constexpr int kMinNumDetections = 0; -constexpr int kMaxNumDetections = 10; - -constexpr int kDownSampleRate = 4; -constexpr int64 kTimestampDiff = 20000; - -// Returns a singleton random engine for generating random values. The seed is -// fixed for reproducibility. -std::default_random_engine& GetGen() { - static std::default_random_engine generator{0}; - return generator; -} - -// Returns random color with r, g, b in the range of [0, 255]. -cv::Scalar GetRandomColor() { - std::uniform_int_distribution distribution(0, 255); - const int red = distribution(GetGen()); - const int green = distribution(GetGen()); - const int blue = distribution(GetGen()); - return cv::Scalar(red, green, blue); -} - -// Makes a detection set given number of detections. Each detection has randomly -// generated regions within given width and height with random score in [0, 1], -// and is randomly set to be required or non-required. -std::unique_ptr MakeDetections(const int num_detections, - const int width, - const int height) { - std::uniform_int_distribution width_distribution(0, width); - std::uniform_int_distribution height_distribution(0, height); - std::uniform_real_distribution score_distribution(0.0, 1.0); - std::bernoulli_distribution is_required_distribution(0.5); - auto detections = absl::make_unique(); - for (int i = 0; i < num_detections; ++i) { - auto* region = detections->add_detections(); - const int x1 = width_distribution(GetGen()); - const int x2 = width_distribution(GetGen()); - const int y1 = height_distribution(GetGen()); - const int y2 = height_distribution(GetGen()); - const int x_min = std::min(x1, x2), x_max = std::max(x1, x2); - const int y_min = std::min(y1, y2), y_max = std::max(y1, y2); - auto* location = region->mutable_location(); - location->set_x(x_min); - location->set_width(x_max - x_min); - location->set_y(y_min); - location->set_height(y_max - y_min); - region->set_score(score_distribution(GetGen())); - region->set_is_required(is_required_distribution(GetGen())); - } - return detections; -} - -// Makes a detection set given number of detections. Each detection has randomly -// generated regions within given width and height with random score in [0, 1], -// and is randomly set to be required or non-required. -std::unique_ptr MakeCenterDetection(const int width, - const int height) { - auto detections = absl::make_unique(); - auto* region = detections->add_detections(); - auto* location = region->mutable_location(); - location->set_x(width / 2 - 5); - location->set_width(width / 2 + 10); - location->set_y(height / 2 - 5); - location->set_height(height); - region->set_score(1); - return detections; -} - -// Makes an image frame of solid color given color, width, and height. -std::unique_ptr MakeImageFrameFromColor(const cv::Scalar& color, - const int width, - const int height) { - auto image_frame = - absl::make_unique(ImageFormat::SRGB, width, height); - auto mat = formats::MatView(image_frame.get()); - mat = color; - return image_frame; -} - -// Adds key frame detection features given time (in ms) to the input stream. -// Randomly generates a number of detections in the range of kMinNumDetections -// and kMaxNumDetections. Optionally add a key image frame of random solid color -// and given size. -void AddKeyFrameFeatures(const int64 time_ms, const int key_frame_width, - const int key_frame_height, bool randomize, - CalculatorRunner::StreamContentsSet* inputs) { - Timestamp timestamp(time_ms); - if (inputs->HasTag(kKeyFramesTag)) { - auto key_frame = MakeImageFrameFromColor(GetRandomColor(), key_frame_width, - key_frame_height); - inputs->Tag(kKeyFramesTag) - .packets.push_back(Adopt(key_frame.release()).At(timestamp)); - } - if (randomize) { - const int num_detections = std::uniform_int_distribution( - kMinNumDetections, kMaxNumDetections)(GetGen()); - auto detections = - MakeDetections(num_detections, key_frame_width, key_frame_height); - inputs->Tag(kDetectionFeaturesTag) - .packets.push_back(Adopt(detections.release()).At(timestamp)); - } else { - auto detections = MakeCenterDetection(key_frame_width, key_frame_height); - inputs->Tag(kDetectionFeaturesTag) - .packets.push_back(Adopt(detections.release()).At(timestamp)); - } -} - -// Adds a scene given number of frames to the input stream. Spaces frame at the -// default timestamp interval starting from given start frame index. Scene has -// empty static features. -void AddScene(const int start_frame_index, const int num_scene_frames, - const int frame_width, const int frame_height, - const int key_frame_width, const int key_frame_height, - const int DownSampleRate, - CalculatorRunner::StreamContentsSet* inputs) { - int64 time_ms = start_frame_index * kTimestampDiff; - for (int i = 0; i < num_scene_frames; ++i) { - Timestamp timestamp(time_ms); - if (inputs->HasTag(kVideoFramesTag)) { - auto frame = - MakeImageFrameFromColor(GetRandomColor(), frame_width, frame_height); - inputs->Tag(kVideoFramesTag) - .packets.push_back(Adopt(frame.release()).At(timestamp)); - } else { - auto input_size = - ::absl::make_unique>(frame_width, frame_height); - inputs->Tag(kVideoSizeTag) - .packets.push_back(Adopt(input_size.release()).At(timestamp)); - } - auto static_features = absl::make_unique(); - inputs->Tag(kStaticFeaturesTag) - .packets.push_back(Adopt(static_features.release()).At(timestamp)); - if (DownSampleRate == 1) { - AddKeyFrameFeatures(time_ms, key_frame_width, key_frame_height, false, - inputs); - } else if (i % DownSampleRate == 0) { // is a key frame - AddKeyFrameFeatures(time_ms, key_frame_width, key_frame_height, true, - inputs); - } - if (i == num_scene_frames - 1) { // adds shot boundary - inputs->Tag(kShotBoundariesTag) - .packets.push_back(Adopt(new bool(true)).At(Timestamp(time_ms))); - } - time_ms += kTimestampDiff; - } -} - -// Checks that the output stream for cropped frames has the correct number of -// frames, and that the size of each frame is correct. -void CheckCroppedFrames(const CalculatorRunner& runner, const int num_frames, - const int target_width, const int target_height) { - const auto& outputs = runner.Outputs(); - EXPECT_TRUE(outputs.HasTag(kCroppedFramesTag)); - const auto& cropped_frames_outputs = outputs.Tag(kCroppedFramesTag).packets; - EXPECT_EQ(cropped_frames_outputs.size(), num_frames); - for (int i = 0; i < num_frames; ++i) { - const auto& cropped_frame = cropped_frames_outputs[i].Get(); - EXPECT_EQ(cropped_frame.Width(), target_width); - EXPECT_EQ(cropped_frame.Height(), target_height); - } -} - -// Checks that the calculator checks the maximum scene size is valid. -TEST(SceneCroppingCalculatorTest, ChecksMaxSceneSize) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kConfig, kTargetWidth, kTargetHeight, - kTargetSizeType, 0, kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - const auto status = runner->Run(); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Maximum scene size is non-positive.")); -} - -// Checks that the calculator checks the prior frame buffer size is valid. -TEST(SceneCroppingCalculatorTest, ChecksPriorFrameBufferSize) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kConfig, kTargetWidth, kTargetHeight, - kTargetSizeType, kMaxSceneSize, -1)); - auto runner = absl::make_unique(config); - const auto status = runner->Run(); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Prior frame buffer size is negative.")); -} - -TEST(SceneCroppingCalculatorTest, ChecksDebugConfigWithoutCroppedFrame) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kDebugConfigNoCroppedFrame, kTargetWidth, kTargetHeight)); - auto runner = absl::make_unique(config); - const auto status = runner->Run(); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("can only be used when")); -} - -// Checks that the calculator crops scene frames when there is no input key -// frames stream. -TEST(SceneCroppingCalculatorTest, HandlesNoKeyFrames) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kNoKeyFrameConfig, kTargetWidth, kTargetHeight)); - auto runner = absl::make_unique(config); - AddScene(0, kSceneSize, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth, - kKeyFrameHeight, kDownSampleRate, runner->MutableInputs()); - MP_EXPECT_OK(runner->Run()); - CheckCroppedFrames(*runner, kSceneSize, kTargetWidth, kTargetHeight); -} - -// Checks that the calculator handles scenes longer than maximum scene size ( -// force flush is triggered). -TEST(SceneCroppingCalculatorTest, HandlesLongScene) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, kTargetWidth, kTargetHeight, kTargetSizeType, kMaxSceneSize, - kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - AddScene(0, 2 * kMaxSceneSize, kInputFrameWidth, kInputFrameHeight, - kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate, - runner->MutableInputs()); - MP_EXPECT_OK(runner->Run()); - CheckCroppedFrames(*runner, 2 * kMaxSceneSize, kTargetWidth, kTargetHeight); -} - -// Checks that the calculator can optionally output debug streams. -TEST(SceneCroppingCalculatorTest, OutputsDebugStreams) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kDebugConfig, kTargetWidth, kTargetHeight)); - auto runner = absl::make_unique(config); - const int num_frames = kSceneSize; - AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth, - kKeyFrameHeight, kDownSampleRate, runner->MutableInputs()); - - MP_EXPECT_OK(runner->Run()); - const auto& outputs = runner->Outputs(); - EXPECT_TRUE(outputs.HasTag(kKeyFrameCropRegionVizFramesTag)); - EXPECT_TRUE(outputs.HasTag(kSalientPointFrameVizFramesTag)); - EXPECT_TRUE(outputs.HasTag(kCroppingSummaryTag)); - EXPECT_TRUE(outputs.HasTag(kExternalRenderingPerFrameTag)); - EXPECT_TRUE(outputs.HasTag(kExternalRenderingFullVidTag)); - EXPECT_TRUE(outputs.HasTag(kFramingDetectionsVizFramesTag)); - const auto& crop_region_viz_frames_outputs = - outputs.Tag(kKeyFrameCropRegionVizFramesTag).packets; - const auto& salient_point_viz_frames_outputs = - outputs.Tag(kSalientPointFrameVizFramesTag).packets; - const auto& summary_output = outputs.Tag(kCroppingSummaryTag).packets; - const auto& ext_render_per_frame = - outputs.Tag(kExternalRenderingPerFrameTag).packets; - const auto& ext_render_full_vid = - outputs.Tag(kExternalRenderingFullVidTag).packets; - const auto& framing_viz_frames_output = - outputs.Tag(kFramingDetectionsVizFramesTag).packets; - EXPECT_EQ(crop_region_viz_frames_outputs.size(), num_frames); - EXPECT_EQ(salient_point_viz_frames_outputs.size(), num_frames); - EXPECT_EQ(framing_viz_frames_output.size(), num_frames); - EXPECT_EQ(summary_output.size(), 1); - EXPECT_EQ(ext_render_per_frame.size(), num_frames); - EXPECT_EQ(ext_render_full_vid.size(), 1); - EXPECT_EQ(ext_render_per_frame[0].Get().timestamp_us(), - 0); - EXPECT_EQ(ext_render_full_vid[0] - .Get>()[0] - .timestamp_us(), - 0); - EXPECT_EQ(ext_render_per_frame[1].Get().timestamp_us(), - 20000); - EXPECT_EQ(ext_render_full_vid[0] - .Get>()[1] - .timestamp_us(), - 20000); - - for (int i = 0; i < num_frames; ++i) { - const auto& crop_region_viz_frame = - crop_region_viz_frames_outputs[i].Get(); - EXPECT_EQ(crop_region_viz_frame.Width(), kInputFrameWidth); - EXPECT_EQ(crop_region_viz_frame.Height(), kInputFrameHeight); - const auto& salient_point_viz_frame = - salient_point_viz_frames_outputs[i].Get(); - EXPECT_EQ(salient_point_viz_frame.Width(), kInputFrameWidth); - EXPECT_EQ(salient_point_viz_frame.Height(), kInputFrameHeight); - } - const auto& summary = summary_output[0].Get(); - EXPECT_EQ(summary.scene_summaries_size(), 2); - const auto& summary_0 = summary.scene_summaries(0); - EXPECT_TRUE(summary_0.is_padded()); - EXPECT_TRUE(summary_0.camera_motion().has_steady_motion()); -} - -// Checks that the calculator handles the case of generating landscape frames. -TEST(SceneCroppingCalculatorTest, HandlesLandscapeTarget) { - const int input_width = 900; - const int input_height = 1600; - const int target_width = 1200; - const int target_height = 800; - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, target_width, target_height, kTargetSizeType, kMaxSceneSize, - kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - for (int i = 0; i < kNumScenes; ++i) { - AddScene(i * kSceneSize, kSceneSize, input_width, input_height, - kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate, - runner->MutableInputs()); - } - const int num_frames = kSceneSize * kNumScenes; - MP_EXPECT_OK(runner->Run()); - CheckCroppedFrames(*runner, num_frames, target_width, target_height); -} - -// Checks that the calculator crops scene frames to target size when the target -// size type is the default USE_TARGET_DIMENSION. -TEST(SceneCroppingCalculatorTest, CropsToTargetSize) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, kTargetWidth, kTargetHeight, kTargetSizeType, kMaxSceneSize, - kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - for (int i = 0; i < kNumScenes; ++i) { - AddScene(i * kSceneSize, kSceneSize, kInputFrameWidth, kInputFrameHeight, - kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate, - runner->MutableInputs()); - } - const int num_frames = kSceneSize * kNumScenes; - MP_EXPECT_OK(runner->Run()); - CheckCroppedFrames(*runner, num_frames, kTargetWidth, kTargetHeight); -} - -// Checks that the calculator crops scene frames to input size when the target -// size type is KEEP_ORIGINAL_DIMENSION. -TEST(SceneCroppingCalculatorTest, CropsToOriginalDimension) { - // target_width and target_height are ignored - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, /*target_width*/ 2, /*target_height*/ 2, - SceneCroppingCalculatorOptions::KEEP_ORIGINAL_DIMENSION, - kMaxSceneSize, kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - for (int i = 0; i < kNumScenes; ++i) { - AddScene(i * kSceneSize, kSceneSize, kInputFrameWidth, kInputFrameHeight, - kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate, - runner->MutableInputs()); - } - const int num_frames = kSceneSize * kNumScenes; - MP_EXPECT_OK(runner->Run()); - CheckCroppedFrames(*runner, num_frames, kInputFrameWidth, kInputFrameHeight); -} - -// Checks that the calculator keeps original height if the target size type is -// set to KEEP_ORIGINAL_HEIGHT. -TEST(SceneCroppingCalculatorTest, KeepsOriginalHeight) { - const auto target_size_type = - SceneCroppingCalculatorOptions::KEEP_ORIGINAL_HEIGHT; - const int target_height = kInputFrameHeight; - const double target_aspect_ratio = - static_cast(kTargetWidth) / kTargetHeight; - int target_width = std::round(target_height * target_aspect_ratio); - if (target_width % 2 == 1) target_width--; - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, kTargetWidth, kTargetHeight, target_size_type, kMaxSceneSize, - kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - AddScene(0, kMaxSceneSize, kInputFrameWidth, kInputFrameHeight, - kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate, - runner->MutableInputs()); - MP_EXPECT_OK(runner->Run()); - CheckCroppedFrames(*runner, kMaxSceneSize, target_width, target_height); -} - -// Checks that the calculator keeps original width if the target size type is -// set to KEEP_ORIGINAL_WIDTH. -TEST(SceneCroppingCalculatorTest, KeepsOriginalWidth) { - const auto target_size_type = - SceneCroppingCalculatorOptions::KEEP_ORIGINAL_WIDTH; - const int target_width = kInputFrameWidth; - const double target_aspect_ratio = - static_cast(kTargetWidth) / kTargetHeight; - int target_height = std::round(target_width / target_aspect_ratio); - if (target_height % 2 == 1) target_height--; - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, kTargetWidth, kTargetHeight, target_size_type, kMaxSceneSize, - kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - AddScene(0, kMaxSceneSize, kInputFrameWidth, kInputFrameHeight, - kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate, - runner->MutableInputs()); - MP_EXPECT_OK(runner->Run()); - CheckCroppedFrames(*runner, kMaxSceneSize, target_width, target_height); -} - -// Checks that the calculator rejects odd target size. -TEST(SceneCroppingCalculatorTest, RejectsOddTargetSize) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, kTargetWidth - 1, kTargetHeight, kTargetSizeType, - kMaxSceneSize, kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - AddScene(0, kMaxSceneSize, kInputFrameWidth, kInputFrameHeight, - kKeyFrameWidth, kKeyFrameHeight, kDownSampleRate, - runner->MutableInputs()); - const auto status = runner->Run(); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Target width cannot be odd")); -} - -// Checks that the calculator always produces even frame size given even input -// frame size and even target under all target size types. -TEST(SceneCroppingCalculatorTest, ProducesEvenFrameSize) { - // Some commonly used video resolution (some are divided by 10 to make the - // test faster), and some odd input frame sizes. - const std::vector> video_sizes = { - {384, 216}, {256, 144}, {192, 108}, {128, 72}, {640, 360}, - {426, 240}, {100, 100}, {214, 100}, {240, 100}, {720, 1124}, - {90, 160}, {641, 360}, {640, 361}, {101, 101}}; - - const std::vector - target_size_types = {SceneCroppingCalculatorOptions::USE_TARGET_DIMENSION, - SceneCroppingCalculatorOptions::KEEP_ORIGINAL_HEIGHT, - SceneCroppingCalculatorOptions::KEEP_ORIGINAL_WIDTH}; - - // Exhaustive check on each size as input and each size as output for each - // target size type. - for (int i = 0; i < video_sizes.size(); ++i) { - const int frame_width = video_sizes[i].first; - const int frame_height = video_sizes[i].second; - for (int j = 0; j < video_sizes.size(); ++j) { - const int target_width = video_sizes[j].first; - const int target_height = video_sizes[j].second; - if (target_width % 2 == 1 || target_height % 2 == 1) continue; - for (int k = 0; k < target_size_types.size(); ++k) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie(absl::Substitute( - kConfig, target_width, target_height, target_size_types[k], - kMaxSceneSize, kPriorFrameBufferSize)); - auto runner = absl::make_unique(config); - AddScene(0, 1, frame_width, frame_height, kKeyFrameWidth, - kKeyFrameHeight, kDownSampleRate, runner->MutableInputs()); - MP_EXPECT_OK(runner->Run()); - const auto& output_frame = runner->Outputs() - .Tag(kCroppedFramesTag) - .packets[0] - .Get(); - EXPECT_EQ(output_frame.Width() % 2, 0); - EXPECT_EQ(output_frame.Height() % 2, 0); - if (target_size_types[k] == - SceneCroppingCalculatorOptions::USE_TARGET_DIMENSION) { - EXPECT_EQ(output_frame.Width(), target_width); - EXPECT_EQ(output_frame.Height(), target_height); - } else if (target_size_types[k] == - SceneCroppingCalculatorOptions::KEEP_ORIGINAL_HEIGHT) { - // Difference could be 1 if input size is odd. - EXPECT_LE(std::abs(output_frame.Height() - frame_height), 1); - } else if (target_size_types[k] == - SceneCroppingCalculatorOptions::KEEP_ORIGINAL_WIDTH) { - EXPECT_LE(std::abs(output_frame.Width() - frame_width), 1); - } - } - } - } -} - -// Checks that the calculator pads the frames with solid color when possible. -TEST(SceneCroppingCalculatorTest, PadsWithSolidColorFromStaticFeatures) { - const int target_width = 100, target_height = 200; - const int input_width = 100, input_height = 100; - CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kNoKeyFrameConfig, target_width, target_height)); - auto* options = config.mutable_options()->MutableExtension( - SceneCroppingCalculatorOptions::ext); - options->set_solid_background_frames_padding_fraction(0.6); - auto runner = absl::make_unique(config); - - const int static_features_downsample_rate = 2; - const float fraction_with_solid_background = 0.7; - const int red = 122, green = 167, blue = 250; - const int num_frames_with_solid_background = - std::round(fraction_with_solid_background * kSceneSize / - static_features_downsample_rate); - - // Add inputs. - auto* inputs = runner->MutableInputs(); - int64 time_ms = 0; - int num_static_features = 0; - for (int i = 0; i < kSceneSize; ++i) { - Timestamp timestamp(time_ms); - auto frame = - MakeImageFrameFromColor(GetRandomColor(), input_width, input_height); - inputs->Tag(kVideoFramesTag) - .packets.push_back(Adopt(frame.release()).At(timestamp)); - if (i % static_features_downsample_rate == 0) { - auto static_features = absl::make_unique(); - if (num_static_features < num_frames_with_solid_background) { - auto* color = static_features->mutable_solid_background(); - // Uses BGR to mimic input from static features solid background color. - color->set_r(blue); - color->set_g(green); - color->set_b(red); - } - inputs->Tag(kStaticFeaturesTag) - .packets.push_back(Adopt(static_features.release()).At(timestamp)); - num_static_features++; - } - if (i % kDownSampleRate == 0) { // is a key frame - // Target crop size is (50, 100). Adds one required detection with size - // (80, 100) larger than the target crop size to force padding. - auto detections = absl::make_unique(); - auto* salient_region = detections->add_detections(); - salient_region->set_is_required(true); - auto* location = salient_region->mutable_location(); - location->set_x(10); - location->set_y(0); - location->set_width(80); - location->set_height(input_height); - inputs->Tag(kDetectionFeaturesTag) - .packets.push_back(Adopt(detections.release()).At(timestamp)); - } - time_ms += kTimestampDiff; - } - - MP_EXPECT_OK(runner->Run()); - - // Checks that the top and bottom borders indeed have the background color. - const int border_size = 37; - const auto& cropped_frames_outputs = - runner->Outputs().Tag(kCroppedFramesTag).packets; - EXPECT_EQ(cropped_frames_outputs.size(), kSceneSize); - for (int i = 0; i < kSceneSize; ++i) { - const auto& cropped_frame = cropped_frames_outputs[i].Get(); - cv::Mat mat = formats::MatView(&cropped_frame); - for (int x = 0; x < target_width; ++x) { - for (int y = 0; y < border_size; ++y) { - EXPECT_EQ(mat.at(y, x)[0], red); - EXPECT_EQ(mat.at(y, x)[1], green); - EXPECT_EQ(mat.at(y, x)[2], blue); - } - for (int y2 = 0; y2 < border_size; ++y2) { - const int y = target_height - 1 - y2; - EXPECT_EQ(mat.at(y, x)[0], red); - EXPECT_EQ(mat.at(y, x)[1], green); - EXPECT_EQ(mat.at(y, x)[2], blue); - } - } - } -} - -// Checks that the calculator removes static borders from frames. -TEST(SceneCroppingCalculatorTest, RemovesStaticBorders) { - const int target_width = 50, target_height = 100; - const int input_width = 100, input_height = 100; - const int top_border_size = 20, bottom_border_size = 20; - const cv::Rect top_border_rect(0, 0, input_width, top_border_size); - const cv::Rect bottom_border_rect(0, input_height - bottom_border_size, - input_width, bottom_border_size); - const cv::Scalar frame_color = cv::Scalar(255, 255, 255); - const cv::Scalar border_color = cv::Scalar(0, 0, 0); - - const auto config = ParseTextProtoOrDie( - absl::Substitute(kNoKeyFrameConfig, target_width, target_height)); - auto runner = absl::make_unique(config); - - // Add inputs. - auto* inputs = runner->MutableInputs(); - const auto timestamp = Timestamp(0); - // Make frame with borders. - auto frame = MakeImageFrameFromColor(frame_color, input_width, input_height); - auto mat = formats::MatView(frame.get()); - mat(top_border_rect) = border_color; - mat(bottom_border_rect) = border_color; - inputs->Tag(kVideoFramesTag) - .packets.push_back(Adopt(frame.release()).At(timestamp)); - // Set borders in static features. - auto static_features = absl::make_unique(); - auto* top_part = static_features->add_border(); - top_part->set_relative_position(Border::TOP); - top_part->mutable_border_position()->set_height(top_border_size); - auto* bottom_part = static_features->add_border(); - bottom_part->set_relative_position(Border::BOTTOM); - bottom_part->mutable_border_position()->set_height(bottom_border_size); - inputs->Tag(kStaticFeaturesTag) - .packets.push_back(Adopt(static_features.release()).At(timestamp)); - // Add empty detections to ensure no padding is used. - auto detections = absl::make_unique(); - inputs->Tag(kDetectionFeaturesTag) - .packets.push_back(Adopt(detections.release()).At(timestamp)); - - MP_EXPECT_OK(runner->Run()); - - // Checks that the top and bottom borders are removed. Each frame should have - // solid color equal to frame color. - const auto& cropped_frames_outputs = - runner->Outputs().Tag(kCroppedFramesTag).packets; - EXPECT_EQ(cropped_frames_outputs.size(), 1); - const auto& cropped_frame = cropped_frames_outputs[0].Get(); - const auto cropped_mat = formats::MatView(&cropped_frame); - for (int x = 0; x < target_width; ++x) { - for (int y = 0; y < target_height; ++y) { - EXPECT_EQ(cropped_mat.at(y, x)[0], frame_color[0]); - EXPECT_EQ(cropped_mat.at(y, x)[1], frame_color[1]); - EXPECT_EQ(cropped_mat.at(y, x)[2], frame_color[2]); - } - } -} - -// Checks external render message with default poly path solver. -TEST(SceneCroppingCalculatorTest, OutputsCropMessagePolyPath) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kExternalRenderConfig, kTargetWidth, kTargetHeight)); - auto runner = absl::make_unique(config); - const int num_frames = kSceneSize; - AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth, - kKeyFrameHeight, 1, runner->MutableInputs()); - - MP_EXPECT_OK(runner->Run()); - const auto& outputs = runner->Outputs(); - const auto& ext_render_per_frame = - outputs.Tag(kExternalRenderingPerFrameTag).packets; - EXPECT_EQ(ext_render_per_frame.size(), num_frames); - - for (int i = 0; i < num_frames - 1; ++i) { - const auto& ext_render_message = - ext_render_per_frame[i].Get(); - EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000); - EXPECT_EQ(ext_render_message.crop_from_location().x(), 725); - EXPECT_EQ(ext_render_message.crop_from_location().y(), 0); - EXPECT_EQ(ext_render_message.crop_from_location().width(), 461); - EXPECT_EQ(ext_render_message.crop_from_location().height(), 720); - EXPECT_EQ(ext_render_message.render_to_location().x(), 0); - EXPECT_EQ(ext_render_message.render_to_location().y(), 0); - EXPECT_EQ(ext_render_message.render_to_location().width(), 720); - EXPECT_EQ(ext_render_message.render_to_location().height(), 1124); - } -} - -// Checks external render message with kinematic path solver. -TEST(SceneCroppingCalculatorTest, OutputsCropMessageKinematicPath) { - CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kDebugConfig, kTargetWidth, kTargetHeight)); - auto* options = config.mutable_options()->MutableExtension( - SceneCroppingCalculatorOptions::ext); - auto* kinematic_options = - options->mutable_camera_motion_options()->mutable_kinematic_options(); - kinematic_options->set_min_motion_to_reframe(1.2); - kinematic_options->set_max_velocity(200); - - auto runner = absl::make_unique(config); - const int num_frames = kSceneSize; - AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth, - kKeyFrameHeight, 1, runner->MutableInputs()); - - MP_EXPECT_OK(runner->Run()); - const auto& outputs = runner->Outputs(); - const auto& ext_render_per_frame = - outputs.Tag(kExternalRenderingPerFrameTag).packets; - EXPECT_EQ(ext_render_per_frame.size(), num_frames); - - for (int i = 0; i < num_frames - 1; ++i) { - const auto& ext_render_message = - ext_render_per_frame[i].Get(); - EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000); - EXPECT_EQ(ext_render_message.crop_from_location().x(), 725); - EXPECT_EQ(ext_render_message.crop_from_location().y(), 0); - EXPECT_EQ(ext_render_message.crop_from_location().width(), 461); - EXPECT_EQ(ext_render_message.crop_from_location().height(), 720); - EXPECT_EQ(ext_render_message.render_to_location().x(), 0); - EXPECT_EQ(ext_render_message.render_to_location().y(), 0); - EXPECT_EQ(ext_render_message.render_to_location().width(), 720); - EXPECT_EQ(ext_render_message.render_to_location().height(), 1124); - } -} - -// Checks external render message with default poly path solver without video -// input. -TEST(SceneCroppingCalculatorTest, OutputsCropMessagePolyPathNoVideo) { - const CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kExternalRenderConfigNoVideo, kTargetWidth, - kTargetHeight, kKeyFrameWidth, kKeyFrameHeight)); - auto runner = absl::make_unique(config); - const int num_frames = kSceneSize; - AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth, - kKeyFrameHeight, 1, runner->MutableInputs()); - - MP_EXPECT_OK(runner->Run()); - const auto& outputs = runner->Outputs(); - const auto& ext_render_per_frame = - outputs.Tag(kExternalRenderingPerFrameTag).packets; - EXPECT_EQ(ext_render_per_frame.size(), num_frames); - - for (int i = 0; i < num_frames - 1; ++i) { - const auto& ext_render_message = - ext_render_per_frame[i].Get(); - EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000); - EXPECT_EQ(ext_render_message.crop_from_location().x(), 725); - EXPECT_EQ(ext_render_message.crop_from_location().y(), 0); - EXPECT_EQ(ext_render_message.crop_from_location().width(), 461); - EXPECT_EQ(ext_render_message.crop_from_location().height(), 720); - EXPECT_EQ(ext_render_message.render_to_location().x(), 0); - EXPECT_EQ(ext_render_message.render_to_location().y(), 0); - EXPECT_EQ(ext_render_message.render_to_location().width(), 720); - EXPECT_EQ(ext_render_message.render_to_location().height(), 1124); - } -} - -// Checks external render message with kinematic path solver without video -// input. -TEST(SceneCroppingCalculatorTest, OutputsCropMessageKinematicPathNoVideo) { - CalculatorGraphConfig::Node config = - ParseTextProtoOrDie( - absl::Substitute(kExternalRenderConfigNoVideo, kTargetWidth, - kTargetHeight, kKeyFrameWidth, kKeyFrameHeight)); - auto* options = config.mutable_options()->MutableExtension( - SceneCroppingCalculatorOptions::ext); - auto* kinematic_options = - options->mutable_camera_motion_options()->mutable_kinematic_options(); - kinematic_options->set_min_motion_to_reframe(1.2); - kinematic_options->set_max_velocity(2.0); - - auto runner = absl::make_unique(config); - const int num_frames = kSceneSize; - AddScene(0, num_frames, kInputFrameWidth, kInputFrameHeight, kKeyFrameWidth, - kKeyFrameHeight, 1, runner->MutableInputs()); - - MP_EXPECT_OK(runner->Run()); - const auto& outputs = runner->Outputs(); - const auto& ext_render_per_frame = - outputs.Tag(kExternalRenderingPerFrameTag).packets; - EXPECT_EQ(ext_render_per_frame.size(), num_frames); - - for (int i = 0; i < num_frames - 1; ++i) { - const auto& ext_render_message = - ext_render_per_frame[i].Get(); - EXPECT_EQ(ext_render_message.timestamp_us(), i * 20000); - EXPECT_EQ(ext_render_message.crop_from_location().x(), 725); - EXPECT_EQ(ext_render_message.crop_from_location().y(), 0); - EXPECT_EQ(ext_render_message.crop_from_location().width(), 461); - EXPECT_EQ(ext_render_message.crop_from_location().height(), 720); - EXPECT_EQ(ext_render_message.render_to_location().x(), 0); - EXPECT_EQ(ext_render_message.render_to_location().y(), 0); - EXPECT_EQ(ext_render_message.render_to_location().width(), 720); - EXPECT_EQ(ext_render_message.render_to_location().height(), 1124); - } -} -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/shot_boundary_calculator.cc b/examples/desktop/autoflip/calculators/shot_boundary_calculator.cc deleted file mode 100644 index 299f60b..0000000 --- a/examples/desktop/autoflip/calculators/shot_boundary_calculator.cc +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include -#include -#include -#include -#include - -#include "mediapipe/examples/desktop/autoflip/calculators/shot_boundary_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/timestamp.h" - -using mediapipe::ImageFrame; -using mediapipe::PacketTypeSet; - -// IO labels. -constexpr char kVideoInputTag[] = "VIDEO"; -constexpr char kShotChangeTag[] = "IS_SHOT_CHANGE"; -// Histogram settings. -const int kSaturationBins = 8; -const int kHistogramChannels[] = {0, 1, 2}; -const int kHistogramBinNum[] = {kSaturationBins, kSaturationBins, - kSaturationBins}; -const float kRange[] = {0, 256}; -const float* kHistogramRange[] = {kRange, kRange, kRange}; - -namespace mediapipe { -namespace autoflip { - -// This calculator computes a shot (or scene) change within a video. It works -// by computing a 3d color histogram and comparing this frame-to-frame. Settings -// to control the shot change logic are presented in the options proto. -// -// Example: -// node { -// calculator: "ShotBoundaryCalculator" -// input_stream: "VIDEO:camera_frames" -// output_stream: "IS_SHOT_CHANGE:is_shot" -// } -class ShotBoundaryCalculator : public mediapipe::CalculatorBase { - public: - ShotBoundaryCalculator() {} - ShotBoundaryCalculator(const ShotBoundaryCalculator&) = delete; - ShotBoundaryCalculator& operator=(const ShotBoundaryCalculator&) = delete; - - static absl::Status GetContract(mediapipe::CalculatorContract* cc); - absl::Status Open(mediapipe::CalculatorContext* cc) override; - absl::Status Process(mediapipe::CalculatorContext* cc) override; - - private: - // Computes the histogram of an image. - void ComputeHistogram(const cv::Mat& image, cv::Mat* image_histogram); - // Transmits signal to next calculator. - void Transmit(mediapipe::CalculatorContext* cc, bool is_shot_change); - // Calculator options. - ShotBoundaryCalculatorOptions options_; - // Last time a shot was detected. - Timestamp last_shot_timestamp_; - // Defines if the calculator has received a frame yet. - bool init_; - // Histogram from the last frame. - cv::Mat last_histogram_; - // History of histogram motion. - std::deque motion_history_; -}; -REGISTER_CALCULATOR(ShotBoundaryCalculator); - -void ShotBoundaryCalculator::ComputeHistogram(const cv::Mat& image, - cv::Mat* image_histogram) { - cv::Mat equalized_image; - cv::cvtColor(image.clone(), equalized_image, cv::COLOR_RGB2GRAY); - - double min, max; - cv::minMaxLoc(equalized_image, &min, &max); - - if (options_.equalize_histogram()) { - cv::equalizeHist(equalized_image, equalized_image); - } - - cv::calcHist(&image, 1, kHistogramChannels, cv::Mat(), *image_histogram, 2, - kHistogramBinNum, kHistogramRange, true, false); -} - -absl::Status ShotBoundaryCalculator::Open(mediapipe::CalculatorContext* cc) { - options_ = cc->Options(); - last_shot_timestamp_ = Timestamp(0); - init_ = false; - return absl::OkStatus(); -} - -void ShotBoundaryCalculator::Transmit(mediapipe::CalculatorContext* cc, - bool is_shot_change) { - if ((cc->InputTimestamp() - last_shot_timestamp_).Seconds() < - options_.min_shot_span()) { - is_shot_change = false; - } - if (is_shot_change) { - LOG(INFO) << "Shot change at: " << cc->InputTimestamp().Seconds() - << " seconds."; - cc->Outputs() - .Tag(kShotChangeTag) - .AddPacket(Adopt(std::make_unique(true).release()) - .At(cc->InputTimestamp())); - } else if (!options_.output_only_on_change()) { - cc->Outputs() - .Tag(kShotChangeTag) - .AddPacket(Adopt(std::make_unique(false).release()) - .At(cc->InputTimestamp())); - } -} - -absl::Status ShotBoundaryCalculator::Process(mediapipe::CalculatorContext* cc) { - // Connect to input frame and make a mutable copy. - cv::Mat frame_org = mediapipe::formats::MatView( - &cc->Inputs().Tag(kVideoInputTag).Get()); - cv::Mat frame = frame_org.clone(); - - // Extract histogram from the current frame. - cv::Mat current_histogram; - ComputeHistogram(frame, ¤t_histogram); - - if (!init_) { - last_histogram_ = current_histogram; - init_ = true; - Transmit(cc, false); - return absl::OkStatus(); - } - - double current_motion_estimate = - 1 - cv::compareHist(current_histogram, last_histogram_, CV_COMP_CORREL); - last_histogram_ = current_histogram; - motion_history_.push_front(current_motion_estimate); - - if (motion_history_.size() != options_.window_size()) { - Transmit(cc, false); - return absl::OkStatus(); - } - - // Shot detection algorithm is a mixture of adaptive (controlled with - // shot_measure) and hard thresholds. In saturation it uses hard thresholds - // to account for black startups, shot cuts across high motion etc. - // In the operating region it uses an adaptive threshold to tune motion vs. - // cut boundary. - double current_max = - *std::max_element(motion_history_.begin(), motion_history_.end()); - double shot_measure = current_motion_estimate / current_max; - - if ((shot_measure > options_.min_shot_measure() && - current_motion_estimate > options_.min_motion_with_shot_measure()) || - current_motion_estimate > options_.min_motion()) { - Transmit(cc, true); - last_shot_timestamp_ = cc->InputTimestamp(); - } else { - Transmit(cc, false); - } - - // Store histogram for next frame. - last_histogram_ = current_histogram; - motion_history_.pop_back(); - return absl::OkStatus(); -} - -absl::Status ShotBoundaryCalculator::GetContract( - mediapipe::CalculatorContract* cc) { - cc->Inputs().Tag(kVideoInputTag).Set(); - cc->Outputs().Tag(kShotChangeTag).Set(); - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/shot_boundary_calculator.proto b/examples/desktop/autoflip/calculators/shot_boundary_calculator.proto deleted file mode 100644 index b0f3bd9..0000000 --- a/examples/desktop/autoflip/calculators/shot_boundary_calculator.proto +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/framework/calculator.proto"; - -message ShotBoundaryCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional ShotBoundaryCalculatorOptions ext = 281194049; - } - // Parameters to shot detection algorithm. All the constraints (the fields - // named with 'min_') need to be satisfied for a frame to be a shot boundary. - // - // Minimum motion to be considered as a shot boundary frame. - optional double min_motion = 1 [default = 0.2]; - // Minimum number of shot duration (in seconds). - optional double min_shot_span = 2 [default = 2]; - // A window for computing shot measure (see the definition in min_shot_measure - // field). - optional int32 window_size = 3 [default = 7]; - // Minimum shot measure to be considered as a shot boundary frame. - // Must also satisfy the min_motion_with_shot_measure constraint. - // The shot measure is defined as the ratio of the motion of the - // current frame to the maximum motion of the frames in the window (defined - // as window_size). - optional double min_shot_measure = 4 [default = 10]; - // Minimum motion to be considered as a shot boundary frame. - // Must also satisfy the min_shot_measure constraint. - optional double min_motion_with_shot_measure = 5 [default = 0.05]; - // Only send results if the shot value is true. - optional bool output_only_on_change = 6 [default = true]; - // Perform histogram equalization before computing keypoints/features. - optional bool equalize_histogram = 7 [default = false]; -} diff --git a/examples/desktop/autoflip/calculators/shot_boundary_calculator_test.cc b/examples/desktop/autoflip/calculators/shot_boundary_calculator_test.cc deleted file mode 100644 index 9ea79ba..0000000 --- a/examples/desktop/autoflip/calculators/shot_boundary_calculator_test.cc +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "absl/container/btree_set.h" -#include "absl/flags/flag.h" -#include "absl/strings/string_view.h" -#include "mediapipe/examples/desktop/autoflip/calculators/shot_boundary_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/deps/file_path.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgcodecs_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -using mediapipe::Adopt; -using mediapipe::CalculatorGraphConfig; -using mediapipe::CalculatorRunner; -using mediapipe::ImageFormat; -using mediapipe::ImageFrame; -using mediapipe::PacketTypeSet; -using mediapipe::ParseTextProtoOrDie; -using mediapipe::Timestamp; - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kIsShotChangeTag[] = "IS_SHOT_CHANGE"; -constexpr char kVideoTag[] = "VIDEO"; - -const char kConfig[] = R"( - calculator: "ShotBoundaryCalculator" - input_stream: "VIDEO:camera_frames" - output_stream: "IS_SHOT_CHANGE:is_shot" - )"; -const int kTestFrameWidth = 640; -const int kTestFrameHeight = 480; - -void AddFrames(const int number_of_frames, - const absl::btree_set& skip_frames, - CalculatorRunner* runner) { - cv::Mat image = - cv::imread(file::JoinPath("./", - "/mediapipe/examples/desktop/" - "autoflip/calculators/testdata/dino.jpg")); - - for (int i = 0; i < number_of_frames; i++) { - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kTestFrameWidth, kTestFrameHeight); - cv::Mat input_mat = mediapipe::formats::MatView(input_frame.get()); - input_mat.setTo(cv::Scalar(0, 0, 0)); - cv::Mat sub_image = - image(cv::Rect(i, i, kTestFrameWidth, kTestFrameHeight)); - cv::Mat frame_area = - input_mat(cv::Rect(0, 0, sub_image.cols, sub_image.rows)); - if (skip_frames.count(i) < 1) { - sub_image.copyTo(frame_area); - } - runner->MutableInputs()->Tag(kVideoTag).packets.push_back( - Adopt(input_frame.release()).At(Timestamp(i * 1000000))); - } -} - -void CheckOutput(const int number_of_frames, - const absl::btree_set& shot_frames, - const std::vector& output_packets) { - ASSERT_EQ(number_of_frames, output_packets.size()); - for (int i = 0; i < number_of_frames; i++) { - if (shot_frames.count(i) < 1) { - EXPECT_FALSE(output_packets[i].Get()); - } else { - EXPECT_TRUE(output_packets[i].Get()); - } - } -} - -TEST(ShotBoundaryCalculatorTest, NoShotChange) { - CalculatorGraphConfig::Node node = - ParseTextProtoOrDie(kConfig); - node.mutable_options() - ->MutableExtension(ShotBoundaryCalculatorOptions::ext) - ->set_output_only_on_change(false); - auto runner = ::absl::make_unique(node); - - AddFrames(10, {}, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckOutput(10, {}, runner->Outputs().Tag(kIsShotChangeTag).packets); -} - -TEST(ShotBoundaryCalculatorTest, ShotChangeSingle) { - CalculatorGraphConfig::Node node = - ParseTextProtoOrDie(kConfig); - node.mutable_options() - ->MutableExtension(ShotBoundaryCalculatorOptions::ext) - ->set_output_only_on_change(false); - auto runner = ::absl::make_unique(node); - - AddFrames(20, {10}, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckOutput(20, {10}, runner->Outputs().Tag(kIsShotChangeTag).packets); -} - -TEST(ShotBoundaryCalculatorTest, ShotChangeDouble) { - CalculatorGraphConfig::Node node = - ParseTextProtoOrDie(kConfig); - node.mutable_options() - ->MutableExtension(ShotBoundaryCalculatorOptions::ext) - ->set_output_only_on_change(false); - auto runner = ::absl::make_unique(node); - - AddFrames(20, {14, 17}, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckOutput(20, {14, 17}, runner->Outputs().Tag(kIsShotChangeTag).packets); -} - -TEST(ShotBoundaryCalculatorTest, ShotChangeFiltered) { - CalculatorGraphConfig::Node node = - ParseTextProtoOrDie(kConfig); - node.mutable_options() - ->MutableExtension(ShotBoundaryCalculatorOptions::ext) - ->set_min_shot_span(5); - node.mutable_options() - ->MutableExtension(ShotBoundaryCalculatorOptions::ext) - ->set_output_only_on_change(false); - - auto runner = ::absl::make_unique(node); - - AddFrames(24, {16, 19}, runner.get()); - MP_ASSERT_OK(runner->Run()); - CheckOutput(24, {16}, runner->Outputs().Tag(kIsShotChangeTag).packets); -} - -TEST(ShotBoundaryCalculatorTest, ShotChangeSingleOnOnChange) { - CalculatorGraphConfig::Node node = - ParseTextProtoOrDie(kConfig); - node.mutable_options() - ->MutableExtension(ShotBoundaryCalculatorOptions::ext) - ->set_output_only_on_change(true); - auto runner = ::absl::make_unique(node); - - AddFrames(20, {15}, runner.get()); - MP_ASSERT_OK(runner->Run()); - auto output_packets = runner->Outputs().Tag(kIsShotChangeTag).packets; - ASSERT_EQ(output_packets.size(), 1); - ASSERT_EQ(output_packets[0].Get(), true); - ASSERT_EQ(output_packets[0].Timestamp().Value(), 15000000); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/signal_fusing_calculator.cc b/examples/desktop/autoflip/calculators/signal_fusing_calculator.cc deleted file mode 100644 index 85b2d96..0000000 --- a/examples/desktop/autoflip/calculators/signal_fusing_calculator.cc +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include -#include -#include -#include -#include - -#include "absl/container/btree_map.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/signal_fusing_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -using mediapipe::Packet; -using mediapipe::PacketTypeSet; -using mediapipe::autoflip::DetectionSet; -using mediapipe::autoflip::SalientRegion; -using mediapipe::autoflip::SignalType; - -constexpr char kIsShotBoundaryTag[] = "IS_SHOT_BOUNDARY"; -constexpr char kSignalInputsTag[] = "SIGNAL"; -constexpr char kOutputTag[] = "OUTPUT"; - -namespace mediapipe { -namespace autoflip { - -struct InputSignal { - SalientRegion signal; - int source; -}; - -struct Frame { - std::vector input_detections; - mediapipe::Timestamp time; -}; - -// This calculator takes one scene change signal (optional, see below) and an -// arbitrary number of detection signals and outputs a single list of -// detections. The scores for the detections can be re-normalized using the -// options proto. Additionally, if a detection has a consistent tracking id -// during a scene the score for that detection is averaged over the whole scene. -// -// Example (ordered interface): -// node { -// calculator: "SignalFusingCalculator" -// input_stream: "scene_change" (required for ordered interface) -// input_stream: "detection_faces" -// input_stream: "detection_custom_text" -// output_stream: "salient_region" -// options:{ -// [mediapipe.autoflip.SignalFusingCalculatorOptions.ext]:{ -// signal_settings{ -// type: {standard: FACE} -// min_score: 0.5 -// max_score: 0.6 -// } -// signal_settings{ -// type: {custom: "custom_text"} -// min_score: 0.9 -// max_score: 1.0 -// } -// } -// } -// } -// -// Example (tag interface): -// node { -// calculator: "SignalFusingCalculator" -// input_stream: "IS_SHOT_BOUNDARY:scene_change" (optional) -// input_stream: "SIGNAL:0:detection_faces" -// input_stream: "SIGNAL:1:detection_custom_text" -// output_stream: "OUTPUT:salient_region" -// options:{ -// [mediapipe.autoflip.SignalFusingCalculatorOptions.ext]:{ -// signal_settings{ -// type: {standard: FACE} -// min_score: 0.5 -// max_score: 0.6 -// } -// signal_settings{ -// type: {custom: "custom_text"} -// min_score: 0.9 -// max_score: 1.0 -// } -// } -// } -// } -class SignalFusingCalculator : public mediapipe::CalculatorBase { - public: - SignalFusingCalculator() - : tag_input_interface_(false), process_by_scene_(true) {} - SignalFusingCalculator(const SignalFusingCalculator&) = delete; - SignalFusingCalculator& operator=(const SignalFusingCalculator&) = delete; - - static absl::Status GetContract(mediapipe::CalculatorContract* cc); - absl::Status Open(mediapipe::CalculatorContext* cc) override; - absl::Status Process(mediapipe::CalculatorContext* cc) override; - absl::Status Close(mediapipe::CalculatorContext* cc) override; - - private: - absl::Status ProcessScene(mediapipe::CalculatorContext* cc); - std::vector GetSignalPackets(mediapipe::CalculatorContext* cc); - SignalFusingCalculatorOptions options_; - std::map settings_by_type_; - std::vector scene_frames_; - bool tag_input_interface_; - bool process_by_scene_; -}; -REGISTER_CALCULATOR(SignalFusingCalculator); - -namespace { -std::string CreateSettingsKey(const SignalType& signal_type) { - if (signal_type.has_standard()) { - return "standard_" + std::to_string(signal_type.standard()); - } else { - return "custom_" + signal_type.custom(); - } -} -std::string CreateKey(const InputSignal& detection) { - std::string id_source = std::to_string(detection.source); - std::string id_signal = std::to_string(detection.signal.tracking_id()); - std::string id = id_source + ":" + id_signal; - return id; -} -void SetupTagInput(mediapipe::CalculatorContract* cc) { - if (cc->Inputs().HasTag(kIsShotBoundaryTag)) { - cc->Inputs().Tag(kIsShotBoundaryTag).Set(); - } - for (int i = 0; i < cc->Inputs().NumEntries(kSignalInputsTag); i++) { - cc->Inputs().Get(kSignalInputsTag, i).Set(); - } - cc->Outputs().Tag(kOutputTag).Set(); -} - -void SetupOrderedInput(mediapipe::CalculatorContract* cc) { - cc->Inputs().Index(0).Set(); - for (int i = 1; i < cc->Inputs().NumEntries(); ++i) { - cc->Inputs().Index(i).Set(); - } - cc->Outputs().Index(0).Set(); -} -} // namespace - -absl::Status SignalFusingCalculator::Open(mediapipe::CalculatorContext* cc) { - options_ = cc->Options(); - for (const auto& setting : options_.signal_settings()) { - settings_by_type_[CreateSettingsKey(setting.type())] = setting; - } - if (cc->Inputs().HasTag(kSignalInputsTag)) { - tag_input_interface_ = true; - if (!cc->Inputs().HasTag(kIsShotBoundaryTag)) { - process_by_scene_ = false; - } - } - return absl::OkStatus(); -} - -absl::Status SignalFusingCalculator::Close(mediapipe::CalculatorContext* cc) { - if (!scene_frames_.empty()) { - MP_RETURN_IF_ERROR(ProcessScene(cc)); - scene_frames_.clear(); - } - return absl::OkStatus(); -} - -absl::Status SignalFusingCalculator::ProcessScene( - mediapipe::CalculatorContext* cc) { - absl::btree_map detection_count; - absl::btree_map multiframe_score; - // Create a unified score for all items with temporal ids. - for (const Frame& frame : scene_frames_) { - for (const auto& detection : frame.input_detections) { - if (detection.signal.has_tracking_id()) { - // Create key for each detector type - if (detection_count.find(CreateKey(detection)) == - detection_count.end()) { - multiframe_score[CreateKey(detection)] = 0.0; - detection_count[CreateKey(detection)] = 0; - } - multiframe_score[CreateKey(detection)] += detection.signal.score(); - detection_count[CreateKey(detection)]++; - } - } - } - // Average scores. - for (auto iterator = multiframe_score.begin(); - iterator != multiframe_score.end(); iterator++) { - multiframe_score[iterator->first] = - iterator->second / detection_count[iterator->first]; - } - // Process detections. - for (const Frame& frame : scene_frames_) { - std::unique_ptr processed_detections(new DetectionSet()); - for (auto detection : frame.input_detections) { - float score = detection.signal.score(); - if (detection.signal.has_tracking_id()) { - std::string id_source = std::to_string(detection.source); - std::string id_signal = std::to_string(detection.signal.tracking_id()); - std::string id = id_source + ":" + id_signal; - score = multiframe_score[id]; - } - // Normalize within range. - float min_value = 0.0; - float max_value = 1.0; - - auto settings_it = settings_by_type_.find( - CreateSettingsKey(detection.signal.signal_type())); - if (settings_it != settings_by_type_.end()) { - min_value = settings_it->second.min_score(); - max_value = settings_it->second.max_score(); - detection.signal.set_is_required(settings_it->second.is_required()); - detection.signal.set_only_required(settings_it->second.only_required()); - } - - float final_score = score * (max_value - min_value) + min_value; - detection.signal.set_score(final_score); - *processed_detections->add_detections() = detection.signal; - } - if (tag_input_interface_) { - cc->Outputs() - .Tag(kOutputTag) - .Add(processed_detections.release(), frame.time); - } else { - cc->Outputs().Index(0).Add(processed_detections.release(), frame.time); - } - } - - return absl::OkStatus(); -} - -std::vector SignalFusingCalculator::GetSignalPackets( - mediapipe::CalculatorContext* cc) { - std::vector signal_packets; - if (tag_input_interface_) { - for (int i = 0; i < cc->Inputs().NumEntries(kSignalInputsTag); i++) { - const Packet& packet = cc->Inputs().Get(kSignalInputsTag, i).Value(); - signal_packets.push_back(packet); - } - } else { - for (int i = 1; i < cc->Inputs().NumEntries(); i++) { - const Packet& packet = cc->Inputs().Index(i).Value(); - signal_packets.push_back(packet); - } - } - return signal_packets; -} - -absl::Status SignalFusingCalculator::Process(mediapipe::CalculatorContext* cc) { - bool is_boundary = false; - if (process_by_scene_) { - const auto& shot_tag = (tag_input_interface_) - ? cc->Inputs().Tag(kIsShotBoundaryTag) - : cc->Inputs().Index(0); - if (!shot_tag.Value().IsEmpty()) { - is_boundary = shot_tag.Get(); - } - } - - if (is_boundary) { - MP_RETURN_IF_ERROR(ProcessScene(cc)); - scene_frames_.clear(); - } - - Frame frame; - const auto& signal_packets = GetSignalPackets(cc); - for (int i = 0; i < signal_packets.size(); i++) { - const Packet& packet = signal_packets[i]; - if (packet.IsEmpty()) { - continue; - } - const auto& detection_set = packet.Get(); - for (const auto& detection : detection_set.detections()) { - InputSignal input; - input.signal = detection; - input.source = i; - frame.input_detections.push_back(input); - } - } - frame.time = cc->InputTimestamp(); - scene_frames_.push_back(frame); - - // Flush buffer on same input if it exceeds max_scene_size or if there is not - // shot input information. - if (scene_frames_.size() > options_.max_scene_size() || !process_by_scene_) { - MP_RETURN_IF_ERROR(ProcessScene(cc)); - scene_frames_.clear(); - } - - return absl::OkStatus(); -} - -absl::Status SignalFusingCalculator::GetContract( - mediapipe::CalculatorContract* cc) { - if (cc->Inputs().NumEntries(kSignalInputsTag) > 0) { - SetupTagInput(cc); - } else { - SetupOrderedInput(cc); - } - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/signal_fusing_calculator.proto b/examples/desktop/autoflip/calculators/signal_fusing_calculator.proto deleted file mode 100644 index 3f14935..0000000 --- a/examples/desktop/autoflip/calculators/signal_fusing_calculator.proto +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/examples/desktop/autoflip/autoflip_messages.proto"; -import "mediapipe/framework/calculator.proto"; - -// Next tag: 3 -message SignalFusingCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional SignalFusingCalculatorOptions ext = 280092372; - } - // Setting related to each type of signal this calculator could process. - repeated SignalSettings signal_settings = 1; - - // Force a flush of the frame buffer after this number of frames. - optional int32 max_scene_size = 2 [default = 600]; -} - -// Next tag: 6 -message SignalSettings { - // The type of signal these settings pertain to. - optional SignalType type = 1; - - // Force a normalized incoming score to be re-normalized to within this range. - // (set values to min:0 and max:1 for no change in the incoming score) - // Values must be between 0-1, min must be less than max. - // - // Example of score adjustment: - // Incoming OCR score: .7 - // Min OCR Score: .9 - // Max OCR Score: 1.0 - // --Result: .97 - optional float min_score = 2 [default = 0]; - optional float max_score = 3 [default = 1.0]; - - // Is this signal required within the output cropped video? If it is it will - // be included or the video will be marked as failed to convert. - optional bool is_required = 4 [default = false]; - - // When used with ContentZoomingCalculator, this flag can be set indicating - // that areas outside of these salient regions can be cropped from the frame. - // When no salient regions have this flag set true, no zooming is performed. - // When one or more salient regions have this flag set true, the max zoom - // value will be used that keeps all “only_required” detections within view. - // The ContentZoomingCalculator currently supports zooming by finding the size - // of non-salient top/bottom borders regions and provides this information to - // the SceneCroppingCalculator for reframing. - optional bool only_required = 5 [default = false]; -} diff --git a/examples/desktop/autoflip/calculators/signal_fusing_calculator_test.cc b/examples/desktop/autoflip/calculators/signal_fusing_calculator_test.cc deleted file mode 100644 index c494e80..0000000 --- a/examples/desktop/autoflip/calculators/signal_fusing_calculator_test.cc +++ /dev/null @@ -1,569 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "absl/strings/string_view.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/calculators/signal_fusing_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -using mediapipe::autoflip::DetectionSet; - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kOutputTag[] = "OUTPUT"; -constexpr char kIsShotBoundaryTag[] = "IS_SHOT_BOUNDARY"; - -const char kConfigA[] = R"( - calculator: "SignalFusingCalculator" - input_stream: "scene_change" - input_stream: "detection_set_a" - input_stream: "detection_set_b" - output_stream: "salient_region" - options:{ - [mediapipe.autoflip.SignalFusingCalculatorOptions.ext]:{ - signal_settings{ - type: {standard: FACE_FULL} - min_score: 0.5 - max_score: 0.6 - } - signal_settings{ - type: {standard: TEXT} - min_score: 0.9 - max_score: 1.0 - } - } - })"; - -const char kConfigB[] = R"( - calculator: "SignalFusingCalculator" - input_stream: "scene_change" - input_stream: "detection_set_a" - input_stream: "detection_set_b" - input_stream: "detection_set_c" - output_stream: "salient_region" - options:{ - [mediapipe.autoflip.SignalFusingCalculatorOptions.ext]:{ - signal_settings{ - type: {standard: FACE_FULL} - min_score: 0.5 - max_score: 0.6 - } - signal_settings{ - type: {custom: "text"} - min_score: 0.9 - max_score: 1.0 - } - signal_settings{ - type: {standard: LOGO} - min_score: 0.1 - max_score: 0.3 - } - } - })"; - -const char kConfigC[] = R"( - calculator: "SignalFusingCalculator" - input_stream: "IS_SHOT_BOUNDARY:scene_change" - input_stream: "SIGNAL:0:detection_set_a" - input_stream: "SIGNAL:1:detection_set_b" - output_stream: "OUTPUT:salient_region" - options:{ - [mediapipe.autoflip.SignalFusingCalculatorOptions.ext]:{ - signal_settings{ - type: {standard: FACE_FULL} - min_score: 0.5 - max_score: 0.6 - } - signal_settings{ - type: {standard: TEXT} - min_score: 0.9 - max_score: 1.0 - } - } - })"; - -TEST(SignalFusingCalculatorTest, TwoInputNoTracking) { - auto runner = absl::make_unique( - ParseTextProtoOrDie(kConfigA)); - - auto input_border = absl::make_unique(false); - runner->MutableInputs()->Index(0).packets.push_back( - Adopt(input_border.release()).At(Timestamp(0))); - - auto input_face = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.5 - signal_type: { standard: FACE_FULL } - } - detections { - score: 0.3 - signal_type: { standard: FACE_FULL } - } - )pb")); - - runner->MutableInputs()->Index(1).packets.push_back( - Adopt(input_face.release()).At(Timestamp(0))); - - auto input_ocr = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.3 - signal_type: { standard: TEXT } - } - detections { - score: 0.9 - signal_type: { standard: TEXT } - } - )pb")); - - runner->MutableInputs()->Index(2).packets.push_back( - Adopt(input_ocr.release()).At(Timestamp(0))); - - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Index(0).packets; - const auto& detection_set = output_packets[0].Get(); - - ASSERT_EQ(detection_set.detections().size(), 4); - EXPECT_FLOAT_EQ(detection_set.detections(0).score(), .55); - EXPECT_FLOAT_EQ(detection_set.detections(1).score(), .53); - EXPECT_FLOAT_EQ(detection_set.detections(2).score(), .93); - EXPECT_FLOAT_EQ(detection_set.detections(3).score(), .99); -} - -TEST(SignalFusingCalculatorTest, TwoInputShotLabeledTags) { - auto runner = absl::make_unique( - ParseTextProtoOrDie(kConfigC)); - - auto input_shot = absl::make_unique(false); - runner->MutableInputs() - ->Tag(kIsShotBoundaryTag) - .packets.push_back(Adopt(input_shot.release()).At(Timestamp(0))); - - auto input_face = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.5 - signal_type: { standard: FACE_FULL } - } - detections { - score: 0.3 - signal_type: { standard: FACE_FULL } - } - )pb")); - - runner->MutableInputs() - ->Get("SIGNAL", 0) - .packets.push_back(Adopt(input_face.release()).At(Timestamp(0))); - - auto input_ocr = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.3 - signal_type: { standard: TEXT } - } - detections { - score: 0.9 - signal_type: { standard: TEXT } - } - )pb")); - - runner->MutableInputs() - ->Get("SIGNAL", 1) - .packets.push_back(Adopt(input_ocr.release()).At(Timestamp(0))); - - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kOutputTag).packets; - const auto& detection_set = output_packets[0].Get(); - - ASSERT_EQ(detection_set.detections().size(), 4); - EXPECT_FLOAT_EQ(detection_set.detections(0).score(), .55); - EXPECT_FLOAT_EQ(detection_set.detections(1).score(), .53); - EXPECT_FLOAT_EQ(detection_set.detections(2).score(), .93); - EXPECT_FLOAT_EQ(detection_set.detections(3).score(), .99); -} - -TEST(SignalFusingCalculatorTest, TwoInputNoShotLabeledTags) { - auto runner = absl::make_unique( - ParseTextProtoOrDie(kConfigC)); - - auto input_face = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.5 - signal_type: { standard: FACE_FULL } - } - detections { - score: 0.3 - signal_type: { standard: FACE_FULL } - } - )pb")); - - runner->MutableInputs() - ->Get("SIGNAL", 0) - .packets.push_back(Adopt(input_face.release()).At(Timestamp(0))); - - auto input_ocr = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.3 - signal_type: { standard: TEXT } - } - detections { - score: 0.9 - signal_type: { standard: TEXT } - } - )pb")); - - runner->MutableInputs() - ->Get("SIGNAL", 1) - .packets.push_back(Adopt(input_ocr.release()).At(Timestamp(0))); - - MP_ASSERT_OK(runner->Run()); - - const std::vector& output_packets = - runner->Outputs().Tag(kOutputTag).packets; - const auto& detection_set = output_packets[0].Get(); - - ASSERT_EQ(detection_set.detections().size(), 4); - EXPECT_FLOAT_EQ(detection_set.detections(0).score(), .55); - EXPECT_FLOAT_EQ(detection_set.detections(1).score(), .53); - EXPECT_FLOAT_EQ(detection_set.detections(2).score(), .93); - EXPECT_FLOAT_EQ(detection_set.detections(3).score(), .99); -} - -TEST(SignalFusingCalculatorTest, ThreeInputTracking) { - auto runner = absl::make_unique( - ParseTextProtoOrDie(kConfigB)); - - auto input_border_0 = absl::make_unique(false); - runner->MutableInputs()->Index(0).packets.push_back( - Adopt(input_border_0.release()).At(Timestamp(0))); - - // Time zero. - auto input_face_0 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.2 - signal_type: { standard: FACE_FULL } - tracking_id: 0 - } - detections { - score: 0.0 - signal_type: { standard: FACE_FULL } - tracking_id: 1 - } - detections { - score: 0.1 - signal_type: { standard: FACE_FULL } - } - )pb")); - - runner->MutableInputs()->Index(1).packets.push_back( - Adopt(input_face_0.release()).At(Timestamp(0))); - - auto input_ocr_0 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.2 - signal_type: { custom: "text" } - } - )pb")); - - runner->MutableInputs()->Index(2).packets.push_back( - Adopt(input_ocr_0.release()).At(Timestamp(0))); - - auto input_agn_0 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.3 - signal_type: { standard: LOGO } - tracking_id: 0 - } - )pb")); - - runner->MutableInputs()->Index(3).packets.push_back( - Adopt(input_agn_0.release()).At(Timestamp(0))); - - // Time one - auto input_border_1 = absl::make_unique(false); - runner->MutableInputs()->Index(0).packets.push_back( - Adopt(input_border_1.release()).At(Timestamp(1))); - - auto input_face_1 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.7 - signal_type: { standard: FACE_FULL } - tracking_id: 0 - } - detections { - score: 0.9 - signal_type: { standard: FACE_FULL } - tracking_id: 1 - } - detections { - score: 0.2 - signal_type: { standard: FACE_FULL } - } - )pb")); - - runner->MutableInputs()->Index(1).packets.push_back( - Adopt(input_face_1.release()).At(Timestamp(1))); - - auto input_ocr_1 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.3 - signal_type: { custom: "text" } - } - )pb")); - - runner->MutableInputs()->Index(2).packets.push_back( - Adopt(input_ocr_1.release()).At(Timestamp(1))); - - auto input_agn_1 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.3 - signal_type: { standard: LOGO } - tracking_id: 0 - } - )pb")); - - runner->MutableInputs()->Index(3).packets.push_back( - Adopt(input_agn_1.release()).At(Timestamp(1))); - - // Time two - auto input_border_2 = absl::make_unique(false); - runner->MutableInputs()->Index(0).packets.push_back( - Adopt(input_border_2.release()).At(Timestamp(2))); - - auto input_face_2 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.8 - signal_type: { standard: FACE_FULL } - tracking_id: 0 - } - detections { - score: 0.9 - signal_type: { standard: FACE_FULL } - tracking_id: 1 - } - detections { - score: 0.3 - signal_type: { standard: FACE_FULL } - } - )pb")); - - runner->MutableInputs()->Index(1).packets.push_back( - Adopt(input_face_2.release()).At(Timestamp(2))); - - auto input_ocr_2 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.3 - signal_type: { custom: "text" } - } - )pb")); - - runner->MutableInputs()->Index(2).packets.push_back( - Adopt(input_ocr_2.release()).At(Timestamp(2))); - - auto input_agn_2 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.9 - signal_type: { standard: LOGO } - tracking_id: 0 - } - )pb")); - - runner->MutableInputs()->Index(3).packets.push_back( - Adopt(input_agn_2.release()).At(Timestamp(2))); - - // Time three (new scene) - auto input_border_3 = absl::make_unique(true); - runner->MutableInputs()->Index(0).packets.push_back( - Adopt(input_border_3.release()).At(Timestamp(3))); - - auto input_face_3 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.2 - signal_type: { standard: FACE_FULL } - tracking_id: 0 - } - detections { - score: 0.3 - signal_type: { standard: FACE_FULL } - tracking_id: 1 - } - detections { - score: 0.4 - signal_type: { standard: FACE_FULL } - } - )pb")); - - runner->MutableInputs()->Index(1).packets.push_back( - Adopt(input_face_3.release()).At(Timestamp(3))); - - auto input_ocr_3 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.5 - signal_type: { custom: "text" } - } - )pb")); - - runner->MutableInputs()->Index(2).packets.push_back( - Adopt(input_ocr_3.release()).At(Timestamp(3))); - - auto input_agn_3 = - absl::make_unique(ParseTextProtoOrDie( - R"pb( - detections { - score: 0.6 - signal_type: { standard: LOGO } - tracking_id: 0 - } - )pb")); - - runner->MutableInputs()->Index(3).packets.push_back( - Adopt(input_agn_3.release()).At(Timestamp(3))); - - MP_ASSERT_OK(runner->Run()); - - // Check time 0 - std::vector output_packets = runner->Outputs().Index(0).packets; - DetectionSet detection_set = output_packets[0].Get(); - - float face_id_0 = (.2 + .7 + .8) / 3; - face_id_0 = face_id_0 * .1 + .5; - float face_id_1 = (0.0 + .9 + .9) / 3; - face_id_1 = face_id_1 * .1 + .5; - float face_3 = 0.1; - face_3 = face_3 * .1 + .5; - float ocr_1 = 0.2; - ocr_1 = ocr_1 * .1 + .9; - float agn_1 = (.3 + .3 + .9) / 3; - agn_1 = agn_1 * .2 + .1; - - ASSERT_EQ(detection_set.detections().size(), 5); - EXPECT_FLOAT_EQ(detection_set.detections(0).score(), face_id_0); - EXPECT_FLOAT_EQ(detection_set.detections(1).score(), face_id_1); - EXPECT_FLOAT_EQ(detection_set.detections(2).score(), face_3); - EXPECT_FLOAT_EQ(detection_set.detections(3).score(), ocr_1); - EXPECT_FLOAT_EQ(detection_set.detections(4).score(), agn_1); - - // Check time 1 - detection_set = output_packets[1].Get(); - - face_id_0 = (.2 + .7 + .8) / 3; - face_id_0 = face_id_0 * .1 + .5; - face_id_1 = (0.0 + .9 + .9) / 3; - face_id_1 = face_id_1 * .1 + .5; - face_3 = 0.2; - face_3 = face_3 * .1 + .5; - ocr_1 = 0.3; - ocr_1 = ocr_1 * .1 + .9; - agn_1 = (.3 + .3 + .9) / 3; - agn_1 = agn_1 * .2 + .1; - - ASSERT_EQ(detection_set.detections().size(), 5); - EXPECT_FLOAT_EQ(detection_set.detections(0).score(), face_id_0); - EXPECT_FLOAT_EQ(detection_set.detections(1).score(), face_id_1); - EXPECT_FLOAT_EQ(detection_set.detections(2).score(), face_3); - EXPECT_FLOAT_EQ(detection_set.detections(3).score(), ocr_1); - EXPECT_FLOAT_EQ(detection_set.detections(4).score(), agn_1); - - // Check time 2 - detection_set = output_packets[2].Get(); - - face_id_0 = (.2 + .7 + .8) / 3; - face_id_0 = face_id_0 * .1 + .5; - face_id_1 = (0.0 + .9 + .9) / 3; - face_id_1 = face_id_1 * .1 + .5; - face_3 = 0.3; - face_3 = face_3 * .1 + .5; - ocr_1 = 0.3; - ocr_1 = ocr_1 * .1 + .9; - agn_1 = (.3 + .3 + .9) / 3; - agn_1 = agn_1 * .2 + .1; - - ASSERT_EQ(detection_set.detections().size(), 5); - EXPECT_FLOAT_EQ(detection_set.detections(0).score(), face_id_0); - EXPECT_FLOAT_EQ(detection_set.detections(1).score(), face_id_1); - EXPECT_FLOAT_EQ(detection_set.detections(2).score(), face_3); - EXPECT_FLOAT_EQ(detection_set.detections(3).score(), ocr_1); - EXPECT_FLOAT_EQ(detection_set.detections(4).score(), agn_1); - - // Check time 3 (new scene) - detection_set = output_packets[3].Get(); - - face_id_0 = 0.2; - face_id_0 = face_id_0 * .1 + .5; - face_id_1 = 0.3; - face_id_1 = face_id_1 * .1 + .5; - face_3 = 0.4; - face_3 = face_3 * .1 + .5; - ocr_1 = 0.5; - ocr_1 = ocr_1 * .1 + .9; - agn_1 = .6; - agn_1 = agn_1 * .2 + .1; - - ASSERT_EQ(detection_set.detections().size(), 5); - EXPECT_FLOAT_EQ(detection_set.detections(0).score(), face_id_0); - EXPECT_FLOAT_EQ(detection_set.detections(1).score(), face_id_1); - EXPECT_FLOAT_EQ(detection_set.detections(2).score(), face_3); - EXPECT_FLOAT_EQ(detection_set.detections(3).score(), ocr_1); - EXPECT_FLOAT_EQ(detection_set.detections(4).score(), agn_1); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/testdata/dino.jpg b/examples/desktop/autoflip/calculators/testdata/dino.jpg deleted file mode 100644 index 0b00f50f127a983923c909582002203174adad10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 467082 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

>$Lt?)j=sH;FA~ zlcB5j*adF*0@w$HvLGT#!^DF|Hfvwr*Z~!65v)5KOy;U%ZZ!nKiC3j1inIQoR9+7H zkt(a4-0<{3S=}OQYVs}}PqTnyX{l#xgZRPQqbcyOw1U^0@+qIp|8#afXD#53I*GE( zYHwov50GO~8OXfhq$7TPF~VY>@9ekuoF~X-O9H(k?V~1u<9@H)MHmJHZoPu_vu#ex z9i920!-_P)>wR896Br%Z`^4-2%ZfbIteCr+KoU&ApfyFQuJ=2bQaYFfdLyq!4f8N; zZd9^pNSkytC9|g1Mr9N_GV7{%X76#8W`Cl?KKIKG-bSM=^QB zn(ipo3^$v4*N5gMPWcBr#vW>T*Cc7c^j}J%ly4#Pst?!pbor$xL5i#m6kE5O$t9+$M zVyL}Zv#RuQthYA<_JCu!-;@_B5Xy+^wb`g!hMgL!pwtZl;_>zw;u*42wWXztsua41 zms>>elzMRLbinw{mYdpZmqU?o8soV`MPFg^6xD%(6wTK;2dNl7xD^Q##^ur-vrM@r zQ?Q-OW?$aXbFfZ($%Hr7AqE=Vqh|(c5*9Dzf{RSWt*SA_QxZ|OvDkD*?Ua^B&x9A zDLtHiPzx84Sk8u#7@{pAS)YTDFVdHAk++OmAXx>)o)=a>b2$VL=#`RJHdHNQT5+tW zO70qBMJ#hTnUJi`l8G=yKxTtymx3^jv0LRdsW;|#PE)~44b(EtvmE-dfd2#7Nmia;sZc3x;TRukEt7)5gfKral#~^6l3exIzH4i|tYnc{ zRY80}9jY!45tNMbu$-!9tK599Lb35po$%Bv5j#74TWacV+bqyBF6BF%L6+OIFt%yr z^d_!g-S#T81MiCB)Cu<)GyKZV}sW(YO;7ItSFo-&pp?LXbeWzzzxJO3^AMEK+qj1;m+Q(9ezWC(Kzzs&)r=zC z9e^%*p=E#EY|x7JwD2~p>haWdaB@PRs&)} zNYKH9;BoKs0mKIb)q564IbVfLyTzH&AwaaBD2>^~xfmf>mC%20Empre*hpBIC4~k& zBrKYo-wo4AaSg1*$PCG|00fY!-76R+BB%VFkH(Dl;u^xU0pAk14;_#Si#7~3ai@|1aT19u|?LPx3AS0Nen(!@nb(!pV= z_|Yy!kR)&NQl`AWS`7`%!1h+%Q!xx@xc3a!a-eYLQa5zg5zLjKA~8h=PR>aaV~Sm- z0|IF5dVl#Q_FQ|*?#fv^x{Qp7v|O{)SWLAJl<g;uVA0FUu5`aN5hv!&|k60EA6<*b8O1`GMW10 zA?1}%eV}8uVEkGXaXOLV&&bV~$<*V)aTXZC8ov^?V)6Z`4jr*T&IhC+o?p?L;WURy zlWLClPMGeAre&H3`Ry}6Tr{=`BaR~`qW=Ld8~kOpqGjL|&+nyDO}po~(cyq^D#re! zCp-olm??#QImvgL9w?Ty_NETGPWl>^-?B%~A#cU9!+zkw{{6s3#c#ux!tpqoEe9VI zKWiwP)c0dTcUEm;z&8eyGd8k8@pDu$@%SW)Sn&Tod(;3j?IzKhMWF_E&wt5P&d@Uw z`D4&00YI>g;ZBfwAjM0ZJIu;@^D63>h9>zL79Hu&MAHQwz6)RMIWhnok`a0@%&52P zxUV;hV=_=DYl0Cn_^C@L^oaI|d-0^l07i7w@Y0HUlBr9Wm>SwK-$Qn*A9j9SaAB9W z#^KLPI|>M*`DKW?BiqlSosSl5Nco6aRYazFVq{%ukfq5qaQ#%qe1Hk@X@FEPSe)ZMGrwy>K^sDh?O(wkNdahMz6gGCcQw!l2E#*Z%J^= zfI0>nM{FOmSUuLkqZ$+LjQVN*4b@o|-x*XUd$~q|oA#&}$n>Pd$)-T@rxN*rbDg&a zulyZ)!$Z(rFwzQBG`36A>tl2y51t+r?NUPMgSF}QI>f@P;(wJX$Nzs}EdRXBA4B0X z4=s2v<@N9)0~@xdj(abThlXIk6=oT^V~8bW_2kj+Yy3s&%8-SH8^%|3@NIp*U7(>K zdUN@3yE71^SoWNYfsZK}M@l`1jHaG*an|I1T0#4tNuT_&lS#w3z0rdS9CQQsKw005 z>a$*@;LH~AHJ&BnxwPwccn%;@YN?o`{0>TlG>#klWbfopHrRqj<*R|D8{@-L&BzB% zPhys&x1#&ej)Uh$%Wo;^|9&lGY)fg57-Y@wHvBNS0@t$J!hEtxPdL! zgsjg%8ue`O`~4w;NA>PRU*uRugb=QYt5^?mzixssr^@d#Q{jTEk5lHwc5UmenR%Yx ztr38K)>D8nIIdV#XK`)>(|u$;h^H~gX%ma8PG&R;h#g43Nq%h=(fuSuDZ(-8_~frR zc8uV4{7sa<;ryZ8)^~~r@hF3z1teO-L;4o{jfU+Ru4?YfvS7xpqL{*(!)S%)%m|25 zuIOshS%~$KtwKDdmcI=`JR$xOo%b(2{0~4cg?gb+QJg?)p;`+wB*JKW9A?wsoRgS% zZy0clrS7mG|15zkqn>Wvoc1mlYQ%h)_y69t8<0Aj`ks^LD91JFVQLq6!FPt5Uh1R- zp*@w_IBBYpvM#9JA^AT5lH)%BE^%e>yiS4oG@Ls^Y@EDxi|U*||GQjNhP62aRX_Sc z_A*2%Yb=}PQR3Fd$q4&-$160C=>bGi4=*aW_vPrbJF$BIaJc z*nQ(Yv0pGe&sn~5i>kMRm%IP6C+GvZ$ze2|RkYuWdlP`(+wT;Q{qrBdHZH+67@2I? zCJ^RMD~e!9#c4($@T)=aa1Xtc5tu?VVQ~#R^nMM0kej^p1zOPEb%Nay+sc;v($_s_ zrU9hG#W#6O|Fx22?4j%CZvYOS7;kUI=BrFdKrb3T=w@Gf9xV6UPEj#sd>(^f)HSZ*p4pkhIYiZGpau1Jk^X z)`a3aX4evXTA^sEr7x#~h2-}4ZK?N=gY4-}lsol4qHiG z4@G+|awDo(^Q;P_cF*ygq3)~ssqd)Me*kIqxUWV;&fK3tX%U{ncNKTh>{dLps93J6 zJsTKswCo77X7J>oG_3ls0E6uu0zOh&WS({G)^s>nd;@ZO9&%lUbIS5Z!T3d{IUn!y z5EI;ZL?2O`*o%75{+*Ps>Z4q^O8MTbu*;5XAn$+K*CJ@=;OltK{Zqf;mfn~_l!G45 zJssgCUf!Lh?Aq*aVTpMrN9Ef-tE(4Yb9PB+Tktcbwb1HdaIh8YM>O9}t70!?Np?RM zYZ&SGo+c$)0K!o>Mx}q2)#+2@-<*n0tC!t=a;sUm0`g6pRw1O0Sf>OI2ZS7uR3`oS zE`&P~_0bN7q(<5ZaLw;qV?GzCPZco@!HC&kPFndE76n=-`ayyh%W0!^RyWj(gwq4> z1|J2WgK9H0X0J0M?SS1yKkL}#BZ2$n*=>=LoMrn5$`c6PH2(f;D^OC~={?Kx3u!^? zK`TcmqnTOFu75$7xroK#79ZGrPe)W@pj783m$%%zS0PVF#GF2;$u8C-UIngdaDob& zf!oTy=2ntN?jeg0Zp~?EyUjPg-i1PT{z%U(fp)qH_ej26z>w37D9sA4zw=>PlC4uu z{Ki40+*Eagk#m;SYgcmV)uu91loddE;XXT>XN%DCNyOdCHowfk{3S%^ho9_U*#8@) zNdDV3dwDNaH}~mP)xIN>TM`HM{ z$~a~~*GJzOAIiw?#BToONRPnT%>+4XUG7xs+T&K}Vl=8zQ(0GUzbIS?Dt!2e??*is zv|vm|b3ea4)%JIWchAn$uh@*2h8b?n%X+>EM+6cvLBB+$*Aph+E0m%(aGAVFu;Jy8 zXCNA!>QNu7Qd;;Xx&r0R0k0>NOhiE-M7Er33!;Lru4=c0FK;@m^POXKuyE|&x>nbB zhh!zz$R?I{wuafOec6S;%&~RfFn`fb1!+OgXUakar~ zJiIc((AVwQ$TZFxnc@E&Rsxxq>`+>Zar#j(|A5T_9lhVFRzLCtOb77^>ChX2AWj4ZY_4x?g-*PpZ?1=+UnAIkTx5 z$4#|kRC2$jT6GAq{VC*0<=+z1iic2jaCw8HOJRv`trt_)2Om-f#AV+ylmsQ@1}~fV zc7mR19-7jU`eUUjP0oCGy-{~MTI*Gfm~}zG5V{IX1DcBePW?rA6>%qWY^u3U_|cVL zCl!*aP17JWl2ezYS68)NGs|mb6!l&Ae^WJiEbb1(_7eAOq;la1T%FW8l6S-T z)mNdpkC>ldNUo5@41V#xM(&`4#VfD8pFC~3!kl~+AQCKLid>wXMS}nDJ3PXSj~cOu zeYjQt(@&e6ylyZd%E2+!XDQmegqU*BdU4mx^kz3wOl=|N{eT|B)o|CgM&=+9Uj1J| z8`PsexoZ3d&`@wA7<1XN)XSAvrN?mZ=a<@F^&cOzrVKW6@}B+#fcUADEt5uI1i|F` z&Sd{m*)Dga_wKB`rq#a%cB1HLNvHfOAqyV3?f9&^-ZZBE>`P{fbSxer;W_|#rU~#x z6J-m7p0AFZ*=DLf4(C%B|hh*h)(ddNIX{!wWI&oMat@0)}Et zrDmUj7;}Y%`NrE6mP84IG$-6O-oQR-oKJWiZ5~U^ z>nB|nPPoLWmdO}=srOno^&cOX%0Aa($PJHBY`fk#zxij|>J3{>6@7JWdf4;QS{=$wBzuBd@kP@lIZ4C1SPH z3%Rt_m->8tLiVOR4MbMYse=vGU`1LFH3TVWJj~Cr0D{9TE_{-Xg zGjCg9Ajfvv@nyCK_D(4kigLMjP8d5z9L6PfGfhjTh9H1uue=s|VZPz*Y*XtP`Im$2 zy){oLQhX;ezI=-SKKS-($mvio?&Q|<;h&ra?-FjFGgc(E*HEO%eDQog2(RHfVD{#r zp9RT1ELrj)-bB9OrC&Ub-AsyJIfK5Vw2lSR+(dj+=y#nvY44CuEypvwKl3pe5mh;Z zo#F;Rk7feNW2>@)Oq|R*lg$1elSm-nTG!nMq}=bc%zFBmUJVnb%lAwdFKU<|{IVXe zS3T8s9L&9XzYi(%m^V4)^fe)S1EYv>`LzsvUOoKPxb4o*dno{!!kR_v?<)IvrQa-L z|Ddn7MWe|h9>R5KWG{E*{cRHZfG^y2>Rwj-H9WnZ_q8|0i>ztmI_TY$#!T-Goo!Id zuGzhwX1HWDTG4xFhxPvjbX(N$BE^Y5!>lx(vk;c4;Nsh@y#_oz>&+u_mz5pQzZb;goT#V52AAq%;C_O!)kSLydUWwB91&5`E zr)sMYGO)U7$&xX1=CQvFjj}xZ);hJpo4{!8RagBFRM$sEj`sshU!^t!;b)3$GmWmD z9_Re41D1Bb)g$X{3m64u2RV;_dHHy`j=gFk5B;p-Ggap}WIcR|u6Yuzy0lSd27!~vK99^ zY;LdLH@N4*$on{EhkYV@&B}!GcCOXDLn5}K(LGs=`DUL72<_vA0-L};4RhxNHrGh! zeX5HZv${mhRr@ob$aXdv_K)vEC?e|oV(y>M^@|rZ2S#AcD*0=~^c~NCYppAR%;nKW z^Ik?+Kv;Gh!RJ!TL1|&O%v+n34M$Z~@ju%vj8uhUo64JdTky|MeqASet*qTYk-BL{ zzgr}6Lq?xCYaL`=J~|f<`uGSJ-aga~)yT-G=Zo#0 zi?E=}(iQKfYHlAaDRtfTwDzujSCc|Fo*VrDy`!Zc9sY}H9~Iqr(aAl>?3t&nm9xo( z%&0Djki;B72vgt&gYcB2%*#IZt?`I8YTg6#Odu`9I3afb)>`%T0Pf%aewlHIX}12L zTfzBG;;Kl~GCnm{wr)X7w`+kt@5v0OK3a!`S=a7XUHZ_MKBkdeJsXu4Wo^acjTz(Y za}7zE?BFOXxR!sxiZBf{%*(y2>u=B+5Lgpxfa#s!5IFK}_*Y3=gNnHRpNF zwFU`3%jaf-_dmRgmXQgD8}VtJ2iCk@oDdxU%DjL^Ur)jk#_JG}WwsaQU0ZKFMC~4E zFEEF9h$=wdqaLr6_X~Svv5KS`eIk8Y!U$*eBgLX$2>er5ZiX^zT7;O497Nox{v^fo zO(3eOqKvM&70im~T(YhheZ&0h>AiRvOPwA1OWbcSSNV2MBfuuE6v#X{;IonZ8>~~ z1-#rF!r>e-8w27xZ55W4hcbOTxykH89VvOKryXi!mHnzYaZRINwO$4r01l|q8!J+^ zhIgm4h4-&OzS)Ue?~m6>=&v6mdnl7+O-m zUDJsXN{yM};_%RaXjNotoPhT}qByjvMentK__u@2*<6t+K(CKfBwOkX_w4Ckp{bq| z*4M`8Jvx@foP7}Jrh?aW?tTk=lo06$bos{fhk1uNUW~p=8k>fVP;^Y+?RR`A?(2_o z7X647ic>#sXvrW0Ct|EsS3ZoUSN*hk_g(^^Mo01jtgHtSL^{k6{&(RDI&U~>Bg*rn|&uuEBCnKfh&@<#FRRBkfQkj9RRJx*@G z@Jeniuy*zUR7@mjW{vhy^Bbq(+B(&v5Qpzw)=#Fz=l5s6L=>3brjA;2+4q2zmph zVZjuR_wOZzo$ON{R&m&+Xg3S6suAcj-x@fQKj0Dnj*FOG*+M_r@r-{QZ}7iNR6a>U z-KcWBFx0{k3+nsvy6%kh3w^(Lgl_G`wmFJr9(?#Kou;L_o$pBF%sY@zp zrQ4^opBZ&?6lJyS0;B1DgfxY3;}3hzW)*!C-rnf6Bz(HDqbD`dV5d{5(!u-lm4DZ= zrq@$z3P0BSLW2nlS}5Qj8UPrO#fN2>)IsndK>BP&ezDau0GC)=O)v#8N3Jm+d}TV5Q(Wcn@nQDQvQKrzZtPnNgW+TPLO;E`%MutE3(JY#A9=e;m#WM6Z zx+my@Nz@q)+U?k81;IYYxn}$a$OVmM@6jnRu1&n94MTEME=lvse7(cO*gR;5GVBZ_4m1l#u|`$*jZhGci=c zwO*b~+Y4zbtC3m;m5zL`_e!vxH7g?fg({2aGb-d=W&;>=QJc(JB!TAAxWhAez94y@AVJ}cF`0* zf;*t=GCR=>zwWuY5y$C*&6>hp)B4ACd|S<}!pJA_GFkB0jzoUpir3oYO~-cbqnv zi*NjHU|p1D4jThX;J?jyy8Nb`99;hAwsB|shRGxt1yG;qls;_SQhxI#@f-HuXw}=w zDszYAmUP)P-!#ApJK@O0a`vr{-{LD$kb7jPV;Le7!hX*g``$9b?reGxLD@e*kn?PU zuA6XOX7|!H$7MUFZkZ|5-^QhGA8MW2*N^}D5Afp(<{y)G7q#996&*N{^{jF^ksZo@ zKJY9en3OPPHgy;DFW{B>ge(Nr04fX(O(3gqu8Q+5_Oc#3*J<(*5a(aaz8~;neR1xb zpH0gu<2Owop*OPXYh})17SzVt60$kvok{w;(f0FI!DvnHv7F(_{gV?TEPaq2Fcb!9 z0HjQH>n9(PXza6QR}uH;8jJN)iRk^ zo^dAbN>agGzt?!dSS!aX5og-9rG7WqR3X$wwG35I2BsB7JBIJuynwpF?qec9-$?KK~Vfyn!pE1@mx0rB7)U>s{*g zU8Ns<<3ZdKtfItHl$y2dT1|V~FO7w>zXRTQZh~4u)hW#NZohywlMs6>!@ekt07?%l z>L|>zIv;N0=r7Js;}zH15!zHZW_vvyw9b(tYjr1>BBv_u_E4si(;g_0;YUKpOg99Q zHXBdv=-%30Gfkz{4ZWt37E-8pkD`QuhaHUp&vM`ZMR!R4lf%M=#=K5>%|HB76!(#k z&{jGbU7dl2eS%&aA4Ys(3fO*y1MkJC26v2R?DQs<3V6p@Kb|>SmFdwFuki)q6jlf% zYuPY$qtNeQdwpzoWW-iJt)JEPsp*rI${F5yy?3+kfdc&Ggbld*{KdNTdunNqc;jHA z#9=m?1hG_ru~&ydsBkDuH7Q7WcaQfjZ9e-@=MPC4$IBh zUyGuyt#<8W_e5ZG+g`Bd&G7!P)H!7sPPGluuILD=S+RaRwd{81j)nBuz%)qXPMQE_ z?_0FQN!vk0BjR5%bF=NErgXcW842E0!7VlERa!G;t-Cr{ zT*OG6nFuGZ5gt>=DSL$nbw6uqh1fR9HKORfsg{9o=V96Ed>)pDRa=pV@*CMPRpq!( zU}O_?{I_1al~mHBgy80GmGe(z`r6M=G4RCenJ z!8hw1R~f0H#_E-wrtY7;2aa~yfU!^|tO_-Bn1O(zWWC9W)|vwK^yL7{9a{Rm5m$Ex zx)fz9t|_`hYe?Gj1ePc5*JPv^7owsFD6)Rgz<+?NAn27v4ruLs%H^~OI_l$CcdJVl z;8gq*=55W0Ao%EEjN66%VU2u0E&LLm2TZTLo8(IVtq^;f!?UW%BkWjO66!z$9H{?o z&Kn)7hcKWSfVL>%9?MWb2IxPI3{K|o9Z%McyVbT zMj|5SMn4sqi;K~v^wbh)&$bFxr0ey%7BUW%XFTNQHPQ6KuaF3z|60~WE2v%kl7{rz zq1OcQSG~EDy!q4$Gy50qSp1N)QtZX5sK<0}NC@kTG4pH_7!-qx=ZyEiI4Y*B6iB*R zq5JdM)lk|9J6mhRjW>XI(B?*h@mlx6eo-ju+66R{hpPFaFST81NA2Jwg5v^adyQtDa7M`-dx$ZF+Lc{f$e6e~qxjd}|eDp9=Z-D$Xh z+TXQ)7|}m-^{e?y4>Nf4n3pb$pc`HvN(Ahr-g*{-2|p_YJsWKImFQwBI~{|oD3E7< zUt$;W3pe_~;rfp0?P>eViD=k~U+HJZBk-UeyaT)C(DelDKDD++Bf^Bc{-Mf|oTfvA zEf0#A0_nXjLHQ!S)~{{7sUDWnbUH}INri+X2rmk`#j{geAILgI((NSShsBeRAdjcr z1+_XAy>9(20eFeI7!Z1s~^cpYfcSmVPbKc^M}NzOMU zNPM!T`NxlRyEO4>sD1&sDti*~uEVA9+P}}B_&-Q3O5HQMLFQRQq?+C)c6a54uHNy| zSLD`-arv9Kskc#fnrDB`TrP#l$Cnk~2Cbb3?-isqr8(Q)?YZ)`Gvu3|swQZ7IPXy- zE8!TH{r1!IBNyq;$&=)B{&(761v-sFym$7CZ~DNNJ_Km6>RIfa zhlLJu$*{z*LA*8eELc#j@!LZI0OV+uk*4lQu_`)iofC}A&l72kj`^{`zSrSGLq3nE z1|pv@6Sqg~klf@Ni|6WdLC0NSmIYb>`WZ!w_1=273~!rsOlu5HXNP_N(E7)F;w;0C z24Q!0R7P~BHoClfEQ6Q>zf~Ahu4v)^4`4lz9wrI#Vx2|c#F2=($L4$mi$F2Nl}9r4 zvwP$$vg&pbiZoQkoArI`@!MnaXqEtv{?Q8w^mphoXl*oVE~vf~G=`+fr9{pm>ly^D z`=#VITfqWQD#$}9NEf8PVMJ`z=WmV~Wb5J~3@g_aH;&Po9aq#;GD1#=7gZe~ppMRq z(<)V$gLGwyd?!)ooD~hHj>o44mi4xuRec(7ZcE&pMkU|po^S>-fWdn|nkw+r==Byz zfO3vX`+0+jb(l;FiYWgdVDPE;3;=R2lEfe64B{V>DsX!5ew43(E5%ck7<>H=PI?u4<)MpKsD!ozWFIQe=y@1g#32xh&;`LV&9&N*c3Kr}mY)L3)ig_IF zT9&{-2#P@+EpwZo3)<@tI=aE%C8J!1BJ7sD^%uVTZ*xG)U~A3A zfAj8zO2H1d!J>(UQG~}r+u)$nA4(<LjP^B^&^9TfGZL2Nsku(_d)}t8;f>{qWM*!0MB}LC|f3($g*R*}qn^p}c zLTR^!u4mk!O=e`k7EKL(vAV0r;`H-!Ms5@H9BJW!G=ZaKdjRa{f_(zai0~f+eXFef z+VZ{sUzk#))MPvlc#pc)7Y3VMaCd!<&U&YQ>6wNJGcQ8-BlWCsiWOnmwnI>$*-3~W zo#OcgfAQf4bWPIpdI=j(O(Dq;maX%=nvz8VN7ui#vpw1e|iln!&=nZU{CoEGq~7|I=L#ZZy<_BXapj&M3Op99hd*Z|(Fu`t;hS)eVETgpW$K0W=)`tm2 z(kwyriqw9>npNtrNax8w`0zRBBLVCO$c8<3e~AW3WQxAqBw2GO_2k@!m?NjrOs6eS zN5*br*v6tmqsLB2h&<`Wt8E!RnkD9lIV&_O?WRjy@V_H9)@P;%Ed;#+1nCLwmy^5f z8%L<8>9&e{7W2_$Zf>fx6nq-;${Tp-JMmtDTA?l3YWg$ckWaoGxU*t~DU8D0A`DaV zdY}W4**%qfmCv84=Z62zS=Cq20SpWO0kVoiq?LDMH=crigTEjLOlGOd-oqnVi)&kI zM=;iBEZ!+Hs#I1u@#}9v1ewDJy=$-?$1kNtJ)*{2huCE?eo?;cO-xPQlUsQN-s3Z$EVXdm)O>W8k`>y7ta zkcS!P{_YXX>dAF)c;+;%+cD~a{T2>&?$M&)|PWYe%Wejkw;!p6~h`@#T2*zLEp%)#kjE~;1;)ISDa8dhCcWq zV_MA;LI#ePor5KCPf9eHD|$>((=Z%N0 zbc!!0UhlPY=H_Pnk|YJ#%wrcy^1LNHc;H(c@G8qiZ^D3-X0YVhc>`A;l7QPK=-U zgg^vb((U`Z?ms#f>!II`^IO44?~n`j>FYj}4M1%#(MqC~rGjAlpc=WH9O^?AJE^|n z1EZHb0o)H>?WPjFx=oo--bT;NiFaO0>U-N`A)tAOHcp z1!71h42BZ_vj1zlnaS5C)DS%;1?7DkJ3zNZ;LiIp^1;}bADXnP=vF;6!Qgr8W1nW*Dm`xW+6ca9qYXx4iz8kIg?2pqrOvL=`jF{*Pg0Z2 zl`&+JmyePOZBNhnAHdFZ6n585=gwdjfuTZPJA6VT=<+a=0$;gW#s_3`$z0u#+-a-Nx5iuro>#Snu(R}?%@yKA@2<~uC(M!k1hmYZ#Ns!b0FKzXSz^CDgx4w2BZ)QE@pm*VTe=>4`_pH^ zPnyq~HUdk0+nd1l2_xDUYVWvL!=&46(Couy3nzb3aZTJM*E!MLX===!Pdo&%wlb_> z+lpd2#->Uc5e&^`ovRK7W!u|tp%;t4jgZR{(oOr0vJQJ-=o?WqO7i1+Yclw4vYNW0~}U@JU@1 zr!_Wx8IV&d5aKtgDhl>s>3c=ss{u0)=GYpn2g_H?EmdZ%CqFn6E3iOo{{96%8}cRo z_MZWIC=7&;5s`}9ydQ)8%NprfBajlE>()h;* zZ>{4-J)JObcxuzQmKZ!mA<~4km|+gnnmh8a!lp#q%}ZgQMkjAcm!+|2_}sreo8mj@ zP>+iHm6w)Q>GeegS>OzD1+?!whyojfOMrNyj-znEXqjdeyWh{AHV@y!I)?hdPLza6 z@OB0Mdf=Zq`FM1cm<^|`Vsiab3qS5_vFvSVL;2qvug6`VLt@gFK~~=y)<~rZf_3FV zf|+LBW6eHlqCk~TD;fN<7F?ay*VIJ1D$AZ&FmIS*2F)w(>(jggYkEJg@!n3^WSmcu zx+ZmOcjey>3_Ob&(N0wDIBJ7Bzn4FtgcB=Y3dLSY0SGv5d8a6>r0-`%pI8{5SX_8U zzYX#%nMqSSUtXuxb4;1&{*vmJ>*Xn?zPSeQ{@5u6CMDzH`=zEwYLx5uhuv*$H13d$ zkj(&J9 z_q_=_2VW&!xQNfO%5UQOlr+XGf2?O;)?-&qIn?c$XG^1Xu|j%i`K4NrbAV8_fp zGhQuIu4&SEDb`j7^@?9N6j|chcP?`@RqTEj{K(E&PqG)`p9`ha{4UCKo>o6AC6YF6 zETqk{mmDPqg(|5b?#>O5nOs0b@w;}b{0Ta|PN|c6$bEd@5sr(EA4%PKzJC{Ve_O}A z3MW@R63N1%PhV!*y^$pStAHBtS14tEdUOm3eneyKYV%%YpIjhs7nkw|4RPbN=HFht z&hPRoCK-FW1|ugdPz}6YIe$&%R9M!{)PU)O#tSE$l#l1j99=XksS4XWw5fzW&&SMl zfB?PpfD%hqY~T(1x0UX10wu)~F$$}}>xvPavsv_1_fQ7Yy2QNLo%@mI4kVmOL8mt^ zO|oU97XR9(%e-^A@k7?@|!k1 zPWVg@1SiARtR_g34ESKXGVM4Su?yW3<%TSD3S?-pQHiH3wEr9&ruMtqXXm^8uEr2Z z6sEBLTN;Cl{+t)_KZ?%6p~pl->?h}9xPuh(ei;Ruhy6b2&QktK4{KrMtF|Wi}eSYWmxm>HPGUw1{daYCRvM}Eh zi|K>sWbF5AX$r%)6(PPJJ$?)E=~J_sGdF$ISK$$+$YOghMRJ+m_NrqxIJahc?lk+T6ds{i_c+jIq3i3F7A|2STIdBK6 zlCux9SGE|+y$iAM^k9~3@XGdh7u>dzLiVb6VYZh>rbm*()wj2Q4}`AR(oTQvEwszm ze$nM3I3rjQlD&Q{THS9mx#8Or&@T8p_g^N~_(S_3A|w*~#_OS{Q^O4cLajQ(VRD6tUKwXPqQb{={sd8r!05I34}AE zSn0|NF6IkbSq{*hp4|A8cT9#B|GLUNhBWCYbp&_MEk;ycJ&mg`(gVhpkDbwjO9&=eR;DvQ$L%TwVK%=qPXx ztl3=)t=YQk+!22vw^EeB$S`iKyOrZ~?(6X>T;a*oaeX$Mp*x(6pPZ*F5OF|AgKu?* zN>*dT^ZW((d2%bugGhN3tOlhvk*2mH1NfRBbEq7(s`md?`b5nsf|Efz ztE4|m;PF0zI5Qd%q-x`my)Q9MuVFCx#O52HjtEFgbraFvBN?!Vn<&c2ezgyqlF$mB z^4`vzz9(BW96cHZ!encWUF>ZQIFjumavegG?50X!wNbN04X;r0YF~7R zG{V~G7Sypso0GVf2mI%sjxyfpD?3AV1_tP1AJLujKU2TX9$!HHWTj*P9QG?t2cvB3 zu{M>-5Ruw+>mI`DBk66(652tTI69rb+x#CO{UW}PN{GF%q9V5GxsP_cdx};D#MH9(_v!*^u(505bV^UYY;b(?`bgjdXxm%K+Bdl% zca;z*kF{{7E^kE1fCv4py4JxzPq?t@vlwmVD^cPAVL8zjM75seyR0@4c7qa|uw|M_At2gp-ihxP-<(jM#hgw_P}1eW+9 zHf`wNH1UWvyi(UQgTvENy>(`rW2h@%kFkh5uJR3@e?n{9FXWj7bG~=pR(1ruYzUB= zF{?jCxVdM&`!d9>5Rk0*Z^b!kv(Kyg?{sTNUx8hXR%SK@fY)Agg~$i)Ab{}YA+7D^ zRYAmLOX=Vq+XY_nPgoK%sNBtNGVviXk;*X6ncRtjuhubFbA+Uhu%9_y=6L%k&6!#S zAkafLE!D0#7%UMj&Ts^-KxClqzfEr!lfl;UI!@xfNy?rW>ViAH1t&KL03^OcChLm* z3Cmx0XkD%DFo4n*jtNBM96>jQ{W+Z-t9M)9-Vw-Ay*vc3tZcs`s+Nq@OE82zuBrK; zFY&v~GP;v+popQb&Uw$!D1FjJqV6QMbb9|WQ3*_JCkQ0*^9tZ$%fFTR#(9LB7AT>_=FgDq& z9;}P^Zu)P{9z9k9ga>9^eQ+050r@a3@7N$eNwz}EfUD&UtgL~k5}Z-X2vKr1whV@L z#h$xk7Z%;5ma%+#V}C<89WCR^S8ywjh(f~14;7EUMi0%GlwGoqW@(DWW>GQ2`#1u* z6Do{FN5XjjO!q&022P9y=JyfUz->FD)j{}wwO@Y>{n0IC%E1iwsC?j_`!)rMfYTH-cj))s1`wZ>QZJuYzk8A++sc2h_Unq|n$lkErs4Npr`rVAKI}Y@=aav}h)5iX zuO@V!V<8?|$~Cz)911u1Xu!Vz`Lf36eV-FQ5y&@8WIHLj9#yECx17SVnop{Pg>{>& zb)+6$xg9f9nb7lr*EfwPAY#$#fH>T5>YW+BVT_yj@0E9w1@KN>|J`Lp=QY3X5M z>_^|Y%&jO*B^89RFK|3XzLB?Ofi&-K?lbkVcA&!o1kt+{b~<|o(#%63P=$^y^x|o` zP5W9nt}M*EwrYeoxh0sNgCV$=OM9m{__*>-3;$XozbDv-|K(g%P#+{pIr3=ZxH(d; zUnub9)Ta`~nbc^kp(9?7`0koB6C4m=60+Tkt=l;@ORA#w7Ym~4bNs;XyyP1~JUf9R zNI9}&vg8=P``>N+75hjD<24>c&|XZw2n?`DsF0d;l-pFg+jTnyP{`F;PXM70y6H`a zK4RJPyl?$7y=_ZvaqEgSNaU!Rln(~@kaWnLjm!)LGHZS!1eoKvFwscQu)*!wlq!5d zRMvr+Ve#kYxfG{5PE|-p9}o$-%qG5mcyvCTnJUa(%U#GHr%C@IVtuEry;A7(r|^Zc zkfLDzF(=;f+blW%oi-dPb_rj`6{^puh2;yhZQ^cL&7=x|aCaPt6x6k=VAmJUE$ntH zu^l59=@<37w0di5tR#VFfBnFf(6{frH>i&n{ZxtB*w*2xKIfI)SwHO3?9n$j%Zh}x zRZIn!sX2cs=m2}_SG=A*k`uc*-(q!@a}DWF!@9r^gwI$$#wadXUsY-@pe}Rsj~0N+ zHK&t{T$4P#*K)n%Z>>H(D0#`TeFULtdk1s&QeUp}gK_E6wwSl^y9f)a=NzhF=0uyD z>nzUR<%I8$u$0(?l_!}mrk{UtR;j~5ThEVf^!2iyk3$u@+{-MuJPI2}#|d<=o%T=> zCAn6Hg~-MWIG&9_k_^_ka9<@%H1;~o)^#IVc&qqiQ}qhaHKWaXa>c_-x?AVt6pbS8 zGtaY*zY6>e?7&C+KtUlJ_G3SEtPVtPW|;;L`mw8g)_mxz1K-h08ERldf2bp_UVi=k z!;1smY=VNv6EBa;PLE#QZaK8kE<2a>Q6qOkY@#FG4?1_}%>&XNh$xeNYHUCSZ?0$W z6prnqK!V(Lo7VHpEl-nQ*ekN(&JI5>IaXd}wcO`;>*iTO#n|y%2D30<>G7kJtUA^y zfENR8!1uw5uhL%D#k~=ua=vE5ByfeFl|mTyB9=K`OLO+v(0Z;>U9`7iw4VPiGO*5l zYGWBAsr4uUm8&0+ysWrQxhk5D>-Pn(AFBMC+S+ydaPQsC)T1w+{ENFm%jLn5pUl(5 z6l&r6tL)isXBj*Zt@``0sL8uZlZTCtXP@z9Gu-CrFTjJ!2d851n@lddT(dN%r&Dpc=;g_mYF2DkCnWXRyD`kYjw{f9p}Xa&Id|;&6K^AidzFvhl{CW5 z2PXbVrHZJ8tFo7IoR21rIF!ZTvlUJNZiJX&5_mL(_!(g#-0!V-&rK|#YXIRLHD*QZ zej%q{F|d^SNcJx2NFEl>V<`lwj3EmubYy!F=$;%b3D{(91FDe1$gIzB&mZ=9UpL?6 zJv_dQ?1;~!xTTqc2?;S{`GO+>TmcL4EBlPzs6GW`HR0kW1z|ym5FXphSvf zfhF!2e;Wqs7MV@In@ArZ?B!7&VY!5pg^499)%(Eo@ zhU;Iz%g+*kD1OzE#zc;I=w(iS2UD#`r&e(Ljb-CHfP z@HIyRxGY~S3kUm$xJP&1^XiGn%kB#67IJBm$TgqjvWsU$sEwqf4s;Rv1^fB)QLIM1 zs-O7$^O69Nsx#AH%khv=HdnGJgz?K_ps_6AW2w}(tCbMhuO8V0BU!*W3uje`+2jb5 z+MnmS&6YoTq#eh`U2I!=%B=?CdA8t(ve`lhs+14qp6iB`k!=jq?tmCI1yYeFo4av)g?(%FZgNN*~|c2$}sybpUCs0ee;vf;?R#7W0j8k`>=O09D*g9xxK!TB3%;? zi6ar9G4f?F+mo=bCMzy~**MQ5s)sjrtLXCn`4&>7?(1Ms1u5W8z`H*yjDOzX$70s-)79Dd4AO zL-6NZ3vc6^V>(EH3nY)oGmgYw@-Nr`VC$>E%7P{Zp_+JWw-l-Z!@G|=okMPe$O9|U zDiUEvY1{Hq_o?ht#2(Rq_&y6iJme(K=SzcwpLNXyKUqY6 z{8g7ymrC)iw@evOH*@O5dBU=XPY4qi`*G#~W}&*?W{$-R){-;M)CdnpT8hU7Mk6fZZLDoxQHvqOqLAtqq#eZ%=3EnFYC_Qik_p2TCq32_lI=hD z?^Qr|AbTH^|JCvrZNe}uDNxUjYsQUXAlR4xozTom=0z=^E2xUEa&-R~J!1yTnA31P zGim5R1`Yjl@g4^Dac1vT(u65w#uvSmPH-?_F5Jg*mI|I4)A{(4EmzR|eW;TcCg1qI zuJ(N&`!dUYnKlk3CsMQMwOrlmq6+K`vSgibZ4D|bVSDR#rVBzcjD?F1`q|y6PM;~n zOUX6HdweQEzatldA~4jXwQTmxoO9#+VAtU>?v{&RZP zZZh=byCTHtAS$?R%?P$zsiEEE>hhs{%EHf=2&jRPpVI;{XT=G_=b_pY-iL3~sZj_*S1!aJ&{`L|*xJ@Qw}7+oqdWh0lwAy=tRRyQiF?i#P~4iw_rKe_67{L~f?z0TKJD z$#x@*yCil z0LHd2`QPCUhE6+JR>;w|=zW=OxSizC<|BJ-vmbs?h?`EaLQ$6 zgP)KN%TYh${3n3PwMI2U&Wj%*cyy4mX5Djzt+alJc)lOZ+r~a5!3P{>T!^m?n!)bA z@phm<$S5x!2XH#p+GcjTPHX5DSa$QKbRpT7m{+Ur_)cvYYa1&ej|LqslO|@kp-bL3 zp3kvWjHusn8t|b)L=~pb)d4Z%lb6DKWfOQn!xABbFE%D%n>9jFWKLTNI2g{ua{c}l ztxqOWIwc{0HiT(b!OLT1(99xxe&yX;vF*?DyqW%_CPxR9R=5v!(}CL(XD6nu`$w&e z-qkj~^?S6>H(%$^)y5NsSeULp^rpnRz0BIH5@uhN+KI*U;HOJZ0}!&3zrcEbm@@>? zA3|u4uQkfLm&ad-nSIfF_ZgW&C?T~2n-(PS?@%oa_7M=TtWGR3Zv7OtwHLdT8hTpc zC77B&51L9BkXh>ogQ4d%hdfM|Iw`?$%_>`ss)KQDRb0r+)RMwLlbt!S8P+~NL>iq> zL9sG$(tK8bb%Di7u{x#h_H-wkpL#T(r%hCs-{Y%!>Pm@Ii#_4h*g>GAn30f=r1?x7 z;E>O4bD|;gt;(&Iv8UeiSC~0%a!oKCg_`?$ne2<_=0#2F6k2~?)Vy=NVXSf;uB|vI znE%TGam_g_5ZYI`j~uZP5*A(24%iQIa+zrr%q~UjX1hIPTeS}oFrmFqr~rjo)K5MW z3-}Z4v^wbt{2D#Vm9VH}CVR`!#$86+XMmz1e?57~x9cKG)vE8?&(KLCM0tth4R2Bq z|Fb)4aE7((0?1NpqpkJBls)(2?QJPeZ|-5RiX~^3ul65I6!AgxXxqGi^)siM7k}TZ z+`sqdk<<39`gR_H*Pn%G$M&mo%R2Fu<9pB4Cv_$?QT6u}ab|tw zRGzuujolM+(fzMFbF`q*98&s~LK5TR^3(liPk?tX1WN=3*X^hwUj$J;P~nIy?5&mZJFvEQML@mxhw250e}d_Qiok%F?Vk9jXf4Vw&@V2!Qc~A3u{fHH$@i{&qJ4oy~am0WNl!aZ4SaT z`&FXyr>x>~$<(nQvl*gkz5$rdQH&4x9A6st6mNA)<*s_yTTb}7KUE}W&ISHc5Xd5F z-PFl@UV1fZ$w$YKJE&rG*}F^E>1Wcs|D%0NX|l`EVB&X`xvFOaMr4PQEi7RaX&W!( z|L!9{mauUIu@d(-q&?p6VdEpP-`+1~KE0#yFM<0RkyUXGapf`6`ts&Qe9ZDo2VRj8 z${|Xy=sz_sSjmBo!Jwr`x7~`z~--{l<^>zd{EUs&!3H4@2tzfw5M{JCs4}H>qw|m$vhM zu-?(de*5X~00Y0m6Nw$@MD>4Jg1t^YjkSnh7}f|*gdIGLcKr2m;_Y{#JrVY^sb~^1 z{b0IGfQzn2poeNmo&QRibocrXkcntzx#WE(N|QySxg#c(#_WY9wr>clD0K2$g%5N- zz%pD`C7&`j8pnKy44>j~?~mq8@@OR;Vt!#AgxAM^+QWUh|Unoq5D!NPv-49#S5=7?(KS+vSWS)V9~(W+P*m?Zktab_!6t`>culLv z)jT$Yqz!bEc1!_qqLdJW_oklZGJ9Zn>^!;c69v$6*sL)hWGY@}Fr-6o!XUb1GM=`bB=ra`YSuo`dJv zt6Xlndu=7m?PS?E6bx~J(vKAEW;OgZO;-UjPhvUkedvPPF2AX|415uua}V9=gvBp; z<}q@YWk;=ySVPm+wqc6W! z(qBaaLu~K#)R1x(&VA)tjIUz1%~s6`7)dPGRf2on=&N_KC?bKr=cD4%qd4++ z^!EV515U`avlWKny>7oqO8>*An*E|TW#4i&o}{+lnuB?@#)a8^b{Bsaz>!m>+?YBR z0U>Y&RTj0nRI+#{xI6xCAA*I?LAAl%;1NBWI8ltmUPybk-sASGnG`B0)$tLwRSp~E z7U;Oj=SHYnB=C(wJ;qf8X>F9g^ZsPiV;$1jXI;!b@KpLI`Xfehg+A;M$JIcykqsE{0I11$k11+5530=ydn%i ze_n|;oNg5bXQjoqKLr9woi}*Lc=|)`1GctwaQ{B&Q1E~_(R}ka<)5gHHpfz0^{p{t zi$~&!tixB$+kn3H;Z*eK8o1JYH*A3MMN(Z492MdtwK@IE(ePLz?vBQSPIAtoBZ3JJ zRW**UU$|j=w-x30SG+JyWxcPBcxC}4s3uA`SEl6NuUCA&5E$EsbqF;_zmB{uZ_V-S ziSXn7l#tX>a{FtL+*_>&B(^7Olc_*skmJfjwp|@k?O!UH!^qwDI;Nc4`BqgD_DF0; zr{*8qj%|G#A^XtK3-eFVOlt(-l(k11O3zh=mp${VYSrL*GtWEEqOHD=hKP-wbgXRl zwAS+aE}|V95v=?JR{DQjz(FNT%b6xXlDZ97`*>$M|Hq!LabZ-;LUAx|BF7wwGu{>W z1veg5=QQWGwWQQFIv>*sfpG2-QdkvpQhB0ksaUYejlgXn?WFQkj)=rxzC z0ra0?D5FjbnCA_1wyfd#&F5a`@FC_156i0Q?UdNb^N8BJ3wKrSDGSHXNykM6%pog+fi238 zK$YshlACL+1-HsE@7dVaPidxDWnW4p-Uw@^NyS!sx^au?*rQMM+F8lNg9mlKxIP~2 zXK2h{#|^_L>OB!l6m2@$g8X#4Th|+l+vh8se&y84iPt>S#scQ~W>S?Q7V=EQDGVMFp!4(FEdrTdx)V;k9%~6LXVLYc@RlTO_nO-y) zFcfPH0<*3stu;k1uGX2s-PL)C-u$=eLJ_$mDSrxCUz*8>$<*@ zb`+v9vA=8?54_!W!X+uyqec`QY35kc>5i{{_wNJ{rAJg1K6jbtV$+zK}v7m4NE1Otd(;7iIwQC=YD*1af2BIyFQgq7P?y`iF1)TJS8JY}((N*5TT z$nOFozlbnD@a{l!=s6-*3}+np<8A3=wu7<5_7(+U)~6Asy8awexgTt`wcDL`+7{14 zqlbJ%_{Gu1kjIOD+0v%~4CZVtQiLEYw5ly+8NEKkgHuIYZMATnmKiV$pcgoac+SY2 zC%EFj!DL*L%+Yw2++;_`Cqs5!Dz|5&GZXpJCsB$-JPE}6^ip8?e$pM&$n%9U{L}kD zzB80+IYMb76BDU^C8GUl=gq_3FJhibHfVmeV!4F|3HSh}My}*4Ijbo2Gn*bA$lp zqi(C=BVQEN5Dc`p{DZi@9TND>DuSgQOmYk8{9Swmd>5NOlLP##t>m;=S)0!tUq;M<6;3})AU|F%Ai2@C;4Q4D&CKtg zI?tg#bO(HYE*fRta4aXzHu=`rMsf(oceZEyd^g7Is;Hd)+%@6AM08@yVa0`ag#p{T zZu@wb$TP#2a4kN$#BNPCEQJ;ir}rI!h8P^m`wKE z@_XkIvL$!2!S8+@VIdd|7I9_Uk|dNg_|I_yZELnk`a>au#(y|{C{iN0brdhj4{gfr(HzV$Sn zi*$({xAJQ*RWH^pT4N?tX9fcGD~)Cbg_+w-+_dD};6{MOjAXSCjqp7~&=zwUh4xbn ztH}*Ux25%j`8Zhslm0Ju4>I&%QhMNV@w$ZI1C+? zAIl?c97Jks3C7~O5jn$vTYPkfjc{FT62N{F^SO!GMWfy7+D7otDA@PsJIg+N6H)!Q zMgEmXw%dBcJpA;s$y9{+Mb(h5VB+(GB6wOjTJE;prsgAAo;VtvVqpqh3OzBSv37;^ z`5X#SaXr2}`qYIz!Xf9wH@t|uU$QV)rBq)sWED z%O~V#eN3XPW+jZos-sfKren%(89ADIerd~7`_-{v1!tc71wi)X7%BM6CIR3q%)j=> zUJnDMbo)|wj0Y7w0UDd52bXTU-0Fw2uMZ39lSpE2A%b;dFJ!uFAzd!eH}w<`YGfzS zUTsR@r%tb5HvafUoq`fkcu*|9lcbaPNF&w&B_i*mGz_#f(`i;5r5XKB>%@7D8G9Wv zIw!&=^quFwKWn31NgKpflq9u`%PO#nd>2+7?2T%nE$@hQow>y8Zt~7XJTs4ss^k@= zLm*?5_HX9CFbn_eBtIM)1|4!o+`(R!>qV(4I|Y3lBj3@7Gh?`nG0)c3$fZ8vGu#yyR$r(3uVwG%gIZxJC0^ zX`4^pFZR!2pY3r;W znVt}d7kDmFT$tt}lYgMIbjo*WDQKc{4@sJgN(o17J%_b%>B~4G5)G8jqMV|CvEmfB z>G^py9|wJV9HV?o*>Ov{Oqm)7^nJ&+nLeb-UX$V?K-B!k-OAV?Dv*PETzlCymt?Hgn+Ur7I7GWx+R$P(w|h*nd9? z_4r~eC|aIOt8F&MrYY@BRGiv%q&3+7a}ky`)uXNF_|utYg35~m*n+!Vg3`MNWS)tw zIZAgSuN@Y*O(a>l)q7x7TwfBCQ%Q7mjuswuPiY-CM>2osl{9AiEeBs+XyV^y4%nXl zbUToR(dy2Vl*$EkAbF){Jv7vUp0K4=`K$mfzRDxcjA)`?29>t;^1l2U^z_J~`a-T> z?}0VNb4$@%v(Y(pWIfdDR!V!9n(r6wBe%I7SWmF{IAwC z@~gGTUt&q{mGsygVu~Vn+r@b41BZ}>j{g7&Qe}_lI{n2&n86zV21fZss@iO4{knZ4 zxvu%1+4OmEYSFfYe8{24sFf(oOfrq#P^{$N!y>)Jyb7z{{Mxma*3Qf7o6TFy#M!DH z;Xp$;WV$`Oa6jR4tKob=E%5YPigdmMvkEf!n)9EZjdL*BwDTuA- zR`@;t{Po($bu+&dhJJ6Dq~pQR=C5gTZk(z;xHmPm(=RTR64I9bFlTafHbznJAOp%O z>F2dxGL9@yL3)?C4QDj(I!+1^;SdIk@)qt+?r(0i>J*I{0c)z z8X(gfc&6(_# zUsOeCKYQK$>!4xa4{1qbX2Q+uzDldB;=|C)MwCWbmA1MCw%YN(b z^Ra_IH^&|wc0+2u-d%5Yv*hd8?_C42k-p<7`Crc8XPLe)rjC+g2W&;hn;5>d@nsqa z)iNjpF*!b}45RBd6;vQ*Zq`vVFB`+xCqfZG*-x5=KWN(YzZNOImM{j-VQvMrwj0#n&sz2ef4Uoz$U_6Tn1selhJ@fBu?+c_ z9@ZvH?3BMXpdVQA)eiRG*@_aWeR~b&*Skqo<-16`^Guqbb45ZFRi|1M&_aLu`lKo@ z)PI_pURKi2_jEC}mvw{c@>^Gv`qKOOeC5wOY4_`Z+qX(eN}y7xnA%A{;f5cpNsTJe zyR9ASO4c~OD*yZH(aBxNPn|QPMIp2`3+rzRydD`|fxg&n;_6v?aC^=UwQrq3ib4Eu>X{;-hmg_*LbBMNA`gz2^8!F9{*EvtK-tC3R zNPH5nw*cJQjBZ=UgoGka%@rKSzqPYYdt{5tj9eOg3zi^vw_tNd^X_-%6KWz2@yH-M zSVQ&1!-R)ix4Lq20NTthqu)7QJVPjI-|{vz$Gc?k`)rH6A2J9zx^lnuG?>^}xBtg6 zX0<>k)7medwO#e$Y}`-)GDRE4l6Ao&{-%E~D9~-xMeb|RJoVqTb^a)gZoRW77~K~q z)(hn%_kuxaeb@b2E31M5S<*iP!2Q7}Xh2%m`J*@OzZJU4Y5IsJ zi-HXm$_uTJTWqdQ?O;Jar>h-A(g7O9^OFSA5Do?^qPJ-FA4aH^ElBTHfCUHEbD!veII>L>s6@C@hdP{b-$XnFwqJA|8F^N>qOZs6T?e;ad{Ngdm18CcfU35~3;7g&job&=IDjTb-wCeA9J~S%y)qd8HI(dYJ za4%fBA1%CAyxhq8esvjQA~X#(tNU@%>uA z;~^_-eS;^M>UpO)(W z0|ZvCl1fU(`tH*o1$sT5QC;@L*fV_YrN$o6wukF3i8c#DMXa3z@#Gd+3+?{y{Q|5O zst@~QW>jKT2-W%)9FMNKPhUu7!vs()*Z~xWTQ6`F`aaahV*zrvV$NFGh*dsU%?k#4_l3ytS5^p+Q~+B`SYQ%_`UpXyHq+nm)gYtGdnTCMdSZ1H zPf-5>=8q4t`ireg%w3883?dtID8-==bKeueEbN~Mg*}#TWMG=cqAoV!dN=aDeMk(v zJ2)$PnS%b#WmQVdYU0I^FATDXiR@_R=|R5B0^Eg3QrNe4b%oHCK$|y-&`4`nq^W)x zq{M_!$9)ApVm&OYS9cAsUm_J!K#lDqvxm>5&D&-*Fg~jEr2kUqJ%{Narv}Yf>Ru4< z_7>KQ6!Lm|wZdy7JfJ<+T3Sik*hMBgOU9nUUt14qGM5!{fR>GxoH6U&|bO3huH{i-D(E z&F@*bq|nA7KJ=z-!IGBrGuSH*I?D{4S!$gl9wQoT_Eg(Thl{ZA7O%iTo7|QrjZJP0 z^sVC|c?iIp*~j7%kC~?X?Qj0g?2%SmZ=Y#zgPdF7bPSzoQFZNP=@%r&pUM@T3MdlU-{s#a-OtL~^ssv2UqU(PE$bLL*@)WkR6}a-&LwoPts)e zH=8{=O?p|K=$v8?Aj5)zbzHrhITm357k{y$(HV9P@L7z`sVVYR)bCU0Wd^rUmi9hU zW;^|@-qq&z9en!c^T@ZCw)|;N1JAhLMo&6L@5Y6U2c1IbG*yu5HtmC^*bCkP`4PXmd!E1j1$f2~bMM`i8IjSJDr7 z%{XhDhDfn;PoDdK6X2qc^MXv+>$W+fcj!t=)I{^rItRdhU*Q5f8(JyOKIGvn*k*0FIUp&%RYw|vxcJ+9*JsMfx%Cj< zyqt`Zh&IwQG`#YdYjr87Q7aea`KT&5Od14j;;KFGz5WVdn^mHl{||8e^&Hkdt(HVJ zB!yGG@-BCgdF0>f2(>(7lRdTH83Gx*Q+xNlBAus)Bya`(fAe{2$+E5e{_vxs~{mgNlJ=mu}e_GjCQi;lCOj{EX6M|LA0* z?Nl$-PQCEA1^RD{+%_OXA~DHZ=M$-DGjL1SJYesQFVOxc=i)9*<@Lel^SfO;@yknL zYsP&+L`-<|HzJPSW6M+GcT*`Zk+%{Wi7OA6Sma?X`zo!D1LI7ByIrSZGjX{VUFkD@ zKTMu{^XI*OeXP=Q^pBhAw6%dUN~DI6a!D-V4O#vbA#*9s)bTP|&-w8D=$tNB|E#5B zeaS)Udbf|jb%j3odbjj2#BF#;zJV5Vwg?}?lP#;pdNWAR)B8y~f!t+OkSDOm|7tjPP~Z50x5 zV{`Ayy)x;VUZDSOEeGrQ!+Y0UuF)HV9PtNb67dnz zo+^&c{^@soSYy=0Lc-})^*<77J5>d;uE%cGTPREWsFQOmli^VrGdI@)@!a>bhP;3D zc2(%rz+-y#POG3BR*zrFs34<&fw?~r$hoWE&W`hV4Tf`knHfWpN@Rnn$ca01 zU$7$=z5lHc;RvM$vk_xd=^}?jH|?l{l3kW``43F+CX=g|>Pt0`GnX<}cuGhbS8h4R z2ORg+K;-^g*y@0jnmn`>sXAHKv@9hR+lj#1iK3hEg{iKCcGgP|(Bs-^y^4?e-tPoQ z(VLdjll^H>5(Wq4t9!3M7MY#2OotS1u$fZF7gK#xPP7RL%umy6(KbCB&h{+EpJ3zw*M;^)iP@JS*^f=NUxakrwN(~jwH zTvXVP*knT8ku&7loLC$9suwhjnI(0Y^+luA2n-B#xTV#*qLMQlA=5RdpW~9gpNfTF ze#5D71#L4`xW8g$2@;Q2C-@Ah9YGG(yMt?fQVHkWpp(I?5^QSn7ZYB8aDbWxggBu> zeYL~aOFtBN zzTL0LyOL`Mb05r?4Y`?(Y=)$$iTG0CmL@)vRN`6^XUtuyE2FC{cV_8{)&<~Os-&4v zxe@bd8)#Dxi6EV~q^F@jOmwVWPLT+4yPtemZ(l-1&5+k&rVF;={D9-f~E;b%}(^6M1225sB2_l}EnY&6DQdm7NspgAa z1GjB%UVgE}K_cq&K;f=B8}v($<1ZUW^0|T)7kPH`Kq3dmDM{|Nl@qB4*#I5LagXv* zVF2M$2>$PuT)fm|q!l1T0({Io&Pnne4W)9&G@0CZvl~Jm;+OGzG=O9_?FKxHG1jIh z9#+boQvC;bnD>6w+X;7hGk4WC)0Gl#>%6O&t6)x20={dDR9xdd1V{CLqnb7MSmBLc zH*<-S$-a#(;xnZz@!CTD8o5noF#j( z1Z0y;+l*;3Id}bny&}(>4<#y9FtGN0OyT;!F*}_q{;Wt{@S+hau=$qLdG&1^H zKu!4hsPJU*k6~q6*|J}xE4Y=~*bSieIiPs>4*Q_df$+)`Lm@KcFF`@s!Vps}P>D?v zV9{F<&oobJ3*94?c3vWpSD_Z53VFFkv#U)v&^7e=%*pwIp&@b)e^B^z5UMlrG3nJK zBO`}!5pCHKI$^plX?lHBk;@_J`VTZFuz&uHFk>>jAEo-=$m&eb#GmjiOV7;8ha*yP zfK2j(ti>1FzTs6ile^4+JgQ(!xgz$41F6+ctY^BMG1@RVw)sWGpo&Pe_||uoKf%00 zNqXKwey@lEK9Nkr7KK4OOHMof-jQxn(%Ktd_vWHD6umPJ`6?A5WR?X1M@p)RtyFim zH>f}T_?~m7-$j?Va!z3DQJT2KC?>IChDo`nj)!fqk9^;|y%y%*jQw5|*_zekL%W}4 zHaQ<}S@r?iYy?5~dPYYvfX!$BT;twL$}B4@yC~VS zY}W|Ub*~*Vv&+cdE8`NmLauBsF0OUWi_7Qt{{H@id*83|e4Xd>oX78ftc}F<@`B5b zF5;+f(PGWaU=eo^wuFc}TC=?Wh0OHK-PLW;uQcIF1UILK_SLIR*M4S!Ts^Wp#v9x? z0FmJT%1>nj$7Axbi>9gi^vnf${bY{ zXi0wf-a`e}R57u{kvKq?cjcQqgTg@vtgRMzj+3h!?zMjvG9V^!Q_$nle;|H+e31~k zwvvqhIdWN7;9_WOr##}FByIpr1Fq-!{Ls1ZP!_xbb z%2A%!ib=$Cld+iZ;KV*rg_l0IjF`j9#4T`{Ipy}E;wR1<_>HA65%z&g_b+WisaW>J z1vf_JLd!?H4qTcqT9SGfr+lA0S;|sVKB{;%{Ki%`Q&M$B;+^wOV3j;Q(pUDbX|Vbi zing*>;Jtb6FNSf5jIO-8E!sNi5Gb7>9s6|fur-ZfWWOCMRGg;DvTCbh?Ap-E(8VFY zZL3@|^NOO8A7>iN?8gd=16=vAKRU9_j*>{e;nV%U<$ntstyXeo0 z+$m>APa67Zq}We4=i_J*fe+Y*w{-Vaez0*jeAlrmnXx~A)Z?4q*Sy#8 zT1TS~+JRNw@GarTr*5hxZOrD_{&)l0OTXmx21mVvQJgj(;Ox5c4;&$e)hD4&fY!qs z+{Zmt4<$V&*FDbA({kF4sRnOX8W543|2I%J+Vik)$<4B7FXS5t3f)p!8*e2nxP1`6 z<>XWUL2(2AUgfsd**Gvj!AmT=cQt8hST__7YKfj*SGx?^3}+m=Caxj)147K9e*Zy< z-}xr3gtvfd{CKVO?Mh3jAHie(#-{lk3)K)Gw#BYUJM{8R-vt#QuYI+f7Y1`0M83uF zd=k0#jA_Eb@L~AM&n31}m44gl(^1X^t80I)Cbu}XTv&eziF(AMCTzT>D}<*_>`%1< z&1#!cF4SJ)6|ns44wG(RmWMnK7FD_`liU>%FLc3F?)`kBG6BU~yPO5jlFclNWo7L6 zIZqe9im=WpGZ^mAdyf#IR%3ye}O^b&i>X5Z3@3Crz>pUty^f(`N7 zsO9KaQo4u$4o-L`>G{w4E8Yv5ugo;YBq13M+?pK6x~f^q6)qI)POZywtU%bqX`@eK zaoQj`29?-kPh3(gU&XnnKorI9r= zkKr`G{?F?*W9;=c}90lWftzFB@fg|yqS>q?PWo%swLJ z*ZSL-pu)u`m-+M~TqDvVH;6(9i*2cxLtg+z(&EABdYw@_#7LXjV;@%`^IkGDdf$nq z=99dcN%{!kO4X~f`pXR=zQ-`R;CG)08ZPBRZ(pHa@%)STchSdM{X6!>X)SJ@3uzZV zt$W+=!KKzKag*Dt_Zkr2usNAOa<1h3YNXrtzU*>X?8H(rm?ubKT-robPW_Yb-!w+G z8Hj%4d}hI}SFgMv`w+#cXLnL-_faoGK6EBbwwUA6^?ls)?B~|nfo73Cn+EHI+Q#{Z>Z@l6@zwZepVm#%0te1L=C-V3e?^$57193-gCmf*@@k? zQ!5?p-YEqe@l+|`%}?d2Jf|j;LYbZDw?d|ek)Wj|uj%+Pbaupp55OO`q|&q$!OJ$ByH&KRm#shk2MUvo(yUA86q+sQ zd)9b-bE|}yK?n=TF4NC8dHiPgh-W_K&AJzEyz0UIPehLah!=&J{sG&!xqa?sUVMZw z81M}MhXfACF1t*eMqiHe$}Z_cWQwez&&Zuc>deyUx#Il;YJ|q11mg29mE^)w@aJue zf%+)Kf6Sm!kaOPqWOSQO1kjPt`Q8j)YZ8}m^1_1rD3;vwcT$=O8;5T-^7u>oIkuqZ zrr){L8XrDON=|Z{3DIGJVUdG|uk;+{3wW{F1@|9&8Rl`Wi4?l7w+Wo;Gb{^Cv-~=5 z@xr4B4QnRwn+}KLN3WipUj4z(w;(t-oU!W^2hal6HnRuj_P>q$TO`W<6{7XC?Zg7l zx(c4P0C>?Lp}~hSCN{&++8a|E_;T^G`IO(QfN3ZSG)(hD`dulP>WewSBs*pN``NTfn5byG;|3HSfD~D}E6anE;RVkx6 zYIQz;nooM$rS;0a_f*;8JdnF+n1w88NWv45y$*Qv@&ks~`ikxU1UyWR{S>Qm$Do=& z&WHZ}+PO!}qLRs}w8N1`V6wRnJmUteaUWMCgO|w?R0prT3f78h$FJ=9R-le_M0tg}*Iu*Ls3{v=Uk9Yl z^lF#<8h*dRENW5d=Ona@&;_0RcYYOYg~-{qid~%h$E9O7^f+G4H*ou)<8nWIrKQVw z=l9>l*x?3HVE6a;TTnsXvBpHW`n6|sYbjaVJ|=3rT?J>$&y4v57>XtQIcOix(>eWkrf1M zg?;}jN{kh0D<%c<5SD7;I!gV8E}2%7 zTJRHfeRpqpiHx#^%CEcAY}^feme0vxiTA1$WhA<)9D5AW9Qz*T54w8kBhrK%P*>(5 z-q=6_ki7p5F#2ElOmmBl2+5M8RWDxF@sCg68yL7z^-n22HPT&iZ!7CeO*&?yh9x>m z5${wdoro{0m)|e7JLP^{Lda&CHCN|)v=B0B*T2Nlbs(HQ^xaWmYDPfpYa}W>uICd- zAtN*_0OT~&cEi^VSD!JM+p_pVlN3I2txqs!7x5|Vp3JV4?h`OnTuuFKZ~HlGy)EBU zGOgLdO3+8sz{XDM%@X$2No==v0Of0Fo|hwsn^(tLoFA|OzO`w#;Ip=D?4X>&d?oz^ zAcW)ZmNoM>;Yt7PB<5@KPnkRXvkjnyczw06_Brcwm&Vk;_^~8KK|AR9W)75$EzE5F z0z5(>85!P3(DEUmp)**|auF*2C4~6OJ_{BlgZL<`ZT}!8jo%igDU2B@sI%<)sbI(* z*snaD?Ze}N&d*XlG0C7lADvItTwa*iLu2uRygIm)CQGO_ZX6-T8x?nWLt$f;4OBuzHPv@@p?>S~k9lE(~RZ;9a5DWre z8I|XRXA{})$BLW&tMp+e)bEuz*Nl6y8X?5drlDD_Fwv<>Iq0Uf!Bpd=2%M0OJ#QXj zUQAB&mF4>-wqOGVq7w4tdm$x$;k}%ySV&*PkO~xc0DSN9a$A#n2`Qzt#Xb=UTC%%x#EVeC1D`%swayMGs{!|_f++QfR5U3EBALca{+NW8s|nU zazjGN*XO}PtXsYiB#>v#!)TuuC^c?n?k?Y9Ij1_!S5)9-1^r=SP<9ll0VVG3Q@@V} z%I$1##Jj4A-?6x$by9`-2IKb+%LxncI{45p#^W>jV@$mjOK>tVo3$n(;VNYo{t^%uNQVc7QpBEY zFk3v16(N-o`g3V7%-sV$=3pfxHRb1-(bq7wwhL5$WUI<~Zjq(YiD_F$x_aRseB*6z zM#?!_BBVYRWV%c5Zq1)(wThTUX90Ym&X+bZdg@OT@x?>qoBI{;6#E9#v2Q@qSm#o} zX%;noee+$E?^9PFv6t0b3PJLRYtJdRPanVRJ2=T|IFRwVB`AwvN*(@+;%!e|M;1J@ zuV{Ju+>PF*1S`Bonyqj&JM6dolG`f>U~W}-XB13wb-$WVPnN_Qy{1nL9g@uUN>{ay zUYq4XvAG8WF5>dSy{>+U_8qABO`aX($CFTC7k}}sHcgm)dR2=UK6E#Gr%_YP_h0`FI-#5U~wK6G*QJLC1sCd>s z^5GLsr;F+|=(b;Rx1XP&#M`cGJsz)CDlFIzC_Q4WoEG-3I*L0%zEr2Q2J)L}bn5zy zAC3=w_t5%kBusLqkuOs5?1@16lj66XrVGl1wFAOrb!5Ac_(e|84*2;74Cc=(X3TOu zzf`lcg9G>U44&*bJ0j2NQIpm34qS-5FTzUVW2DL-mL*E?v!CXBvb3xA_!qYOU;lb{ zRg!X0ilpnk6)G9_J`vka+BDpnNjviDpGR6(?3d0KX`$0IBO_RY713U}hufdpufH1I ztfDq@7~uwog15jtio{xPD~{0=4x!AUVVYi#1dl9m5x@*zn44tj@ws`Erhr}z@i!dT zrkg8RUqpCpG+4NSwjyA4_8wO8rA>M4lXg-5G`;@mCv*$gk7_@;Z)@;9Fz^COx3(8Q zI&D~hptSo=e*!NrCSDKKgz_+M;&cL#v95;_sEWopsw?GD{=;DrdF}=X#&TkN(@K86 z;zn)rrt3{5q7vVlwJF8Nf=!HJ*}NVchR7aiQD3a+tTV8~2zk!P^Jmd)#-EJQ8BZMY zF;X%#Ilx}q-_4xn;-ucpS>)yyL4}~ewdF7 zY?LI`>RygK6mEc>cv(NC+V0>1N@i8LQs0j`k^Y}(b>yzo%4Ksp@fV#;R0*7j3U(#O z4y|bLgm;E)-cb?&p0ok|upESTVy!$nORYm@q1vfdKi_b2UNTRtLDAr#JS+ehi!Thx z#x<_u;@IAia#M}FoQomsGqe**8M?Y2i0)(k<%i~|vqU467mYL%#2lUCE zZ*QH@7;(JS|%s}saMcm_Tz&~zuzzdY;y*Qu4> z&`uDWyVL*)cfkJV{8yXEX;5^ej$OSo+HVqsZ5Kv6a;U4@E<5!F*1Z#oj6K{NV6w*- zufo1JP}Iq@8gG<0+{^ZC8-{=~GFoi zq&2>4G=f9;tjsnnA-Gr~rd4N11zN$$qMMzvOM+%S4oI(ZRRf7r?xo;`nq52^fGd z7`V8p@3F_}l+Q5l!&{r?k?*`Y+XTCr($5z)QrY74?P4Pow1+z2JUBYYb-dZAbk&aU zXT7tV{^)>5{IFNw=CMiK4AuDQRT>NCX(Ab7n9dTrs}4O$e}6l#n#>o@u$c#9{tu+T zFEbwu-yu9AYe_W=9e)uE1VyhdG4Y3^J*SlBor*Pm}v5<`vc616f@|OG_&!wVl4XS0epLF+6}=nK~I%4-WS&m>#jY!XEv>R z2uP{t-(1ffw|01e!NLDv*Gcxk#2kO$}FFYSA&9+6g&~lt1MnttJv@J@gr{vjv z|AGFFgm5q@co|^bk(rNTW7Ms6GT+jgdSLMB7Ua>LzCs4UB3;tcE6O+SEe$+yw%_aV z!*<3!=`B_Klq}>=$9F)o3cVcLMao{Y^4p2~4^*^H8hTpEm$MsTC5hfQJYOe-7r?t_oCu*lV#^`ncIUBiFsoW2H zc*mGh(=R-(si6~Mh)YMwCdok1<|b1S{OW6&Yx3exfAy$?oV9^?=$}EA_a%FL)evKUi?T&qa+9UE1bolVkeqr^gUeS2yga+1Z4~Y`K*(B!8 z>-_1s5K-W709*|1KU?Mq)+c9>{hoisbI>%4#X%11+R;TR_ki_^Ta*5kvw!L zn`=I8ETwSdm0ielFRi3;_Ovts!#Ak&q_iBGQ$s^yj$Y))>2vsb5==}+PN4KhzN3lr zr~{MgZs!!WWa@TWJ&YG8q|PzWa7aw-Pbbz`|1} z^Gy6CTID}byI+M$rXRv;Y&gwYMsQSGh^9as;f61x9JR|0P_r`d{szA9d^;(hlz1}&kpNVADgUp&7{RlYj&#LvO= zdZWI$lq>BM9%V2H%Be(zNe-jy7ks)`XMfJvAs-Cr2?@RY0%64olUG{)0}<4tbL`JH zw8ouZ(20huZh@XR1ZmEbE&A7TE1ow~;ulM5FVoy>n1`|n5QUiKn0QTup`;l6j_}W< zkI~6S^dd!`DA!l*^75R*b%b;*<>pHs)D@?PJr)nL=~2GELlTTi_SH;2M-^I8i(E7L zKmPR!IlK7zfMVY70edjy`bM-f;rt;bvovA&Mb6-sPh6gmRCa1s8W_BmeoWH+AuA`l zJKe|gcxsM67WF$TgCGt#XM}q!xea2>O=pQn@tD}@#9h?E4ElnbuR^csws1{5#yxAS z4^2fgaNJc|vh&U#h>hPc``mjFFp>A_N=94?808mE38Jjewntd(AYvBHg?=U5IbpsO z%g6t?c)kX-qeySs9%l6<2euBf-4wey`0w(lJPFtCDOONL$-dXZz$W)SFf^^n4T^&t z5!WUQS0;J4S$aOb-|cCw(iZo-yjw2Vw-kc>aI#Xh)h43ksV@G#c36W|kIAc_bBS)Q zz#V>*c7#UER+|56oorb6cmG}gYvYY&PKNf}Nr~avdt-Lz?@;-@ODWi5svRW(uV>yj z)Uz{RbYxO2%%j;Wzh>oQUnV&5>!RXdXcVNFgMWIVCY zw)*?(q$fW!`Kl~3LgO-~qGGS)me{)XJdSp%>FFNm#rq#|aU7BB;J^n!XQ>f))iL^Z zCz_+T+YV*RCn;n@tYai?TE*uCax}c1Rc8>Qi~JlR^v#pYBt2C(Ah7ricLQ`g9NUJ}A{=CDxs!-S^EkcS#s=QoMd<`u0ACh`>1Pc>4YeQ z^G(#sI)}CpBAYMditDLVUwS$`PO>F@|iEkLGaB$h0mD78o4pj9IdQnH}QATqA z0fc}`*G$hhtUmjE|2F3J@=r~knqZsGz!JLY#OxBGm9{|qv|p7v=TOIWe_kDcV@3VG zuY>Eos#z3NT>R|M)us59+q&SnAHiI0X|t7vfWCp>{xY+0gM-7MZ;JhP*jh%17|O z3+?o7$}Me?%Cys**I7ey6C>y#Os4_(=R00KpIIQ}YaH74-jY9#r|JGG&X2A%>kz>vHBIo+ z?NH&|$y{IXL75x(v7FDJvc{plKSH9?drY3{PXmSxb$k}KBL((GIB%v2F-Q+}B_`ue zwKx2zPK{(-m+cioi}Ptq?VRu}8XTup>bjJgN0x7S-RVz{`uAWNUso?X&0q_{ql_i4sO*49K0czF}xAE zc~SW<>%G!zzyC?xUk=f`O7xu%GYUCYS2c0^GTb%R{aY==d|y4;H)^4$TLjbaTQuU; zuNN^13h$u2e{XzVIQ_J0`@$$ZpGY$t4dUz71leMe{8_Dt3ls1GHldMxOrl_H$c;A} z<|Rg}7mWV~x(*`W)1nS|v$EPT?96fq?O(oftS|1qg8iLXs%?*aWf`{h-W#*^Gvoqd z+ddJeKU=?fAQU01!qzK^4uPC385@2sXp{#Fx9{Pg#U^vLP8|6dyc9L@a#)Pve8o(n zkk6RRYHlVYkQ&@55V1C|=C6Zc{^uzK+Zk9}Fy*&3TQ1 z(#=gae@8Ny^|&2=Pa9+Q%-N9y+?uh%g2ZYF;YvvCdcSpLze5v!)wShvA9rSE27kb1 zTRgVsTXWlV5U35C0(QZ|z>Jv9H9G%bj! zpvvd{ycasMg@ir19o^7q+I2G)8VkshJ{$yaVSvdu(m5YNOgy`pDaz@kALr2Q2f}6Q z_Z1v#;M-@?UZ3p!?#rp!y=-2wD5gK;jV!5Nl1Q7CMI96Ln^W7kM)Mm(esg`ev?6Of zqynZB9{=vh_4rSQ0uZq1ME~IIU<4ps7^H3p_~>yt@M=o_igf-l#E}D`Xho%oM4>6mR2PxR$NBEe7 zx$ZSF-p2mQi=qz)N~)iFRvW zwH)fUVOk=Gt_Rp2wL!CO(JJb;Kl78wFW6w*NGT7PZT%i zX#kqSzG(2T9OzXpB~HOA$CkXpd*4-4!Yr_$Z}_M?{-RIqP=j)AqjuHz z%1=h%X`-GA;b)2_#D`lsiNDCZ@fj7tk~k~(EAO*tDQ7{TOSmLXkWDq8YJx7{>^w8l zl>SwcrWal_gQ<6u#0B~zt^NC)4>`7U-N?WF)I-}HOy3;ai$T&l`{^MW_Dc5f91p8} zbQSfFMyX^U)5R0=nSF>FwnC0TvMvDrcIR5^g-}u5-^Cri@{JG!x zyMeNgygBFDp(V11g(%WXg3LFGmY1hWYxox;Ol);tHPool(A|SArxiqWcBSR|58sd8 z29GY#C$5@?F-Z0kir5d;Qc@52z0zkJ$x_{{v&x?mk1E6CN9v<@Q>PQJ7%Aq>f1l&% zv0E$pRUkCc+_#|0`|oI-=EU8@0vYSVZijw-97Y5;@Tei~Bg@5_faX$&sd>$?rFpX3 z!>b#aARZdQ+!+#1c)Ufu3ogy}P5qYT^j#h)uqqBLnAq3z+@6s7OR{S`S!@moj(qyJ27daDp8u*W6rV+ z;J@QQov(`I0WQv|V8Q!YVOjqe5tQI2f0$&e#;Q89BJfRB;{XTAD~aMomh}~SoRrzX zt>({kKw7h?-}LYt+Ns~ksHCb7oY)!hq0Fw#)I^I+xbzNR>{Yjo^XNrb;7wCV#mcO| zK3|f}2oFcu5Gn}0!Ab8LmTNJ|0*MSwe9V-BJR8PiMmjJQ1Q-Z{McY3G^4zP55HbKL zg;**)t@LeFnM0GjaTMN~xlOb?L7m1qT18OE1@w*{6^K=`9Z6i={#01iw?R85rAjMLIy6K8`MRAb zG-b^y?VGior3fa?LZ_N^Z}anbxO5D)dEPbjMahajRZb@J5+Ct!c+ z-qX<5 zNTek5fTS;tZ3zic1Y~c{MRj$kiK*5ScY7Cjfo3S9q;gDcIs6xytf^Je9 z%jtmhh5_%wg{q#RUCq<3w?E+(G5x3wXSj~~Tj+_awD(aY9Lni5wtHr-WIWIqzlUW-|BQ&PXuB=NlUpH>Fy zrMi_Oc~0hgaXIqSTP2qNKqjr~55n~3>n+H7>D0a)X|@c%P32DMV^AuAb}pT=Vs)td-y~g9n<}C+ zehjcdCd4T*X5bO;EW(ey-zyCG2Z(- z4}J8L`EPXcGyMmW+_sjdRCnsy>1@pJbDY21{13$3f&}n#>rf?t(!Qws!C;p`^}JHY zsL38pEPME7A8tIdGKxsGD)vjuZziJL%ib6~h(p+EQbUL1J- zf{%hpc*yb6s=_s3G6x@nqnI!B`V6onOXBLJsVC#Yo9g5UmjwxI?9lsimK`*UV2DRw z>3AtVZDIi~PIWSmIu#OLQjO~DQ1r;k44pqcDss$XLht?yw)g?EL2L3{JTdtVXa8Gy z%QoQ!T605T&sv+p(!F5hF^pm%HT#_w|I|{ilt` zQ40Vl`6P;f2)(iOuP_$r$CUv>e%je^THciHkeFvv>9qQARi2nDJeKbK$@HL|fs*HN zIr}9JknOYmkLDORnlQhhZw?4s1ek7cc5;m?h6EkyzBMW^uMOw+?MZpW z*a_xb!xoJf6zZ=4-e4vEL!S@m>(oBwPNjt=kKs_9Mfz4SyYE<7M&>WC#EC;}9V6$q z6`t(W@n_xmH9?>Fm91S*!Jf~XoPMnJk5?AqtI2y4-MHV;v57B)i4wluy|a^$YEa}fLpi{*t3X;a#nT*4d(HC?J={ckk&dYa)K{XpBem=^3+;r z+{|E80l1=y1e5S|PJ?y2)Kuy(mR~gTeGrT%gTlB^^^XL&&mS30=V?KL1N^+bNM?Vo zYwjAYQItHt3o!%Tft3F3>(+BomBB_K#A6y_Pcmv_qmbkOa@5nalx*TGVuB;gbJq~H z54v`qdKKR?jqPTd&yo(s3-j##_Ca`ww)d!8$Zp_8&jSOLAShuY-hHy%I!6CUzoSM@JSP91Ooqu8!P) znUyC9WNZ5<+4s!SOw)APLGqaMASl)jfJ)O&@mSQYRlOb2!E5t0PtKSQg*w6!tez}X zd}0PC^KI$ z7c$bJ4mvtFvw}pCmnSp1ttGQlW_RLPTy+*kK{koUe_2p$^jz;t*g~EbS4sQ_dTU4I zi|5=B8%8+6gnqwvL2?`~?k?eM^tRyCvjAh`dufQe!w+ohlMCuh4MNP!?rKOHYq<8p zGV(}AN<|Q}65L8!Ye&iD84TyN>Zd$;pw2xVRwv%7t2tuo_-$ycO(I+H6w0a>= zAD)Qk2W}nv@D}leI7obahCiTne%tioK7{e&533su+;fbfq+w^=m^^0Z?|Djm3=)HfbkN7m{q zlcEv6+4<-DUPuK@b^`5cFGh_^)78 zLzQKDUYKVC%#Xcq&Bx#oy-UX34j})ogp#d2b=m{Uz!ZWl{8t)bX8-J!T%J!OMU$9z z{{zKzKZ_tGK#5AhN-RW^b6$rFFU&bW%xA6Ze5@2tjVYwCd@pzuTDHxMtG^KS5*SM` zMP^-b3Zjp>2TaLcHajFsl`HqrW$)o- z#J>(zzt_9f-|5l{1L{%H6ucEsk`Ia9AsWA6_4Ky~tTE+o`hR1-^`=fslM)yU>@@M~ zaF~Xa_B#^L(I-*GLjJN@stcWO@y*X~dCUoqQk@LC7U&ZtQl2KSdmX6%6st)qn=K*&#G8V5Ufc7+TwDs%#fqgiuWl`N5mVi!7XfUKldCuTm@KEhxiYTs*Va8f&`w_q{HtZrt~g?>mepuIyp zEYFIf_J-)L0r!kqyH5^nf$#Zaq*ZHiYfL59m)p3pPGdn+=lYUsKX(uVOacFA4s?E} z|K3#ReFHUt1%QdyHo#TgXf!Uh0d`j{rQ9O!Sde4Uw2Cgrbk&{jsJ{?~i z<=jw6&|StK8`Ab=h3XFP9S}!a>JQ^75%&rcdBy@01n!}gnbK_k0|lA<&Zcj7EGbV> ziF#w8D#p_wc>2L^p1HA%$?Vt*w;@F|0ZDM?q;lKsX!M#8cs3z3^+qTQUT1#0{xx8 zH62_MyfT2sY{v6@%WC(yWbNoYlnoQ|O8Cx#-!?FzGjH)(?`@mH6)0&gb{rbYua)Pd zFF*zBkzMS$3lG?zymNyr#2ZDb^M`<6`P(<{f1S5@fSy^0P4t+fnL_$h8HOa+p$KBk zheF9_P-O?__LGiR1Lr{wA7WbXXQZ-z z4*41u6+*S| z(s}LLRS6u2{QufrUgWcOFPj-jB#5f*q0E~n!3mSc?iPm>B<)dHmNiubLvqUauY+>} zKG=+wT=qzJT*=Lo4INU{$C@!k!DrU6llh^SN3RHKW_>_r?L!e~{vgM6+r^BrOvl)( zA~-WAy*X<~mkYO+o->O)P~ll+drDJyO$S`2UKRG_PTp@N-n*!_q>YF79WLh7*UAm% z8-=BsWyfHyUsN9Bf_REt2aT$7-S(%`qrdII4inXMl;Mj7`5kr7%3R_94^;cVUa+C= zeuZkIem@}vhDi1!_6Q*yM|A0qc9E-vXJW%!k|~JI=Qn!XJyb{2*4f_vz_RMqYB@@c znKG=T8)sOhnq`K(eArv?zHsFe&3|L$%hsXwh*zmXxn^|X-0lshT?uqvCjoz-x%JfH z>{6tD8W>OH%W<2^-OoP|EPgrOToE~UBQLF>xjAXmQ}!|9Z{2k!LatkxpGmBc`KIm> z$LsqMCUj!qL!fyZFN;bu?s~5gxE$r;ooqM$>FtY8W*Wk5ojtG~Yr~`MuP-|YXs#G} z7OLzLqAme;u8&^0g(-|f`{?HEu+UbRRI}7XA3q4R7M^vmhmC|k{PJ{d{$=nmsTFjb zD|LhPFD8Rco)l9p`@0=|E>P)YPBH0hK6PWe!e0E5C|c3RKi`YG%ovBq%$TFA3D>Oq`-k zE`NxgG8<_dJbx~`QLg!>zn@3?#7ldjg06Ht@wXrk`G;uQhWWDK6ioT)k|%A?QP2zHsd ztJN#cn&St{Ao%HlAdjiLTmbWdfOU(LxPh%F5Km0p+PR2RMS1ktSWPO$nA_!=#+K@b zh~fFO@>x`3dUCmLa8VBvQjDi~I>D}Lvgq$&P+Xxd(u{;d5!C%&VVEu{z&-OgrZq@E ze5_S1t;4*(T>kPxDACnLK#~2BI^u#88{36JOEDLy!h0Kbt)CRTAnIeR*{LAt0bSi) zt|B`(-*!T6ZyrlRrHCQ#OFOZ!ypj%>Sp4X{TZ(plvmI`<|K-2)BKRdA_YvQtP7fR~ z!Z6wUO?)t=dh?YYoSdCz9gmKBuMnC(g{MkFR!ax}G{t|m`e4s5u_b*n;=5;oUcc=7 z_(j&WYTrlN_a(f>h>*RJnns@ud&<*oJ=+VB%(Pq1=YglTl@;%HGJBdFMtb1O4KvB8 zC5U9-D}VeD7sTXh^TSKsODRSq?CdQd1c)FEuRSo?Ad}HUc2{Q}$BJ;PHt@my*}5T+ z9hK8s<_9^`*0x>SRG)M7j$mEioRdH`YfA=-nIgO+vub@=Nz%RUnyw=;;EjCLg}N|J z#B=-@aqWe|R+%eaT7@V2?C;RZ+m6>t;R!QX_!;bbLBk7YVS7DtkMA`{ew)j-+#1WO zgfMq$tW3rINY998$Cj;}#Lv7?ZQqFt0(y&&za&p#fKr<9kCcwxA@*?QwK9d@@oF>h z%!|9}PKw`Pq{Em^eVPwEN|@GSiYcB?_5Tj6?+$Sqn3@MZrc2>x!3lS9I=jm9CUz%x z>3#3E0fReCbVRbP&c0^-9HyDmi0N006On{=Iulmv%bd z>3Jkhb{}^;v=dw4=}(QbG;8e^y>(-E^F5?l9aVhnpWxf^+rcq$+VMu53hl#7;mmt< zk-T4g36#bjGj-RSjON$wYw5c!z43()ZJqC~_6QK%Ql%KhKg5B0K^^O`X8|T%d^aqy ztdXV{eFsFFB^Lbc*U-x4whLGwHJROda8KCK=F&bf;Ub5Szg@aa_RWF&0rnYm9eufv z&*a4BV`|M(#GXu*m{RWib9HsS^WLXPFF)X}ko{R+R#4@(u^pcF0-D{>h~wCh4qGmH zOg<=Ok41Z8@vV*yDIHg^8HB`hFT?ng73;(W2}!La_R|CDbdsO^E3EudB2ZcO7VS3g zWsuReZx&a*LO?`NKrrW-W9DF{B6HjE{x@S-+soaGQP`O}5WSL{V8ft$e0~2zoNkUV zx`o+y{h1Xd=na0bpH3||*!W%;_H;^J5PiI6u++q>hLG*D$@^{Q;P>t|x0q~liVWu) zPNOYd0(E^ZI`G;%h~hZn3)o$0Lohq_$iVmK-tTvF)tIi%!-0~^53`=98l@)Io-Ent z$j>tw_iR-w%*>iMry4Y@rEqb2UDU=dLfX*j8+pv;^|NTh2&YHxti3-U%nYT0fgQTzdS6-B@ z*BEOPqk)nwo|1YlEF`kzijWxM=IGO~&t zW8UNqed+@={&#fU zRJR-JG3Evh?c4EZF<3v+}P_GV+k3f~=25$PO+PEuzD)~+OM2dU@0LtBUn+nUpPx_gnDIxvnADdUGLA&kWxqJy#tu?THm*G zww=cKR}P(~i_0|Yzej~nn3KmyW`*w8ZJ0rjfFQ-^>ZE^nv>{4HTA${C@;Yi5V=<=v zZR1!2%CXTl*EjC{RtE+wuJ6Zxn?;UzkAFK}2C=}7LFBpi0vN9LxxVoeF>U?u43PZ) zCX2X^KOZJgD6G%Qszq>Yvi{l1>Z65X`Db~-N~y_ZE^r5Cqcr4)BTmA-IodYOMvA4E ztmWa>(1u;T^|n0#&-*QAxgMy7%nG=8D(W_i41>8XY~2^PP{%0A>iA7dV*<&ORe<~4 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ð{QPSFn`aM{*R6PHCb9q64-He? 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 detZ7_BgI+Jd{|TFey|Jl3Nk5T{t diff --git a/examples/desktop/autoflip/calculators/video_filtering_calculator.cc b/examples/desktop/autoflip/calculators/video_filtering_calculator.cc deleted file mode 100644 index 8d67eb8..0000000 --- a/examples/desktop/autoflip/calculators/video_filtering_calculator.cc +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include - -#include "absl/strings/string_view.h" -#include "absl/strings/substitute.h" -#include "mediapipe/examples/desktop/autoflip/calculators/video_filtering_calculator.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/status_builder.h" - -namespace mediapipe { -namespace autoflip { -namespace { -constexpr char kInputFrameTag[] = "INPUT_FRAMES"; -constexpr char kOutputFrameTag[] = "OUTPUT_FRAMES"; -} // namespace - -// This calculator filters out frames based on criteria specified in the -// options. One use case is to filter based on the aspect ratio. Future work -// can implement more filter types. -// -// Input: Video frames. -// Output: Video frames that pass all filters. -// -// Example config: -// node { -// calculator: "VideoFilteringCalculator" -// input_stream: "INPUT_FRAMES:frames" -// output_stream: "OUTPUT_FRAMES:output_frames" -// options: { -// [mediapipe.autoflip.VideoFilteringCalculatorOptions.ext]: { -// fail_if_any: true -// aspect_ratio_filter { -// target_width: 400 -// target_height: 600 -// filter_type: UPPER_ASPECT_RATIO_THRESHOLD -// } -// } -// } -// } -class VideoFilteringCalculator : public CalculatorBase { - public: - VideoFilteringCalculator() = default; - ~VideoFilteringCalculator() override = default; - - static absl::Status GetContract(CalculatorContract* cc); - - absl::Status Process(CalculatorContext* cc) override; -}; -REGISTER_CALCULATOR(VideoFilteringCalculator); - -absl::Status VideoFilteringCalculator::GetContract(CalculatorContract* cc) { - cc->Inputs().Tag(kInputFrameTag).Set(); - cc->Outputs().Tag(kOutputFrameTag).Set(); - return absl::OkStatus(); -} - -absl::Status VideoFilteringCalculator::Process(CalculatorContext* cc) { - const auto& options = cc->Options(); - - const Packet& input_packet = cc->Inputs().Tag(kInputFrameTag).Value(); - const ImageFrame& frame = input_packet.Get(); - - RET_CHECK(options.has_aspect_ratio_filter()); - const auto filter_type = options.aspect_ratio_filter().filter_type(); - RET_CHECK_NE( - filter_type, - VideoFilteringCalculatorOptions::AspectRatioFilter::UNKNOWN_FILTER_TYPE); - if (filter_type == - VideoFilteringCalculatorOptions::AspectRatioFilter::NO_FILTERING) { - cc->Outputs().Tag(kOutputFrameTag).AddPacket(input_packet); - return absl::OkStatus(); - } - const int target_width = options.aspect_ratio_filter().target_width(); - const int target_height = options.aspect_ratio_filter().target_height(); - RET_CHECK_GT(target_width, 0); - RET_CHECK_GT(target_height, 0); - - bool should_pass = false; - cv::Mat frame_mat = mediapipe::formats::MatView(&frame); - const double ratio = static_cast(frame_mat.cols) / frame_mat.rows; - const double target_ratio = static_cast(target_width) / target_height; - if (filter_type == VideoFilteringCalculatorOptions::AspectRatioFilter:: - UPPER_ASPECT_RATIO_THRESHOLD && - ratio <= target_ratio) { - should_pass = true; - } else if (filter_type == VideoFilteringCalculatorOptions::AspectRatioFilter:: - LOWER_ASPECT_RATIO_THRESHOLD && - ratio >= target_ratio) { - should_pass = true; - } - if (should_pass) { - cc->Outputs().Tag(kOutputFrameTag).AddPacket(input_packet); - return absl::OkStatus(); - } - if (options.fail_if_any()) { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) << absl::Substitute( - "Failing due to aspect ratio. Target aspect ratio: $0. Frame " - "width: $1, height: $2.", - target_ratio, frame.Width(), frame.Height()); - } - - return absl::OkStatus(); -} -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/calculators/video_filtering_calculator.proto b/examples/desktop/autoflip/calculators/video_filtering_calculator.proto deleted file mode 100644 index 997696a..0000000 --- a/examples/desktop/autoflip/calculators/video_filtering_calculator.proto +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/framework/calculator.proto"; - -message VideoFilteringCalculatorOptions { - extend mediapipe.CalculatorOptions { - optional VideoFilteringCalculatorOptions ext = 278504113; - } - - // If true, when an input frame needs be filtered out according to the filter - // type and conditions, the calculator would return a FAIL status. Otherwise, - // the calculator would simply skip the filtered frames and would not pass it - // down to downstream nodes. - optional bool fail_if_any = 1 [default = false]; - - message AspectRatioFilter { - // Target width and height, which define the aspect ratio - // (i.e. target_width / target_height) to compare input frames with. The - // actual values of these fields do not matter, only the ratio between them - // does. These values must be set to positive. - optional int32 target_width = 1 [default = -1]; - optional int32 target_height = 2 [default = -1]; - enum FilterType { - UNKNOWN_FILTER_TYPE = 0; - // Use this type when the target width and height defines an upper bound - // (inclusive) of the aspect ratio. - UPPER_ASPECT_RATIO_THRESHOLD = 1; - // Use this type when the target width and height defines a lower bound - // (inclusive) of the aspect ratio. - LOWER_ASPECT_RATIO_THRESHOLD = 2; - // Use this type to configure the calculator as a no-op pass-through node. - NO_FILTERING = 3; - } - optional FilterType filter_type = 3; - } - - oneof filter { - AspectRatioFilter aspect_ratio_filter = 2; - } -} diff --git a/examples/desktop/autoflip/calculators/video_filtering_calculator_test.cc b/examples/desktop/autoflip/calculators/video_filtering_calculator_test.cc deleted file mode 100644 index 87facf9..0000000 --- a/examples/desktop/autoflip/calculators/video_filtering_calculator_test.cc +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include -#include -#include - -#include "absl/strings/string_view.h" -#include "absl/strings/substitute.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/calculator_runner.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/status_builder.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { -namespace { - -constexpr char kOutputFramesTag[] = "OUTPUT_FRAMES"; -constexpr char kInputFramesTag[] = "INPUT_FRAMES"; - -// Default configuration of the calculator. -CalculatorGraphConfig::Node GetCalculatorNode( - const std::string& fail_if_any, const std::string& extra_options = "") { - return ParseTextProtoOrDie( - absl::Substitute(R"( - calculator: "VideoFilteringCalculator" - input_stream: "INPUT_FRAMES:frames" - output_stream: "OUTPUT_FRAMES:output_frames" - options: { - [mediapipe.autoflip.VideoFilteringCalculatorOptions.ext]: { - fail_if_any: $0 - $1 - } - } - )", - fail_if_any, extra_options)); -} - -TEST(VideoFilterCalculatorTest, UpperBoundNoPass) { - CalculatorGraphConfig::Node config = GetCalculatorNode("false", R"( - aspect_ratio_filter { - target_width: 2 - target_height: 1 - filter_type: UPPER_ASPECT_RATIO_THRESHOLD - } - )"); - - auto runner = ::absl::make_unique(config); - const int kFixedWidth = 1000; - const double kAspectRatio = 5.0 / 1.0; - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kFixedWidth, - static_cast(kFixedWidth / kAspectRatio), 16); - runner->MutableInputs() - ->Tag(kInputFramesTag) - .packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000))); - MP_ASSERT_OK(runner->Run()); - const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets; - EXPECT_TRUE(output_packet.empty()); -} - -TEST(VerticalFrameRemovalCalculatorTest, UpperBoundPass) { - CalculatorGraphConfig::Node config = GetCalculatorNode("false", R"( - aspect_ratio_filter { - target_width: 2 - target_height: 1 - filter_type: UPPER_ASPECT_RATIO_THRESHOLD - } - )"); - - auto runner = ::absl::make_unique(config); - const int kWidth = 1000; - const double kAspectRatio = 1.0 / 5.0; - const double kHeight = static_cast(kWidth / kAspectRatio); - auto input_frame = - ::absl::make_unique(ImageFormat::SRGB, kWidth, kHeight, 16); - runner->MutableInputs() - ->Tag(kInputFramesTag) - .packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000))); - MP_ASSERT_OK(runner->Run()); - const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets; - EXPECT_EQ(1, output_packet.size()); - auto& output_frame = output_packet[0].Get(); - EXPECT_EQ(kWidth, output_frame.Width()); - EXPECT_EQ(kHeight, output_frame.Height()); -} - -TEST(VideoFilterCalculatorTest, LowerBoundNoPass) { - CalculatorGraphConfig::Node config = GetCalculatorNode("false", R"( - aspect_ratio_filter { - target_width: 2 - target_height: 1 - filter_type: LOWER_ASPECT_RATIO_THRESHOLD - } - )"); - - auto runner = ::absl::make_unique(config); - const int kFixedWidth = 1000; - const double kAspectRatio = 1.0 / 1.0; - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kFixedWidth, - static_cast(kFixedWidth / kAspectRatio), 16); - runner->MutableInputs() - ->Tag(kInputFramesTag) - .packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000))); - MP_ASSERT_OK(runner->Run()); - const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets; - EXPECT_TRUE(output_packet.empty()); -} - -TEST(VerticalFrameRemovalCalculatorTest, LowerBoundPass) { - CalculatorGraphConfig::Node config = GetCalculatorNode("false", R"( - aspect_ratio_filter { - target_width: 2 - target_height: 1 - filter_type: LOWER_ASPECT_RATIO_THRESHOLD - } - )"); - - auto runner = ::absl::make_unique(config); - const int kWidth = 1000; - const double kAspectRatio = 5.0 / 1.0; - const double kHeight = static_cast(kWidth / kAspectRatio); - auto input_frame = - ::absl::make_unique(ImageFormat::SRGB, kWidth, kHeight, 16); - runner->MutableInputs() - ->Tag(kInputFramesTag) - .packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000))); - MP_ASSERT_OK(runner->Run()); - const auto& output_packet = runner->Outputs().Tag(kOutputFramesTag).packets; - EXPECT_EQ(1, output_packet.size()); - auto& output_frame = output_packet[0].Get(); - EXPECT_EQ(kWidth, output_frame.Width()); - EXPECT_EQ(kHeight, output_frame.Height()); -} - -// Test that an error should be generated when fail_if_any is true. -TEST(VerticalFrameRemovalCalculatorTest, OutputError) { - CalculatorGraphConfig::Node config = GetCalculatorNode("true", R"( - aspect_ratio_filter { - target_width: 2 - target_height: 1 - filter_type: LOWER_ASPECT_RATIO_THRESHOLD - } - )"); - - auto runner = ::absl::make_unique(config); - const int kFixedWidth = 1000; - const double kAspectRatio = 1.0 / 1.0; - auto input_frame = ::absl::make_unique( - ImageFormat::SRGB, kFixedWidth, - static_cast(kFixedWidth / kAspectRatio), 16); - runner->MutableInputs() - ->Tag(kInputFramesTag) - .packets.push_back(Adopt(input_frame.release()).At(Timestamp(1000))); - absl::Status status = runner->Run(); - EXPECT_EQ(status.code(), absl::StatusCode::kUnknown); - EXPECT_THAT(status.ToString(), - ::testing::HasSubstr("Failing due to aspect ratio")); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/cropping.proto b/examples/desktop/autoflip/quality/cropping.proto deleted file mode 100644 index f8a62b2..0000000 --- a/examples/desktop/autoflip/quality/cropping.proto +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -import "mediapipe/examples/desktop/autoflip/autoflip_messages.proto"; -import "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.proto"; - -// All relevant information for key frames, including timestamp and detected -// features. This object should be generated by calling PackKeyFrameInfo() in -// the util namespace. It is passed in to ComputeFrameCropRegion(). -message KeyFrameInfo { - // Frame timestamp (in microseconds). - optional int64 timestamp_ms = 1; - // Detected features. - optional DetectionSet detections = 2; -} - -// User-specified key frame crop options (such as target width and height). -message KeyFrameCropOptions { - // Target crop size. - // Note: if you are using the SceneCroppingCalculator, DO NOT set these fields - // manually as they will be then overwritten inside the calculator. - optional int32 target_width = 1; - optional int32 target_height = 2; - // Option for how region score is aggregated from individual feature scores. - // TODO: consider merging this enum type into the signal fusing - // calculator. - enum ScoreAggregationType { - // Unknown value (should not be used). - UNKNOWN = 0; - // Takes the score of the feature with maximum score. - MAXIMUM = 1; - // Takes the sum of the scores of the required regions. - SUM_REQUIRED = 2; - // Takes the sum of the scores of all the regions that are fully covered. - SUM_ALL = 3; - // Uses a constant score 1.0 for all crop regions. - CONSTANT = 4; - } - optional ScoreAggregationType score_aggregation_type = 3 [default = SUM_ALL]; - // Minimum centered coverage fraction (in length, not area) for a non-required - // region to be included in the crop region. Applies to both dimensions. - optional float non_required_region_min_coverage_fraction = 4 [default = 0.5]; -} - -// Key frame crop result containing the crop region rectangle, along with -// summary information on the cropping, such as whether all required regions -// could fit inside the target size, and what fraction of non-required regions -// are fully covered. This object is returned by ComputeFrameCropRegion() in -// the FrameCropRegionComputer class. -message KeyFrameCropResult { - // Successfully covers all required features. If there are no required - // regions, this field is set to true. - optional bool are_required_regions_covered_in_target_size = 1; - // Fraction of non-required features covered. - optional float fraction_non_required_covered = 2; - // Whether required crop region is empty (no detections). - optional bool required_region_is_empty = 3; - // Whether (full) crop region is empty (no detections). - optional bool region_is_empty = 4; - // Computed required crop region. - optional Rect required_region = 5; - // Computed (full) crop region. - optional Rect region = 6; - // Score of the computed crop region based on the detected features. - optional float region_score = 7; - // Frame timestamp (in microseconds). - optional int64 timestamp_ms = 8; -} - -// Compact processed scene key frame info containing timestamp, center position, -// and score. Each key frame has one SceneKeyFrameCompactInfo in -// SceneKeyFrameCropSummary. -message SceneKeyFrameCompactInfo { - // Key frame timestamp (in microseconds). - optional int64 timestamp_ms = 1; - // Key frame crop region center in the horizontal/vertical directions (in - // pixels). - optional float center_x = 2; - optional float center_y = 3; - // Key frame crop region score. - optional float score = 4; -} - -// Summary information for the key frame crop results in a scene. Computed by -// AnalyzeSceneKeyFrameCropResults() in the SceneCameraMotionAnalyzer class. -// Used to decide camera motion type and populate salient point frames. -message SceneKeyFrameCropSummary { - // Scene frame size. - optional int32 scene_frame_width = 1; - optional int32 scene_frame_height = 2; - - // Number of key frames in the scene. - optional int32 num_key_frames = 3; - // Scene key frame compact infos. - repeated SceneKeyFrameCompactInfo key_frame_compact_infos = 4; - - // The minimum/maximum values of key frames' crop centers in the horizontal/ - // vertical directions. - optional float key_frame_center_min_x = 5; - optional float key_frame_center_max_x = 6; - optional float key_frame_center_min_y = 7; - optional float key_frame_center_max_y = 8; - - // The union of all the key frame required crop regions. When camera is steady - // the crop window is set to cover this union. - optional Rect key_frame_required_crop_region_union = 9; - - // The minimum/maximum scores of key frames' crop regions. - optional float key_frame_min_score = 10; - optional float key_frame_max_score = 11; - - // Size of the scene's crop window, calculated as the maximum of the target - // size and the largest size of the key frames' crop regions in the scene. - optional int32 crop_window_width = 12; - optional int32 crop_window_height = 13; - - // Indicator for whether the scene has any frame with any salient region. - optional bool has_salient_region = 14; - // Indicator for whether the scene has any frame with any required salient - // region. - optional bool has_required_salient_region = 15; - // Percentage of key frames that are successfully cropped (i.e. covers all - // required regions inside the target size). - optional float frame_success_rate = 16; - // Amount of motion in the horizontal/vertical direction (i.e. the horizontal/ - // vertical range of the key frame crop centers' position as a fraction of - // frame width/height). - optional float horizontal_motion_amount = 17; - optional float vertical_motion_amount = 18; -} - -// Scene camera motion determined by the SceneCameraMotionAnalyzer class. -message SceneCameraMotion { - // Camera focuses on a fixed center throughout the scene. - message SteadyMotion { - // Steady look-at center in horizontal/vertical directions (in pixels). - optional float steady_look_at_center_x = 1; - optional float steady_look_at_center_y = 2; - } - // Camera tracks key frame salient region centers. - message TrackingMotion { - // Fields to be added if necessary. - } - // Camera sweeps from one point to another. - message SweepingMotion { - // Starting and ending center positions for camera sweeping in pixels. - optional float sweep_start_center_x = 1; - optional float sweep_start_center_y = 2; - optional float sweep_end_center_x = 3; - optional float sweep_end_center_y = 4; - } - oneof motion_type { - SteadyMotion steady_motion = 1; - TrackingMotion tracking_motion = 2; - SweepingMotion sweeping_motion = 3; - // Other types that we might support later. - } -} - -// User-specified options for analyzing scene camera motion from a collection of -// key frame crop regions. -message SceneCameraMotionAnalyzerOptions { - reserved 9; - // If there is small motion within the scene keep the camera steady at the - // center. - optional float motion_stabilization_threshold_percent = 1 [default = .30]; - // Snap to center if there is small motion and already focused closed to the - // center. - optional float snap_center_max_distance_percent = 2 [default = .08]; - // Maximum weight for a constraint. Scales scores accordingly so that the - // maximum score is equal to this weight. - optional float maximum_salient_point_weight = 3 [default = 100.0]; - // Normalized bound for SalientPoint's in the frame from the border. This is - // uniformly applied to the left, right, top, and bottom. It should be - // strictly less than 0.5. A narrower bound (closer to 0.5) gives better - // constraint enforcement. - optional float salient_point_bound = 4 [default = 0.48]; - // Indicator for whether sweeping is allowed. Note that if a scene can be - // seamlessly padded with solid background color, sweeping will be disabled - // regardlessly of the value of this flag. - optional bool allow_sweeping = 5 [default = true]; - // Minimal scene time span in seconds to allow camera sweeping. - optional float minimum_scene_span_sec_for_sweeping = 6 [default = 1.0]; - // If success rate in a scene is less than this, then use camera sweeping. - optional float minimum_success_rate_for_sweeping = 7 [default = 0.4]; - // If true, sweep entire frame. Otherwise, sweep the crop window. - optional bool sweep_entire_frame = 8 [default = true]; - // When no salient region is received, the default behavior is the return the - // camera to center-focused location. When this flag is set to a value >0, - // the camera will remain at its last position for this amount of time before - // recentering (if the last scene camera motion type was steady). - optional int64 duration_before_centering_us = 10; -} - -// Video cropping summary information for debugging/statistics. -message VideoCroppingSummary { - message SceneCroppingSummary { - // Scene span in seconds. - optional float start_sec = 1; - optional float end_sec = 2; - // Indicator for whether this scene was cut at a real physical scene - // boundary (as opposed to force flush). - optional bool is_end_of_scene = 3; - // Scene camera motion. - optional SceneCameraMotion camera_motion = 4; - // Indicator for whether the scene is padded. - optional bool is_padded = 5; - } - // Cropping summaries for all the scenes in the video. - repeated SceneCroppingSummary scene_summaries = 1; -} - -message CameraMotionOptions { - message PolynomialRegressionPathSolver { - // Number of frames from prior buffer to be used to smooth out camera - // trajectory when it was a forced flush. - optional int32 prior_frame_buffer_size = 1 [default = 30]; - } - oneof camera_model_oneof { - // Fits a poly line to keypoints to find a smooth camera path. - PolynomialRegressionPathSolver polynomial_path_solver = 1; - // Maintains a kinematic state of the camera, updated with keypoints, to - // find a smooth camera path. Currently optimized for real-time operation. - KinematicOptions kinematic_options = 2; - } -} diff --git a/examples/desktop/autoflip/quality/focus_point.proto b/examples/desktop/autoflip/quality/focus_point.proto deleted file mode 100644 index e53b3ed..0000000 --- a/examples/desktop/autoflip/quality/focus_point.proto +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -// Focus point location (normalized w.r.t. frame_width and frame_height, i.e. -// specified in the domain [0, 1] x [0, 1]). - -// For TYPE_INCLUDE: -// During retargeting and stabilization focus points introduce constraints -// that will try to keep the normalized location in the rectangle -// frame_size - normalized bounds. -// For this soft constraints are used, therefore the weight specifies -// how "important" the focus point is (higher is better). -// In particular for each point p the retargeter introduces two pairs of -// constraints of the form: -// x - slack < width - right -// and x + slack > 0 + left, with slack > 0 -// where the weight specifies the importance of the slack. -// -// For TYPE_EXCLUDE_*: -// Similar to above, but constraints are introduced to keep -// the point to the left of the left bound OR the right of the right bound. -// In particular: -// x - slack < left OR -// x + slack >= right -// Similar to above, the weight specifies the importance of the slack. -// -// Note: Choosing a too high weight can lead to -// jerkiness as the stabilization essentially starts tracking the focus point. -message FocusPoint { - // Normalized location of the point (within domain [0, 1] x [0, 1]. - optional float norm_point_x = 1 [default = 0.0]; - optional float norm_point_y = 2 [default = 0.0]; - - enum FocusPointType { - TYPE_INCLUDE = 1; - TYPE_EXCLUDE_LEFT = 2; - TYPE_EXCLUDE_RIGHT = 3; - } - - // Focus point type. By default we try to frame the focus point within - // the bounding box specified by left, bottom, right, top. Alternatively, one - // can choose to exclude the point. For details, see discussion above. - optional FocusPointType type = 11 [default = TYPE_INCLUDE]; - - // Bounds are specified in normalized coordinates [0, 1], FROM the specified - // border. Opposing bounds (e.g. left and right) may not add to values - // larger than 1. - // Default bounds center focus point within centering third of the frame. - optional float left = 3 [default = 0.3]; - optional float bottom = 4 [default = 0.3]; - optional float right = 9 [default = 0.3]; - optional float top = 10 [default = 0.3]; - - optional float weight = 5 [default = 15]; - - extensions 20000 to max; -} - -// Aggregates FocusPoint's for a frame. -message FocusPointFrame { - repeated FocusPoint point = 1; - - extensions 20000 to max; -} diff --git a/examples/desktop/autoflip/quality/frame_crop_region_computer.cc b/examples/desktop/autoflip/quality/frame_crop_region_computer.cc deleted file mode 100644 index 5916d18..0000000 --- a/examples/desktop/autoflip/quality/frame_crop_region_computer.cc +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/frame_crop_region_computer.h" - -#include - -#include "mediapipe/examples/desktop/autoflip/quality/utils.h" -#include "mediapipe/framework/port/ret_check.h" - -namespace mediapipe { -namespace autoflip { - -absl::Status FrameCropRegionComputer::ExpandSegmentUnderConstraint( - const Segment& segment_to_add, const Segment& base_segment, - const int max_length, Segment* combined_segment, - CoverType* cover_type) const { - RET_CHECK(combined_segment != nullptr) << "Combined segment is null."; - RET_CHECK(cover_type != nullptr) << "Cover type is null."; - - const LeftPoint segment_to_add_left = segment_to_add.first; - const RightPoint segment_to_add_right = segment_to_add.second; - RET_CHECK(segment_to_add_right >= segment_to_add_left) - << "Invalid segment to add."; - const LeftPoint base_segment_left = base_segment.first; - const RightPoint base_segment_right = base_segment.second; - RET_CHECK(base_segment_right >= base_segment_left) << "Invalid base segment."; - const int base_length = base_segment_right - base_segment_left; - RET_CHECK(base_length <= max_length) - << "Base segment length exceeds max length."; - - const int segment_to_add_length = segment_to_add_right - segment_to_add_left; - const int max_leftout_amount = - std::ceil((1.0 - options_.non_required_region_min_coverage_fraction()) * - segment_to_add_length / 2); - const LeftPoint min_coverage_segment_to_add_left = - segment_to_add_left + max_leftout_amount; - const LeftPoint min_coverage_segment_to_add_right = - segment_to_add_right - max_leftout_amount; - - LeftPoint combined_segment_left = - std::min(segment_to_add_left, base_segment_left); - RightPoint combined_segment_right = - std::max(segment_to_add_right, base_segment_right); - - LeftPoint min_coverage_combined_segment_left = - std::min(min_coverage_segment_to_add_left, base_segment_left); - RightPoint min_coverage_combined_segment_right = - std::max(min_coverage_segment_to_add_right, base_segment_right); - - if ((combined_segment_right - combined_segment_left) <= max_length) { - *cover_type = FULLY_COVERED; - } else if (min_coverage_combined_segment_right - - min_coverage_combined_segment_left <= - max_length) { - *cover_type = PARTIALLY_COVERED; - combined_segment_left = min_coverage_combined_segment_left; - combined_segment_right = min_coverage_combined_segment_right; - } else { - *cover_type = NOT_COVERED; - combined_segment_left = base_segment_left; - combined_segment_right = base_segment_right; - } - - *combined_segment = - std::make_pair(combined_segment_left, combined_segment_right); - return absl::OkStatus(); -} - -absl::Status FrameCropRegionComputer::ExpandRectUnderConstraints( - const Rect& rect_to_add, const int max_width, const int max_height, - Rect* base_rect, CoverType* cover_type) const { - RET_CHECK(base_rect != nullptr) << "Base rect is null."; - RET_CHECK(cover_type != nullptr) << "Cover type is null."; - RET_CHECK(base_rect->width() <= max_width && - base_rect->height() <= max_height) - << "Base rect already exceeds target size."; - - const LeftPoint rect_to_add_left = rect_to_add.x(); - const RightPoint rect_to_add_right = rect_to_add.x() + rect_to_add.width(); - const LeftPoint rect_to_add_top = rect_to_add.y(); - const RightPoint rect_to_add_bottom = rect_to_add.y() + rect_to_add.height(); - const LeftPoint base_rect_left = base_rect->x(); - const RightPoint base_rect_right = base_rect->x() + base_rect->width(); - const LeftPoint base_rect_top = base_rect->y(); - const RightPoint base_rect_bottom = base_rect->y() + base_rect->height(); - - Segment horizontal_combined_segment, vertical_combined_segment; - CoverType horizontal_cover_type, vertical_cover_type; - const auto horizontal_status = ExpandSegmentUnderConstraint( - std::make_pair(rect_to_add_left, rect_to_add_right), - std::make_pair(base_rect_left, base_rect_right), max_width, - &horizontal_combined_segment, &horizontal_cover_type); - MP_RETURN_IF_ERROR(horizontal_status); - const auto vertical_status = ExpandSegmentUnderConstraint( - std::make_pair(rect_to_add_top, rect_to_add_bottom), - std::make_pair(base_rect_top, base_rect_bottom), max_height, - &vertical_combined_segment, &vertical_cover_type); - MP_RETURN_IF_ERROR(vertical_status); - - if (horizontal_cover_type == NOT_COVERED || - vertical_cover_type == NOT_COVERED) { - // Gives up if the segment is not covered in either direction. - *cover_type = NOT_COVERED; - } else { - // Tries to (partially) cover the new rect to be added. - base_rect->set_x(horizontal_combined_segment.first); - base_rect->set_y(vertical_combined_segment.first); - base_rect->set_width(horizontal_combined_segment.second - - horizontal_combined_segment.first); - base_rect->set_height(vertical_combined_segment.second - - vertical_combined_segment.first); - if (horizontal_cover_type == FULLY_COVERED && - vertical_cover_type == FULLY_COVERED) { - *cover_type = FULLY_COVERED; - } else { - *cover_type = PARTIALLY_COVERED; - } - } - - return absl::OkStatus(); -} - -void FrameCropRegionComputer::UpdateCropRegionScore( - const KeyFrameCropOptions::ScoreAggregationType score_aggregation_type, - const float feature_score, const bool is_required, - float* crop_region_score) { - if (feature_score < 0.0) { - LOG(WARNING) << "Ignoring negative score"; - return; - } - - switch (score_aggregation_type) { - case KeyFrameCropOptions::MAXIMUM: { - *crop_region_score = std::max(feature_score, *crop_region_score); - break; - } - case KeyFrameCropOptions::SUM_REQUIRED: { - if (is_required) { - *crop_region_score += feature_score; - } - break; - } - case KeyFrameCropOptions::SUM_ALL: { - *crop_region_score += feature_score; - break; - } - case KeyFrameCropOptions::CONSTANT: { - *crop_region_score = 1.0; - break; - } - default: { - LOG(WARNING) << "Unknown CropRegionScoreType " << score_aggregation_type; - break; - } - } -} - -absl::Status FrameCropRegionComputer::ComputeFrameCropRegion( - const KeyFrameInfo& frame_info, KeyFrameCropResult* crop_result) const { - RET_CHECK(crop_result != nullptr) << "KeyFrameCropResult is null."; - - // Set timestamp of KeyFrameCropResult - crop_result->set_timestamp_ms(frame_info.timestamp_ms()); - - // Sorts required and non-required regions. - std::vector required_regions, non_required_regions; - const auto sort_status = SortDetections( - frame_info.detections(), &required_regions, &non_required_regions); - MP_RETURN_IF_ERROR(sort_status); - - int target_width = options_.target_width(); - int target_height = options_.target_height(); - auto* region = crop_result->mutable_region(); - - bool crop_region_is_empty = true; - float crop_region_score = 0.0; - - // Gets union of all required regions. - for (int i = 0; i < required_regions.size(); ++i) { - const Rect& required_region = required_regions[i].location(); - if (crop_region_is_empty) { - *region = required_region; - crop_region_is_empty = false; - } else { - RectUnion(required_region, region); - } - UpdateCropRegionScore(options_.score_aggregation_type(), - required_regions[i].score(), true, - &crop_region_score); - } - crop_result->set_required_region_is_empty(crop_region_is_empty); - if (!crop_region_is_empty) { - *crop_result->mutable_required_region() = *region; - crop_result->set_are_required_regions_covered_in_target_size( - region->width() <= target_width && region->height() <= target_height); - target_width = std::max(target_width, region->width()); - target_height = std::max(target_height, region->height()); - } else { - crop_result->set_are_required_regions_covered_in_target_size(true); - } - - // Tries to fit non-required regions. - int num_covered = 0; - for (int i = 0; i < non_required_regions.size(); ++i) { - const Rect& non_required_region = non_required_regions[i].location(); - CoverType cover_type = NOT_COVERED; - if (crop_region_is_empty) { - // If the crop region is empty, tries to expand an empty base region - // at the center of this region to include itself. - region->set_x(non_required_region.x() + non_required_region.width() / 2); - region->set_y(non_required_region.y() + non_required_region.height() / 2); - region->set_width(0); - region->set_height(0); - MP_RETURN_IF_ERROR(ExpandRectUnderConstraints(non_required_region, - target_width, target_height, - region, &cover_type)); - if (cover_type != NOT_COVERED) { - crop_region_is_empty = false; - } - } else { - // Otherwise tries to expand the crop region to cover the non-required - // region under target size constraint. - MP_RETURN_IF_ERROR(ExpandRectUnderConstraints(non_required_region, - target_width, target_height, - region, &cover_type)); - } - - // Updates number of covered non-required regions and score. - if (cover_type == FULLY_COVERED) { - num_covered++; - UpdateCropRegionScore(options_.score_aggregation_type(), - non_required_regions[i].score(), false, - &crop_region_score); - } - } - - const float fraction_covered = - non_required_regions.empty() - ? 0.0 - : static_cast(num_covered) / non_required_regions.size(); - crop_result->set_fraction_non_required_covered(fraction_covered); - - crop_result->set_region_is_empty(crop_region_is_empty); - crop_result->set_region_score(crop_region_score); - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/frame_crop_region_computer.h b/examples/desktop/autoflip/quality/frame_crop_region_computer.h deleted file mode 100644 index b2be9e2..0000000 --- a/examples/desktop/autoflip/quality/frame_crop_region_computer.h +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_FRAME_CROP_REGION_COMPUTER_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_FRAME_CROP_REGION_COMPUTER_H_ - -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// This class computes per-frame crop regions based on crop frame options. -// It aggregates required regions and then tries to fit in non-required regions -// with best effort. It does not make use of static features. -class FrameCropRegionComputer { - public: - FrameCropRegionComputer() = delete; - - explicit FrameCropRegionComputer( - const KeyFrameCropOptions& crop_frame_options) - : options_(crop_frame_options) {} - - ~FrameCropRegionComputer() {} - - // Computes the crop region for the key frame using the crop options. The crop - // region covers all the required regions, and attempts to cover the - // non-required regions with best effort. Note: this function does not - // consider static features, and simply tries to fit the detected features - // within the target frame size. The score of the crop region is aggregated - // from individual feature scores given the score aggregation type. - absl::Status ComputeFrameCropRegion(const KeyFrameInfo& frame_info, - KeyFrameCropResult* crop_result) const; - - protected: - // A segment is a 1-d object defined by its left and right point. - using LeftPoint = int; - using RightPoint = int; - using Segment = std::pair; - // How much a segment is covered in the combined segment. - enum CoverType { - FULLY_COVERED = 1, - PARTIALLY_COVERED = 2, - NOT_COVERED = 3, - }; - // Expands a base segment to cover a segment to be added given maximum length - // constraint. The operation is best-effort. The resulting enlarged segment is - // set in the returned combined segment. Returns a CoverType to indicate the - // coverage of the segment to be added in the combined segment. - // There are 3 cases: - // case 1: the length of the union of the two segments is not larger than - // the maximum length. - // In this case the combined segment is simply the union, and cover - // type is FULLY_COVERED. - // case 2: the union of the two segments exceeds the maximum length, but the - // union of the base segment and required minimum centered fraction - // of the new segment fits in the maximum length. - // In this case the combined segment is this latter union, and cover - // type is PARTIALLY_COVERED. - // case 3: the union of the base segment and required minimum centered - // fraction of the new segment exceeds the maximum length. - // In this case the combined segment is the base segment, and cover - // type is NOT_COVERED. - absl::Status ExpandSegmentUnderConstraint(const Segment& segment_to_add, - const Segment& base_segment, - const int max_length, - Segment* combined_segment, - CoverType* cover_type) const; - - // Expands a base rectangle to cover a new rectangle to be added under width - // and height constraints. The operation is best-effort. It considers - // horizontal and vertical directions separately, using the - // ExpandSegmentUnderConstraint function for each direction. The cover type is - // FULLY_COVERED if the new rectangle is fully covered in both directions, - // PARTIALLY_COVERED if it is at least partially covered in both directions, - // and NOT_COVERED if it is not covered in either direction. - absl::Status ExpandRectUnderConstraints(const Rect& rect_to_add, - const int max_width, - const int max_height, Rect* base_rect, - CoverType* cover_type) const; - - // Updates crop region score given current feature score, whether the feature - // is required, and the score aggregation type. Ignores negative scores. - static void UpdateCropRegionScore( - const KeyFrameCropOptions::ScoreAggregationType score_aggregation_type, - const float feature_score, const bool is_required, - float* crop_region_score); - - private: - // Crop frame options. - KeyFrameCropOptions options_; -}; -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_FRAME_CROP_REGION_COMPUTER_H_ diff --git a/examples/desktop/autoflip/quality/frame_crop_region_computer_test.cc b/examples/desktop/autoflip/quality/frame_crop_region_computer_test.cc deleted file mode 100644 index eb60ea6..0000000 --- a/examples/desktop/autoflip/quality/frame_crop_region_computer_test.cc +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/frame_crop_region_computer.h" - -#include - -#include "absl/memory/memory.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { - -using ::testing::HasSubstr; - -const int kSegmentMaxLength = 10; -const int kTargetWidth = 500; -const int kTargetHeight = 1000; - -// Makes a rectangle given the corner (x, y) and the size (width, height). -Rect MakeRect(const int x, const int y, const int width, const int height) { - Rect rect; - rect.set_x(x); - rect.set_y(y); - rect.set_width(width); - rect.set_height(height); - return rect; -} - -// Adds a detection to the key frame info given its location, whether it is -// required, and its score. The score is default to 1.0. -void AddDetection(const Rect& rect, const bool is_required, - KeyFrameInfo* key_frame_info, const float score = 1.0) { - auto* detection = key_frame_info->mutable_detections()->add_detections(); - *(detection->mutable_location()) = rect; - detection->set_score(score); - detection->set_is_required(is_required); -} - -// Makes key frame crop options given target width and height. -KeyFrameCropOptions MakeKeyFrameCropOptions(const int target_width, - const int target_height) { - KeyFrameCropOptions options; - options.set_target_width(target_width); - options.set_target_height(target_height); - return options; -} - -// Checks whether rectangle a is inside rectangle b. -bool CheckRectIsInside(const Rect& rect_a, const Rect& rect_b) { - return (rect_b.x() <= rect_a.x() && rect_b.y() <= rect_a.y() && - rect_b.x() + rect_b.width() >= rect_a.x() + rect_a.width() && - rect_b.y() + rect_b.height() >= rect_a.y() + rect_a.height()); -} - -// Checks whether two rectangles are equal. -bool CheckRectsEqual(const Rect& rect1, const Rect& rect2) { - return (rect1.x() == rect2.x() && rect1.y() == rect2.y() && - rect1.width() == rect2.width() && rect1.height() == rect2.height()); -} - -// Checks whether two rectangles have non-zero overlapping area. -bool CheckRectsOverlap(const Rect& rect1, const Rect& rect2) { - const int x1_left = rect1.x(), x1_right = rect1.x() + rect1.width(); - const int y1_top = rect1.y(), y1_bottom = rect1.y() + rect1.height(); - const int x2_left = rect2.x(), x2_right = rect2.x() + rect2.width(); - const int y2_top = rect2.y(), y2_bottom = rect2.y() + rect2.height(); - const int x_left = std::max(x1_left, x2_left); - const int x_right = std::min(x1_right, x2_right); - const int y_top = std::max(y1_top, y2_top); - const int y_bottom = std::min(y1_bottom, y2_bottom); - return (x_right > x_left && y_bottom > y_top); -} - -// Checks that all the required regions in the detections in KeyFrameInfo are -// covered in the KeyFrameCropResult. -void CheckRequiredRegionsAreCovered(const KeyFrameInfo& key_frame_info, - const KeyFrameCropResult& result) { - bool has_required = false; - for (int i = 0; i < key_frame_info.detections().detections_size(); ++i) { - const auto& detection = key_frame_info.detections().detections(i); - if (detection.is_required()) { - has_required = true; - EXPECT_TRUE( - CheckRectIsInside(detection.location(), result.required_region())); - } - } - EXPECT_EQ(has_required, !result.required_region_is_empty()); - if (has_required) { - EXPECT_FALSE(result.region_is_empty()); - EXPECT_TRUE(CheckRectIsInside(result.required_region(), result.region())); - } -} - -// Testable class that can access protected types and methods in the class. -class TestableFrameCropRegionComputer : public FrameCropRegionComputer { - public: - explicit TestableFrameCropRegionComputer(const KeyFrameCropOptions& options) - : FrameCropRegionComputer(options) {} - using FrameCropRegionComputer::CoverType; - using FrameCropRegionComputer::ExpandRectUnderConstraints; - using FrameCropRegionComputer::ExpandSegmentUnderConstraint; - using FrameCropRegionComputer::FULLY_COVERED; - using FrameCropRegionComputer::LeftPoint; // int - using FrameCropRegionComputer::NOT_COVERED; - using FrameCropRegionComputer::PARTIALLY_COVERED; - using FrameCropRegionComputer::RightPoint; // int - using FrameCropRegionComputer::Segment; // std::pair - using FrameCropRegionComputer::UpdateCropRegionScore; - - // Makes a segment from two endpoints. - static Segment MakeSegment(const LeftPoint left, const RightPoint right) { - return std::make_pair(left, right); - } - - // Checks that two segments are equal. - static bool CheckSegmentsEqual(const Segment& segment1, - const Segment& segment2) { - return (segment1.first == segment2.first && - segment1.second == segment2.second); - } -}; -using TestClass = TestableFrameCropRegionComputer; - -// Returns an instance of the testable class given -// non_required_region_min_coverage_fraction. -std::unique_ptr GetTestableClass( - const float non_required_region_min_coverage_fraction = 0.5) { - KeyFrameCropOptions options; - options.set_non_required_region_min_coverage_fraction( - non_required_region_min_coverage_fraction); - auto test_class = absl::make_unique(options); - return test_class; -} - -// Checks that ExpandSegmentUnderConstraint checks output pointers are not null. -TEST(FrameCropRegionComputerTest, ExpandSegmentUnderConstraintCheckNull) { - auto test_class = GetTestableClass(); - TestClass::CoverType cover_type; - TestClass::Segment base_segment = TestClass::MakeSegment(10, 15); - TestClass::Segment segment_to_add = TestClass::MakeSegment(5, 8); - TestClass::Segment combined_segment; - // Combined segment is null. - auto status = test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, nullptr, &cover_type); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Combined segment is null.")); - // Cover type is null. - status = test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, &combined_segment, - nullptr); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Cover type is null.")); -} - -// Checks that ExpandSegmentUnderConstraint checks input segments are valid. -TEST(FrameCropRegionComputerTest, ExpandSegmentUnderConstraintCheckValid) { - auto test_class = GetTestableClass(); - TestClass::CoverType cover_type; - TestClass::Segment combined_segment; - - // Invalid base segment. - TestClass::Segment base_segment = TestClass::MakeSegment(15, 10); - TestClass::Segment segment_to_add = TestClass::MakeSegment(5, 8); - auto status = test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, &combined_segment, - &cover_type); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Invalid base segment.")); - - // Invalid segment to add. - base_segment = TestClass::MakeSegment(10, 15); - segment_to_add = TestClass::MakeSegment(8, 5); - status = test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, &combined_segment, - &cover_type); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Invalid segment to add.")); - - // Base segment exceeds max length. - base_segment = TestClass::MakeSegment(10, 100); - segment_to_add = TestClass::MakeSegment(5, 8); - status = test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, &combined_segment, - &cover_type); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Base segment length exceeds max length.")); -} - -// Checks that ExpandSegmentUnderConstraint handles case 1 properly: the length -// of the union of the two segments is not larger than the maximum length. -TEST(FrameCropRegionComputerTest, ExpandSegmentUnderConstraintCase1) { - auto test_class = GetTestableClass(); - TestClass::Segment combined_segment; - TestClass::CoverType cover_type; - TestClass::Segment base_segment = TestClass::MakeSegment(5, 10); - TestClass::Segment segment_to_add = TestClass::MakeSegment(3, 8); - MP_EXPECT_OK(test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, &combined_segment, - &cover_type)); - EXPECT_EQ(cover_type, TestClass::FULLY_COVERED); - EXPECT_TRUE(TestClass::CheckSegmentsEqual(combined_segment, - TestClass::MakeSegment(3, 10))); -} - -// Checks that ExpandSegmentUnderConstraint handles case 2 properly: the union -// of the two segments exceeds the maximum length, but the union of the base -// segment with the minimum coverage fraction of the new segment is within the -// maximum length. -TEST(FrameCropRegionComputerTest, ExpandSegmentUnderConstraintCase2) { - TestClass::Segment combined_segment; - TestClass::CoverType cover_type; - TestClass::Segment base_segment = TestClass::MakeSegment(4, 8); - TestClass::Segment segment_to_add = TestClass::MakeSegment(0, 16); - auto test_class = GetTestableClass(); - MP_EXPECT_OK(test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, &combined_segment, - &cover_type)); - EXPECT_EQ(cover_type, TestClass::PARTIALLY_COVERED); - EXPECT_TRUE(TestClass::CheckSegmentsEqual(combined_segment, - TestClass::MakeSegment(4, 12))); -} - -// Checks that ExpandSegmentUnderConstraint handles case 3 properly: the union -// of the base segment with the minimum coverage fraction of the new segment -// exceeds the maximum length. -TEST(FrameCropRegionComputerTest, ExpandSegmentUnderConstraintCase3) { - TestClass::Segment combined_segment; - TestClass::CoverType cover_type; - auto test_class = GetTestableClass(); - TestClass::Segment base_segment = TestClass::MakeSegment(6, 14); - TestClass::Segment segment_to_add = TestClass::MakeSegment(0, 4); - MP_EXPECT_OK(test_class->ExpandSegmentUnderConstraint( - segment_to_add, base_segment, kSegmentMaxLength, &combined_segment, - &cover_type)); - EXPECT_EQ(cover_type, TestClass::NOT_COVERED); - EXPECT_TRUE(TestClass::CheckSegmentsEqual(combined_segment, base_segment)); -} - -// Checks that ExpandRectUnderConstraints checks output pointers are not null. -TEST(FrameCropRegionComputerTest, ExpandRectUnderConstraintsChecksNotNull) { - auto test_class = GetTestableClass(); - TestClass::CoverType cover_type; - Rect base_rect, rect_to_add; - // Base rect is null. - auto status = test_class->ExpandRectUnderConstraints( - rect_to_add, kTargetWidth, kTargetHeight, nullptr, &cover_type); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Base rect is null.")); - // Cover type is null. - status = test_class->ExpandRectUnderConstraints( - rect_to_add, kTargetWidth, kTargetHeight, &base_rect, nullptr); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Cover type is null.")); -} - -// Checks that ExpandRectUnderConstraints checks base rect is valid. -TEST(FrameCropRegionComputerTest, ExpandRectUnderConstraintsChecksBaseValid) { - auto test_class = GetTestableClass(); - TestClass::CoverType cover_type; - Rect base_rect = MakeRect(0, 0, 2 * kTargetWidth, 2 * kTargetHeight); - Rect rect_to_add; - const auto status = test_class->ExpandRectUnderConstraints( - rect_to_add, kTargetWidth, kTargetHeight, &base_rect, &cover_type); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Base rect already exceeds target size.")); -} - -// Checks that ExpandRectUnderConstraints properly handles the case where the -// rectangle to be added can be fully covered. -TEST(FrameCropRegionComputerTest, ExpandRectUnderConstraintsFullyCovered) { - auto test_class = GetTestableClass(); - TestClass::CoverType cover_type; - Rect base_rect = MakeRect(0, 0, 50, 50); - Rect rect_to_add = MakeRect(30, 30, 30, 30); - MP_EXPECT_OK(test_class->ExpandRectUnderConstraints( - rect_to_add, kTargetWidth, kTargetHeight, &base_rect, &cover_type)); - EXPECT_EQ(cover_type, TestClass::FULLY_COVERED); - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(0, 0, 60, 60))); -} - -// Checks that ExpandRectUnderConstraints properly handles the case where the -// rectangle to be added can be partially covered. -TEST(FrameCropRegionComputerTest, ExpandRectUnderConstraintsPartiallyCovered) { - auto test_class = GetTestableClass(); - TestClass::CoverType cover_type; - // Rectangle to be added can be partially covered in both both dimensions. - Rect base_rect = MakeRect(0, 0, 500, 500); - Rect rect_to_add = MakeRect(0, 300, 600, 900); - MP_EXPECT_OK(test_class->ExpandRectUnderConstraints( - rect_to_add, kTargetWidth, kTargetHeight, &base_rect, &cover_type)); - EXPECT_EQ(cover_type, TestClass::PARTIALLY_COVERED); - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(0, 0, 500, 975))); - - // Rectangle to be added can be fully covered in one dimension and partially - // covered in the other dimension. - base_rect = MakeRect(0, 0, 400, 500); - rect_to_add = MakeRect(100, 300, 400, 900); - MP_EXPECT_OK(test_class->ExpandRectUnderConstraints( - rect_to_add, kTargetWidth, kTargetHeight, &base_rect, &cover_type)); - EXPECT_EQ(cover_type, TestClass::PARTIALLY_COVERED); - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(0, 0, 500, 975))); -} - -// Checks that ExpandRectUnderConstraints properly handles the case where the -// rectangle to be added cannot be covered. -TEST(FrameCropRegionComputerTest, ExpandRectUnderConstraintsNotCovered) { - TestClass::CoverType cover_type; - auto test_class = GetTestableClass(); - Rect base_rect = MakeRect(0, 0, 500, 500); - Rect rect_to_add = MakeRect(550, 300, 100, 900); - MP_EXPECT_OK(test_class->ExpandRectUnderConstraints( - rect_to_add, kTargetWidth, kTargetHeight, &base_rect, &cover_type)); - EXPECT_EQ(cover_type, TestClass::NOT_COVERED); // no overlap in x dimension - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(0, 0, 500, 500))); -} - -// Checks that ComputeFrameCropRegion handles the case of empty detections. -TEST(FrameCropRegionComputerTest, HandlesEmptyDetections) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - EXPECT_TRUE(crop_result.region_is_empty()); -} - -// Checks that ComputeFrameCropRegion covers required regions when their union -// is within target size. -TEST(FrameCropRegionComputerTest, CoversRequiredWithinTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(100, 100, 100, 200), true, &key_frame_info); - AddDetection(MakeRect(200, 400, 300, 500), true, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - CheckRequiredRegionsAreCovered(key_frame_info, crop_result); - EXPECT_TRUE(CheckRectsEqual(MakeRect(100, 100, 400, 800), - crop_result.required_region())); - EXPECT_TRUE( - CheckRectsEqual(crop_result.region(), crop_result.required_region())); - EXPECT_TRUE(crop_result.are_required_regions_covered_in_target_size()); -} - -// Checks that ComputeFrameCropRegion covers required regions when their union -// exceeds target size. -TEST(FrameCropRegionComputerTest, CoversRequiredExceedingTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 100, 500), true, &key_frame_info); - AddDetection(MakeRect(200, 400, 500, 500), true, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - CheckRequiredRegionsAreCovered(key_frame_info, crop_result); - EXPECT_TRUE(CheckRectsEqual(MakeRect(0, 0, 700, 900), crop_result.region())); - EXPECT_TRUE( - CheckRectsEqual(crop_result.region(), crop_result.required_region())); - EXPECT_FALSE(crop_result.are_required_regions_covered_in_target_size()); -} - -// Checks that ComputeFrameCropRegion handles the case of only non-required -// regions and the region fits in the target size. -TEST(FrameCropRegionComputerTest, - HandlesOnlyNonRequiedRegionsInsideTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(300, 600, 100, 100), false, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - EXPECT_TRUE(crop_result.required_region_is_empty()); - EXPECT_FALSE(crop_result.region_is_empty()); - EXPECT_TRUE( - CheckRectsEqual(key_frame_info.detections().detections(0).location(), - crop_result.region())); -} - -// Checks that ComputeFrameCropRegion handles the case of only non-required -// regions and the region exceeds the target size. -TEST(FrameCropRegionComputerTest, - HandlesOnlyNonRequiedRegionsExceedingTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(300, 600, 700, 100), false, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - EXPECT_TRUE(crop_result.required_region_is_empty()); - EXPECT_FALSE(crop_result.region_is_empty()); - EXPECT_TRUE( - CheckRectsEqual(MakeRect(475, 600, 350, 100), crop_result.region())); - EXPECT_EQ(crop_result.fraction_non_required_covered(), 0.0); - EXPECT_TRUE( - CheckRectIsInside(crop_result.region(), - key_frame_info.detections().detections(0).location())); -} - -// Checks that ComputeFrameCropRegion covers non-required regions when their -// union fits within target size. -TEST(FrameCropRegionComputerTest, CoversNonRequiredInsideTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 100, 500), true, &key_frame_info); - AddDetection(MakeRect(300, 600, 100, 100), false, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - CheckRequiredRegionsAreCovered(key_frame_info, crop_result); - EXPECT_TRUE(CheckRectsEqual(MakeRect(0, 0, 400, 700), crop_result.region())); - EXPECT_TRUE(crop_result.are_required_regions_covered_in_target_size()); - EXPECT_EQ(crop_result.fraction_non_required_covered(), 1.0); - for (int i = 0; i < key_frame_info.detections().detections_size(); ++i) { - EXPECT_TRUE( - CheckRectIsInside(key_frame_info.detections().detections(i).location(), - crop_result.region())); - } -} - -// Checks that ComputeFrameCropRegion does not cover non-required regions that -// are outside the target size. -TEST(FrameCropRegionComputerTest, DoesNotCoverNonRequiredExceedingTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 500, 1000), true, &key_frame_info); - AddDetection(MakeRect(500, 0, 100, 100), false, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - CheckRequiredRegionsAreCovered(key_frame_info, crop_result); - EXPECT_TRUE(CheckRectsEqual(MakeRect(0, 0, 500, 1000), crop_result.region())); - EXPECT_TRUE(crop_result.are_required_regions_covered_in_target_size()); - EXPECT_EQ(crop_result.fraction_non_required_covered(), 0.0); - EXPECT_FALSE( - CheckRectIsInside(key_frame_info.detections().detections(1).location(), - crop_result.region())); -} - -// Checks that ComputeFrameCropRegion partially covers non-required regions that -// can partially fit in the target size. -TEST(FrameCropRegionComputerTest, - PartiallyCoversNonRequiredContainingTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(100, 0, 350, 1000), true, &key_frame_info); - AddDetection(MakeRect(0, 0, 650, 100), false, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - CheckRequiredRegionsAreCovered(key_frame_info, crop_result); - EXPECT_TRUE( - CheckRectsEqual(MakeRect(100, 0, 387, 1000), crop_result.region())); - EXPECT_TRUE(crop_result.are_required_regions_covered_in_target_size()); - EXPECT_EQ(crop_result.fraction_non_required_covered(), 0.0); - EXPECT_TRUE( - CheckRectsOverlap(key_frame_info.detections().detections(1).location(), - crop_result.region())); -} - -// Checks that ComputeFrameCropRegion covers non-required regions when the -// required regions exceed target size. -TEST(FrameCropRegionComputerTest, - CoversNonRequiredWhenRequiredExceedsTargetSize) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 600, 1000), true, &key_frame_info); - AddDetection(MakeRect(450, 0, 100, 100), false, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - CheckRequiredRegionsAreCovered(key_frame_info, crop_result); - EXPECT_TRUE(CheckRectsEqual(MakeRect(0, 0, 600, 1000), crop_result.region())); - EXPECT_FALSE(crop_result.are_required_regions_covered_in_target_size()); - EXPECT_EQ(crop_result.fraction_non_required_covered(), 1.0); - for (int i = 0; i < key_frame_info.detections().detections_size(); ++i) { - EXPECT_TRUE( - CheckRectIsInside(key_frame_info.detections().detections(i).location(), - crop_result.region())); - } -} - -// Checks that ComputeFrameCropRegion does not extend the crop region when -// the non-required region is too far. -TEST(FrameCropRegionComputerTest, - DoesNotExtendRegionWhenNonRequiredRegionIsTooFar) { - const auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 400, 400), true, &key_frame_info); - AddDetection(MakeRect(600, 0, 100, 100), false, &key_frame_info); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - CheckRequiredRegionsAreCovered(key_frame_info, crop_result); - EXPECT_TRUE(CheckRectsEqual(MakeRect(0, 0, 400, 400), crop_result.region())); - EXPECT_TRUE(crop_result.are_required_regions_covered_in_target_size()); - EXPECT_EQ(crop_result.fraction_non_required_covered(), 0.0); - EXPECT_FALSE( - CheckRectsOverlap(key_frame_info.detections().detections(1).location(), - crop_result.region())); -} - -// Checks that ComputeFrameCropRegion computes the score correctly when the -// aggregation type is maximum. -TEST(FrameCropRegionComputerTest, ComputesScoreWhenAggregationIsMaximum) { - auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - options.set_score_aggregation_type(KeyFrameCropOptions::MAXIMUM); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 400, 400), true, &key_frame_info, 0.1); - AddDetection(MakeRect(300, 300, 200, 500), true, &key_frame_info, 0.9); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - EXPECT_FLOAT_EQ(crop_result.region_score(), 0.9f); -} - -// Checks that ComputeFrameCropRegion computes the score correctly when the -// aggregation type is sum required regions. -TEST(FrameCropRegionComputerTest, ComputesScoreWhenAggregationIsSumRequired) { - auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - options.set_score_aggregation_type(KeyFrameCropOptions::SUM_REQUIRED); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 400, 400), true, &key_frame_info, 0.1); - AddDetection(MakeRect(300, 300, 200, 500), true, &key_frame_info, 0.9); - AddDetection(MakeRect(300, 300, 200, 500), false, &key_frame_info, 0.5); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - EXPECT_FLOAT_EQ(crop_result.region_score(), 1.0f); -} - -// Checks that ComputeFrameCropRegion computes the score correctly when the -// aggregation type is sum all covered regions. -TEST(FrameCropRegionComputerTest, ComputesScoreWhenAggregationIsSumAll) { - auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - options.set_score_aggregation_type(KeyFrameCropOptions::SUM_ALL); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 400, 400), true, &key_frame_info, 0.1); - AddDetection(MakeRect(300, 300, 200, 500), true, &key_frame_info, 0.9); - AddDetection(MakeRect(300, 300, 200, 500), false, &key_frame_info, 0.5); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - EXPECT_FLOAT_EQ(crop_result.region_score(), 1.5f); -} - -// Checks that ComputeFrameCropRegion computes the score correctly when the -// aggregation type is constant. -TEST(FrameCropRegionComputerTest, ComputesScoreWhenAggregationIsConstant) { - auto options = MakeKeyFrameCropOptions(kTargetWidth, kTargetHeight); - options.set_score_aggregation_type(KeyFrameCropOptions::CONSTANT); - FrameCropRegionComputer computer(options); - KeyFrameInfo key_frame_info; - AddDetection(MakeRect(0, 0, 400, 400), true, &key_frame_info, 0.1); - AddDetection(MakeRect(300, 300, 200, 500), true, &key_frame_info, 0.9); - AddDetection(MakeRect(300, 300, 200, 500), false, &key_frame_info, 0.5); - KeyFrameCropResult crop_result; - MP_EXPECT_OK(computer.ComputeFrameCropRegion(key_frame_info, &crop_result)); - EXPECT_FLOAT_EQ(crop_result.region_score(), 1.0f); -} -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/kinematic_path_solver.cc b/examples/desktop/autoflip/quality/kinematic_path_solver.cc deleted file mode 100644 index 5d3c59a..0000000 --- a/examples/desktop/autoflip/quality/kinematic_path_solver.cc +++ /dev/null @@ -1,325 +0,0 @@ -#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h" - -constexpr float kMinVelocity = 0.5; - -namespace mediapipe { -namespace autoflip { - -namespace { -int Median(const std::deque>& positions_raw) { - std::deque positions; - for (const auto& position : positions_raw) { - positions.push_back(position.second); - } - - size_t n = positions.size() / 2; - nth_element(positions.begin(), positions.begin() + n, positions.end()); - return positions[n]; -} -} // namespace - -bool KinematicPathSolver::IsMotionTooSmall(double delta_degs) { - if (options_.has_min_motion_to_reframe()) { - return abs(delta_degs) < options_.min_motion_to_reframe(); - } else if (delta_degs > 0) { - return delta_degs < options_.min_motion_to_reframe_upper(); - } else { - return abs(delta_degs) < options_.min_motion_to_reframe_lower(); - } -} - -void KinematicPathSolver::ClearHistory() { raw_positions_at_time_.clear(); } - -absl::Status KinematicPathSolver::PredictMotionState(int position, - const uint64 time_us, - bool* state) { - if (!initialized_) { - *state = false; - return absl::OkStatus(); - } - - auto raw_positions_at_time_copy = raw_positions_at_time_; - - raw_positions_at_time_copy.push_front( - std::pair(time_us, position)); - while (raw_positions_at_time_copy.size() > 1) { - if (static_cast(raw_positions_at_time_copy.back().first) < - static_cast(time_us) - options_.filtering_time_window_us()) { - raw_positions_at_time_copy.pop_back(); - } else { - break; - } - } - - int filtered_position = Median(raw_positions_at_time_copy); - filtered_position = - std::clamp(filtered_position, min_location_, max_location_); - - double delta_degs = - (filtered_position - current_position_px_) / pixels_per_degree_; - - // If the motion is smaller than the min_motion_to_reframe and camera is - // stationary, don't use the update. - if (IsMotionTooSmall(delta_degs) && !motion_state_) { - *state = false; - } else if (abs(delta_degs) < options_.reframe_window() && motion_state_) { - // If the motion is smaller than the reframe_window and camera is moving, - // don't use the update. - *state = false; - } else if (prior_position_px_ == current_position_px_ && motion_state_) { - // Camera isn't actually moving. Likely face is past bounds. - *state = false; - } else { - // Apply new position, plus the reframe window size. - *state = true; - } - - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::AddObservation(int position, - const uint64 time_us) { - if (!initialized_) { - if (position < min_location_) { - current_position_px_ = min_location_; - } else if (position > max_location_) { - current_position_px_ = max_location_; - } else { - current_position_px_ = position; - } - target_position_px_ = position; - prior_position_px_ = current_position_px_; - motion_state_ = false; - mean_delta_t_ = -1; - raw_positions_at_time_.push_front( - std::pair(time_us, position)); - current_time_ = time_us; - initialized_ = true; - current_velocity_deg_per_s_ = 0; - RET_CHECK_GT(pixels_per_degree_, 0) - << "pixels_per_degree must be larger than 0."; - RET_CHECK_GE(options_.update_rate_seconds(), 0) - << "update_rate_seconds must be greater than 0."; - RET_CHECK_GE(options_.filtering_time_window_us(), 0) - << "update_rate_seconds must be greater than 0."; - RET_CHECK_GE(options_.mean_period_update_rate(), 0) - << "mean_period_update_rate must be greater than 0."; - RET_CHECK(options_.has_min_motion_to_reframe() ^ - (options_.has_min_motion_to_reframe_upper() && - options_.has_min_motion_to_reframe_lower())) - << "Must set min_motion_to_reframe or min_motion_to_reframe_upper and " - "min_motion_to_reframe_lower."; - if (options_.has_min_motion_to_reframe()) { - RET_CHECK_GE(options_.min_motion_to_reframe(), options_.reframe_window()) - << "Reframe window cannot exceed min_motion_to_reframe."; - } else { - RET_CHECK_GE(options_.min_motion_to_reframe_upper(), - options_.reframe_window()) - << "Reframe window cannot exceed min_motion_to_reframe."; - RET_CHECK_GE(options_.min_motion_to_reframe_lower(), - options_.reframe_window()) - << "Reframe window cannot exceed min_motion_to_reframe."; - } - RET_CHECK(options_.has_max_velocity() ^ - (options_.has_max_velocity_scale() && - options_.has_max_velocity_shift())) - << "Must either set max_velocity or set both max_velocity_scale and " - "max_velocity_shift."; - return absl::OkStatus(); - } - - RET_CHECK(current_time_ < time_us) - << "Observation added before a prior observations."; - - raw_positions_at_time_.push_front(std::pair(time_us, position)); - while (raw_positions_at_time_.size() > 1) { - if (static_cast(raw_positions_at_time_.back().first) < - static_cast(time_us) - options_.filtering_time_window_us()) { - raw_positions_at_time_.pop_back(); - } else { - break; - } - } - - int filtered_position = Median(raw_positions_at_time_); - - float min_reframe = (options_.has_min_motion_to_reframe() - ? options_.min_motion_to_reframe() - : options_.min_motion_to_reframe_lower()) * - pixels_per_degree_; - float max_reframe = (options_.has_min_motion_to_reframe() - ? options_.min_motion_to_reframe() - : options_.min_motion_to_reframe_upper()) * - pixels_per_degree_; - - filtered_position = fmax(min_location_ - min_reframe, filtered_position); - filtered_position = fmin(max_location_ + max_reframe, filtered_position); - - double delta_degs = - (filtered_position - current_position_px_) / pixels_per_degree_; - - double max_velocity = - options_.has_max_velocity() - ? options_.max_velocity() - : fmax(abs(delta_degs * options_.max_velocity_scale()) + - options_.max_velocity_shift(), - kMinVelocity); - - // If the motion is smaller than the min_motion_to_reframe and camera is - // stationary, don't use the update. - if (IsMotionTooSmall(delta_degs) && !motion_state_) { - delta_degs = 0; - motion_state_ = false; - } else if (abs(delta_degs) < options_.reframe_window() && motion_state_) { - // If the motion is smaller than the reframe_window and camera is moving, - // don't use the update. - delta_degs = 0; - motion_state_ = false; - } else if (delta_degs > 0) { - // Apply new position, less the reframe window size. - target_position_px_ = - filtered_position - pixels_per_degree_ * options_.reframe_window(); - delta_degs = - (target_position_px_ - current_position_px_) / pixels_per_degree_; - motion_state_ = true; - } else { - // Apply new position, plus the reframe window size. - target_position_px_ = - filtered_position + pixels_per_degree_ * options_.reframe_window(); - delta_degs = - (target_position_px_ - current_position_px_) / pixels_per_degree_; - motion_state_ = true; - } - - // Time and position updates. - double delta_t_sec = (time_us - current_time_) / 1000000.0; - if (options_.max_delta_time_sec() > 0) { - // If updates are very infrequent, then limit the max time difference. - delta_t_sec = fmin(delta_t_sec, options_.max_delta_time_sec()); - } - // Time since last state/prediction update, smoothed by - // mean_period_update_rate. - if (mean_delta_t_ < 0) { - mean_delta_t_ = delta_t_sec; - } else { - mean_delta_t_ = mean_delta_t_ * (1 - options_.mean_period_update_rate()) + - delta_t_sec * options_.mean_period_update_rate(); - } - - // Observed velocity and then weighted update of this velocity (deg/sec). - double observed_velocity = delta_degs / delta_t_sec; - double update_rate = std::min(mean_delta_t_ / options_.update_rate_seconds(), - options_.max_update_rate()); - double updated_velocity = current_velocity_deg_per_s_ * (1 - update_rate) + - observed_velocity * update_rate; - current_velocity_deg_per_s_ = updated_velocity > 0 - ? fmin(updated_velocity, max_velocity) - : fmax(updated_velocity, -max_velocity); - - // Update prediction based on time input. - return UpdatePrediction(time_us); -} - -absl::Status KinematicPathSolver::UpdatePrediction(const int64 time_us) { - RET_CHECK(current_time_ < time_us) - << "Prediction time added before a prior observation or prediction."; - - // Store prior pixel location. - prior_position_px_ = current_position_px_; - - // Position update limited by min/max. - double update_position_px = - current_position_px_ + - current_velocity_deg_per_s_ * mean_delta_t_ * pixels_per_degree_; - - if (update_position_px < min_location_) { - current_position_px_ = min_location_; - current_velocity_deg_per_s_ = 0; - motion_state_ = false; - } else if (update_position_px > max_location_) { - current_position_px_ = max_location_; - current_velocity_deg_per_s_ = 0; - motion_state_ = false; - } else { - current_position_px_ = update_position_px; - } - current_time_ = time_us; - - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::GetState(int* position) { - RET_CHECK(initialized_) << "GetState called before first observation added."; - *position = round(current_position_px_); - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::GetState(float* position) { - RET_CHECK(initialized_) << "GetState called before first observation added."; - *position = current_position_px_; - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::GetDeltaState(float* delta_position) { - RET_CHECK(initialized_) << "GetState called before first observation added."; - *delta_position = current_position_px_ - prior_position_px_; - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::SetState(const float position) { - RET_CHECK(initialized_) << "SetState called before first observation added."; - current_position_px_ = std::clamp(position, static_cast(min_location_), - static_cast(max_location_)); - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::GetTargetPosition(int* target_position) { - RET_CHECK(initialized_) - << "GetTargetPosition called before first observation added."; - - // Provide target position clamped by min/max locations. - if (target_position_px_ < min_location_) { - *target_position = min_location_; - } else if (target_position_px_ > max_location_) { - *target_position = max_location_; - } else { - *target_position = round(target_position_px_); - } - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::UpdatePixelsPerDegree( - const float pixels_per_degree) { - RET_CHECK_GT(pixels_per_degree, 0) - << "pixels_per_degree must be larger than 0."; - pixels_per_degree_ = pixels_per_degree; - return absl::OkStatus(); -} - -absl::Status KinematicPathSolver::UpdateMinMaxLocation(const int min_location, - const int max_location) { - if (!initialized_) { - max_location_ = max_location; - min_location_ = min_location; - return absl::OkStatus(); - } - - double prior_distance = max_location_ - min_location_; - double updated_distance = max_location - min_location; - double scale_change = updated_distance / prior_distance; - current_position_px_ = current_position_px_ * scale_change; - prior_position_px_ = prior_position_px_ * scale_change; - target_position_px_ = target_position_px_ * scale_change; - max_location_ = max_location; - min_location_ = min_location; - auto original_positions_at_time = raw_positions_at_time_; - raw_positions_at_time_.clear(); - for (auto position_at_time : original_positions_at_time) { - position_at_time.second = position_at_time.second * scale_change; - raw_positions_at_time_.push_front(position_at_time); - } - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/kinematic_path_solver.h b/examples/desktop/autoflip/quality/kinematic_path_solver.h deleted file mode 100644 index 76c13d9..0000000 --- a/examples/desktop/autoflip/quality/kinematic_path_solver.h +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UNIFORM_ACCELERATION_PATH_SOLVER_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UNIFORM_ACCELERATION_PATH_SOLVER_H_ - -#include - -#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.pb.h" -#include "mediapipe/framework/port/integral_types.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// Kinematic path solver class is a stateful 1d position estimator based loosely -// on a differential kalman filter that is specifically designed to control a -// camera. It utilizes a Kalman filters predict/update interface for estimating -// the best camera focus position and updating that estimate when a measurement -// is available. Tuning controls include: update_rate: how much to update the -// existing state with a new state. max_velocity: max speed of the state per -// second. min_motion_to_reframe: only updating the state if a measurement -// exceeds this threshold. -class KinematicPathSolver { - public: - KinematicPathSolver(const KinematicOptions& options, const int min_location, - const int max_location, float pixels_per_degree) - : options_(options), - min_location_(min_location), - max_location_(max_location), - initialized_(false), - pixels_per_degree_(pixels_per_degree) {} - // Add an observation (detection) at a position and time. - absl::Status AddObservation(int position, const uint64 time_us); - // Get the predicted position at a time. - absl::Status UpdatePrediction(const int64 time_us); - // Get the state at a time, as an int. - absl::Status GetState(int* position); - // Get the state at a time, as a float. - absl::Status GetState(float* position); - // Overwrite the current state value. - absl::Status SetState(const float position); - // Update PixelPerDegree value. - absl::Status UpdatePixelsPerDegree(const float pixels_per_degree); - // Provide the current target position of the reframe action. - absl::Status GetTargetPosition(int* target_position); - // Change min/max location and update state based on new scaling. - absl::Status UpdateMinMaxLocation(const int min_location, - const int max_location); - // Check if motion is within the reframe window, return false if not. - bool IsMotionTooSmall(double delta_degs); - // Check if a position measurement will cause the camera to be in motion - // without updating the internal state. - absl::Status PredictMotionState(int position, const uint64 time_us, - bool* state); - // Clear any history buffer of positions that are used when - // filtering_time_window_us is set to a non-zero value. - void ClearHistory(); - // Provides the change in position from last state. - absl::Status GetDeltaState(float* delta_position); - - bool IsInitialized() { return initialized_; } - - private: - // Tuning options. - KinematicOptions options_; - // Min and max value the state can be. - int min_location_; - int max_location_; - bool initialized_; - float pixels_per_degree_; - // Current state values. - double current_position_px_; - double prior_position_px_; - double current_velocity_deg_per_s_; - uint64 current_time_ = 0; - // History of observations (second) and their time (first). - std::deque> raw_positions_at_time_; - // Current target position. - double target_position_px_; - // Defines if the camera is moving to a target (true) or reached a target - // within a tolerance (false). - bool motion_state_; - // Average period of incoming frames. - double mean_delta_t_; -}; - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UNIFORM_ACCELERATION_PATH_SOLVER_H_ diff --git a/examples/desktop/autoflip/quality/kinematic_path_solver.proto b/examples/desktop/autoflip/quality/kinematic_path_solver.proto deleted file mode 100644 index 61b48b6..0000000 --- a/examples/desktop/autoflip/quality/kinematic_path_solver.proto +++ /dev/null @@ -1,45 +0,0 @@ -syntax = "proto2"; - -package mediapipe.autoflip; - -message KinematicOptions { - // Weighted update of new camera velocity (measurement) vs current state - // (prediction). - optional double update_rate = 1 [default = 0.5, deprecated = true]; - // Max velocity (degrees per second) that the camera can move. Cannot be used - // with max_velocity_scale or max_velocity_shift. - optional double max_velocity = 2; - // Min motion (in degrees) to react for both upper and lower directions. Must - // not be set if using min_motion_to_reframe_lower and - // min_motion_to_reframe_upper. - optional float min_motion_to_reframe = 3; - // Min motion (in degrees) for upper and lower direction to react. Both must - // be set and min_motion_to_reframe cannot be set if these are specified. - optional float min_motion_to_reframe_lower = 9; - optional float min_motion_to_reframe_upper = 10; - // When motion exceeds min_motion_to_reframe, move within this distance of the - // camera from the starting direction. Setting this value non-zero reduces - // total reframe distance on average. Value cannot exceed - // min_motion_to_reframe value. - optional float reframe_window = 4 [default = 0]; - // Calculation of internal velocity state is: - // min((delta_time_s / update_rate_seconds), max_update_rate) - // where delta_time_s is the time since the last frame. - optional double update_rate_seconds = 5 [default = 0.20]; - optional double max_update_rate = 6 [default = 0.8]; - // History time window of observations to be median filtered. - optional int64 filtering_time_window_us = 7 [default = 0]; - // Weighted update of average period, used for motion updates. - optional float mean_period_update_rate = 8 [default = 0.25]; - // When set, caps the maximum time difference (seconds) calculated between new - // updates/observations. Useful when updates come very infrequently. - optional double max_delta_time_sec = 13; - // Scale factor for max velocity, to be multiplied by the distance from center - // in degrees. Cannot be used with max_velocity and must be used with - // max_velocity_shift. - optional float max_velocity_scale = 11; - // Shift factor for max velocity, to be added to the scaled distance from - // center in degrees. Cannot be used with max_velocity and must be used with - // max_velocity_scale. - optional float max_velocity_shift = 12; -} diff --git a/examples/desktop/autoflip/quality/kinematic_path_solver_test.cc b/examples/desktop/autoflip/quality/kinematic_path_solver_test.cc deleted file mode 100644 index cb7ec5c..0000000 --- a/examples/desktop/autoflip/quality/kinematic_path_solver_test.cc +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h" - -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/integral_types.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -constexpr int64 kMicroSecInSec = 1000000; -constexpr float kWidthFieldOfView = 60; - -namespace mediapipe { -namespace autoflip { -namespace { - -TEST(KinematicPathSolverTest, FailZeroPixelsPerDegree) { - KinematicOptions options; - KinematicPathSolver solver(options, 0, 1000, 0); - EXPECT_FALSE(solver.AddObservation(500, kMicroSecInSec * 0).ok()); -} - -TEST(KinematicPathSolverTest, FailNotInitializedState) { - KinematicOptions options; - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - EXPECT_FALSE(solver.GetState(&state).ok()); -} - -TEST(KinematicPathSolverTest, FailNotInitializedPrediction) { - KinematicOptions options; - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - int64 timestamp = 0; - EXPECT_FALSE(solver.UpdatePrediction(timestamp).ok()); -} - -TEST(KinematicPathSolverTest, PassNotEnoughMotionLargeImg) { - KinematicOptions options; - // Set min motion to 2deg - options.set_min_motion_to_reframe(2.0); - options.set_update_rate(1); - options.set_max_velocity(1000); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to not move. - EXPECT_FLOAT_EQ(state, 500); -} - -TEST(KinematicPathSolverTest, PassNotEnoughMotionSmallImg) { - KinematicOptions options; - // Set min motion to 2deg - options.set_min_motion_to_reframe(2.0); - options.set_update_rate(1); - options.set_max_velocity(500); - // Set degrees / pixel to 8.3 - KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0)); - // Move target by 10px / 8.3 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to not move. - EXPECT_FLOAT_EQ(state, 400); -} - -TEST(KinematicPathSolverTest, PassEnoughMotionFiltered) { - KinematicOptions options; - // Set min motion to 2deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1); - options.set_max_velocity(1000); - options.set_filtering_time_window_us(3000000); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 2)); - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 3)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to not move. - EXPECT_FLOAT_EQ(state, 500); -} - -TEST(KinematicPathSolverTest, PassEnoughMotionNotFiltered) { - KinematicOptions options; - // Set min motion to 2deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1); - options.set_max_velocity(1000); - options.set_filtering_time_window_us(0); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 2)); - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 3)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to not move. - EXPECT_FLOAT_EQ(state, 506.4); -} - -TEST(KinematicPathSolverTest, PassEnoughMotionLargeImg) { - KinematicOptions options; - // Set min motion to 1deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(.0000001); - options.set_max_update_rate(1.0); - options.set_max_velocity(1000); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to move. - EXPECT_FLOAT_EQ(state, 520); -} - -TEST(KinematicPathSolverTest, PassEnoughMotionSmallImg) { - KinematicOptions options; - // Set min motion to 2deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(.0000001); - options.set_max_update_rate(1.0); - options.set_max_velocity(18); - // Set degrees / pixel to 8.3 - KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0)); - // Move target by 10px / 8.3 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to move. - EXPECT_FLOAT_EQ(state, 410); -} - -TEST(KinematicPathSolverTest, FailReframeWindowSetting) { - KinematicOptions options; - // Set min motion to 1deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1); - options.set_max_velocity(1000); - // Set reframe window size to .75 for test. - options.set_reframe_window(1.1); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - ASSERT_FALSE(solver.AddObservation(500, kMicroSecInSec * 0).ok()); -} - -TEST(KinematicPathSolverTest, PassReframeWindow) { - KinematicOptions options; - // Set min motion to 1deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(.0000001); - options.set_max_update_rate(1.0); - options.set_max_velocity(1000); - // Set reframe window size to .75 for test. - options.set_reframe_window(0.75); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to move 1.2-.75 deg, * 16.6 = 7.47px + 500 = - EXPECT_FLOAT_EQ(state, 507.5); -} - -TEST(KinematicPathSolverTest, PassReframeWindowLowerUpper) { - KinematicOptions options; - // Set min motion to 1deg - options.set_min_motion_to_reframe_upper(1.3); - options.set_min_motion_to_reframe_lower(1.0); - options.set_update_rate_seconds(.0000001); - options.set_max_update_rate(1.0); - options.set_max_velocity(1000); - // Set reframe window size to .75 for test. - options.set_reframe_window(0.75); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to not move - EXPECT_FLOAT_EQ(state, 500); - MP_ASSERT_OK(solver.AddObservation(480, kMicroSecInSec * 2)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to move - EXPECT_FLOAT_EQ(state, 492.5); -} - -TEST(KinematicPathSolverTest, PassCheckState) { - KinematicOptions options; - // Set min motion to 1deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(.0000001); - options.set_max_update_rate(1.0); - options.set_max_velocity(1000); - // Set reframe window size to .75 for test. - options.set_reframe_window(0.75); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - bool motion_state; - MP_ASSERT_OK( - solver.PredictMotionState(520, kMicroSecInSec * 1, &motion_state)); - EXPECT_TRUE(motion_state); -} - -TEST(KinematicPathSolverTest, PassUpdateRate30FPS) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(.25); - options.set_max_update_rate(0.8); - options.set_max_velocity(18); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1 / 30)); - MP_ASSERT_OK(solver.GetState(&state)); - // (0.033 / .25) * 20 = - EXPECT_FLOAT_EQ(state, 502.6667); -} - -TEST(KinematicPathSolverTest, PassUpdateRate10FPS) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(.25); - options.set_max_update_rate(0.8); - options.set_max_velocity(18); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1 / 10)); - MP_ASSERT_OK(solver.GetState(&state)); - // (0.1 / .25) * 20 = - EXPECT_FLOAT_EQ(state, 508); -} - -TEST(KinematicPathSolverTest, PassUpdateRate) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(4); - options.set_max_update_rate(1.0); - options.set_max_velocity(18); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - int target_position; - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.GetTargetPosition(&target_position)); - EXPECT_EQ(target_position, 500); - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetTargetPosition(&target_position)); - EXPECT_EQ(target_position, 520); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 505); -} - -TEST(KinematicPathSolverTest, PassUpdateRateResolutionChange) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(4); - options.set_max_update_rate(1.0); - options.set_max_velocity(18); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - int target_position; - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.GetTargetPosition(&target_position)); - EXPECT_EQ(target_position, 500); - MP_ASSERT_OK(solver.UpdateMinMaxLocation(0, 500)); - MP_ASSERT_OK(solver.UpdatePixelsPerDegree(500.0 / kWidthFieldOfView)); - MP_ASSERT_OK(solver.AddObservation(520 * 0.5, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetTargetPosition(&target_position)); - EXPECT_EQ(target_position, 520 * 0.5); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 252.5); -} - -TEST(KinematicPathSolverTest, PassMaxVelocityInt) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1.0); - options.set_max_velocity(6); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - int state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.AddObservation(1000, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_EQ(state, 600); -} - -TEST(KinematicPathSolverTest, PassMaxVelocity) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1.0); - options.set_max_velocity(6); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.AddObservation(1000, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 600); -} - -TEST(KinematicPathSolverTest, PassMaxVelocityScale) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1.0); - options.set_max_velocity_scale(0.4); - options.set_max_velocity_shift(-2.0); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.AddObservation(1000, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 666.6667); -} - -TEST(KinematicPathSolverTest, PassDegPerPxChange) { - KinematicOptions options; - // Set min motion to 2deg - options.set_min_motion_to_reframe(2.0); - options.set_update_rate(1); - options.set_max_velocity(1000); - // Set degrees / pixel to 16.6 - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - // Move target by 20px / 16.6 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to not move. - EXPECT_FLOAT_EQ(state, 500); - MP_ASSERT_OK(solver.UpdatePixelsPerDegree(500.0 / kWidthFieldOfView)); - MP_ASSERT_OK(solver.AddObservation(520, kMicroSecInSec * 2)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to move. - EXPECT_FLOAT_EQ(state, 516); -} - -TEST(KinematicPathSolverTest, NoTimestampSmoothing) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1.0); - options.set_max_velocity(6); - options.set_mean_period_update_rate(1.0); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, 0)); - MP_ASSERT_OK(solver.AddObservation(1000, 1000000)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 600); - MP_ASSERT_OK(solver.AddObservation(1000, 2200000)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 720); -} - -TEST(KinematicPathSolverTest, TimestampSmoothing) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_update_rate(1.0); - options.set_max_velocity(6); - options.set_mean_period_update_rate(0.05); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(500, 0)); - MP_ASSERT_OK(solver.AddObservation(1000, 1000000)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 600); - MP_ASSERT_OK(solver.AddObservation(1000, 2200000)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 701); -} - -TEST(KinematicPathSolverTest, PassSetPosition) { - KinematicOptions options; - // Set min motion to 2deg - options.set_min_motion_to_reframe(1.0); - options.set_update_rate_seconds(.0000001); - options.set_max_update_rate(1.0); - options.set_max_velocity(18); - // Set degrees / pixel to 8.3 - KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0)); - // Move target by 10px / 8.3 = 1.2deg - MP_ASSERT_OK(solver.AddObservation(410, kMicroSecInSec * 1)); - MP_ASSERT_OK(solver.GetState(&state)); - // Expect cam to move. - EXPECT_FLOAT_EQ(state, 410); - MP_ASSERT_OK(solver.SetState(400)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 400); - // Expect to stay in bounds. - MP_ASSERT_OK(solver.SetState(600)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 500); - MP_ASSERT_OK(solver.SetState(-100)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 0); -} -TEST(KinematicPathSolverTest, PassBorderTest) { - KinematicOptions options; - options.set_min_motion_to_reframe(1.0); - options.set_max_update_rate(0.25); - options.set_max_velocity_scale(0.5); - options.set_max_velocity_shift(-1.0); - - KinematicPathSolver solver(options, 0, 500, 500.0 / kWidthFieldOfView); - float state; - MP_ASSERT_OK(solver.AddObservation(400, kMicroSecInSec * 0)); - MP_ASSERT_OK(solver.AddObservation(800, kMicroSecInSec * 0.1)); - MP_ASSERT_OK(solver.GetState(&state)); - EXPECT_FLOAT_EQ(state, 404.56668); -} - -TEST(KinematicPathSolverTest, PassUpdateUpdateMinMaxLocationIfUninitialized) { - KinematicOptions options; - options.set_min_motion_to_reframe(2.0); - options.set_max_velocity(1000); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - MP_EXPECT_OK(solver.UpdateMinMaxLocation(0, 500)); -} - -TEST(KinematicPathSolverTest, PassUpdateUpdateMinMaxLocationIfInitialized) { - KinematicOptions options; - options.set_min_motion_to_reframe(2.0); - options.set_max_velocity(1000); - KinematicPathSolver solver(options, 0, 1000, 1000.0 / kWidthFieldOfView); - MP_ASSERT_OK(solver.AddObservation(500, kMicroSecInSec * 0)); - MP_EXPECT_OK(solver.UpdateMinMaxLocation(0, 500)); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/math_utils.h b/examples/desktop/autoflip/quality/math_utils.h deleted file mode 100644 index bac9901..0000000 --- a/examples/desktop/autoflip/quality/math_utils.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_MATH_UTILS_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_MATH_UTILS_H_ - -class MathUtil { - public: - // Clamps value to the range [low, high]. Requires low <= high. Returns false - // if this check fails, otherwise returns true. Caller should first check the - // returned boolean. - template // T models LessThanComparable. - static bool Clamp(const T& low, const T& high, const T& value, T* result) { - // Prevents errors in ordering the arguments. - if (low > high) { - return false; - } - if (high < value) { - *result = high; - } else if (value < low) { - *result = low; - } else { - *result = value; - } - return true; - } -}; - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_MATH_UTILS_H_ diff --git a/examples/desktop/autoflip/quality/padding_effect_generator.cc b/examples/desktop/autoflip/quality/padding_effect_generator.cc deleted file mode 100644 index 3d489c3..0000000 --- a/examples/desktop/autoflip/quality/padding_effect_generator.cc +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/padding_effect_generator.h" - -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" - -namespace mediapipe { -namespace autoflip { - -PaddingEffectGenerator::PaddingEffectGenerator(const int input_width, - const int input_height, - const double target_aspect_ratio, - bool scale_to_multiple_of_two) { - target_aspect_ratio_ = target_aspect_ratio; - const double input_aspect_ratio = - static_cast(input_width) / static_cast(input_height); - input_width_ = input_width; - input_height_ = input_height; - is_vertical_padding_ = input_aspect_ratio > target_aspect_ratio; - output_width_ = is_vertical_padding_ - ? std::round(target_aspect_ratio * input_height) - : input_width; - output_height_ = is_vertical_padding_ - ? input_height - : std::round(input_width / target_aspect_ratio); - if (scale_to_multiple_of_two) { - output_width_ = output_width_ / 2 * 2; - output_height_ = output_height_ / 2 * 2; - } -} - -absl::Status PaddingEffectGenerator::Process( - const ImageFrame& input_frame, const float background_contrast, - const int blur_cv_size, const float overlay_opacity, - ImageFrame* output_frame, const cv::Scalar* background_color_in_rgb) { - RET_CHECK_EQ(input_frame.Width(), input_width_); - RET_CHECK_EQ(input_frame.Height(), input_height_); - RET_CHECK(output_frame); - - cv::Mat original_image = formats::MatView(&input_frame); - // This is the canvas that we are going to draw the padding effect on to. - cv::Mat canvas(output_height_, output_width_, original_image.type()); - - const int effective_input_width = - is_vertical_padding_ ? input_width_ : input_height_; - const int effective_input_height = - is_vertical_padding_ ? input_height_ : input_width_; - const int effective_output_width = - is_vertical_padding_ ? output_width_ : output_height_; - const int effective_output_height = - is_vertical_padding_ ? output_height_ : output_width_; - - if (!is_vertical_padding_) { - original_image = original_image.t(); - canvas = canvas.t(); - } - - const int foreground_height = - effective_input_height * effective_output_width / effective_input_width; - int x = -1, y = -1, width = -1, height = -1; - - // The following steps does the padding operation, with several steps. - // #1, we prepare the background. If a solid background color is given, we use - // it directly. Otherwise, we first crop a region of size "output_width_ * - // output_height_" off of the original frame to become the background of - // the final frame, and then we blur it and adjust contrast and opacity. - if (background_color_in_rgb != nullptr) { - canvas = *background_color_in_rgb; - } else { - // Copy the original image to the background. - x = 0.5 * (effective_input_width - effective_output_width); - y = 0; - width = effective_output_width; - height = effective_output_height; - cv::Rect crop_window_for_background(x, y, width, height); - original_image(crop_window_for_background).copyTo(canvas); - - // Blur. - const int cv_size = - blur_cv_size % 2 == 1 ? blur_cv_size : (blur_cv_size + 1); - const cv::Size kernel(cv_size, cv_size); - // TODO: the larger the kernel size, the slower the blurring - // operation is. Consider running multiple sequential blurs with smaller - // sizes to simulate the effect of using a large size. This might be able to - // speed up the process. - x = 0; - width = effective_output_width; - const cv::Rect canvas_rect(0, 0, canvas.cols, canvas.rows); - // Blur the top region (above foreground). - y = 0; - height = (effective_output_height - foreground_height) / 2 + cv_size; - const cv::Rect top_blur_region = - cv::Rect(x, y, width, height) & canvas_rect; - if (top_blur_region.area() > 0) { - cv::Mat top_blurred = canvas(top_blur_region); - cv::GaussianBlur(top_blurred, top_blurred, kernel, 0, 0); - } - // Blur the bottom region (below foreground). - y = height + foreground_height - cv_size; - height = effective_output_height - y; - const cv::Rect bottom_blur_region = - cv::Rect(x, y, width, height) & canvas_rect; - if (bottom_blur_region.area() > 0) { - cv::Mat bottom_blurred = canvas(bottom_blur_region); - cv::GaussianBlur(bottom_blurred, bottom_blurred, kernel, 0, 0); - } - - const float kEqualThreshold = 0.0001f; - // Background contrast adjustment. - if (std::abs(background_contrast - 1.0f) > kEqualThreshold) { - canvas *= background_contrast; - } - - // Alpha blend a translucent black layer. - if (std::abs(overlay_opacity - 0.0f) > kEqualThreshold) { - cv::Mat overlay = cv::Mat::zeros(canvas.size(), canvas.type()); - cv::addWeighted(overlay, overlay_opacity, canvas, 1 - overlay_opacity, 0, - canvas); - } - } - - // #2, we crop the entire region off of the original frame. This will become - // the foreground in the final frame. - x = 0; - y = 0; - width = effective_input_width; - height = effective_input_height; - - cv::Rect crop_window_for_foreground(x, y, width, height); - - // #3, we specify a region of size computed as below in the final frame to - // embed the foreground that we obtained in #2. The aspect ratio of - // this region should be the same as the foreground, but with a - // smaller size. Therefore, the height and width are derived using - // the ratio of the sizes. - // - embed size: output_width_ * height (to be computed) - // - foreground: input_width * input_height - // - // The location of this region is horizontally centralized in the - // frame, and saturated in horizontal dimension. - x = 0; - y = (effective_output_height - foreground_height) / 2; - width = effective_output_width; - height = foreground_height; - - cv::Rect region_to_embed_foreground(x, y, width, height); - cv::Mat dst = canvas(region_to_embed_foreground); - cv::resize(original_image(crop_window_for_foreground), dst, dst.size()); - - if (!is_vertical_padding_) { - canvas = canvas.t(); - } - - output_frame->CopyPixelData(input_frame.Format(), canvas.cols, canvas.rows, - canvas.data, - ImageFrame::kDefaultAlignmentBoundary); - return absl::OkStatus(); -} - -cv::Rect PaddingEffectGenerator::ComputeOutputLocation() { - const int effective_input_width = - is_vertical_padding_ ? input_width_ : input_height_; - const int effective_input_height = - is_vertical_padding_ ? input_height_ : input_width_; - const int effective_output_width = - is_vertical_padding_ ? output_width_ : output_height_; - const int effective_output_height = - is_vertical_padding_ ? output_height_ : output_width_; - - // Step 3 from "process" call above, compute foreground location. - const int foreground_height = - effective_input_height * effective_output_width / effective_input_width; - const int x = 0; - const int y = (effective_output_height - foreground_height) / 2; - const int width = effective_output_width; - const int height = foreground_height; - - cv::Rect region_to_embed_foreground(x, y, width, height); - - return region_to_embed_foreground; -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/padding_effect_generator.h b/examples/desktop/autoflip/quality/padding_effect_generator.h deleted file mode 100644 index 2d33593..0000000 --- a/examples/desktop/autoflip/quality/padding_effect_generator.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_PADDING_EFFECT_GENERATOR_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_PADDING_EFFECT_GENERATOR_H_ - -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// Generates padding effects given input frames. Depending on where the padded -// contents are added, there are two cases: -// 1) Pad on the top and bottom of the input frame, aka vertical padding, i.e. -// input_aspect_ratio > target_aspect_ratio. In this case, output frames will -// have the same height as input frames, and the width will be adjusted to -// match the target aspect ratio. -// 2) Pad on the left and right of the input frame, aka horizontal padding, i.e. -// input_aspect_ratio < target_aspect_ratio. In this case, output frames will -// have the same width as original frames, and the height will be adjusted to -// match the target aspect ratio. -// If a background color is given, the background of the output frame will be -// filled with this solid color; otherwise, it is a blurred version of the input -// frame. -// -// Note: in both horizontal and vertical padding effects, the output frame size -// will be at most as large as the input frame size, with one dimension the -// same as the input (horizontal padding: width, vertical padding: height). If -// you intented to have the output frame be larger, you could add a -// ScaleImageCalculator as an upstream node before calling this calculator in -// your MediaPipe graph (not as a downstream node, because visual details may -// lose after appling the padding effect). -class PaddingEffectGenerator { - public: - // Always outputs width and height that are divisible by 2 if - // scale_to_multiple_of_two is set to true. - PaddingEffectGenerator(const int input_width, const int input_height, - const double target_aspect_ratio, - bool scale_to_multiple_of_two = false); - - // Apply the padding effect on the input frame. - // - blur_cv_size: The cv::Size() parameter used in creating blurry effects - // for padding backgrounds. - // - background_contrast: Contrast adjustment for padding background. This - // value should between 0 and 1, and the smaller the value, the darker the - // background. - // - overlay_opacity: In addition to adjusting the contrast, a translucent - // black layer will be alpha blended with the background. This value defines - // the opacity of the black layer. - // - background_color_in_rgb: If not null, uses this solid color as background - // instead of blurring the image, and does not adjust contrast or opacity. - absl::Status Process(const ImageFrame& input_frame, - const float background_contrast, const int blur_cv_size, - const float overlay_opacity, ImageFrame* output_frame, - const cv::Scalar* background_color_in_rgb = nullptr); - - // Compute the "render location" on the output frame where the "crop from" - // location is to be placed. For use with external rendering soutions. - cv::Rect ComputeOutputLocation(); - - private: - double target_aspect_ratio_; - int input_width_ = -1; - int input_height_ = -1; - int output_width_ = -1; - int output_height_ = -1; - bool is_vertical_padding_; -}; - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_PADDING_EFFECT_GENERATOR_H_ diff --git a/examples/desktop/autoflip/quality/padding_effect_generator_test.cc b/examples/desktop/autoflip/quality/padding_effect_generator_test.cc deleted file mode 100644 index 787baa3..0000000 --- a/examples/desktop/autoflip/quality/padding_effect_generator_test.cc +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/padding_effect_generator.h" - -#include "absl/flags/flag.h" -#include "absl/strings/str_cat.h" -#include "mediapipe/framework/deps/file_path.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/file_helpers.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_imgcodecs_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status_builder.h" -#include "mediapipe/framework/port/status_matchers.h" - -ABSL_FLAG(std::string, input_image, "", "The path to an input image."); -ABSL_FLAG(std::string, output_folder, "", - "The folder to output test result images."); - -namespace mediapipe { -namespace autoflip { -namespace { - -// An 320x180 RGB test image. -constexpr char kTestImage[] = - "mediapipe/examples/desktop/autoflip/quality/testdata/" - "google.jpg"; -constexpr char kResultImagePrefix[] = - "mediapipe/examples/desktop/autoflip/quality/testdata/" - "result_"; - -const cv::Scalar kRed = cv::Scalar(255, 0, 0); - -void TestWithAspectRatio(const double aspect_ratio, - const cv::Scalar* background_color_in_rgb = nullptr) { - std::string test_image; - const bool process_arbitrary_image = - !absl::GetFlag(FLAGS_input_image).empty(); - if (!process_arbitrary_image) { - std::string test_image_path = mediapipe::file::JoinPath("./", kTestImage); - MP_ASSERT_OK(mediapipe::file::GetContents(test_image_path, &test_image)); - } else { - MP_ASSERT_OK(mediapipe::file::GetContents(absl::GetFlag(FLAGS_input_image), - &test_image)); - } - - const std::vector contents_vector(test_image.begin(), test_image.end()); - cv::Mat decoded_mat = - cv::imdecode(contents_vector, -1 /* return the loaded image as-is */); - - ImageFormat::Format image_format = ImageFormat::UNKNOWN; - cv::Mat output_mat; - switch (decoded_mat.channels()) { - case 1: - image_format = ImageFormat::GRAY8; - output_mat = decoded_mat; - break; - case 3: - image_format = ImageFormat::SRGB; - cv::cvtColor(decoded_mat, output_mat, cv::COLOR_BGR2RGB); - break; - case 4: - MP_ASSERT_OK(mediapipe::UnimplementedErrorBuilder(MEDIAPIPE_LOC) - << "4-channel image isn't supported yet"); - break; - default: - MP_ASSERT_OK(mediapipe::FailedPreconditionErrorBuilder(MEDIAPIPE_LOC) - << "Unsupported number of channels: " - << decoded_mat.channels()); - } - std::unique_ptr test_frame = absl::make_unique( - image_format, decoded_mat.size().width, decoded_mat.size().height); - output_mat.copyTo(formats::MatView(test_frame.get())); - - PaddingEffectGenerator generator(test_frame->Width(), test_frame->Height(), - aspect_ratio); - ImageFrame result_frame; - MP_ASSERT_OK(generator.Process(*test_frame, 0.3, 40, 0.0, &result_frame, - background_color_in_rgb)); - cv::Mat original_mat = formats::MatView(&result_frame); - cv::Mat input_mat; - switch (original_mat.channels()) { - case 1: - input_mat = original_mat; - break; - case 3: - // OpenCV assumes the image to be BGR order. To use imencode(), do color - // conversion first. - cv::cvtColor(original_mat, input_mat, cv::COLOR_RGB2BGR); - break; - case 4: - MP_ASSERT_OK(mediapipe::UnimplementedErrorBuilder(MEDIAPIPE_LOC) - << "4-channel image isn't supported yet"); - break; - default: - MP_ASSERT_OK(mediapipe::FailedPreconditionErrorBuilder(MEDIAPIPE_LOC) - << "Unsupported number of channels: " - << original_mat.channels()); - } - - std::vector parameters; - parameters.push_back(cv::IMWRITE_JPEG_QUALITY); - constexpr int kEncodingQuality = 75; - parameters.push_back(kEncodingQuality); - - std::vector encode_buffer; - // Note that imencode() will store the data in RGB order. - // Check its JpegEncoder::write() in "imgcodecs/src/grfmt_jpeg.cpp" for more - // info. - if (!cv::imencode(".jpg", input_mat, encode_buffer, parameters)) { - MP_ASSERT_OK(mediapipe::InternalErrorBuilder(MEDIAPIPE_LOC) - << "Fail to encode the image to be jpeg format."); - } - - std::string output_string(absl::string_view( - reinterpret_cast(&encode_buffer[0]), encode_buffer.size())); - - if (!process_arbitrary_image) { - std::string result_string_path = mediapipe::file::JoinPath( - "./", absl::StrCat(kResultImagePrefix, aspect_ratio, - background_color_in_rgb ? "_solid_background" : "", - ".jpg")); - std::string result_image; - MP_ASSERT_OK( - mediapipe::file::GetContents(result_string_path, &result_image)); - EXPECT_EQ(result_image, output_string); - } else { - std::string output_string_path = mediapipe::file::JoinPath( - absl::GetFlag(FLAGS_output_folder), - absl::StrCat("result_", aspect_ratio, - background_color_in_rgb ? "_solid_background" : "", - ".jpg")); - MP_ASSERT_OK( - mediapipe::file::SetContents(output_string_path, output_string)); - } -} - -TEST(PaddingEffectGeneratorTest, Success) { - TestWithAspectRatio(0.3); - TestWithAspectRatio(0.6); - TestWithAspectRatio(1.0); - TestWithAspectRatio(1.6); - TestWithAspectRatio(2.5); - TestWithAspectRatio(3.4); -} - -TEST(PaddingEffectGeneratorTest, SuccessWithBackgroundColor) { - TestWithAspectRatio(0.3, &kRed); - TestWithAspectRatio(0.6, &kRed); - TestWithAspectRatio(1.0, &kRed); - TestWithAspectRatio(1.6, &kRed); - TestWithAspectRatio(2.5, &kRed); - TestWithAspectRatio(3.4, &kRed); -} - -TEST(PaddingEffectGeneratorTest, ScaleToMultipleOfTwo) { - int input_width = 30; - int input_height = 30; - double target_aspect_ratio = 0.5; - int expect_width = 14; - int expect_height = input_height; - auto test_frame = absl::make_unique(/*format=*/ImageFormat::SRGB, - input_width, input_height); - - PaddingEffectGenerator generator(test_frame->Width(), test_frame->Height(), - target_aspect_ratio, - /*scale_to_multiple_of_two=*/true); - ImageFrame result_frame; - MP_ASSERT_OK(generator.Process(*test_frame, 0.3, 40, 0.0, &result_frame)); - EXPECT_EQ(result_frame.Width(), expect_width); - EXPECT_EQ(result_frame.Height(), expect_height); -} - -TEST(PaddingEffectGeneratorTest, ComputeOutputLocation) { - PaddingEffectGenerator generator(1920, 1080, 1.0); - - auto result_rect = generator.ComputeOutputLocation(); - EXPECT_EQ(result_rect.x, 0); - EXPECT_EQ(result_rect.y, 236); - EXPECT_EQ(result_rect.width, 1080); - EXPECT_EQ(result_rect.height, 607); -} -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/piecewise_linear_function.cc b/examples/desktop/autoflip/quality/piecewise_linear_function.cc deleted file mode 100644 index fb8f44f..0000000 --- a/examples/desktop/autoflip/quality/piecewise_linear_function.cc +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/piecewise_linear_function.h" - -#include - -#include -#include -#include - -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -void PiecewiseLinearFunction::AddPoint(double x, double y) { - if (!points_.empty()) { - CHECK_GE(x, points_.back().x) - << "Points must be provided in non-decreasing x order."; - } - points_.push_back(PiecewiseLinearFunction::Point(x, y)); -} - -std::vector::const_iterator -PiecewiseLinearFunction::GetIntervalIterator(double input) const { - PiecewiseLinearFunction::Point input_point(input, 0); - std::vector::const_iterator iter = - std::lower_bound(points_.begin(), points_.end(), input_point, - PointCompare()); - return iter; -} - -double PiecewiseLinearFunction::Interpolate( - const PiecewiseLinearFunction::Point& p1, - const PiecewiseLinearFunction::Point& p2, double input) const { - CHECK_LT(p1.x, input); - CHECK_GE(p2.x, input); - - return p2.y - (p2.x - input) / (p2.x - p1.x) * (p2.y - p1.y); -} - -double PiecewiseLinearFunction::Evaluate(double const input) const { - std::vector::const_iterator i = - GetIntervalIterator(input); - if (i == points_.begin()) { - return points_.front().y; - } - if (i == points_.end()) { - return points_.back().y; - } - - std::vector::const_iterator prev = i - 1; - return Interpolate(*prev, *i, input); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/piecewise_linear_function.h b/examples/desktop/autoflip/quality/piecewise_linear_function.h deleted file mode 100644 index 67311a8..0000000 --- a/examples/desktop/autoflip/quality/piecewise_linear_function.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_PIECEWISE_LINEAR_FUNCTION_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_PIECEWISE_LINEAR_FUNCTION_H_ - -#include - -namespace mediapipe { -namespace autoflip { - -// Implementation of piecewise linear functions. The function is specified as a -// series of points (x1,y1), (x2,y2),..., (xn,yn). It can be constructed -// programmatically by repeatedly calling the AddPoint(x, y) method. -class PiecewiseLinearFunction { - public: - PiecewiseLinearFunction() {} - - // Evaluate the function at the specified input. The output - // saturates at the values of the first and last interpolation - // points. - // f(x) = y1 for x <= x1 // Saturate at the lowest value - // f(x) = yn for x > xn // Saturate at the highest value - // f(x) = (x-xj)/(xk-xj)*(yk-yj) + yk for xj < x <= xk and k = j+1 - double Evaluate(double input) const; - - // Adds the given point to the function. Points must be added in - // non-decreasing x order. Because the points are given in sorted - // order, this function can be used to construct discontinuous - // functions. For example, if one defines - // f.AddPoint(-1.0, 0.0) - // f.AddPoint( 0.0, 0.0) - // f.AddPoint( 0.0, 1.0) - // f.AddPoint( 1.0, 1.0) - // the result function f is discontinuous at 0.0. By convention, - // this function will return f.Evaluate(0.0) = 0.0, and - // f.Evaluate(1e-12) = 1.0. This convention corresponds to the - // natural behavior of GetIntervalIterator(). - void AddPoint(double x, double y); - - private: - struct Point { - double x; - double y; - Point(double X, double Y) : x(X), y(Y) {} - }; - - // A functor for use with stl algorithms like sort() and lower_bound() that - // sorts by the point's x value. - class PointCompare { - public: - bool operator()(const Point& p1, const Point& p2) const { - return p1.x < p2.x; - } - }; - - // Returns the iterator, i, closest to points_.begin() such that - // input <= i->x or it returns points_.end() if input > all x values - // in points_. - std::vector::const_iterator GetIntervalIterator(double input) const; - - // Given two points p1 and p2 such that p1.x < input and p2.x >= input this - // returns the linear interpolation of the y value. - double Interpolate(const Point& p1, const Point& p2, double input) const; - - std::vector points_; -}; - -} // namespace autoflip -} // namespace mediapipe -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_PIECEWISE_LINEAR_FUNCTION_H_ diff --git a/examples/desktop/autoflip/quality/piecewise_linear_function_test.cc b/examples/desktop/autoflip/quality/piecewise_linear_function_test.cc deleted file mode 100644 index 4197814..0000000 --- a/examples/desktop/autoflip/quality/piecewise_linear_function_test.cc +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/piecewise_linear_function.h" - -#include - -#include - -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/status.h" - -namespace { - -using mediapipe::autoflip::PiecewiseLinearFunction; - -// It should be OK to pass a spec that's out of order as it gets sorted. -TEST(PiecewiseLinearFunctionTest, ReordersSpec) { - PiecewiseLinearFunction f; - // This defines the line y = x between 0 and 5 - f.AddPoint(0, 0); - f.AddPoint(1, 1); - f.AddPoint(2, 2); - f.AddPoint(3, 3); - f.AddPoint(5, 5); - - // Should be 0 as -1 is less than the smallest x value in the spec so it - // should saturate. - ASSERT_EQ(0, f.Evaluate(-1)); - - // These shoud all be on the line y = x - ASSERT_EQ(0, f.Evaluate(0)); - ASSERT_EQ(0.5, f.Evaluate(0.5)); - ASSERT_EQ(4.5, f.Evaluate(4.5)); - ASSERT_EQ(5, f.Evaluate(5)); - - // Saturating on the high end. - ASSERT_EQ(5, f.Evaluate(6)); -} - -TEST(PiecewiseLinearFunctionTest, TestAddPoints) { - PiecewiseLinearFunction function; - function.AddPoint(0.0, 0.0); - function.AddPoint(1.0, 1.0); - EXPECT_DOUBLE_EQ(0.0, function.Evaluate(-1.0)); - EXPECT_DOUBLE_EQ(0.0, function.Evaluate(0.0)); - EXPECT_DOUBLE_EQ(0.25, function.Evaluate(0.25)); -} - -TEST(PiecewiseLinearFunctionTest, AddPointsDiscontinuous) { - PiecewiseLinearFunction function; - function.AddPoint(-1.0, 0.0); - function.AddPoint(0.0, 0.0); - function.AddPoint(0.0, 1.0); - function.AddPoint(1.0, 1.0); - EXPECT_DOUBLE_EQ(0.0, function.Evaluate(-1.0)); - EXPECT_DOUBLE_EQ(0.0, function.Evaluate(0.0)); - EXPECT_DOUBLE_EQ(1.0, function.Evaluate(1e-12)); - EXPECT_DOUBLE_EQ(1.0, function.Evaluate(3.14)); -} - -} // namespace diff --git a/examples/desktop/autoflip/quality/polynomial_regression_path_solver.cc b/examples/desktop/autoflip/quality/polynomial_regression_path_solver.cc deleted file mode 100644 index 0c5d221..0000000 --- a/examples/desktop/autoflip/quality/polynomial_regression_path_solver.cc +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h" - -#include "ceres/autodiff_cost_function.h" -#include "ceres/cost_function.h" -#include "ceres/loss_function.h" -#include "ceres/solver.h" -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -using ceres::AutoDiffCostFunction; -using ceres::CauchyLoss; -using ceres::CostFunction; -using ceres::Problem; -using ceres::Solve; -using ceres::Solver; - -namespace { - -// A residual operator that computes the error using polynomial fitting. -struct PolynomialResidual { - PolynomialResidual(double in, double out) : in_(in), out_(out) {} - - template - bool operator()(const T* const a, const T* const b, const T* const c, - const T* const d, const T* const k, T* residual) const { - residual[0] = out_ - a[0] * in_ - b[0] * in_ * in_ - - c[0] * in_ * in_ * in_ - d[0] * in_ * in_ * in_ * in_ - k[0]; - return true; - } - - private: - const double in_; - const double out_; -}; - -// Computes the amount of delta position change along the fitted polynomial -// curve, translates the delta from being relative to the origin of the original -// dimension to being relative to the center of the original dimension, then -// regulates the delta to avoid moving camera off the frame boundaries. -float ComputeDelta(const float in, const int original_dimension, - const int output_dimension, const double a, const double b, - const double c, const double d, const double k) { - // The value `out` here represents a normalized distance between the center of - // the output window and the origin of the original window. - float out = - a * in + b * in * in + c * in * in * in + d * in * in * in * in + k; - // Translate `out` to a pixel distance between the center of the output window - // and the center of the original window. This value can be negative, 0, or - // positive. - float delta = (out - 0.5) * original_dimension; - - // Make sure delta doesn't move the camera off the frame boundary. - const float max_delta = (original_dimension - output_dimension) / 2.0f; - if (delta > max_delta) { - delta = max_delta; - } else if (delta < -max_delta) { - delta = -max_delta; - } - return delta; -} -} // namespace - -void PolynomialRegressionPathSolver::AddCostFunctionToProblem( - const double in, const double out, Problem* problem, double* a, double* b, - double* c, double* d, double* k) { - // Creating a cost function, with 1D residual and 5 1D parameter blocks. This - // is what the "1, 1, 1, 1, 1, 1" string below means. - CostFunction* cost_function = - new AutoDiffCostFunction( - new PolynomialResidual(in, out)); - VLOG(1) << "------- adding " << in << ": " << out; - problem->AddResidualBlock(cost_function, new CauchyLoss(0.5), a, b, c, d, k); -} - -absl::Status PolynomialRegressionPathSolver::ComputeCameraPath( - const std::vector& focus_point_frames, - const std::vector& prior_focus_point_frames, - const int original_width, const int original_height, const int output_width, - const int output_height, std::vector* all_transforms) { - RET_CHECK_GE(original_width, output_width); - RET_CHECK_GE(original_height, output_height); - const bool should_solve_x_problem = original_width != output_width; - const bool should_solve_y_problem = original_height != output_height; - RET_CHECK_GT(focus_point_frames.size() + prior_focus_point_frames.size(), 0); - Problem problem_x, problem_y; - for (int i = 0; i < prior_focus_point_frames.size(); ++i) { - const auto& spf = prior_focus_point_frames[i]; - for (const auto& sp : spf.point()) { - const double center_x = sp.norm_point_x(); - const double center_y = sp.norm_point_y(); - const auto t = i; - if (should_solve_x_problem) { - AddCostFunctionToProblem(t, center_x, &problem_x, &xa_, &xb_, &xc_, - &xd_, &xk_); - } - if (should_solve_y_problem) { - AddCostFunctionToProblem(t, center_y, &problem_y, &ya_, &yb_, &yc_, - &yd_, &yk_); - } - } - } - for (int i = 0; i < focus_point_frames.size(); ++i) { - const auto& spf = focus_point_frames[i]; - for (const auto& sp : spf.point()) { - const double center_x = sp.norm_point_x(); - const double center_y = sp.norm_point_y(); - const auto t = i + prior_focus_point_frames.size(); - if (should_solve_x_problem) { - AddCostFunctionToProblem(t, center_x, &problem_x, &xa_, &xb_, &xc_, - &xd_, &xk_); - } - if (should_solve_y_problem) { - AddCostFunctionToProblem(t, center_y, &problem_y, &ya_, &yb_, &yc_, - &yd_, &yk_); - } - } - } - - Solver::Options options; - options.linear_solver_type = ceres::DENSE_QR; - - Solver::Summary summary_x, summary_y; - Solve(options, &problem_x, &summary_x); - Solve(options, &problem_y, &summary_y); - all_transforms->clear(); - for (int i = 0; - i < focus_point_frames.size() + prior_focus_point_frames.size(); i++) { - // Code below assigns values into an affine model, defined as: - // [1 0 dx] - // [0 1 dy] - // When the camera moves along x axis, we assign delta to dx; otherwise we - // assign delta to dy. - cv::Mat transform = cv::Mat::eye(2, 3, CV_32FC1); - const float in = static_cast(i); - if (should_solve_x_problem) { - const float delta = ComputeDelta(in, original_width, output_width, xa_, - xb_, xc_, xd_, xk_); - transform.at(0, 2) = delta; - } - if (should_solve_y_problem) { - const float delta = ComputeDelta(in, original_height, output_height, ya_, - yb_, yc_, yd_, yk_); - transform.at(1, 2) = delta; - } - all_transforms->push_back(transform); - } - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h b/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h deleted file mode 100644 index a510169..0000000 --- a/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_POLYNOMIAL_REGRESSION_PATH_SOLVER_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_POLYNOMIAL_REGRESSION_PATH_SOLVER_H_ - -#include "ceres/problem.h" -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -class PolynomialRegressionPathSolver { - public: - PolynomialRegressionPathSolver() - : xa_(0.0), - xb_(0.0), - xc_(0.0), - xd_(0.0), - xk_(0.0), - ya_(0.0), - yb_(0.0), - yc_(0.0), - yd_(0.0), - yk_(0.0) {} - - // Given a series of focus points on frames, uses polynomial regression to - // compute a best guess of a 1D camera movement trajectory along x-axis and - // y-axis, such that focus points can be preserved as much as possible. The - // returned |all_transforms| hold the camera location at each timestamp - // corresponding to each input frame. - absl::Status ComputeCameraPath( - const std::vector& focus_point_frames, - const std::vector& prior_focus_point_frames, - const int original_width, const int original_height, - const int output_width, const int output_height, - std::vector* all_transforms); - - private: - // Adds a new cost function, constructed using |in| and |out|, into |problem|. - void AddCostFunctionToProblem(const double in, const double out, - ceres::Problem* problem, double* a, double* b, - double* c, double* d, double* k); - - // The current implementation fixes the polynomial order at 4, i.e. the - // equation to estimate is: out = a * in + b * in^2 + c * in^3 + d * in^4 + k. - // The two sets of parameters below are for estimating trajectories along - // x-axis and y-axis, respectively. - double xa_, xb_, xc_, xd_, xk_; - double ya_, yb_, yc_, yd_, yk_; -}; - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_POLYNOMIAL_REGRESSION_PATH_SOLVER_H_ diff --git a/examples/desktop/autoflip/quality/polynomial_regression_path_solver_test.cc b/examples/desktop/autoflip/quality/polynomial_regression_path_solver_test.cc deleted file mode 100644 index c21245c..0000000 --- a/examples/desktop/autoflip/quality/polynomial_regression_path_solver_test.cc +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h" - -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { -namespace { - -// A series of focus point locations from a real video. -constexpr int kNumObservations = 291; -constexpr double data[] = { - 1, 0.4072740648, 2, 0.406096287, 3, 0.4049185093, 4, 0.4037407361, - 5, 0.402562963, 6, 0.4013851806, 7, 0.4002074074, 8, 0.399029625, - 9, 0.3978518519, 10, 0.3966740741, 11, 0.3954963056, 12, 0.3943185231, - 13, 0.39314075, 14, 0.3919629676, 15, 0.3907851944, 16, 0.3896074167, - 17, 0.3884296435, 18, 0.3872518611, 19, 0.386074088, 20, 0.3848963194, - 21, 0.3837185417, 22, 0.3825407685, 23, 0.3813629861, 24, 0.3801852037, - 25, 0.3790074398, 26, 0.3778296574, 27, 0.376651875, 28, 0.3754741111, - 29, 0.3742963287, 30, 0.3731185509, 31, 0.3719407685, 32, 0.3707629861, - 33, 0.3695852222, 34, 0.3684074398, 35, 0.3672296759, 36, 0.3661564352, - 37, 0.3651877315, 38, 0.3643235509, 39, 0.3635639213, 40, 0.3629088241, - 41, 0.36235825, 42, 0.3619122222, 43, 0.3615707269, 44, 0.3613337639, - 45, 0.3612013519, 46, 0.3611734583, 47, 0.3612500926, 48, 0.3614312778, - 49, 0.361717, 50, 0.3621072546, 51, 0.362602037, 52, 0.3632013519, - 53, 0.3639052083, 54, 0.3647136111, 55, 0.3656265417, 56, 0.3666440046, - 57, 0.3677659907, 58, 0.3689925139, 59, 0.3703235926, 60, 0.3716546528, - 61, 0.3729857269, 62, 0.374316787, 63, 0.3756478611, 64, 0.3769789259, - 65, 0.37831, 66, 0.3796410648, 67, 0.3809721296, 68, 0.3823031944, - 69, 0.3836342685, 70, 0.384965338, 71, 0.3862963981, 72, 0.3876274676, - 73, 0.388958537, 74, 0.3902290324, 75, 0.391438963, 76, 0.3925883241, - 77, 0.3936771204, 78, 0.3947053426, 79, 0.3956729954, 80, 0.396580088, - 81, 0.3974265972, 82, 0.3982125509, 83, 0.3989379259, 84, 0.3996027407, - 85, 0.4002069815, 86, 0.400750662, 87, 0.4012337639, 88, 0.4016562963, - 89, 0.4020182731, 90, 0.4023196667, 91, 0.4025604954, 92, 0.4027407593, - 93, 0.4028604491, 94, 0.4029195787, 95, 0.4029181296, 96, 0.4028561204, - 97, 0.4027407593, 98, 0.4026254028, 99, 0.4025100417, 100, 0.4023946852, - 101, 0.4022793241, 102, 0.402163963, 103, 0.4020486065, 104, 0.4019332454, - 105, 0.4018178889, 106, 0.4017025278, 107, 0.4015871759, 108, 0.4014718102, - 109, 0.4013564491, 110, 0.4012410972, 111, 0.4011257315, 112, 0.4010103704, - 113, 0.4008950185, 114, 0.400779662, 115, 0.4008743935, 116, 0.4011792083, - 117, 0.4016941157, 118, 0.4024191111, 119, 0.4033541944, 120, 0.4044993704, - 121, 0.4058546343, 122, 0.4074199815, 123, 0.4091954259, 124, 0.4111809537, - 125, 0.4133765741, 126, 0.415782287, 127, 0.4183980787, 128, 0.4212239676, - 129, 0.4242599352, 130, 0.427506, 131, 0.4309621528, 132, 0.4346283935, - 133, 0.4385047222, 134, 0.4425911407, 135, 0.4468876481, 136, 0.4513942454, - 137, 0.4561109306, 138, 0.4608276204, 139, 0.4655443056, 140, 0.4702609861, - 141, 0.4749776736, 142, 0.4796943574, 143, 0.4844110435, 144, 0.4891277287, - 145, 0.4938444148, 146, 0.4985611, 147, 0.5032777852, 148, 0.5079944708, - 149, 0.512711156, 150, 0.5174278403, 151, 0.5221445259, 152, 0.526861212, - 153, 0.5315778981, 154, 0.5362945833, 155, 0.5410112662, 156, 0.5457279537, - 157, 0.5504446435, 158, 0.5551613241, 159, 0.5598780093, 160, 0.5643503102, - 161, 0.5685782454, 162, 0.572561787, 163, 0.5763009583, 164, 0.5797957546, - 165, 0.583046162, 166, 0.5860521944, 167, 0.5888138611, 168, 0.5913311296, - 169, 0.5936040278, 170, 0.5956325463, 171, 0.5974166806, 172, 0.5989564491, - 173, 0.6002518287, 174, 0.6013028241, 175, 0.6021094491, 176, 0.6026716944, - 177, 0.6029895556, 178, 0.6030630556, 179, 0.602892162, 180, 0.6024768889, - 181, 0.6018172361, 182, 0.6009132083, 183, 0.5997648009, 184, 0.5983720139, - 185, 0.5967348519, 186, 0.595057662, 187, 0.5933804769, 188, 0.5917032963, - 189, 0.5900261065, 190, 0.588348912, 191, 0.5866717315, 192, 0.5849945417, - 193, 0.5833173519, 194, 0.5816401713, 195, 0.5799629861, 196, 0.578285787, - 197, 0.5766086111, 198, 0.5749314213, 199, 0.5732542315, 200, 0.5715770509, - 201, 0.5698998611, 202, 0.5682226667, 203, 0.5665454861, 204, 0.5648682963, - 205, 0.5631911157, 206, 0.5615139213, 207, 0.559989287, 208, 0.5586172083, - 209, 0.5573976713, 210, 0.5563306944, 211, 0.5554162662, 212, 0.5546543889, - 213, 0.5540450648, 214, 0.5535882917, 215, 0.5532840694, 216, 0.5531324028, - 217, 0.5531332824, 218, 0.5532867176, 219, 0.5535927083, 220, 0.5540512454, - 221, 0.5546623333, 222, 0.5554259769, 223, 0.5563421667, 224, 0.5574109148, - 225, 0.5586322083, 226, 0.5600060556, 227, 0.5615324583, 228, 0.563211412, - 229, 0.565042912, 230, 0.5670269722, 231, 0.5691635833, 232, 0.5714527315, - 233, 0.5738944491, 234, 0.576488713, 235, 0.5792355231, 236, 0.581982338, - 237, 0.5847291528, 238, 0.5874759676, 239, 0.5902227824, 240, 0.5929695972, - 241, 0.5957164074, 242, 0.5984632269, 243, 0.601210037, 244, 0.6039568565, - 245, 0.6067036667, 246, 0.6094504815, 247, 0.6121973056, 248, 0.6149441111, - 249, 0.6176909352, 250, 0.62043775, 251, 0.6231845556, 252, 0.6258408148, - 253, 0.628406537, 254, 0.6308817269, 255, 0.6332663426, 256, 0.6355604167, - 257, 0.6377639352, 258, 0.6398769259, 259, 0.6418993519, 260, 0.6438312407, - 261, 0.6456725787, 262, 0.6474233704, 263, 0.6490836111, 264, 0.650653287, - 265, 0.6521324444, 266, 0.653521037, 267, 0.6548190926, 268, 0.656026588, - 269, 0.6571435417, 270, 0.6581699537, 271, 0.6591058102, 272, 0.6599511204, - 273, 0.6607058796, 274, 0.6613700926, 275, 0.6620343056, 276, 0.6626985185, - 277, 0.6633627454, 278, 0.6640269537, 279, 0.6646911759, 280, 0.6653553843, - 281, 0.6660195972, 282, 0.6666838056, 283, 0.6673480278, 284, 0.6680122361, - 285, 0.668676463, 286, 0.6693406713, 287, 0.6700048935, 288, 0.6706691019, - 289, 0.6713333333, 290, 0.671997537, 291, 0.6726617454, -}; - -constexpr double prediction[] = { - 18.885935, 16.560495, 14.316487, 12.15229, 10.066307, 8.056951, - 6.1226487, 4.2618394, 2.4729967, 0.7545829, -0.894928, -2.47702, - -3.9931893, -5.4449024, -6.833619, -8.160782, -9.427822, -10.63615, - -11.78717, -12.882269, -13.922811, -14.910168, -15.845669, -16.730648, - -17.566418, -18.354284, -19.095533, -19.791435, -20.443249, -21.052212, - -21.619564, -22.146511, -22.634256, -23.08399, -23.496883, -23.874086, - -24.216753, -24.526012, -24.80297, -25.048738, -25.264395, -25.451015, - -25.609661, -25.74137, -25.84718, -25.928093, -25.985123, -26.01925, - -26.031458, -26.022684, -25.993896, -25.946003, -25.879932, -25.796581, - -25.696838, -25.581581, -25.451654, -25.307919, -25.151188, -24.982292, - -24.802023, -24.611176, -24.410517, -24.200804, -23.98278, -23.757189, - -23.52473, -23.286121, -23.042034, -22.79315, -22.540123, -22.283602, - -22.024214, -21.762579, -21.499294, -21.234953, -20.970118, -20.70536, - -20.441223, -20.178228, -19.916899, -19.65773, -19.401217, -19.147831, - -18.898027, -18.65226, -18.410952, -18.174517, -17.943365, -17.717875, - -17.498428, -17.285383, -17.079079, -16.879856, -16.688019, -16.503883, - -16.327726, -16.15982, -16.000439, -15.849811, -15.7081785, -15.575754, - -15.452737, -15.339321, -15.235674, -15.141964, -15.058327, -14.9848995, - -14.9218025, -14.869129, -14.826971, -14.795404, -14.774489, -14.764267, - -14.764768, -14.776015, -14.798016, -14.830744, -14.874178, -14.9282875, - -14.993011, -15.068281, -15.15401, -15.250105, -15.356457, -15.472937, - -15.599405, -15.73571, -15.881681, -16.03713, -16.201872, -16.37569, - -16.558355, -16.749626, -16.94926, -17.156982, -17.372507, -17.595535, - -17.825771, -18.062872, -18.306505, -18.55632, -18.811947, -19.072998, - -19.339085, -19.609785, -19.884687, -20.16334, -20.4453, -20.730091, - -21.017235, -21.306234, -21.59658, -21.887743, -22.179192, -22.470362, - -22.760695, -23.049604, -23.336494, -23.620754, -23.901754, -24.17887, - -24.451435, -24.718779, -24.980236, -25.235092, -25.482649, -25.722181, - -25.952942, -26.174181, -26.38514, -26.585024, -26.77304, -26.948387, - -27.110231, -27.257734, -27.39005, -27.506304, -27.605618, -27.68709, - -27.749819, -27.792877, -27.81533, -27.816212, -27.794569, -27.749413, - -27.679752, -27.584576, -27.462858, -27.313555, -27.135622, -26.927996, - -26.689583, -26.419294, -26.11602, -25.778639, -25.40601, -24.996979, - -24.550379, -24.06503, -}; - -void GenerateDataPointsFromRealVideo( - const int focus_point_frames_length, - const int prior_focus_point_frames_length, - std::vector* focus_point_frames, - std::vector* prior_focus_point_frames) { - CHECK(focus_point_frames_length + prior_focus_point_frames_length <= - kNumObservations); - for (int i = 0; i < prior_focus_point_frames_length; i++) { - FocusPoint sp; - sp.set_norm_point_x(data[i]); - FocusPointFrame spf; - *spf.add_point() = sp; - prior_focus_point_frames->push_back(spf); - } - for (int i = 0; i < focus_point_frames_length; i++) { - FocusPoint sp; - sp.set_norm_point_x(data[i]); - FocusPointFrame spf; - *spf.add_point() = sp; - focus_point_frames->push_back(spf); - } -} - -TEST(PolynomialRegressionPathSolverTest, SuccessInTrackingCameraMode) { - PolynomialRegressionPathSolver solver; - std::vector focus_point_frames; - std::vector prior_focus_point_frames; - std::vector all_xforms; - GenerateDataPointsFromRealVideo(/* focus_point_frames_length = */ 100, - /* prior_focus_point_frames_length = */ 100, - &focus_point_frames, - &prior_focus_point_frames); - constexpr int kFrameWidth = 200; - constexpr int kFrameHeight = 300; - constexpr int kCropWidth = 100; - constexpr int kCropHeight = 300; - MP_ASSERT_OK(solver.ComputeCameraPath( - focus_point_frames, prior_focus_point_frames, kFrameWidth, kFrameHeight, - kCropWidth, kCropHeight, &all_xforms)); - ASSERT_EQ(all_xforms.size(), 200); - for (int i = 0; i < all_xforms.size(); i++) { - cv::Mat mat = all_xforms[i]; - EXPECT_FLOAT_EQ(mat.at(0, 2), prediction[i]); - } -} - -TEST(PolynomialRegressionPathSolverTest, SuccessInStationaryCameraMode) { - PolynomialRegressionPathSolver solver; - std::vector focus_point_frames; - std::vector prior_focus_point_frames; - std::vector all_xforms; - - constexpr int kFocusPointFramesLength = 100; - constexpr float kNormStationaryFocusPointX = 0.34f; - - for (int i = 0; i < kFocusPointFramesLength; i++) { - FocusPoint sp; - // Add a fixed normalized focus point location. - sp.set_norm_point_x(kNormStationaryFocusPointX); - FocusPointFrame spf; - *spf.add_point() = sp; - focus_point_frames.push_back(spf); - } - constexpr int kFrameWidth = 300; - constexpr int kFrameHeight = 300; - constexpr int kCropWidth = 100; - constexpr int kCropHeight = 300; - MP_ASSERT_OK(solver.ComputeCameraPath( - focus_point_frames, prior_focus_point_frames, kFrameWidth, kFrameHeight, - kCropWidth, kCropHeight, &all_xforms)); - ASSERT_EQ(all_xforms.size(), kFocusPointFramesLength); - - constexpr int kExpectedShift = -48; - for (int i = 0; i < all_xforms.size(); i++) { - cv::Mat mat = all_xforms[i]; - EXPECT_FLOAT_EQ(mat.at(0, 2), kExpectedShift); - } -} - -// Test the case where focus points are so close to boundaries that the amount -// of shifts would have moved the camera to go outside frame boundaries. In this -// case, the solver should regulate the camera position shift to keep it stay -// inside the viewport. -TEST(PolynomialRegressionPathSolverTest, SuccessWhenFocusPointCloseToBoundary) { - PolynomialRegressionPathSolver solver; - std::vector focus_point_frames; - std::vector prior_focus_point_frames; - std::vector all_xforms; - - constexpr int kFocusPointFramesLength = 100; - constexpr float kNormStationaryFocusPointX = 0.99f; - - for (int i = 0; i < kFocusPointFramesLength; i++) { - FocusPoint sp; - // Add a fixed normalized focus point location. - sp.set_norm_point_x(kNormStationaryFocusPointX); - FocusPointFrame spf; - *spf.add_point() = sp; - focus_point_frames.push_back(spf); - } - constexpr int kFrameWidth = 500; - constexpr int kFrameHeight = 300; - constexpr int kCropWidth = 100; - constexpr int kCropHeight = 300; - MP_ASSERT_OK(solver.ComputeCameraPath( - focus_point_frames, prior_focus_point_frames, kFrameWidth, kFrameHeight, - kCropWidth, kCropHeight, &all_xforms)); - ASSERT_EQ(all_xforms.size(), kFocusPointFramesLength); - - // Regulate max delta change = (500 - 100) / 2. - constexpr int kExpectedShift = 200; - for (int i = 0; i < all_xforms.size(); i++) { - cv::Mat mat = all_xforms[i]; - EXPECT_FLOAT_EQ(mat.at(0, 2), kExpectedShift); - } -} - -TEST(PolynomialRegressionPathSolverTest, FewFramesShouldWork) { - PolynomialRegressionPathSolver solver; - std::vector focus_point_frames; - std::vector prior_focus_point_frames; - std::vector all_xforms; - GenerateDataPointsFromRealVideo(/* focus_point_frames_length = */ 1, - /* prior_focus_point_frames_length = */ 1, - &focus_point_frames, - &prior_focus_point_frames); - constexpr int kFrameWidth = 200; - constexpr int kFrameHeight = 300; - constexpr int kCropWidth = 100; - constexpr int kCropHeight = 300; - MP_ASSERT_OK(solver.ComputeCameraPath( - focus_point_frames, prior_focus_point_frames, kFrameWidth, kFrameHeight, - kCropWidth, kCropHeight, &all_xforms)); - ASSERT_EQ(all_xforms.size(), 2); -} - -TEST(PolynomialRegressionPathSolverTest, OneCurrentFrameShouldWork) { - PolynomialRegressionPathSolver solver; - std::vector focus_point_frames; - std::vector prior_focus_point_frames; - std::vector all_xforms; - GenerateDataPointsFromRealVideo(/* focus_point_frames_length = */ 1, - /* prior_focus_point_frames_length = */ 0, - &focus_point_frames, - &prior_focus_point_frames); - constexpr int kFrameWidth = 200; - constexpr int kFrameHeight = 300; - constexpr int kCropWidth = 100; - constexpr int kCropHeight = 300; - MP_ASSERT_OK(solver.ComputeCameraPath( - focus_point_frames, prior_focus_point_frames, kFrameWidth, kFrameHeight, - kCropWidth, kCropHeight, &all_xforms)); - ASSERT_EQ(all_xforms.size(), 1); -} - -TEST(PolynomialRegressionPathSolverTest, ZeroFrameShouldFail) { - PolynomialRegressionPathSolver solver; - std::vector focus_point_frames; - std::vector prior_focus_point_frames; - std::vector all_xforms; - GenerateDataPointsFromRealVideo(/* focus_point_frames_length = */ 0, - /* prior_focus_point_frames_length = */ 0, - &focus_point_frames, - &prior_focus_point_frames); - constexpr int kFrameWidth = 200; - constexpr int kFrameHeight = 300; - constexpr int kCropWidth = 100; - constexpr int kCropHeight = 300; - ASSERT_FALSE(solver - .ComputeCameraPath(focus_point_frames, - prior_focus_point_frames, kFrameWidth, - kFrameHeight, kCropWidth, kCropHeight, - &all_xforms) - .ok()); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.cc b/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.cc deleted file mode 100644 index 0bfe725..0000000 --- a/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.cc +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.h" - -#include - -#include "absl/memory/memory.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "mediapipe/examples/desktop/autoflip/quality/math_utils.h" -#include "mediapipe/examples/desktop/autoflip/quality/piecewise_linear_function.h" -#include "mediapipe/examples/desktop/autoflip/quality/utils.h" -#include "mediapipe/framework/port/integral_types.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/timestamp.h" - -namespace mediapipe { -namespace autoflip { - -absl::Status SceneCameraMotionAnalyzer::AnalyzeSceneAndPopulateFocusPointFrames( - const KeyFrameCropOptions& key_frame_crop_options, - const std::vector& key_frame_crop_results, - const int scene_frame_width, const int scene_frame_height, - const std::vector& scene_frame_timestamps, - const bool has_solid_color_background, - SceneKeyFrameCropSummary* scene_summary, - std::vector* focus_point_frames, - SceneCameraMotion* scene_camera_motion) { - has_solid_color_background_ = has_solid_color_background; - total_scene_frames_ = scene_frame_timestamps.size(); - MP_RETURN_IF_ERROR(AggregateKeyFrameResults( - key_frame_crop_options, key_frame_crop_results, scene_frame_width, - scene_frame_height, scene_summary)); - - const int64 scene_span_ms = - scene_frame_timestamps.empty() - ? 0 - : scene_frame_timestamps.back() - scene_frame_timestamps.front(); - const double scene_span_sec = TimestampDiff(scene_span_ms).Seconds(); - SceneCameraMotion camera_motion; - MP_RETURN_IF_ERROR(DecideCameraMotionType( - key_frame_crop_options, scene_span_sec, scene_frame_timestamps.back(), - scene_summary, &camera_motion)); - if (scene_summary->has_salient_region()) { - last_scene_with_salient_region_ = camera_motion; - time_since_last_salient_region_us_ = scene_frame_timestamps.back(); - } - if (scene_camera_motion != nullptr) { - *scene_camera_motion = camera_motion; - } - - return PopulateFocusPointFrames(*scene_summary, camera_motion, - scene_frame_timestamps, focus_point_frames); -} - -absl::Status SceneCameraMotionAnalyzer::ToUseSteadyMotion( - const float look_at_center_x, const float look_at_center_y, - const int crop_window_width, const int crop_window_height, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const { - scene_summary->set_crop_window_width(crop_window_width); - scene_summary->set_crop_window_height(crop_window_height); - auto* steady_motion = scene_camera_motion->mutable_steady_motion(); - steady_motion->set_steady_look_at_center_x(look_at_center_x); - steady_motion->set_steady_look_at_center_y(look_at_center_y); - return absl::OkStatus(); -} - -absl::Status SceneCameraMotionAnalyzer::ToUseSweepingMotion( - const float start_x, const float start_y, const float end_x, - const float end_y, const int crop_window_width, - const int crop_window_height, const double time_duration_in_sec, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const { - auto* sweeping_motion = scene_camera_motion->mutable_sweeping_motion(); - sweeping_motion->set_sweep_start_center_x(start_x); - sweeping_motion->set_sweep_start_center_y(start_y); - sweeping_motion->set_sweep_end_center_x(end_x); - sweeping_motion->set_sweep_end_center_y(end_y); - scene_summary->set_crop_window_width(crop_window_width); - scene_summary->set_crop_window_height(crop_window_height); - const auto sweeping_log = absl::StrFormat( - "Success rate %.2f is low - Camera is sweeping from (%.1f, %.1f) to " - "(%.1f, %.1f) in %.2f seconds.", - scene_summary->frame_success_rate(), start_x, start_y, end_x, end_y, - time_duration_in_sec); - VLOG(1) << sweeping_log; - return absl::OkStatus(); -} - -absl::Status SceneCameraMotionAnalyzer::DecideCameraMotionType( - const KeyFrameCropOptions& key_frame_crop_options, - const double scene_span_sec, const int64 end_time_us, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const { - RET_CHECK_GE(scene_span_sec, 0.0) << "Scene time span is negative."; - RET_CHECK_NE(scene_summary, nullptr) << "Scene summary is null."; - RET_CHECK_NE(scene_camera_motion, nullptr) << "Scene camera motion is null."; - const float scene_frame_center_x = scene_summary->scene_frame_width() / 2.0f; - const float scene_frame_center_y = scene_summary->scene_frame_height() / 2.0f; - - // If no frame has any focus region, that is, the scene has no focus - // regions, then default to look at the center. - if (!scene_summary->has_salient_region()) { - VLOG(1) << "No focus regions - camera is set to be steady on center."; - float no_salient_position_x = scene_frame_center_x; - float no_salient_position_y = scene_frame_center_y; - if (end_time_us - time_since_last_salient_region_us_ < - options_.duration_before_centering_us() && - last_scene_with_salient_region_.has_steady_motion()) { - no_salient_position_x = last_scene_with_salient_region_.steady_motion() - .steady_look_at_center_x(); - no_salient_position_y = last_scene_with_salient_region_.steady_motion() - .steady_look_at_center_y(); - } - MP_RETURN_IF_ERROR(ToUseSteadyMotion( - no_salient_position_x, no_salient_position_y, - scene_summary->crop_window_width(), scene_summary->crop_window_height(), - scene_summary, scene_camera_motion)); - return absl::OkStatus(); - } - - // Sweep across the scene when 1) success rate is too low, AND 2) the current - // scene is long enough. - if (options_.allow_sweeping() && !has_solid_color_background_ && - scene_summary->frame_success_rate() < - options_.minimum_success_rate_for_sweeping() && - scene_span_sec >= options_.minimum_scene_span_sec_for_sweeping()) { - float start_x = -1.0, start_y = -1.0, end_x = -1.0, end_y = -1.0; - if (options_.sweep_entire_frame()) { - if (scene_summary->crop_window_width() > - key_frame_crop_options.target_width()) { // horizontal sweeping - start_x = 0.0f; - start_y = scene_frame_center_y; - end_x = scene_summary->scene_frame_width(); - end_y = scene_frame_center_y; - } else { // vertical sweeping - start_x = scene_frame_center_x; - start_y = 0.0f; - end_x = scene_frame_center_x; - end_y = scene_summary->scene_frame_height(); - } - } else { - start_x = scene_summary->key_frame_center_min_x(); - start_y = scene_summary->key_frame_center_min_y(); - end_x = scene_summary->key_frame_center_max_x(); - end_y = scene_summary->key_frame_center_max_y(); - } - MP_RETURN_IF_ERROR(ToUseSweepingMotion( - start_x, start_y, end_x, end_y, key_frame_crop_options.target_width(), - key_frame_crop_options.target_height(), scene_span_sec, scene_summary, - scene_camera_motion)); - return absl::OkStatus(); - } - - // If scene motion is small, then look at a steady point in the scene. - if ((scene_summary->horizontal_motion_amount() < - options_.motion_stabilization_threshold_percent() && - scene_summary->vertical_motion_amount() < - options_.motion_stabilization_threshold_percent()) || - total_scene_frames_ == 1) { - return DecideSteadyLookAtRegion(key_frame_crop_options, scene_summary, - scene_camera_motion); - } - - // Otherwise, tracks the focus regions. - scene_camera_motion->mutable_tracking_motion(); - return absl::OkStatus(); -} - -// If there is no required focus region, looks at the middle of the center -// range, and snaps to the scene center if close. Otherwise, look at the center -// of the union of the required focus regions, and ensures the crop region -// covers this union. -absl::Status SceneCameraMotionAnalyzer::DecideSteadyLookAtRegion( - const KeyFrameCropOptions& key_frame_crop_options, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const { - const float scene_frame_width = scene_summary->scene_frame_width(); - const float scene_frame_height = scene_summary->scene_frame_height(); - const int target_width = key_frame_crop_options.target_width(); - const int target_height = key_frame_crop_options.target_height(); - float center_x = -1, center_y = -1; - float crop_width = -1, crop_height = -1; - - if (scene_summary->has_required_salient_region()) { - // Set look-at position to be the center of the union of required focus - // regions and the crop window size to be the maximum of this union size - // and the target size. - const auto& required_region_union = - scene_summary->key_frame_required_crop_region_union(); - center_x = required_region_union.x() + required_region_union.width() / 2.0f; - center_y = - required_region_union.y() + required_region_union.height() / 2.0f; - crop_width = std::max(target_width, required_region_union.width()); - crop_height = std::max(target_height, required_region_union.height()); - } else { - // Set look-at position to be the middle of the center range, and the crop - // window size to be the target size. - center_x = (scene_summary->key_frame_center_min_x() + - scene_summary->key_frame_center_max_x()) / - 2.0f; - center_y = (scene_summary->key_frame_center_min_y() + - scene_summary->key_frame_center_max_y()) / - 2.0f; - crop_width = target_width; - crop_height = target_height; - - // Optionally snap the look-at position to the scene frame center. - const float center_x_distance = - std::fabs(center_x - scene_frame_width / 2.0f); - const float center_y_distance = - std::fabs(center_y - scene_frame_height / 2.0f); - if (center_x_distance / scene_frame_width < - options_.snap_center_max_distance_percent()) { - center_x = scene_frame_width / 2.0f; - } - if (center_y_distance / scene_frame_height < - options_.snap_center_max_distance_percent()) { - center_y = scene_frame_height / 2.0f; - } - } - - // Clamp the region to be inside the frame. - // TODO: this may not be necessary. - float clamped_center_x, clamped_center_y; - RET_CHECK(MathUtil::Clamp(crop_width / 2.0f, - scene_frame_width - crop_width / 2.0f, center_x, - &clamped_center_x)); - center_x = clamped_center_x; - RET_CHECK(MathUtil::Clamp(crop_height / 2.0f, - scene_frame_height - crop_height / 2.0f, center_y, - &clamped_center_y)); - center_y = clamped_center_y; - - VLOG(1) << "Motion is small - camera is set to be steady at " << center_x - << ", " << center_y; - MP_RETURN_IF_ERROR(ToUseSteadyMotion(center_x, center_y, crop_width, - crop_height, scene_summary, - scene_camera_motion)); - return absl::OkStatus(); -} - -absl::Status SceneCameraMotionAnalyzer::AddFocusPointsFromCenterTypeAndWeight( - const float center_x, const float center_y, const int frame_width, - const int frame_height, const FocusPointFrameType type, const float weight, - const float bound, FocusPointFrame* focus_point_frame) const { - RET_CHECK_NE(focus_point_frame, nullptr) << "Focus point frame is null."; - const float norm_x = center_x / frame_width; - const float norm_y = center_y / frame_height; - const std::vector extremal_values = {0, 1}; - if (type == TOPMOST_AND_BOTTOMMOST) { - for (const float extremal_value : extremal_values) { - auto* focus_point = focus_point_frame->add_point(); - focus_point->set_norm_point_x(norm_x); - focus_point->set_norm_point_y(extremal_value); - focus_point->set_weight(weight); - focus_point->set_left(bound); - focus_point->set_right(bound); - } - } else if (type == LEFTMOST_AND_RIGHTMOST) { - for (const float extremal_value : extremal_values) { - auto* focus_point = focus_point_frame->add_point(); - focus_point->set_norm_point_x(extremal_value); - focus_point->set_norm_point_y(norm_y); - focus_point->set_weight(weight); - focus_point->set_top(bound); - focus_point->set_bottom(bound); - } - } else if (type == CENTER) { - auto* focus_point = focus_point_frame->add_point(); - focus_point->set_norm_point_x(norm_x); - focus_point->set_norm_point_y(norm_y); - focus_point->set_weight(weight); - focus_point->set_left(bound); - focus_point->set_right(bound); - focus_point->set_top(bound); - focus_point->set_bottom(bound); - } else { - RET_CHECK_FAIL() << absl::StrCat("Invalid FocusPointFrameType ", type); - } - return absl::OkStatus(); -} - -absl::Status SceneCameraMotionAnalyzer::PopulateFocusPointFrames( - const SceneKeyFrameCropSummary& scene_summary, - const SceneCameraMotion& scene_camera_motion, - const std::vector& scene_frame_timestamps, - std::vector* focus_point_frames) const { - RET_CHECK_NE(focus_point_frames, nullptr) - << "Output vector of FocusPointFrame is null."; - - const int num_scene_frames = scene_frame_timestamps.size(); - RET_CHECK_GT(num_scene_frames, 0) << "No scene frames."; - RET_CHECK_EQ(scene_summary.num_key_frames(), - scene_summary.key_frame_compact_infos_size()) - << "Key frame compact infos has wrong size:" - << " num_key_frames = " << scene_summary.num_key_frames() - << " key_frame_compact_infos size = " - << scene_summary.key_frame_compact_infos_size(); - const int scene_frame_width = scene_summary.scene_frame_width(); - const int scene_frame_height = scene_summary.scene_frame_height(); - RET_CHECK_GT(scene_frame_width, 0) << "Non-positive frame width."; - RET_CHECK_GT(scene_frame_height, 0) << "Non-positive frame height."; - - FocusPointFrameType focus_point_frame_type = - (scene_summary.crop_window_height() == scene_frame_height) - ? TOPMOST_AND_BOTTOMMOST - : (scene_summary.crop_window_width() == scene_frame_width - ? LEFTMOST_AND_RIGHTMOST - : CENTER); - focus_point_frames->reserve(num_scene_frames); - - if (scene_camera_motion.has_steady_motion()) { - // Camera focuses on a steady point of the scene. - const float center_x = - scene_camera_motion.steady_motion().steady_look_at_center_x(); - const float center_y = - scene_camera_motion.steady_motion().steady_look_at_center_y(); - for (int i = 0; i < num_scene_frames; ++i) { - FocusPointFrame focus_point_frame; - MP_RETURN_IF_ERROR(AddFocusPointsFromCenterTypeAndWeight( - center_x, center_y, scene_frame_width, scene_frame_height, - focus_point_frame_type, options_.maximum_salient_point_weight(), - options_.salient_point_bound(), &focus_point_frame)); - focus_point_frames->push_back(focus_point_frame); - } - return absl::OkStatus(); - } else if (scene_camera_motion.has_sweeping_motion()) { - // Camera sweeps across the frame. - const auto& sweeping_motion = scene_camera_motion.sweeping_motion(); - const float start_x = sweeping_motion.sweep_start_center_x(); - const float start_y = sweeping_motion.sweep_start_center_y(); - const float end_x = sweeping_motion.sweep_end_center_x(); - const float end_y = sweeping_motion.sweep_end_center_y(); - for (int i = 0; i < num_scene_frames; ++i) { - const float fraction = - num_scene_frames > 1 ? static_cast(i) / (num_scene_frames - 1) - : 0; - const float position_x = start_x * (1.0f - fraction) + end_x * fraction; - const float position_y = start_y * (1.0f - fraction) + end_y * fraction; - FocusPointFrame focus_point_frame; - MP_RETURN_IF_ERROR(AddFocusPointsFromCenterTypeAndWeight( - position_x, position_y, scene_frame_width, scene_frame_height, - focus_point_frame_type, options_.maximum_salient_point_weight(), - options_.salient_point_bound(), &focus_point_frame)); - focus_point_frames->push_back(focus_point_frame); - } - return absl::OkStatus(); - } else if (scene_camera_motion.has_tracking_motion()) { - // Camera tracks crop regions. - RET_CHECK_GT(scene_summary.num_key_frames(), 0) << "No key frames."; - return PopulateFocusPointFramesForTracking( - scene_summary, focus_point_frame_type, scene_frame_timestamps, - focus_point_frames); - } else { - return absl::Status(StatusCode::kInvalidArgument, "Unknown motion type."); - } -} - -// Linearly interpolates between key frames based on the timestamps using -// piecewise-linear functions for the crop region centers and scores. Adds one -// focus point at the center of the interpolated crop region for each frame. -// The weight for the focus point is proportional to the interpolated score -// and scaled so that the maximum weight is equal to -// maximum_focus_point_weight in the SceneCameraMotionAnalyzerOptions. -absl::Status SceneCameraMotionAnalyzer::PopulateFocusPointFramesForTracking( - const SceneKeyFrameCropSummary& scene_summary, - const FocusPointFrameType focus_point_frame_type, - const std::vector& scene_frame_timestamps, - std::vector* focus_point_frames) const { - RET_CHECK_GE(scene_summary.key_frame_max_score(), 0.0) - << "Maximum score is negative."; - - const int num_key_frames = scene_summary.num_key_frames(); - const auto& key_frame_compact_infos = scene_summary.key_frame_compact_infos(); - const int num_scene_frames = scene_frame_timestamps.size(); - const int scene_frame_width = scene_summary.scene_frame_width(); - const int scene_frame_height = scene_summary.scene_frame_height(); - - PiecewiseLinearFunction center_x_function, center_y_function, score_function; - const int64 timestamp_offset = key_frame_compact_infos[0].timestamp_ms(); - for (int i = 0; i < num_key_frames; ++i) { - const float center_x = key_frame_compact_infos[i].center_x(); - const float center_y = key_frame_compact_infos[i].center_y(); - const float score = key_frame_compact_infos[i].score(); - // Skips empty key frames. - if (center_x < 0 || center_y < 0 || score < 0) { - continue; - } - const double relative_timestamp = - key_frame_compact_infos[i].timestamp_ms() - timestamp_offset; - center_x_function.AddPoint(relative_timestamp, center_x); - center_y_function.AddPoint(relative_timestamp, center_y); - score_function.AddPoint(relative_timestamp, score); - } - - double max_score = 0.0; - const double min_score = 1e-4; // prevent constraints with 0 weight - for (int i = 0; i < num_scene_frames; ++i) { - const double relative_timestamp = - static_cast(scene_frame_timestamps[i] - timestamp_offset); - const double center_x = center_x_function.Evaluate(relative_timestamp); - const double center_y = center_y_function.Evaluate(relative_timestamp); - const double score = - std::max(min_score, score_function.Evaluate(relative_timestamp)); - max_score = std::max(max_score, score); - FocusPointFrame focus_point_frame; - MP_RETURN_IF_ERROR(AddFocusPointsFromCenterTypeAndWeight( - center_x, center_y, scene_frame_width, scene_frame_height, - focus_point_frame_type, score, options_.salient_point_bound(), - &focus_point_frame)); - focus_point_frames->push_back(focus_point_frame); - } - - // Scales weights so that maximum weight = maximum_salient_point_weight. - // TODO: run some experiments to find out if this is necessary. - max_score = std::max(max_score, min_score); - const double scale = options_.maximum_salient_point_weight() / max_score; - for (int i = 0; i < focus_point_frames->size(); ++i) { - for (int j = 0; j < (*focus_point_frames)[i].point_size(); ++j) { - auto* focus_point = (*focus_point_frames)[i].mutable_point(j); - focus_point->set_weight(scale * focus_point->weight()); - } - } - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.h b/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.h deleted file mode 100644 index d7f06a0..0000000 --- a/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.h +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CAMERA_MOTION_ANALYZER_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CAMERA_MOTION_ANALYZER_H_ - -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/framework/port/integral_types.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// This class does the following in order: -// - Aggregates a key frame results to get a SceneKeyFrameCropSummary, -// - Determines the SceneCameraMotion for the scene, and then -// - Populates FocusPointFrames to be used as input for the retargeter. -// -// Upstream inputs: -// - std::vector key_frame_crop_infos. -// - KeyFrameCropOptions key_frame_crop_options. -// - std::vector key_frame_crop_results. -// - int scene_frame_width, scene_frame_height. -// - std::vector scene_frame_timestamps. -// -// Example usage: -// SceneCameraMotionAnalyzerOptions options; -// SceneCameraMotionAnalyzer analyzer(options); -// SceneKeyFrameCropSummary scene_summary; -// std::vector focus_point_frames; -// CHECK_OK(analyzer.AnalyzeScenePopulateFocusPointFrames( -// key_frame_crop_infos, key_frame_crop_options, key_frame_crop_results, -// scene_frame_width, scene_frame_height, scene_frame_timestamps, -// &scene_summary, &focus_point_frames)); -class SceneCameraMotionAnalyzer { - public: - SceneCameraMotionAnalyzer() = delete; - - explicit SceneCameraMotionAnalyzer(const SceneCameraMotionAnalyzerOptions& - scene_camera_motion_analyzer_options) - : options_(scene_camera_motion_analyzer_options), - time_since_last_salient_region_us_(0), - has_solid_color_background_(false), - total_scene_frames_(0) {} - - ~SceneCameraMotionAnalyzer() {} - - // Aggregates information from KeyFrameInfos and KeyFrameCropResults into - // SceneKeyFrameCropSummary, and populates FocusPointFrames given scene - // frame timestamps. Optionally returns SceneCameraMotion. - absl::Status AnalyzeSceneAndPopulateFocusPointFrames( - const KeyFrameCropOptions& key_frame_crop_options, - const std::vector& key_frame_crop_results, - const int scene_frame_width, const int scene_frame_height, - const std::vector& scene_frame_timestamps, - const bool has_solid_color_background, - SceneKeyFrameCropSummary* scene_summary, - std::vector* focus_point_frames, - SceneCameraMotion* scene_camera_motion = nullptr); - - protected: - // Decides SceneCameraMotion based on SceneKeyFrameCropSummary. Updates the - // crop window in SceneKeyFrameCropSummary in the case of steady motion. - absl::Status DecideCameraMotionType( - const KeyFrameCropOptions& key_frame_crop_options, - const double scene_span_sec, const int64 end_time_us, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const; - - // Populates the FocusPointFrames for each scene frame based on - // SceneKeyFrameCropSummary, SceneCameraMotion, and scene frame timestamps. - absl::Status PopulateFocusPointFrames( - const SceneKeyFrameCropSummary& scene_summary, - const SceneCameraMotion& scene_camera_motion, - const std::vector& scene_frame_timestamps, - std::vector* focus_point_frames) const; - - private: - // Decides the look-at region when camera is steady. - absl::Status DecideSteadyLookAtRegion( - const KeyFrameCropOptions& key_frame_crop_options, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const; - - // Types of FocusPointFrames: number and placement of FocusPoint's vary. - enum FocusPointFrameType { - TOPMOST_AND_BOTTOMMOST = 1, // (center_x, 0) and (center_x, frame_height) - LEFTMOST_AND_RIGHTMOST = 2, // (0, center_y) and (frame_width, center_y) - CENTER = 3, // (center_x, center_y) - }; - - // Adds FocusPoint(s) to given FocusPointFrame given center location, - // frame size, FocusPointFrameType, weight, and bound. - absl::Status AddFocusPointsFromCenterTypeAndWeight( - const float center_x, const float center_y, const int frame_width, - const int frame_height, const FocusPointFrameType type, - const float weight, const float bound, - FocusPointFrame* focus_point_frame) const; - - // Populates the FocusPointFrames for each scene frame based on - // SceneKeyFrameCropSummary and scene frame timestamps in the case where - // camera is tracking the crop regions. - absl::Status PopulateFocusPointFramesForTracking( - const SceneKeyFrameCropSummary& scene_summary, - const FocusPointFrameType focus_point_frame_type, - const std::vector& scene_frame_timestamps, - std::vector* focus_point_frames) const; - - // Decide to use steady motion. - absl::Status ToUseSteadyMotion(const float look_at_center_x, - const float look_at_center_y, - const int crop_window_width, - const int crop_window_height, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const; - - // Decide to use sweeping motion. - absl::Status ToUseSweepingMotion( - const float start_x, const float start_y, const float end_x, - const float end_y, const int crop_window_width, - const int crop_window_height, const double time_duration_in_sec, - SceneKeyFrameCropSummary* scene_summary, - SceneCameraMotion* scene_camera_motion) const; - - // Scene camera motion analyzer options. - SceneCameraMotionAnalyzerOptions options_; - - // Last position - SceneCameraMotion last_scene_with_salient_region_; - int64 time_since_last_salient_region_us_; - - // Scene has solid color background. - bool has_solid_color_background_; - - // Total number of frames for this scene. - int total_scene_frames_; -}; - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CAMERA_MOTION_ANALYZER_H_ diff --git a/examples/desktop/autoflip/quality/scene_camera_motion_analyzer_test.cc b/examples/desktop/autoflip/quality/scene_camera_motion_analyzer_test.cc deleted file mode 100644 index 703d175..0000000 --- a/examples/desktop/autoflip/quality/scene_camera_motion_analyzer_test.cc +++ /dev/null @@ -1,805 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/scene_camera_motion_analyzer.h" - -#include -#include -#include -#include - -#include "absl/flags/flag.h" -#include "absl/strings/str_split.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/piecewise_linear_function.h" -#include "mediapipe/framework/deps/file_path.h" -#include "mediapipe/framework/port/file_helpers.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { - -using ::testing::HasSubstr; - -const int kNumKeyFrames = 5; -const int kNumSceneFrames = 30; - -const int64 kKeyFrameTimestampDiff = 1e6 / kNumKeyFrames; -const int64 kSceneFrameTimestampDiff = 1e6 / kNumSceneFrames; -// Default time span of a scene in seconds. -const double kSceneTimeSpanSec = 1.0; - -const int kSceneFrameWidth = 100; -const int kSceneFrameHeight = 100; - -const int kTargetWidth = 50; -const int kTargetHeight = 50; - -constexpr char kCameraTrackingSceneFrameResultsFile[] = - "mediapipe/examples/desktop/autoflip/quality/testdata/" - "camera_motion_tracking_scene_frame_results.csv"; - -// Makes a rectangle given the corner (x, y) and the size (width, height). -Rect MakeRect(const int x, const int y, const int width, const int height) { - Rect rect; - rect.set_x(x); - rect.set_y(y); - rect.set_width(width); - rect.set_height(height); - return rect; -} - -// Returns default values for scene frame timestamps. Populates timestamps using -// the default spacing kSceneFrameTimestampDiff starting from 0. -std::vector GetDefaultSceneFrameTimestamps() { - std::vector scene_frame_timestamps(kNumSceneFrames); - for (int i = 0; i < kNumSceneFrames; ++i) { - scene_frame_timestamps[i] = kSceneFrameTimestampDiff * i; - } - return scene_frame_timestamps; -} - -// Returns default settings for KeyFrameCropOptions. Populates target size to be -// the default target size. -KeyFrameCropOptions GetDefaultKeyFrameCropOptions() { - KeyFrameCropOptions key_frame_crop_options; - key_frame_crop_options.set_target_width(kTargetWidth); - key_frame_crop_options.set_target_height(kTargetHeight); - return key_frame_crop_options; -} - -// Returns default values for KeyFrameCropResults. Sets each frame to have -// covered all the required regions and non-required regions, and have required -// crop region (10, 10+20) x (10, 10+20), (full) crop region (0, 50) x (0, 50), -// and region score 1.0. -std::vector GetDefaultKeyFrameCropResults() { - std::vector key_frame_crop_results(kNumKeyFrames); - for (int i = 0; i < kNumKeyFrames; ++i) { - key_frame_crop_results[i].set_are_required_regions_covered_in_target_size( - true); - key_frame_crop_results[i].set_fraction_non_required_covered(1.0); - key_frame_crop_results[i].set_region_is_empty(false); - key_frame_crop_results[i].set_required_region_is_empty(false); - *(key_frame_crop_results[i].mutable_region()) = MakeRect(0, 0, 50, 50); - *(key_frame_crop_results[i].mutable_required_region()) = - MakeRect(10, 10, 20, 20); - key_frame_crop_results[i].set_region_score(1.0); - key_frame_crop_results[i].set_timestamp_ms(kKeyFrameTimestampDiff * i); - } - return key_frame_crop_results; -} - -// Returns default settings for SceneKeyFrameCropSummary. Sets scene frame size -// to be the default size. Sets each key frame compact info in accordance to the -// default timestamps (using the default spacing kKeyFrameTimestampDiff starting -// from 0), default crop regions (centered at (25, 25)), and default scores -// (1.0). Sets center range to be [25, 25] and [25, 25]. Sets score range to be -// [1.0, 1.0]. Sets crop window size to be (25, 25). Sets has focus region to -// be true. Sets frame success rate to be 1.0. Sets horizontal and vertical -// motion amount to be 0.0. -SceneKeyFrameCropSummary GetDefaultSceneKeyFrameCropSummary() { - SceneKeyFrameCropSummary scene_summary; - scene_summary.set_scene_frame_width(kSceneFrameWidth); - scene_summary.set_scene_frame_height(kSceneFrameHeight); - scene_summary.set_num_key_frames(kNumKeyFrames); - for (int i = 0; i < kNumKeyFrames; ++i) { - auto* compact_info = scene_summary.add_key_frame_compact_infos(); - compact_info->set_timestamp_ms(kKeyFrameTimestampDiff * i); - compact_info->set_center_x(25); - compact_info->set_center_y(25); - compact_info->set_score(1.0); - } - scene_summary.set_key_frame_center_min_x(25); - scene_summary.set_key_frame_center_max_x(25); - scene_summary.set_key_frame_center_min_y(25); - scene_summary.set_key_frame_center_max_y(25); - scene_summary.set_key_frame_min_score(1.0); - scene_summary.set_key_frame_max_score(1.0); - scene_summary.set_crop_window_width(25); - scene_summary.set_crop_window_height(25); - scene_summary.set_has_salient_region(true); - scene_summary.set_frame_success_rate(1.0); - scene_summary.set_horizontal_motion_amount(0.0); - scene_summary.set_vertical_motion_amount(0.0); - return scene_summary; -} - -// Returns a SceneKeyFrameCropSummary with small motion. Sets crop window size -// to default target size. Sets horizontal motion to half the threshold in the -// options and vertical motion to 0. Sets center x range to [45, 55]. -SceneKeyFrameCropSummary GetSceneKeyFrameCropSummaryWithSmallMotion( - const SceneCameraMotionAnalyzerOptions& options) { - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_crop_window_width(kTargetWidth); - scene_summary.set_crop_window_height(kTargetHeight); - scene_summary.set_horizontal_motion_amount( - options.motion_stabilization_threshold_percent() / 2.0); - scene_summary.set_vertical_motion_amount(0.0); - scene_summary.set_key_frame_center_min_x(45); - scene_summary.set_key_frame_center_max_x(55); - return scene_summary; -} - -// Testable class that allows public access to protected methods in the class. -class TestableSceneCameraMotionAnalyzer : public SceneCameraMotionAnalyzer { - public: - explicit TestableSceneCameraMotionAnalyzer( - const SceneCameraMotionAnalyzerOptions& - scene_camera_motion_analyzer_options) - : SceneCameraMotionAnalyzer(scene_camera_motion_analyzer_options) {} - ~TestableSceneCameraMotionAnalyzer() {} - using SceneCameraMotionAnalyzer::DecideCameraMotionType; - using SceneCameraMotionAnalyzer::PopulateFocusPointFrames; -}; - -// Checks that DecideCameraMotionType checks that output pointers are not null. -TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeChecksOutputNotNull) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - KeyFrameCropOptions crop_options = GetDefaultKeyFrameCropOptions(); - SceneKeyFrameCropSummary scene_summary; - SceneCameraMotion camera_motion; - auto status = analyzer.DecideCameraMotionType(crop_options, kSceneTimeSpanSec, - 0, nullptr, &camera_motion); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Scene summary is null.")); - status = analyzer.DecideCameraMotionType(crop_options, kSceneTimeSpanSec, 0, - &scene_summary, nullptr); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Scene camera motion is null.")); -} - -// Checks that DecideCameraMotionType properly handles the case where no key -// frame has any focus region, and sets the camera motion type to steady and -// the look-at position to the scene frame center. -TEST(SceneCameraMotionAnalyzerTest, - DecideCameraMotionTypeWithoutAnyFocusRegion) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - KeyFrameCropOptions crop_options = GetDefaultKeyFrameCropOptions(); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_has_salient_region(false); - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType( - crop_options, kSceneTimeSpanSec, 0, &scene_summary, &camera_motion)); - EXPECT_TRUE(camera_motion.has_steady_motion()); - const auto& steady_motion = camera_motion.steady_motion(); - EXPECT_FLOAT_EQ(steady_motion.steady_look_at_center_x(), - kSceneFrameWidth / 2.0f); - EXPECT_FLOAT_EQ(steady_motion.steady_look_at_center_y(), - kSceneFrameHeight / 2.0f); -} - -// Checks that DecideCameraMotionType properly handles the camera sweeps from -// left to right. -TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeSweepingLeftToRight) { - SceneCameraMotionAnalyzerOptions options; - options.set_sweep_entire_frame(true); - TestableSceneCameraMotionAnalyzer analyzer(options); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_frame_success_rate( - options.minimum_success_rate_for_sweeping() / 2.0f); - scene_summary.set_crop_window_width(kTargetWidth * 1.5f); // horizontal sweep - scene_summary.set_crop_window_height(kTargetHeight); - const double time_span = options.minimum_scene_span_sec_for_sweeping() * 2.0; - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(), - time_span, 0, &scene_summary, - &camera_motion)); - - EXPECT_TRUE(camera_motion.has_sweeping_motion()); - const auto& sweeping_motion = camera_motion.sweeping_motion(); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_start_center_x(), 0.0f); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_end_center_x(), - static_cast(kSceneFrameWidth)); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_start_center_y(), - kSceneFrameHeight / 2.0f); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_end_center_y(), - kSceneFrameHeight / 2.0f); -} - -// Checks that DecideCameraMotionType properly handles the camera sweeps from -// top to bottom. -TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeSweepingTopToBottom) { - SceneCameraMotionAnalyzerOptions options; - options.set_sweep_entire_frame(true); - TestableSceneCameraMotionAnalyzer analyzer(options); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_frame_success_rate( - options.minimum_success_rate_for_sweeping() / 2.0f); - scene_summary.set_crop_window_width(kTargetWidth); - scene_summary.set_crop_window_height(kTargetHeight * 1.5f); // vertical sweep - const double time_span = options.minimum_scene_span_sec_for_sweeping() * 2.0; - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(), - time_span, 0, &scene_summary, - &camera_motion)); - - EXPECT_TRUE(camera_motion.has_sweeping_motion()); - const auto& sweeping_motion = camera_motion.sweeping_motion(); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_start_center_y(), 0.0f); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_end_center_y(), - static_cast(kSceneFrameWidth)); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_start_center_x(), - kSceneFrameWidth / 2.0f); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_end_center_x(), - kSceneFrameWidth / 2.0f); -} - -// Checks that DecideCameraMotionType properly handles the camera sweeps from -// one corner of the center range to another. -TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeSweepingCenterRange) { - SceneCameraMotionAnalyzerOptions options; - options.set_sweep_entire_frame(false); - TestableSceneCameraMotionAnalyzer analyzer(options); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_frame_success_rate( - options.minimum_success_rate_for_sweeping() / 2.0f); - scene_summary.set_crop_window_width(kTargetWidth * 1.5f); - scene_summary.set_crop_window_height(kTargetHeight * 1.5f); - const double time_span = options.minimum_scene_span_sec_for_sweeping() * 2.0; - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(), - time_span, 0, &scene_summary, - &camera_motion)); - - EXPECT_TRUE(camera_motion.has_sweeping_motion()); - const auto& sweeping_motion = camera_motion.sweeping_motion(); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_start_center_x(), - scene_summary.key_frame_center_min_x()); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_start_center_y(), - scene_summary.key_frame_center_min_y()); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_end_center_x(), - scene_summary.key_frame_center_max_x()); - EXPECT_FLOAT_EQ(sweeping_motion.sweep_end_center_y(), - scene_summary.key_frame_center_max_y()); -} - -// Checks that DecideCameraMotionType properly handles the case where motion is -// small and there are no required focus regions. -TEST(SceneCameraMotionAnalyzerTest, - DecideCameraMotionTypeSmallMotionNoRequiredFocusRegion) { - SceneCameraMotionAnalyzerOptions options; - options.set_motion_stabilization_threshold_percent(0.1); - options.set_snap_center_max_distance_percent(0.0); - TestableSceneCameraMotionAnalyzer analyzer(options); - auto scene_summary = GetSceneKeyFrameCropSummaryWithSmallMotion(options); - scene_summary.set_has_required_salient_region(false); - const int crop_region_center_x = 50; - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(), - kSceneTimeSpanSec, 0, - &scene_summary, &camera_motion)); - EXPECT_TRUE(camera_motion.has_steady_motion()); - EXPECT_EQ(camera_motion.steady_motion().steady_look_at_center_x(), - crop_region_center_x); - EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth); - EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight); -} - -// Checks that DecideCameraMotionType properly handles the case where motion is -// small and there are required focus regions that fit in target size. -TEST(SceneCameraMotionAnalyzerTest, - DecideCameraMotionTypeSmallMotionRequiredFocusRegionInTargetSize) { - SceneCameraMotionAnalyzerOptions options; - options.set_motion_stabilization_threshold_percent(0.1); - options.set_snap_center_max_distance_percent(0.0); - TestableSceneCameraMotionAnalyzer analyzer(options); - auto scene_summary = GetSceneKeyFrameCropSummaryWithSmallMotion(options); - scene_summary.set_has_required_salient_region(true); - *scene_summary.mutable_key_frame_required_crop_region_union() = - MakeRect(40, 0, 40, 10); - const int required_region_center_x = 60; - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(), - kSceneTimeSpanSec, 0, - &scene_summary, &camera_motion)); - EXPECT_TRUE(camera_motion.has_steady_motion()); - EXPECT_EQ(camera_motion.steady_motion().steady_look_at_center_x(), - required_region_center_x); - EXPECT_EQ(scene_summary.crop_window_width(), 50); - EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight); -} - -// Checks that DecideCameraMotionType properly handles the case where motion is -// small and there are required focus regions that exceed target size. -TEST(SceneCameraMotionAnalyzerTest, - DecideCameraMotionTypeSmallMotionRequiredFocusRegionExceedingTargetSize) { - SceneCameraMotionAnalyzerOptions options; - options.set_motion_stabilization_threshold_percent(0.1); - options.set_snap_center_max_distance_percent(0.0); - TestableSceneCameraMotionAnalyzer analyzer(options); - auto scene_summary = GetSceneKeyFrameCropSummaryWithSmallMotion(options); - scene_summary.set_has_required_salient_region(true); - *scene_summary.mutable_key_frame_required_crop_region_union() = - MakeRect(20, 0, 70, 10); - const int required_region_center_x = 55; - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(), - kSceneTimeSpanSec, 0, - &scene_summary, &camera_motion)); - EXPECT_TRUE(camera_motion.has_steady_motion()); - EXPECT_EQ(camera_motion.steady_motion().steady_look_at_center_x(), - required_region_center_x); - EXPECT_EQ(scene_summary.crop_window_width(), 70); - EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight); -} - -// Checks that DecideCameraMotionType properly handles the case where motion is -// small and the middle of the key frame crop center range is close to the scene -// frame center. -TEST(SceneCameraMotionAnalyzerTest, - DecideCameraMotionTypeSmallMotionCloseToCenter) { - SceneCameraMotionAnalyzerOptions options; - options.set_motion_stabilization_threshold_percent(0.1); - options.set_snap_center_max_distance_percent(0.1); - TestableSceneCameraMotionAnalyzer analyzer(options); - KeyFrameCropOptions crop_options = GetDefaultKeyFrameCropOptions(); - const float frame_center_x = kSceneFrameWidth / 2.0f; - auto scene_summary = GetSceneKeyFrameCropSummaryWithSmallMotion(options); - scene_summary.set_key_frame_center_min_x(frame_center_x - 2); - scene_summary.set_key_frame_center_max_x(frame_center_x); - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType( - crop_options, kSceneTimeSpanSec, 0, &scene_summary, &camera_motion)); - EXPECT_TRUE(camera_motion.has_steady_motion()); - EXPECT_FLOAT_EQ(camera_motion.steady_motion().steady_look_at_center_x(), - frame_center_x); -} - -// Checks that DecideCameraMotionType properly handles the case where motion is -// not small, and sets the camera motion type to tracking. -TEST(SceneCameraMotionAnalyzerTest, DecideCameraMotionTypeTracking) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_horizontal_motion_amount( - options.motion_stabilization_threshold_percent() * 2.0); - SceneCameraMotion camera_motion; - - MP_EXPECT_OK(analyzer.DecideCameraMotionType(GetDefaultKeyFrameCropOptions(), - kSceneTimeSpanSec, 0, - &scene_summary, &camera_motion)); - EXPECT_TRUE(camera_motion.has_tracking_motion()); -} - -// Checks that PopulateFocusPointFrames checks output pointer is not null. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesChecksOutputNotNull) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - const auto status = analyzer.PopulateFocusPointFrames( - GetDefaultSceneKeyFrameCropSummary(), camera_motion, - GetDefaultSceneFrameTimestamps(), nullptr); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Output vector of FocusPointFrame is null.")); -} - -// Checks that PopulateFocusPointFrames checks scene frames size. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesChecksSceneFramesSize) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - std::vector scene_frame_timestamps(0); - std::vector focus_point_frames; - - const auto status = analyzer.PopulateFocusPointFrames( - GetDefaultSceneKeyFrameCropSummary(), camera_motion, - scene_frame_timestamps, &focus_point_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("No scene frames.")); -} - -// Checks that PopulateFocusPointFrames handles the case of no key frames. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesHandlesNoKeyFrames) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - camera_motion.mutable_steady_motion(); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_num_key_frames(0); - scene_summary.clear_key_frame_compact_infos(); - scene_summary.set_has_salient_region(false); - std::vector focus_point_frames; - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames( - scene_summary, camera_motion, GetDefaultSceneFrameTimestamps(), - &focus_point_frames)); -} - -// Checks that PopulateFocusPointFrames checks KeyFrameCompactInfos has the -// right size. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesChecksKeyFrameCompactInfosSize) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_num_key_frames(2 * kNumKeyFrames); - std::vector focus_point_frames; - - const auto status = analyzer.PopulateFocusPointFrames( - scene_summary, camera_motion, GetDefaultSceneFrameTimestamps(), - &focus_point_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Key frame compact infos has wrong size")); -} - -// Checks that PopulateFocusPointFrames checks SceneKeyFrameCropSummary has -// valid scene frame size. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesChecksSceneFrameSize) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_scene_frame_height(0); - std::vector focus_point_frames; - - const auto status = analyzer.PopulateFocusPointFrames( - scene_summary, camera_motion, GetDefaultSceneFrameTimestamps(), - &focus_point_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Non-positive frame height.")); -} - -// Checks that PopulateFocusPointFrames checks camera motion type is valid. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesChecksCameraMotionType) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - camera_motion.clear_motion_type(); - std::vector focus_point_frames; - - const auto status = analyzer.PopulateFocusPointFrames( - GetDefaultSceneKeyFrameCropSummary(), camera_motion, - GetDefaultSceneFrameTimestamps(), &focus_point_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Unknown motion type.")); -} - -// Checks that PopulateFocusPointFrames properly sets FocusPointFrames when -// camera motion type is steady. -TEST(SceneCameraMotionAnalyzerTest, PopulateFocusPointFramesSteady) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - auto* steady_motion = camera_motion.mutable_steady_motion(); - steady_motion->set_steady_look_at_center_x(40.5); - steady_motion->set_steady_look_at_center_y(25); - std::vector focus_point_frames; - - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames( - GetDefaultSceneKeyFrameCropSummary(), camera_motion, - GetDefaultSceneFrameTimestamps(), &focus_point_frames)); - - EXPECT_EQ(kNumSceneFrames, focus_point_frames.size()); - for (int i = 0; i < kNumSceneFrames; ++i) { - // FocusPointFrameType is CENTER. - EXPECT_EQ(focus_point_frames[i].point_size(), 1); - const auto& point = focus_point_frames[i].point(0); - EXPECT_FLOAT_EQ( - point.norm_point_x(), - steady_motion->steady_look_at_center_x() / kSceneFrameWidth); - EXPECT_FLOAT_EQ( - point.norm_point_y(), - steady_motion->steady_look_at_center_y() / kSceneFrameHeight); - EXPECT_FLOAT_EQ(point.weight(), options.maximum_salient_point_weight()); - } -} - -// Checks that PopulateFocusPointFrames properly sets FocusPointFrames when -// FocusPointFrameType is TOPMOST_AND_BOTTOMMOST. -TEST(SceneCameraMotionAnalyzerTest, PopulateFocusPointFramesTopAndBottom) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - auto* steady_motion = camera_motion.mutable_steady_motion(); - steady_motion->set_steady_look_at_center_x(40.5); - steady_motion->set_steady_look_at_center_y(25); - // Forces FocusPointFrameType to be TOPMOST_AND_BOTTOMOST. - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_crop_window_height(kSceneFrameHeight); - std::vector focus_point_frames; - - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames( - scene_summary, camera_motion, GetDefaultSceneFrameTimestamps(), - &focus_point_frames)); - - EXPECT_EQ(kNumSceneFrames, focus_point_frames.size()); - for (int i = 0; i < kNumSceneFrames; ++i) { - EXPECT_EQ(focus_point_frames[i].point_size(), 2); - const auto& point1 = focus_point_frames[i].point(0); - const auto& point2 = focus_point_frames[i].point(1); - EXPECT_FLOAT_EQ( - point1.norm_point_x(), - steady_motion->steady_look_at_center_x() / kSceneFrameWidth); - EXPECT_FLOAT_EQ(point1.norm_point_y(), 0.0f); - EXPECT_FLOAT_EQ( - point2.norm_point_x(), - steady_motion->steady_look_at_center_x() / kSceneFrameWidth); - EXPECT_FLOAT_EQ(point2.norm_point_y(), 1.0f); - EXPECT_FLOAT_EQ(point1.weight(), options.maximum_salient_point_weight()); - EXPECT_FLOAT_EQ(point2.weight(), options.maximum_salient_point_weight()); - } -} - -// Checks that PopulateFocusPointFrames properly sets FocusPointFrames when -// FocusPointFrameType is LEFTMOST_AND_RIGHTMOST. -TEST(SceneCameraMotionAnalyzerTest, PopulateFocusPointFramesLeftAndRight) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - auto* steady_motion = camera_motion.mutable_steady_motion(); - steady_motion->set_steady_look_at_center_x(40.5); - steady_motion->set_steady_look_at_center_y(25); - // Forces FocusPointFrameType to be LEFTMOST_AND_RIGHTMOST. - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_crop_window_width(kSceneFrameWidth); - std::vector focus_point_frames; - - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames( - scene_summary, camera_motion, GetDefaultSceneFrameTimestamps(), - &focus_point_frames)); - - EXPECT_EQ(kNumSceneFrames, focus_point_frames.size()); - for (int i = 0; i < kNumSceneFrames; ++i) { - EXPECT_EQ(focus_point_frames[i].point_size(), 2); - const auto& point1 = focus_point_frames[i].point(0); - const auto& point2 = focus_point_frames[i].point(1); - EXPECT_FLOAT_EQ(point1.norm_point_x(), 0.0f); - EXPECT_FLOAT_EQ( - point1.norm_point_y(), - steady_motion->steady_look_at_center_y() / kSceneFrameHeight); - EXPECT_FLOAT_EQ(point2.norm_point_x(), 1.0f); - EXPECT_FLOAT_EQ( - point1.norm_point_y(), - steady_motion->steady_look_at_center_y() / kSceneFrameHeight); - EXPECT_FLOAT_EQ(point1.weight(), options.maximum_salient_point_weight()); - EXPECT_FLOAT_EQ(point2.weight(), options.maximum_salient_point_weight()); - } -} - -// Checks that PopulateFocusPointFrames properly sets FocusPointFrames when -// camera motion type is sweeping. -TEST(SceneCameraMotionAnalyzerTest, PopulateFocusPointFramesSweeping) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - auto* sweeping_motion = camera_motion.mutable_sweeping_motion(); - sweeping_motion->set_sweep_start_center_x(5); - sweeping_motion->set_sweep_start_center_y(50); - sweeping_motion->set_sweep_end_center_x(95); - sweeping_motion->set_sweep_end_center_y(50); - const int num_frames = 10; - const std::vector positions_x = {5, 15, 25, 35, 45, - 55, 65, 75, 85, 95}; - const std::vector positions_y = {50, 50, 50, 50, 50, - 50, 50, 50, 50, 50}; - std::vector scene_frame_timestamps(num_frames); - std::iota(scene_frame_timestamps.begin(), scene_frame_timestamps.end(), 0); - std::vector focus_point_frames; - - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames( - GetDefaultSceneKeyFrameCropSummary(), camera_motion, - scene_frame_timestamps, &focus_point_frames)); - - EXPECT_EQ(num_frames, focus_point_frames.size()); - for (int i = 0; i < num_frames; ++i) { - EXPECT_EQ(focus_point_frames[i].point_size(), 1); - const auto& point = focus_point_frames[i].point(0); - EXPECT_FLOAT_EQ(positions_x[i] / kSceneFrameWidth, point.norm_point_x()); - EXPECT_FLOAT_EQ(positions_y[i] / kSceneFrameHeight, point.norm_point_y()); - } -} - -// Checks that PopulateFocusPointFrames checks tracking handles the case when -// maximum score is 0. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesTrackingHandlesZeroScore) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - camera_motion.mutable_tracking_motion(); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_key_frame_max_score(0.0); - for (int i = 0; i < kNumKeyFrames; ++i) { - scene_summary.mutable_key_frame_compact_infos(i)->set_score(0.0); - } - std::vector focus_point_frames; - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames( - scene_summary, camera_motion, GetDefaultSceneFrameTimestamps(), - &focus_point_frames)); -} - -// Checks that PopulateFocusPointFrames skips empty key frames when camera -// motion type is tracking. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesTrackingSkipsEmptyKeyFrames) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - camera_motion.mutable_tracking_motion(); - SceneKeyFrameCropSummary scene_summary; - scene_summary.set_scene_frame_width(kSceneFrameWidth); - scene_summary.set_scene_frame_height(kSceneFrameHeight); - scene_summary.set_num_key_frames(2); - - // Sets first key frame to be empty and second frame to be normal. - const float center_x = 25.0f, center_y = 25.0f; - auto* first_frame_compact_info = scene_summary.add_key_frame_compact_infos(); - first_frame_compact_info->set_center_x(-1.0); - auto* second_frame_compact_info = scene_summary.add_key_frame_compact_infos(); - second_frame_compact_info->set_center_x(center_x); - second_frame_compact_info->set_center_y(center_y); - second_frame_compact_info->set_score(1.0); - scene_summary.set_key_frame_center_min_x(center_x); - scene_summary.set_key_frame_center_max_x(center_x); - scene_summary.set_key_frame_center_min_y(center_y); - scene_summary.set_key_frame_center_max_y(center_y); - scene_summary.set_key_frame_min_score(1.0); - scene_summary.set_key_frame_max_score(1.0); - - // Aligns timestamps of scene frames with key frames. - scene_summary.mutable_key_frame_compact_infos(0)->set_timestamp_ms(10); - scene_summary.mutable_key_frame_compact_infos(1)->set_timestamp_ms(20); - std::vector scene_frame_timestamps = {10, 20}; - - std::vector focus_point_frames; - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames(scene_summary, camera_motion, - scene_frame_timestamps, - &focus_point_frames)); - - // Both scene frames should have focus point frames based on the second key - // frame since the first one is empty and not used. - for (int i = 0; i < 2; ++i) { - EXPECT_EQ(focus_point_frames[i].point_size(), 1); - const auto& point = focus_point_frames[i].point(0); - EXPECT_FLOAT_EQ(point.norm_point_x(), center_x / kSceneFrameWidth); - EXPECT_FLOAT_EQ(point.norm_point_y(), center_y / kSceneFrameHeight); - EXPECT_FLOAT_EQ(point.weight(), options.maximum_salient_point_weight()); - } -} - -// Checks that PopulateFocusPointFrames properly sets FocusPointFrames when -// camera motion type is tracking, piecewise-linearly interpolating key frame -// centers and scores, and scaling scores so that maximum weight is equal to -// maximum_salient_point_weight. -TEST(SceneCameraMotionAnalyzerTest, - PopulateFocusPointFramesTrackingTracksKeyFrames) { - SceneCameraMotionAnalyzerOptions options; - TestableSceneCameraMotionAnalyzer analyzer(options); - SceneCameraMotion camera_motion; - camera_motion.mutable_tracking_motion(); - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - const std::vector centers_x = {14.0, 5.0, 40.0, 70.0, 30.0}; - const std::vector centers_y = {60.0, 50.0, 80.0, 0.0, 20.0}; - const std::vector scores = {0.1, 1.0, 2.0, 0.6, 0.9}; - scene_summary.set_key_frame_min_score(0.1); - scene_summary.set_key_frame_max_score(2.0); - for (int i = 0; i < kNumKeyFrames; ++i) { - auto* compact_info = scene_summary.mutable_key_frame_compact_infos(i); - compact_info->set_center_x(centers_x[i]); - compact_info->set_center_y(centers_y[i]); - compact_info->set_score(scores[i]); - } - - // Get reference scene frame results from csv file. - const std::string scene_frame_results_file_path = - mediapipe::file::JoinPath("./", kCameraTrackingSceneFrameResultsFile); - std::string csv_file_content; - MP_ASSERT_OK(mediapipe::file::GetContents(scene_frame_results_file_path, - &csv_file_content)); - std::vector lines = absl::StrSplit(csv_file_content, '\n'); - std::vector records; - for (const auto& line : lines) { - std::vector r = absl::StrSplit(line, ','); - records.insert(records.end(), r.begin(), r.end()); - } - CHECK_EQ(records.size(), kNumSceneFrames * 3 + 1); - - std::vector focus_point_frames; - MP_EXPECT_OK(analyzer.PopulateFocusPointFrames( - scene_summary, camera_motion, GetDefaultSceneFrameTimestamps(), - &focus_point_frames)); - - float max_weight = 0.0; - const float tolerance = 1e-4; - for (int i = 0; i < kNumSceneFrames; ++i) { - EXPECT_EQ(focus_point_frames[i].point_size(), 1); - const auto& point = focus_point_frames[i].point(0); - const float expected_x = std::stof(records[i * 3]); - const float expected_y = std::stof(records[i * 3 + 1]); - const float expected_weight = std::stof(records[i * 3 + 2]); - EXPECT_LE(std::fabs(point.norm_point_x() - expected_x), tolerance); - EXPECT_LE(std::fabs(point.norm_point_y() - expected_y), tolerance); - EXPECT_LE(std::fabs(point.weight() - expected_weight), tolerance); - max_weight = std::max(max_weight, point.weight()); - } - EXPECT_LE(std::fabs(max_weight - options.maximum_salient_point_weight()), - tolerance); -} - -// Checks that AnalyzeSceneAndPopulateFocusPointFrames analyzes scene and -// populates focus point frames. -TEST(SceneCameraMotionAnalyzerTest, AnalyzeSceneAndPopulateFocusPointFrames) { - SceneCameraMotionAnalyzerOptions options; - SceneCameraMotionAnalyzer analyzer(options); - SceneKeyFrameCropSummary scene_summary; - std::vector focus_point_frames; - - MP_EXPECT_OK(analyzer.AnalyzeSceneAndPopulateFocusPointFrames( - GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(), - kSceneFrameWidth, kSceneFrameHeight, GetDefaultSceneFrameTimestamps(), - false, &scene_summary, &focus_point_frames)); - EXPECT_EQ(scene_summary.num_key_frames(), kNumKeyFrames); - EXPECT_EQ(focus_point_frames.size(), kNumSceneFrames); -} - -// Checks that AnalyzeSceneAndPopulateFocusPointFrames optionally returns -// scene camera motion. -TEST(SceneCameraMotionAnalyzerTest, - AnalyzeSceneAndPopulateFocusPointFramesReturnsSceneCameraMotion) { - SceneCameraMotionAnalyzerOptions options; - SceneCameraMotionAnalyzer analyzer(options); - SceneKeyFrameCropSummary scene_summary; - std::vector focus_point_frames; - SceneCameraMotion scene_camera_motion; - - MP_EXPECT_OK(analyzer.AnalyzeSceneAndPopulateFocusPointFrames( - GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(), - kSceneFrameWidth, kSceneFrameHeight, GetDefaultSceneFrameTimestamps(), - false, &scene_summary, &focus_point_frames, &scene_camera_motion)); - EXPECT_TRUE(scene_camera_motion.has_steady_motion()); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/scene_cropper.cc b/examples/desktop/autoflip/quality/scene_cropper.cc deleted file mode 100644 index a3c6f17..0000000 --- a/examples/desktop/autoflip/quality/scene_cropper.cc +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/scene_cropper.h" - -#include - -#include "absl/memory/memory.h" -#include "mediapipe/examples/desktop/autoflip/quality/polynomial_regression_path_solver.h" -#include "mediapipe/examples/desktop/autoflip/quality/utils.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -// TODO: Parameterize FOV based on camera specs. -constexpr float kWidthFieldOfView = 60; - -namespace mediapipe { -namespace autoflip { - -absl::Status SceneCropper::ProcessKinematicPathSolver( - const SceneKeyFrameCropSummary& scene_summary, - const std::vector& scene_timestamps, - const std::vector& is_key_frames, - const std::vector& focus_point_frames, - const bool continue_last_scene, std::vector* all_xforms) { - // TODO: Fix upstream calculators to not crop beyond portrait target - // value. - /* - RET_CHECK(scene_summary.scene_frame_height() == - scene_summary.crop_window_height()) - << "Kinematic path solver does not yet support horizontal cropping."; - */ - - RET_CHECK(scene_timestamps.size() == focus_point_frames.size()) - << "Kinematic path solver does not yet support downsampled detections."; - - if (!path_solver_initalized_ || !continue_last_scene) { - int min_location = scene_summary.crop_window_width() / 2; - int max_location = scene_summary.scene_frame_width() - - scene_summary.crop_window_width() / 2; - kinematic_path_solver_ = std::make_unique( - camera_motion_options_.kinematic_options(), min_location, max_location, - static_cast(frame_width_) / kWidthFieldOfView); - path_solver_initalized_ = true; - } - int keyframe_counter = 0; - for (int i = 0; i < is_key_frames.size(); i++) { - if (is_key_frames[i]) { - RET_CHECK_EQ(focus_point_frames[keyframe_counter].point().size(), 2) - << "Expected focus_points to equal 2"; - int observed_x = std::round( - focus_point_frames[keyframe_counter].point(0).norm_point_x() * - scene_summary.scene_frame_width()); - MP_RETURN_IF_ERROR(kinematic_path_solver_->AddObservation( - observed_x, scene_timestamps[i])); - keyframe_counter++; - } else { - MP_RETURN_IF_ERROR( - kinematic_path_solver_->UpdatePrediction(scene_timestamps[i])); - } - int x_path; - MP_RETURN_IF_ERROR(kinematic_path_solver_->GetState(&x_path)); - cv::Mat transform = cv::Mat::eye(2, 3, CV_32FC1); - transform.at(0, 2) = - -(x_path - scene_summary.crop_window_width() / 2); - all_xforms->push_back(transform); - } - return absl::OkStatus(); -} - -absl::Status SceneCropper::CropFrames( - const SceneKeyFrameCropSummary& scene_summary, - const std::vector& scene_timestamps, - const std::vector& is_key_frames, - const std::vector& scene_frames_or_empty, - const std::vector& focus_point_frames, - const std::vector& prior_focus_point_frames, - int top_static_border_size, int bottom_static_border_size, - const bool continue_last_scene, std::vector* crop_from_location, - std::vector* cropped_frames) { - const int num_scene_frames = scene_timestamps.size(); - RET_CHECK_GT(num_scene_frames, 0) << "No scene frames."; - RET_CHECK_EQ(focus_point_frames.size(), num_scene_frames) - << "Wrong size of FocusPointFrames."; - - const int frame_width = scene_summary.scene_frame_width(); - const int frame_height = scene_summary.scene_frame_height(); - const int crop_width = scene_summary.crop_window_width(); - const int crop_height = scene_summary.crop_window_height(); - RET_CHECK_GT(crop_width, 0) << "Crop width is non-positive."; - RET_CHECK_GT(crop_height, 0) << "Crop height is non-positive."; - RET_CHECK_LE(crop_width, frame_width) << "Crop width exceeds frame width."; - RET_CHECK_LE(crop_height, frame_height) - << "Crop height exceeds frame height."; - - RET_CHECK(camera_motion_options_.has_polynomial_path_solver() || - camera_motion_options_.has_kinematic_options()) - << "No camera motion model selected."; - - // Computes transforms. - - std::vector scene_frame_xforms; - int num_prior = 0; - if (camera_motion_options_.has_polynomial_path_solver()) { - num_prior = prior_focus_point_frames.size(); - std::vector all_xforms; - PolynomialRegressionPathSolver solver; - RET_CHECK_OK(solver.ComputeCameraPath( - focus_point_frames, prior_focus_point_frames, frame_width, frame_height, - crop_width, crop_height, &all_xforms)); - - scene_frame_xforms = - std::vector(all_xforms.begin() + num_prior, all_xforms.end()); - - // Convert the matrix from center-aligned to upper-left aligned. - for (cv::Mat& xform : scene_frame_xforms) { - cv::Mat affine_opencv = cv::Mat::eye(2, 3, CV_32FC1); - affine_opencv.at(0, 2) = - -(xform.at(0, 2) + frame_width / 2 - crop_width / 2); - affine_opencv.at(1, 2) = - -(xform.at(1, 2) + frame_height / 2 - crop_height / 2); - xform = affine_opencv; - } - } else if (camera_motion_options_.has_kinematic_options()) { - num_prior = 0; - MP_RETURN_IF_ERROR(ProcessKinematicPathSolver( - scene_summary, scene_timestamps, is_key_frames, focus_point_frames, - continue_last_scene, &scene_frame_xforms)); - } - - // Store the "crop from" location on the input frame for use with an external - // renderer. - for (int i = 0; i < num_scene_frames; i++) { - const int left = -(scene_frame_xforms[i].at(0, 2)); - const int top = - top_static_border_size - (scene_frame_xforms[i].at(1, 2)); - crop_from_location->push_back(cv::Rect(left, top, crop_width, crop_height)); - } - - // If no cropped_frames is passed in, return directly. - if (!cropped_frames) { - return absl::OkStatus(); - } - RET_CHECK(!scene_frames_or_empty.empty()) - << "If |cropped_frames| != nullptr, scene_frames_or_empty must not be " - "empty."; - // Prepares cropped frames. - cropped_frames->resize(num_scene_frames); - for (int i = 0; i < num_scene_frames; ++i) { - (*cropped_frames)[i] = cv::Mat::zeros(crop_height, crop_width, - scene_frames_or_empty[i].type()); - } - return AffineRetarget(cv::Size(crop_width, crop_height), - scene_frames_or_empty, scene_frame_xforms, - cropped_frames); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/scene_cropper.h b/examples/desktop/autoflip/quality/scene_cropper.h deleted file mode 100644 index 0e5c332..0000000 --- a/examples/desktop/autoflip/quality/scene_cropper.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CROPPER_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CROPPER_H_ - -#include -#include - -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/kinematic_path_solver.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// This class is a thin wrapper around the Retargeter class to crop a collection -// of scene frames given SceneKeyFrameCropSummary and their FocusPointFrames. -// -// Upstream inputs: -// - SceneKeyFrameCropSummary scene_summary. -// - std::vector focus_point_frames. -// - std::vector prior_focus_point_frames. -// - std::vector scene_frames; -// -// Example usage: -// SceneCropperOptions scene_cropper_options; -// SceneCropper scene_cropper(scene_cropper_options); -// std::vector cropped_frames; -// CHECK_OK(scene_cropper.CropFrames( -// scene_summary, scene_frames, focus_point_frames, -// prior_focus_point_frames, &cropped_frames)); -class SceneCropper { - public: - SceneCropper(const CameraMotionOptions& camera_motion_options, - const int frame_width, const int frame_height) - : path_solver_initalized_(false), - camera_motion_options_(camera_motion_options), - frame_width_(frame_width), - frame_height_(frame_height) {} - ~SceneCropper() {} - - // Computes transformation matrix given SceneKeyFrameCropSummary, - // FocusPointFrames, and any prior FocusPointFrames (to ensure smoothness when - // there was no actual scene change). Optionally crops the input frames based - // on the transform matrix if |cropped_frames| is not nullptr and - // |scene_frames_or_empty| isn't empty. - // TODO: split this function into two separate functions. - absl::Status CropFrames( - const SceneKeyFrameCropSummary& scene_summary, - const std::vector& scene_timestamps, - const std::vector& is_key_frames, - const std::vector& scene_frames_or_empty, - const std::vector& focus_point_frames, - const std::vector& prior_focus_point_frames, - int top_static_border_size, int bottom_static_border_size, - const bool continue_last_scene, std::vector* crop_from_location, - std::vector* cropped_frames); - - absl::Status ProcessKinematicPathSolver( - const SceneKeyFrameCropSummary& scene_summary, - const std::vector& scene_timestamps, - const std::vector& is_key_frames, - const std::vector& focus_point_frames, - const bool continue_last_scene, std::vector* all_xforms); - - private: - bool path_solver_initalized_; - std::unique_ptr kinematic_path_solver_; - CameraMotionOptions camera_motion_options_; - int frame_width_; - int frame_height_; -}; - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CROPPER_H_ diff --git a/examples/desktop/autoflip/quality/scene_cropper_test.cc b/examples/desktop/autoflip/quality/scene_cropper_test.cc deleted file mode 100644 index fb2c989..0000000 --- a/examples/desktop/autoflip/quality/scene_cropper_test.cc +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/scene_cropper.h" - -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { - -using testing::HasSubstr; - -const int kCropWidth = 90; -const int kCropHeight = 160; - -const int kSceneWidth = 320; -const int kSceneHeight = 180; - -const int kNumSceneFrames = 30; - -// Returns default values for SceneKeyFrameCropSummary. Sets scene size and crop -// window size from default values. -SceneKeyFrameCropSummary GetDefaultSceneKeyFrameCropSummary() { - SceneKeyFrameCropSummary scene_summary; - scene_summary.set_scene_frame_width(kSceneWidth); - scene_summary.set_scene_frame_height(kSceneHeight); - scene_summary.set_crop_window_width(kCropWidth); - scene_summary.set_crop_window_height(kCropHeight); - return scene_summary; -} - -// Returns default values for scene frames of size kNumSceneFrames. Stes each -// frame to be solid red color at default scene size. -std::vector GetDefaultSceneFrames() { - std::vector scene_frames(kNumSceneFrames); - for (int i = 0; i < kNumSceneFrames; ++i) { - scene_frames[i] = cv::Mat(kSceneHeight, kSceneWidth, CV_8UC3); - scene_frames[i] = cv::Scalar(255, 0, 0); - } - return scene_frames; -} - -// Makes a vector of FocusPointFrames given size. Stes each FocusPointFrame -// to have one FocusPoint at the center of the frame. -std::vector GetFocusPointFrames(const int num_frames) { - std::vector focus_point_frames(num_frames); - for (int i = 0; i < num_frames; ++i) { - auto* point = focus_point_frames[i].add_point(); - point->set_norm_point_x(0.5); - point->set_norm_point_y(0.5); - } - return focus_point_frames; -} -// Returns default values for FocusPointFrames of size kNumSceneFrames. -std::vector GetDefaultFocusPointFrames() { - return GetFocusPointFrames(kNumSceneFrames); -} - -std::vector GetTimestamps(const int num_frames) { - std::vector timestamps; - for (int i = 0; i < num_frames; ++i) { - timestamps.push_back(i * 100000); - } - return timestamps; -} - -std::vector GetIsKeyframe(const int num_frames) { - std::vector is_keyframe; - for (int i = 0; i < num_frames; ++i) { - is_keyframe.push_back(false); - } - return is_keyframe; -} - -// Checks that CropFrames checks that scene frames size is positive. -TEST(SceneCropperTest, CropFramesChecksSceneFramesSize) { - CameraMotionOptions options; - options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30); - SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight); - std::vector scene_frames(0); - std::vector cropped_frames; - std::vector crop_from_locations; - const auto status = scene_cropper.CropFrames( - GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(scene_frames.size()), - GetIsKeyframe(scene_frames.size()), scene_frames, - GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false, - &crop_from_locations, &cropped_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("No scene frames.")); -} - -// Checks that CropFrames checks that FocusPointFrames has the right size. - -TEST(SceneCropperTest, CropFramesChecksFocusPointFramesSize) { - CameraMotionOptions options; - options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30); - SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight); - std::vector cropped_frames; - std::vector crop_from_locations; - const auto& scene_frames = GetDefaultSceneFrames(); - const auto status = scene_cropper.CropFrames( - GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(kNumSceneFrames), - GetIsKeyframe(kNumSceneFrames), scene_frames, - GetFocusPointFrames(kNumSceneFrames - 1), GetFocusPointFrames(0), 0, 0, - false, &crop_from_locations, &cropped_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Wrong size of FocusPointFrames")); -} - -// Checks that CropFrames checks crop size is positive. -TEST(SceneCropperTest, CropFramesChecksCropSizePositive) { - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_crop_window_width(-1); - CameraMotionOptions options; - options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30); - SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight); - std::vector cropped_frames; - std::vector crop_from_locations; - const auto& scene_frames = GetDefaultSceneFrames(); - const auto status = scene_cropper.CropFrames( - scene_summary, GetTimestamps(kNumSceneFrames), - GetIsKeyframe(kNumSceneFrames), scene_frames, - GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false, - &crop_from_locations, &cropped_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Crop width is non-positive.")); -} - -// Checks that CropFrames checks that crop size does not exceed frame size. -TEST(SceneCropperTest, InitializesRetargeterChecksCropSizeNotExceedFrameSize) { - auto scene_summary = GetDefaultSceneKeyFrameCropSummary(); - scene_summary.set_crop_window_height(kSceneHeight + 1); - CameraMotionOptions options; - options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30); - SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight); - std::vector cropped_frames; - std::vector crop_from_locations; - const auto& scene_frames = GetDefaultSceneFrames(); - const auto status = scene_cropper.CropFrames( - scene_summary, GetTimestamps(kNumSceneFrames), - GetIsKeyframe(kNumSceneFrames), scene_frames, - GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false, - &crop_from_locations, &cropped_frames); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Crop height exceeds frame height.")); -} - -// Checks that CropFrames works when there are not any prior FocusPointFrames. -TEST(SceneCropperTest, CropFramesWorksWithoutPriorFocusPointFrames) { - CameraMotionOptions options; - options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30); - SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight); - std::vector cropped_frames; - std::vector crop_from_locations; - const auto& scene_frames = GetDefaultSceneFrames(); - MP_ASSERT_OK(scene_cropper.CropFrames( - GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(kNumSceneFrames), - GetIsKeyframe(kNumSceneFrames), scene_frames, - GetDefaultFocusPointFrames(), GetFocusPointFrames(0), 0, 0, false, - &crop_from_locations, &cropped_frames)); - ASSERT_EQ(cropped_frames.size(), kNumSceneFrames); - for (int i = 0; i < kNumSceneFrames; ++i) { - EXPECT_EQ(cropped_frames[i].rows, kCropHeight); - EXPECT_EQ(cropped_frames[i].cols, kCropWidth); - } -} - -// Checks that CropFrames works when there are prior FocusPointFrames. -TEST(SceneCropperTest, CropFramesWorksWithPriorFocusPointFrames) { - CameraMotionOptions options; - options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30); - SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight); - std::vector cropped_frames; - std::vector crop_from_locations; - const auto& scene_frames = GetDefaultSceneFrames(); - MP_EXPECT_OK(scene_cropper.CropFrames( - GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(scene_frames.size()), - GetIsKeyframe(scene_frames.size()), scene_frames, - GetDefaultFocusPointFrames(), GetFocusPointFrames(3), 0, 0, false, - &crop_from_locations, &cropped_frames)); - EXPECT_EQ(cropped_frames.size(), kNumSceneFrames); - for (int i = 0; i < kNumSceneFrames; ++i) { - EXPECT_EQ(cropped_frames[i].rows, kCropHeight); - EXPECT_EQ(cropped_frames[i].cols, kCropWidth); - } -} - -// Checks that crop_from_locations gets the correct results. -TEST(SceneCropperTest, CropFromLocation) { - CameraMotionOptions options; - options.mutable_polynomial_path_solver()->set_prior_frame_buffer_size(30); - SceneCropper scene_cropper(options, kSceneWidth, kSceneHeight); - std::vector cropped_frames; - std::vector crop_from_locations; - const auto& scene_frames = GetDefaultSceneFrames(); - MP_EXPECT_OK(scene_cropper.CropFrames( - GetDefaultSceneKeyFrameCropSummary(), GetTimestamps(scene_frames.size()), - GetIsKeyframe(scene_frames.size()), scene_frames, - GetDefaultFocusPointFrames(), GetFocusPointFrames(3), 0, 0, false, - &crop_from_locations, &cropped_frames)); - EXPECT_EQ(cropped_frames.size(), kNumSceneFrames); - for (int i = 0; i < kNumSceneFrames; ++i) { - EXPECT_EQ(cropped_frames[i].rows, kCropHeight); - EXPECT_EQ(cropped_frames[i].cols, kCropWidth); - } - for (int i = 0; i < kNumSceneFrames; ++i) { - EXPECT_EQ(crop_from_locations[i].height, kCropHeight); - EXPECT_EQ(crop_from_locations[i].width, kCropWidth); - } -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/scene_cropping_viz.cc b/examples/desktop/autoflip/quality/scene_cropping_viz.cc deleted file mode 100644 index d27423c..0000000 --- a/examples/desktop/autoflip/quality/scene_cropping_viz.cc +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/scene_cropping_viz.h" - -#include - -#include "absl/memory/memory.h" -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_format.pb.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// Colors for focus signal sources. -const cv::Scalar kCyan = - cv::Scalar(0.0, 255.0, 255.0); // brain object detector -const cv::Scalar kMagenta = cv::Scalar(255.0, 0.0, 255.0); // motion -const cv::Scalar kYellow = cv::Scalar(255.0, 255.0, 0.0); // fg ocr -const cv::Scalar kLightYellow = cv::Scalar(255.0, 250.0, 205.0); // bg ocr -const cv::Scalar kRed = cv::Scalar(255.0, 0.0, 0.0); // logo -const cv::Scalar kGreen = cv::Scalar(0.0, 255.0, 0.0); // face -const cv::Scalar kBlue = - cv::Scalar(0.0, 0.0, 255.0); // creatism saliency model -const cv::Scalar kOrange = - cv::Scalar(255.0, 165.0, 0.0); // ica object detector -const cv::Scalar kWhite = cv::Scalar(255.0, 255.0, 255.0); // others - -absl::Status DrawDetectionsAndCropRegions( - const std::vector& scene_frames, - const std::vector& is_key_frames, - const std::vector& key_frame_infos, - const std::vector& key_frame_crop_results, - const ImageFormat::Format image_format, - std::vector>* viz_frames) { - RET_CHECK(viz_frames) << "Output viz frames is null."; - viz_frames->clear(); - const int num_frames = scene_frames.size(); - - std::pair crop_corners; - std::vector> region_corners; - std::vector region_colors; - auto RectToCvPoints = - [](const Rect& rect) -> std::pair { - return std::make_pair( - cv::Point(rect.x(), rect.y()), - cv::Point(rect.x() + rect.width(), rect.y() + rect.height())); - }; - - int key_frame_idx = 0; - for (int i = 0; i < num_frames; ++i) { - const auto& scene_frame = scene_frames[i]; - auto viz_frame = absl::make_unique( - image_format, scene_frame.cols, scene_frame.rows); - cv::Mat viz_mat = formats::MatView(viz_frame.get()); - scene_frame.copyTo(viz_mat); - - if (is_key_frames[i]) { - const auto& bbox = key_frame_crop_results[key_frame_idx].region(); - crop_corners = RectToCvPoints(bbox); - region_corners.clear(); - region_colors.clear(); - const auto& detections = key_frame_infos[key_frame_idx].detections(); - for (int j = 0; j < detections.detections_size(); ++j) { - const auto& detection = detections.detections(j); - const auto corners = RectToCvPoints(detection.location()); - region_corners.push_back(corners); - if (detection.signal_type().has_standard()) { - switch (detection.signal_type().standard()) { - case SignalType::FACE_FULL: - case SignalType::FACE_LANDMARK: - case SignalType::FACE_ALL_LANDMARKS: - case SignalType::FACE_CORE_LANDMARKS: - region_colors.push_back(kGreen); - break; - case SignalType::HUMAN: - region_colors.push_back(kLightYellow); - break; - case SignalType::CAR: - region_colors.push_back(kMagenta); - break; - case SignalType::PET: - region_colors.push_back(kYellow); - break; - case SignalType::OBJECT: - region_colors.push_back(kCyan); - break; - case SignalType::MOTION: - case SignalType::TEXT: - case SignalType::LOGO: - region_colors.push_back(kRed); - break; - case SignalType::USER_HINT: - default: - region_colors.push_back(kWhite); - break; - } - } else { - // For the case where "custom" signal type is used. - region_colors.push_back(kWhite); - } - } - key_frame_idx++; - } - - cv::rectangle(viz_mat, crop_corners.first, crop_corners.second, kGreen, 4); - for (int j = 0; j < region_corners.size(); ++j) { - cv::rectangle(viz_mat, region_corners[j].first, region_corners[j].second, - region_colors[j], 2); - } - viz_frames->push_back(std::move(viz_frame)); - } - return absl::OkStatus(); -} - -namespace { -cv::Rect LimitBounds(const cv::Rect& rect, const int max_width, - const int max_height) { - cv::Rect result; - result.x = fmax(rect.x, 0); - result.y = fmax(rect.y, 0); - result.width = - result.x + rect.width >= max_width ? max_width - result.x : rect.width; - result.height = result.y + rect.height >= max_height ? max_height - result.y - : rect.height; - return result; -} -} // namespace - -absl::Status DrawDetectionAndFramingWindow( - const std::vector& org_scene_frames, - const std::vector& crop_from_locations, - const ImageFormat::Format image_format, const float overlay_opacity, - std::vector>* viz_frames) { - for (int i = 0; i < org_scene_frames.size(); i++) { - const auto& scene_frame = org_scene_frames[i]; - auto viz_frame = absl::make_unique( - image_format, scene_frame.cols, scene_frame.rows); - cv::Mat darkened = formats::MatView(viz_frame.get()); - scene_frame.copyTo(darkened); - cv::Mat overlay = cv::Mat::zeros(darkened.size(), darkened.type()); - cv::addWeighted(overlay, overlay_opacity, darkened, 1 - overlay_opacity, 0, - darkened); - const auto& crop_from_bounded = - LimitBounds(crop_from_locations[i], scene_frame.cols, scene_frame.rows); - scene_frame(crop_from_bounded).copyTo(darkened(crop_from_bounded)); - viz_frames->push_back(std::move(viz_frame)); - } - return absl::OkStatus(); -} - -absl::Status DrawFocusPointAndCropWindow( - const std::vector& scene_frames, - const std::vector& focus_point_frames, - const float overlay_opacity, const int crop_window_width, - const int crop_window_height, const ImageFormat::Format image_format, - std::vector>* viz_frames) { - RET_CHECK(viz_frames) << "Output viz frames is null."; - viz_frames->clear(); - const int num_frames = scene_frames.size(); - RET_CHECK_GT(crop_window_width, 0) << "Crop window width is non-positive."; - RET_CHECK_GT(crop_window_height, 0) << "Crop window height is non-positive."; - const int half_width = crop_window_width / 2; - const int half_height = crop_window_height / 2; - - for (int i = 0; i < num_frames; ++i) { - const auto& scene_frame = scene_frames[i]; - auto viz_frame = absl::make_unique( - image_format, scene_frame.cols, scene_frame.rows); - cv::Mat darkened = formats::MatView(viz_frame.get()); - scene_frame.copyTo(darkened); - cv::Mat viz_mat = darkened.clone(); - - // Darken the background. - cv::Mat overlay = cv::Mat::zeros(darkened.size(), darkened.type()); - cv::addWeighted(overlay, overlay_opacity, darkened, 1 - overlay_opacity, 0, - darkened); - - if (focus_point_frames[i].point_size() > 0) { - float center_x = 0.0f, center_y = 0.0f; - for (int j = 0; j < focus_point_frames[i].point_size(); ++j) { - const auto& point = focus_point_frames[i].point(j); - const int x = point.norm_point_x() * scene_frame.cols; - const int y = point.norm_point_y() * scene_frame.rows; - cv::circle(viz_mat, cv::Point(x, y), 3, kRed, cv::FILLED); - center_x += x; - center_y += y; - } - center_x /= focus_point_frames[i].point_size(); - center_y /= focus_point_frames[i].point_size(); - cv::Point min_corner(center_x - half_width, center_y - half_height); - cv::Point max_corner(center_x + half_width, center_y + half_height); - viz_mat(cv::Rect(min_corner, max_corner)) - .copyTo(darkened(cv::Rect(min_corner, max_corner))); - } - viz_frames->push_back(std::move(viz_frame)); - } - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/scene_cropping_viz.h b/examples/desktop/autoflip/quality/scene_cropping_viz.h deleted file mode 100644 index 01f8c5d..0000000 --- a/examples/desktop/autoflip/quality/scene_cropping_viz.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/focus_point.pb.h" -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CROPPING_VIZ_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CROPPING_VIZ_H_ - -#include - -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_format.pb.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// Draws the detections and crop regions on the scene frame. To make -// visualization smoother, applies piecewise-constant interpolation on non-key -// frames. This helps visualize the inputs to and outputs from the -// FrameCropRegionComputer. Uses thick green for computed crop regions. Uses -// different colors for different focus signals, faces are green, motion is -// magenta, logos are red, ocrs are yellow (foreground) and light yellow -// (background), brain objects are cyan, ica objects are orange, and the rest -// are white. -absl::Status DrawDetectionsAndCropRegions( - const std::vector& scene_frames, - const std::vector& is_key_frames, - const std::vector& key_frame_infos, - const std::vector& key_frame_crop_results, - const mediapipe::ImageFormat::Format image_format, - std::vector>* viz_frames); - -// Draws the focus point from the given FocusPointFrame and the crop window -// centered around it on the scene frame in red. This helps visualize the input -// to the retargeter. -absl::Status DrawFocusPointAndCropWindow( - const std::vector& scene_frames, - const std::vector& focus_point_frames, - const float overlay_opacity, const int crop_window_width, - const int crop_window_height, - const mediapipe::ImageFormat::Format image_format, - std::vector>* viz_frames); - -// Draws the final smoothed path of the camera retargeter by darkening the -// removed areas. -absl::Status DrawDetectionAndFramingWindow( - const std::vector& org_scene_frames, - const std::vector& crop_from_locations, - const ImageFormat::Format image_format, const float overlay_opacity, - std::vector>* viz_frames); - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_SCENE_CROPPING_VIZ_H_ diff --git a/examples/desktop/autoflip/quality/testdata/camera_motion_tracking_scene_frame_results.csv b/examples/desktop/autoflip/quality/testdata/camera_motion_tracking_scene_frame_results.csv deleted file mode 100644 index 81a57aa..0000000 --- a/examples/desktop/autoflip/quality/testdata/camera_motion_tracking_scene_frame_results.csv +++ /dev/null @@ -1,30 +0,0 @@ -0.14,0.6,5.00005 -0.125,0.583333,12.5 -0.11,0.566667,20 -0.0950004,0.55,27.5 -0.0800006,0.533334,35 -0.0650008,0.516668,42.5 -0.0500009,0.500001,50 -0.108329,0.549996,58.3333 -0.166662,0.599996,66.6667 -0.224995,0.649996,75 -0.283327,0.699995,83.3333 -0.34166,0.749995,91.6667 -0.399993,0.799994,100 -0.449994,0.666684,88.3357 -0.499993,0.533352,76.6691 -0.549993,0.40002,65.0024 -0.599992,0.266688,53.3357 -0.649992,0.133356,41.6691 -0.699991,2.40E-05,30.0024 -0.633346,0.033327,32.4999 -0.56668,0.06666,34.9999 -0.500014,0.099993,37.4999 -0.433348,0.133326,39.9998 -0.366682,0.166659,42.4998 -0.300016,0.199992,44.9999 -0.3,0.2,45.0005 -0.3,0.2,45.0005 -0.3,0.2,45.0005 -0.3,0.2,45.0005 -0.3,0.2,45.0005 diff --git a/examples/desktop/autoflip/quality/testdata/google.jpg b/examples/desktop/autoflip/quality/testdata/google.jpg deleted file mode 100644 index 25d7c19522bbfe074cba09be0f0c288d9486c177..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26189 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 diff --git a/examples/desktop/autoflip/quality/testdata/result_1.jpg b/examples/desktop/autoflip/quality/testdata/result_1.jpg deleted file mode 100644 index c03cd46108d10d57416381874e712adb46f2f71f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8386 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 diff --git a/examples/desktop/autoflip/quality/testdata/result_1_solid_background.jpg b/examples/desktop/autoflip/quality/testdata/result_1_solid_background.jpg deleted file mode 100644 index 97c0355edc7f08993f7f487b7a1b93f3e40778dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8495 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 F{5LtoN^AfC diff --git a/examples/desktop/autoflip/quality/testdata/result_2.5.jpg b/examples/desktop/autoflip/quality/testdata/result_2.5.jpg deleted file mode 100644 index be119b2f7125b8f1cf503bf345c14ccf0406070a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10416 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 diff --git a/examples/desktop/autoflip/quality/testdata/result_2.5_solid_background.jpg b/examples/desktop/autoflip/quality/testdata/result_2.5_solid_background.jpg deleted file mode 100644 index 2a23b16dcd95d1504611d93177c13eaa30c2dfdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10955 zcmdUV2Uru`*6tu6pdz3kf;x<0iQ~slaGW~L#LCSH;^F4x;yTYS zE_9yv5+4_ruGnjTetHKR2o))HJk5>F61b9j7!XVFr#+QBxnG zp{Av!p`o<)p_~V3SZGgOkhp&Iw7wM`uM4Z>i|`bBz8eK~YzDoX{8HAgehkOXu%G4N z6c7{=z9=Fsb5&MOUP1Myn!3gi=+y^8Fu<{o)r3#jhhYG}JV7KmDRQ;z==T78=?M z5=T#6*Qc{`In68if}Zt8cuGMX1D}+^CY!Zu@3Axd(o+IkKVAEqXTRr|-~W?m|I4xe z?bir!f|`obdDJWb7?@s6MeRrKF{h%0fxox>vZ*BEJF&~85i+od&ey7(2taP@71tg( zkb%_1k`$r|AuUjv47lWx7<_LNI8nlw6foB@;ne5AOmUq`AVLfkhCew{j(T#?>Sa|i;ILO`f9Ae?-~hm3&oDB^{BH!;LuXqwVCrGHot<%CmF!0(u79^Re| z2prcY1EsZuiz^X-9u9dAkY`k~raHl64oE-CNR@z8l;|R)xN6+UE(UcIMT88P@v*%c$6>Xbsl(s(<93?y zsa2mU9r|0OgBnL@=bBn`(@a5HA%IE_w7pi1ViQwtdflzM2mMe)4w)_Rlr}p#uq; z$vsC-n+>6qOpp4r_%Rn*mhbbeCa#gVS5zBe9dMp-u~Dn^gMterA!Rx&QM55%+^(uT zVtQJYzmU6Yp(6jbF5mD$!5D5kxWwABBzqInL~&ImE&Io#GQnw_XH@s?ME36|zv-q2 zaEJ@uvIPz{rl72`2# zWB}M9C_oFC0KA)EzuoLicI*yuh20qMaGXyi#&r?@6=f5p@&yYDb+~(PIm4$*#C!de z>zt=W4l&;wn=#%LQa=Xk*wR7=sQQzEWsK#fZwVrANyTUGbBpna0N16@lSq|`e$?i~ zi*=0ic!`O{@dxKlgw(wyFgYXJYIM&=&_@XO@IOc~x^oXuJT^h6cYJg#6Nepbl~+knIcAwf{emhE zs`vt|#>t1I8B^jZV(hLAtG?w8UpZENHQ--bNE;|aj1}l{P4447y*|^Af&tSqCLQpM zdP0SBcU%@f3(JM`j*5kF7uaVpiSO@fqDs?30>=X({rk{_N1VAsEzS}R!*s~JXKZGf z#al(%x6lZ}O3K9R&c&%L(G}6hB%iRyer^?h@@e;4c>@d-{SAhwC&4UfH!9V}-iIS+ z#y_MntsnY;3EzGUjT^>DJ9*y`RWD}k{pxi9sUEnr8qqbPIm~ZeFVl5Y^d%X1>0nA3 z66QmP2Qq6W3)0Y>fR1Io%OXmqP=I_bCwkxt~;hnhdCGnt5$ZCTY~yZcA7M@3aZ0RvYe4 z{;Qbw$N4`;`~R=W-|fEMYb=MuJ|Fh`Lr#8(;b_T_0X?ZZYgNZerD4)HH7MQcm^mr8 zZ_1V9!CMU@HP7f&NAxXS)#j+9PTAv0tyy4uGtG1o=71ZuJshv6p6j3!UG>o@v4O>? zL_Y&702R=oFoTbS=!9c_1Cs&tWrMX2)vEgj+K3g5)HD>DY0=t196vs>*YN)-- z8{lstN}`1)!kn86S3=~k!b@9xqa_@E9hqh0nJ>&%=&fYJNy%2Q#>nhtF zcm9D=&d2lUC!g+)R_8;kz7P`qYQXg*mNz%`U7i@5PD_;gd~Zo61Dt!?tMlm>4-`dj4BmB34JZm z4cZcgy)@UfV(0BuE*)DIwpO~@gKfXhs+pAYC58Ti@+ zm+GeEkXw*`zu(RG{#-m%h^+W>kb%}4ePmz_Lqfp^1Gj9JL%I47o6GP5`%;UKgi}Ai zBtH3_MD=GIk&Da=aGxmxDjEfZaNp;8g76?UuSpPUW%85zJg zN=!`Mj%NBo^?@5Gq1rZUIlxj8I>Sr{)bxrx_5Ki~v)!PbMOb6|T`q!AJX9mGv zdrNHw)j(?5S;_Qjm5JuD-3JfmUcTa92*iDtr{?7S22Q{0|)dXZv3g zg#V6phaO!27F_SBQ(3O*V&&8E?k>5TbTQ7?EtEel55b#e%*Ix$RbN8#tHDDzR5+yx*iGBb;v*uqU zO;V=@;Nc{;73udpQPNM9)MRt>t6K~y1%aAPYc*TCrNwgZkL!GO&eYF#n5N0VefUBR z{Oj4wrW1OZU(@cGMZP-Ap%mw18{DL%qwA6TrlIGKP5YBnl$2S()IU$+?o&n+$4?ri zlY^CNmJBdcN zgRLp|Q11Aj!^~d*QnDPNAy%mt;};s5j!Pc8w5&i^n3}#M9e;F|0>|EmY7RFzT?yC6~ zF>aYe;h+4x!z7BzE>9!Yv7}An;tn&CnYi+$no_kVhV1tR-KSF=4TblU8cAe;LeJ2E zi;-3M#Dj9^0F_Qpa3vDlON*aH4?NC<- zTDBEX>o^r-5OtVA)v_##>GkCr6{u{@xwYLf_CD?L$HZZ-Na%(N&tl}4cBjHo&9JC% z?SM~RzPl>(kPI0(-Qyi_mu_|7wE4bKZAVwnXFmSET+V9kGpkfeNKX=tqL0I8O|}8a zw{7az>CU4z6YMoAd$31~D}i`hZny#gwMyc4=$P9@1WA=>k4EpV*ADIEUBL%EMJNPb zwT(8wS>A1UTvKUUseCrxSUn@J;X$)pOYD_psrti5=GMgFVeXdEufQ4B|_PWn2lda715tYb7>Nh!l23P6U zPp*-C9NNUz7HTR(>Dkibj7QQc1Z%Ige`3i2J%&!HAlOk)e7nwlV7It3#$8X;#Fyd+8it|8k9_S<*m2W( z=;f{1_@e#aPg{15uycQg!PRQ25^-k z83AdJvq$ne({uPAkGa$Jgf{05bx2RI?kT_PhpKDi?bRaGJ^RIC-_J%Q=*W^-9y5_% zcuedyIT{K?ou^Qg-}v;jrMLi5HH-W<$i{tyoQDQzIuE4QucYj*1ox?SeS7MvQQerX z90~c#JFi&G#r*`mWM5@6PkKC5bp~`wyv*y9DQH)YK?M1E;s+UEBGqV5>IiJOmsGoT zoTi=G}C2nV$ceMu;As1V)tVvDqElJwM zHHj0o=f6tfyn<2Sk+0PIafA3IJCa=zJZ-k2aiM2ZgVeci%>Dvm1r|t@H zaAg5Yy`6u&;naI2oy^L3kTP^r2}zmf0?RIM&OMef<49=CP2Og`f8tQ{kkRGGsZs|F z>(XLQLSYcoCkteTxBKc!jyK3E8qHOzi-1|WERqCVoiFIN#e#y!z>1-gBD2r+_~3WC zU>egoUT%ly?*CJ|(63g#A(&?m2b~HYDCp9AOgc%f97&T~fKvk+BpsviHstO1e zb`V4y$tKJoCgOcfa*`OqGb{QORSoeqOAiSzddg&&T?DF@Rk~hqryqnSA&Vvj}ux?_X6u2iO=aK=& z|qHwR$%c>Gh_*49?Zbo1^#H54x6ttLCIg8+aD8)d@x{s~l>3e(7iqVEh75cRy!~}Co|#Y-O{sAg+$~ui!70L+cDbNLAOF6dFE@qN8ixdO zrpO^a81W=W9*WxQ;$7^ioog!*k%x`v*tcdQ33@qmcqIHxZFKU|^v5N!P>@;=My9=h z@uL;h;cWV|EO5(6jsLB_yxlkSYhwNMY0=7NdxE*J;nCr1pGhLfN(-^jl?5qj zm-d>UTvg$eHrlhZs7Y7LTQa<>S!eamdHvI(Ln9{F$!)Y%%qdNxlVcWs5TJV)9Tmx}(-m-#svwc@)#`+4W->70&-okD(r2f$s^2I`&P^0LY0!?YnOY=s5syzyW zx0iQ(BWSgkt>Dx5S)ctRIU=29S86rh%B@iLJC;8EhS#ZV1G)iwaK?3}24$V^49;RU zmiiT8b1%#$*7VKLAIU(W(B(&Kc#_5iPtT`Fk3CsQ>7rY%CRmkkOC;*eu{iUbiSZQE z53i{7f?0LTRTc=xs#5SEo!$f?$8qz^n)kUbVvW`aL$~GFN7Rc{~-H0i?}$gD)m zsoyea!AionWjwl>G3j;YE1IY1I6A5qbrdieDB}^dGrNAY|8PP6@M0m}&enNcHyAuZ zV`$g_J@MT;O@CR^q1AHqZCIfu2^2d>N}rJ3C#fq|bzbze-jT%&}*(wd!O*qoynh!EO*p7#IXIq9t=IEt(7bclbp#RfgqSW*sW33rYQumTmc% zA+c7HWo&&_Rh43ay-h^pY~;E_z1?l^MzjojQDe^M-@DpVI(Ov@>PZ|LK4b@FQ zU)Y4=aG94?CT4wC1-nH(Z|}s1!IBy*D761e4~HPyjS;1}<6)PbuJ~qtmN1MdnO}6^ zk}*QzqWf0A;$KX)UbZoZ^-16`q_K0<|w$ATl3$!qm3SM>Gr7CihM#`YDS} zxHOAK3@K5gmqT|(!Qa6%Z9AQ^CK~1! z?wMup6`Ri25edfPH8bsp7UOI8f?=lR4OV4}Og9j|Y<7@V;n)!Rf60g=x zkmA@i>h)l3lYYN6LYa5KbofRA+5jG{lD?NS^TPZ+T=$*h@YKA?_vz)G=BwHpzA2Ol zu#otxvh~*zQY0wi{d?BpzguJ?NK297{s?Q<5fldeCnH|BWrxmssGwQ-2_<{sd3e12 zJ#bSfC5|fWbSgHDB$O6$W5*lI82m6Sj*RGhx@!(YcCUnjQp4G1TuPz>uG5uxhrNEr z0S?R8i%fWk?RlzXykeT4cTh}~e0W7*kti(#t$B@SZ7EMgh;wrBRJq48?NlV74Z=Q_D!59*aM)n}{0DO&$X&K46 zpd>$u$f@X?xf5LHSnhnW{GsyeRWNLKvDYS;_xVSPV*K1#K2ap8$OLYBxj)5A;q<(_ zodSj4Vcz|zTF^9!)BYb+)IUofh*y8+$67*347<-L)d?TlVz&~dR%-(5{zI|$pDl#? zMf#Y{`DLXI4Ad0%QSK>Zk8@%&X0|&eD-}gJz4hgvF~Pt4UwV@mD-}(tM}K@LjR6RH z?V)vXng8J(mKM@9m7zW)c4UYxdYTFa6HdSKJD2t{rMY79{wLn;~%~N diff --git a/examples/desktop/autoflip/quality/testdata/result_3.4.jpg b/examples/desktop/autoflip/quality/testdata/result_3.4.jpg deleted file mode 100644 index 5ec4ea6ec9b8ee55343a54a70fef50923f352479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7803 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 S`#P6Lp}6huo&hy}{J#JgKQ2-L diff --git a/examples/desktop/autoflip/quality/testdata/result_3.4_solid_background.jpg b/examples/desktop/autoflip/quality/testdata/result_3.4_solid_background.jpg deleted file mode 100644 index a27cb530247788b6601a9ee8d5abaf7028fdbfd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8001 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 gIy(O#`2J?t|2rA(^b diff --git a/examples/desktop/autoflip/quality/utils.cc b/examples/desktop/autoflip/quality/utils.cc deleted file mode 100644 index 7b25930..0000000 --- a/examples/desktop/autoflip/quality/utils.cc +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/utils.h" - -#include - -#include -#include - -#include "absl/memory/memory.h" -#include "mediapipe/examples/desktop/autoflip/quality/math_utils.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" - -namespace mediapipe { -namespace autoflip { -namespace { - -// Returns true if the first pair should be considered greater than the second. -// This is used to sort detections by scores (from high to low). -bool PairCompare(const std::pair& pair1, - const std::pair& pair2) { - return pair1.first > pair2.first; -} - -} // namespace - -template -void ScaleRect(const T& original_location, const double scale_x, - const double scale_y, Rect* scaled_location) { - scaled_location->set_x(round(original_location.x() * scale_x)); - scaled_location->set_y(round(original_location.y() * scale_y)); - scaled_location->set_width(round(original_location.width() * scale_x)); - scaled_location->set_height(round(original_location.height() * scale_y)); -} -template void ScaleRect(const Rect&, const double, const double, Rect*); -template void ScaleRect(const RectF&, const double, const double, Rect*); - -void NormalizedRectToRect(const RectF& normalized_location, const int width, - const int height, Rect* location) { - ScaleRect(normalized_location, width, height, location); -} - -absl::Status ClampRect(const int width, const int height, Rect* location) { - return ClampRect(0, 0, width, height, location); -} - -absl::Status ClampRect(const int x0, const int y0, const int x1, const int y1, - Rect* location) { - RET_CHECK(!(location->x() >= x1 || location->x() + location->width() <= x0 || - location->y() >= y1 || location->y() + location->height() <= y0)); - - int clamped_left, clamped_right, clamped_top, clamped_bottom; - RET_CHECK(MathUtil::Clamp(x0, x1, location->x(), &clamped_left)); - RET_CHECK(MathUtil::Clamp(x0, x1, location->x() + location->width(), - &clamped_right)); - RET_CHECK(MathUtil::Clamp(y0, y1, location->y(), &clamped_top)); - RET_CHECK(MathUtil::Clamp(y0, y1, location->y() + location->height(), - &clamped_bottom)); - location->set_x(clamped_left); - location->set_y(clamped_top); - location->set_width(std::max(0, clamped_right - clamped_left)); - location->set_height(std::max(0, clamped_bottom - clamped_top)); - return absl::OkStatus(); -} - -void RectUnion(const Rect& rect_to_add, Rect* rect) { - const int x1 = std::min(rect->x(), rect_to_add.x()); - const int y1 = std::min(rect->y(), rect_to_add.y()); - const int x2 = std::max(rect->x() + rect->width(), - rect_to_add.x() + rect_to_add.width()); - const int y2 = std::max(rect->y() + rect->height(), - rect_to_add.y() + rect_to_add.height()); - rect->set_x(x1); - rect->set_y(y1); - rect->set_width(x2 - x1); - rect->set_height(y2 - y1); -} - -absl::Status PackKeyFrameInfo(const int64 frame_timestamp_ms, - const DetectionSet& detections, - const int original_frame_width, - const int original_frame_height, - const int feature_frame_width, - const int feature_frame_height, - KeyFrameInfo* key_frame_info) { - RET_CHECK(key_frame_info != nullptr) << "KeyFrameInfo is null"; - RET_CHECK(original_frame_width > 0 && original_frame_height > 0 && - feature_frame_width > 0 && feature_frame_height > 0) - << "Invalid frame size."; - - const double scale_x = - static_cast(original_frame_width) / feature_frame_width; - const double scale_y = - static_cast(original_frame_height) / feature_frame_height; - - key_frame_info->set_timestamp_ms(frame_timestamp_ms); - - // Scales detections and filter out the ones with no bounding boxes. - auto* processed_detections = key_frame_info->mutable_detections(); - for (const auto& original_detection : detections.detections()) { - bool has_valid_location = true; - Rect location; - if (original_detection.has_location_normalized()) { - NormalizedRectToRect(original_detection.location_normalized(), - original_frame_width, original_frame_height, - &location); - } else if (original_detection.has_location()) { - ScaleRect(original_detection.location(), scale_x, scale_y, &location); - } else { - has_valid_location = false; - LOG(ERROR) << "Detection missing a bounding box, skipped."; - } - if (has_valid_location) { - if (!ClampRect(original_frame_width, original_frame_height, &location) - .ok()) { - LOG(ERROR) << "Invalid detection bounding box, skipped."; - continue; - } - auto* detection = processed_detections->add_detections(); - *detection = original_detection; - *(detection->mutable_location()) = location; - } - } - - return absl::OkStatus(); -} - -absl::Status SortDetections(const DetectionSet& detections, - std::vector* required_regions, - std::vector* non_required_regions) { - required_regions->clear(); - non_required_regions->clear(); - - // Makes pairs of score and index. - std::vector> required_score_idx_pairs; - std::vector> non_required_score_idx_pairs; - for (int i = 0; i < detections.detections_size(); ++i) { - const auto& detection = detections.detections(i); - const auto pair = std::make_pair(detection.score(), i); - if (detection.is_required()) { - required_score_idx_pairs.push_back(pair); - } else { - non_required_score_idx_pairs.push_back(pair); - } - } - - // Sorts required regions by score. - std::stable_sort(required_score_idx_pairs.begin(), - required_score_idx_pairs.end(), PairCompare); - for (int i = 0; i < required_score_idx_pairs.size(); ++i) { - const int original_idx = required_score_idx_pairs[i].second; - required_regions->push_back(detections.detections(original_idx)); - } - - // Sorts non-required regions by score. - std::stable_sort(non_required_score_idx_pairs.begin(), - non_required_score_idx_pairs.end(), PairCompare); - for (int i = 0; i < non_required_score_idx_pairs.size(); ++i) { - const int original_idx = non_required_score_idx_pairs[i].second; - non_required_regions->push_back(detections.detections(original_idx)); - } - - return absl::OkStatus(); -} - -absl::Status SetKeyFrameCropTarget(const int frame_width, - const int frame_height, - const double target_aspect_ratio, - KeyFrameCropOptions* crop_options) { - RET_CHECK_NE(crop_options, nullptr) << "KeyFrameCropOptions is null."; - RET_CHECK_GT(frame_width, 0) << "Frame width is non-positive."; - RET_CHECK_GT(frame_height, 0) << "Frame height is non-positive."; - RET_CHECK_GT(target_aspect_ratio, 0) - << "Target aspect ratio is non-positive."; - const double input_aspect_ratio = - static_cast(frame_width) / frame_height; - const int crop_target_width = - target_aspect_ratio < input_aspect_ratio - ? std::round(frame_height * target_aspect_ratio) - : frame_width; - const int crop_target_height = - target_aspect_ratio < input_aspect_ratio - ? frame_height - : std::round(frame_width / target_aspect_ratio); - crop_options->set_target_width(crop_target_width); - crop_options->set_target_height(crop_target_height); - return absl::OkStatus(); -} - -absl::Status AggregateKeyFrameResults( - const KeyFrameCropOptions& key_frame_crop_options, - const std::vector& key_frame_crop_results, - const int scene_frame_width, const int scene_frame_height, - SceneKeyFrameCropSummary* scene_summary) { - RET_CHECK_NE(scene_summary, nullptr) - << "Output SceneKeyFrameCropSummary is null."; - - const int num_key_frames = key_frame_crop_results.size(); - - RET_CHECK_GT(scene_frame_width, 0) << "Non-positive frame width."; - RET_CHECK_GT(scene_frame_height, 0) << "Non-positive frame height."; - - const int target_width = key_frame_crop_options.target_width(); - const int target_height = key_frame_crop_options.target_height(); - RET_CHECK_GT(target_width, 0) << "Non-positive target width."; - RET_CHECK_GT(target_height, 0) << "Non-positive target height."; - RET_CHECK_LE(target_width, scene_frame_width) - << "Target width exceeds frame width."; - RET_CHECK_LE(target_height, scene_frame_height) - << "Target height exceeds frame height."; - - scene_summary->set_scene_frame_width(scene_frame_width); - scene_summary->set_scene_frame_height(scene_frame_height); - scene_summary->set_crop_window_width(target_width); - scene_summary->set_crop_window_height(target_height); - - // Handles the corner case of no key frames. - if (num_key_frames == 0) { - scene_summary->set_has_salient_region(false); - return absl::OkStatus(); - } - - scene_summary->set_num_key_frames(num_key_frames); - scene_summary->set_key_frame_center_min_x(scene_frame_width); - scene_summary->set_key_frame_center_max_x(0); - scene_summary->set_key_frame_center_min_y(scene_frame_height); - scene_summary->set_key_frame_center_max_y(0); - scene_summary->set_key_frame_min_score(std::numeric_limits::max()); - scene_summary->set_key_frame_max_score(0.0); - - const float half_height = target_height / 2.0f; - const float half_width = target_width / 2.0f; - bool has_salient_region = false; - int num_success_frames = 0; - std::unique_ptr required_crop_region_union = nullptr; - for (int i = 0; i < num_key_frames; ++i) { - auto* key_frame_compact_info = scene_summary->add_key_frame_compact_infos(); - const auto& result = key_frame_crop_results[i]; - key_frame_compact_info->set_timestamp_ms(result.timestamp_ms()); - if (result.are_required_regions_covered_in_target_size()) { - num_success_frames++; - } - if (result.region_is_empty()) { - key_frame_compact_info->set_center_x(-1.0); - key_frame_compact_info->set_center_y(-1.0); - key_frame_compact_info->set_score(-1.0); - continue; - } - - has_salient_region = true; - if (!result.required_region_is_empty()) { - if (required_crop_region_union == nullptr) { - required_crop_region_union = - absl::make_unique(result.required_region()); - } else { - RectUnion(result.required_region(), required_crop_region_union.get()); - } - } - - const auto& region = result.region(); - float original_center_x = region.x() + region.width() / 2.0f; - float original_center_y = region.y() + region.height() / 2.0f; - RET_CHECK_GE(original_center_x, 0) << "Negative horizontal center."; - RET_CHECK_GE(original_center_y, 0) << "Negative vertical center."; - // Ensure that centered region of target size does not exceed frame size. - float center_x, center_y; - RET_CHECK(MathUtil::Clamp(half_width, scene_frame_width - half_width, - original_center_x, ¢er_x)); - RET_CHECK(MathUtil::Clamp(half_height, scene_frame_height - half_height, - original_center_y, ¢er_y)); - key_frame_compact_info->set_center_x(center_x); - key_frame_compact_info->set_center_y(center_y); - scene_summary->set_key_frame_center_min_x( - std::min(scene_summary->key_frame_center_min_x(), center_x)); - scene_summary->set_key_frame_center_max_x( - std::max(scene_summary->key_frame_center_max_x(), center_x)); - scene_summary->set_key_frame_center_min_y( - std::min(scene_summary->key_frame_center_min_y(), center_y)); - scene_summary->set_key_frame_center_max_y( - std::max(scene_summary->key_frame_center_max_y(), center_y)); - - scene_summary->set_crop_window_width( - std::max(scene_summary->crop_window_width(), region.width())); - scene_summary->set_crop_window_height( - std::max(scene_summary->crop_window_height(), region.height())); - - const float score = result.region_score(); - RET_CHECK_GE(score, 0.0) << "Negative score."; - key_frame_compact_info->set_score(result.region_score()); - scene_summary->set_key_frame_min_score( - std::min(scene_summary->key_frame_min_score(), score)); - scene_summary->set_key_frame_max_score( - std::max(scene_summary->key_frame_max_score(), score)); - } - - scene_summary->set_has_salient_region(has_salient_region); - scene_summary->set_has_required_salient_region(required_crop_region_union != - nullptr); - if (required_crop_region_union) { - *(scene_summary->mutable_key_frame_required_crop_region_union()) = - *required_crop_region_union; - } - const float success_rate = - static_cast(num_success_frames) / num_key_frames; - scene_summary->set_frame_success_rate(success_rate); - const float motion_x = - static_cast(scene_summary->key_frame_center_max_x() - - scene_summary->key_frame_center_min_x()) / - scene_frame_width; - scene_summary->set_horizontal_motion_amount(motion_x); - const float motion_y = - static_cast(scene_summary->key_frame_center_max_y() - - scene_summary->key_frame_center_min_y()) / - scene_frame_height; - scene_summary->set_vertical_motion_amount(motion_y); - return absl::OkStatus(); -} - -absl::Status ComputeSceneStaticBordersSize( - const std::vector& static_features, int* top_border_size, - int* bottom_border_size) { - RET_CHECK(top_border_size) << "Output top border size is null."; - RET_CHECK(bottom_border_size) << "Output bottom border size is null."; - - *top_border_size = -1; - for (int i = 0; i < static_features.size(); ++i) { - bool has_static_top_border = false; - for (const auto& feature : static_features[i].border()) { - if (feature.relative_position() == Border::TOP) { - has_static_top_border = true; - const int static_size = feature.border_position().height(); - *top_border_size = (*top_border_size > 0) - ? std::min(*top_border_size, static_size) - : static_size; - } - } - if (!has_static_top_border) { - *top_border_size = 0; - break; - } - } - - *bottom_border_size = -1; - for (int i = 0; i < static_features.size(); ++i) { - bool has_static_bottom_border = false; - for (const auto& feature : static_features[i].border()) { - if (feature.relative_position() == Border::BOTTOM) { - has_static_bottom_border = true; - const int static_size = feature.border_position().height(); - *bottom_border_size = (*bottom_border_size > 0) - ? std::min(*bottom_border_size, static_size) - : static_size; - } - } - if (!has_static_bottom_border) { - *bottom_border_size = 0; - break; - } - } - - *top_border_size = std::max(0, *top_border_size); - *bottom_border_size = std::max(0, *bottom_border_size); - return absl::OkStatus(); -} - -absl::Status FindSolidBackgroundColor( - const std::vector& static_features, - const std::vector& static_features_timestamps, - const double min_fraction_solid_background_color, - bool* has_solid_background, - PiecewiseLinearFunction* background_color_l_function, - PiecewiseLinearFunction* background_color_a_function, - PiecewiseLinearFunction* background_color_b_function) { - RET_CHECK(has_solid_background) << "Output boolean is null."; - RET_CHECK(background_color_l_function) << "Output color l function is null."; - RET_CHECK(background_color_a_function) << "Output color a function is null."; - RET_CHECK(background_color_b_function) << "Output color b function is null."; - - *has_solid_background = false; - int solid_background_frames = 0; - for (int i = 0; i < static_features.size(); ++i) { - if (static_features[i].has_solid_background()) { - solid_background_frames++; - const auto& color = static_features[i].solid_background(); - const int64 timestamp = static_features_timestamps[i]; - // BorderDetectionCalculator sets color assuming the input frame is - // BGR, but in reality we have RGB, so we need to revert it here. - // TODO remove this custom logic in BorderDetectionCalculator, - // original CroppingCalculator, and this calculator. - cv::Mat3f rgb_mat(1, 1, cv::Vec3b(color.b(), color.g(), color.r())); - // Necessary scaling of the RGB values from [0, 255] to [0, 1] based on: - // https://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#cvtcolor - rgb_mat *= 1.0 / 255; - cv::Mat3f lab_mat(1, 1); - cv::cvtColor(rgb_mat, lab_mat, cv::COLOR_RGB2Lab); - // TODO change to piecewise constant interpolation if there is - // visual artifact. We can simply add one more point right before the - // next point with same value to mimic piecewise constant behavior. - const auto lab = lab_mat.at(0, 0); - background_color_l_function->AddPoint(timestamp, lab[0]); - background_color_a_function->AddPoint(timestamp, lab[1]); - background_color_b_function->AddPoint(timestamp, lab[2]); - } - } - - if (!static_features.empty() && - static_cast(solid_background_frames) / static_features.size() >= - min_fraction_solid_background_color) { - *has_solid_background = true; - } - return absl::OkStatus(); -} - -absl::Status AffineRetarget(const cv::Size& output_size, - const std::vector& frames, - const std::vector& affine_projection, - std::vector* cropped_frames) { - RET_CHECK(frames.size() == affine_projection.size()) - << "number of frames and retarget offsets must be the same."; - RET_CHECK(cropped_frames->size() == frames.size()) - << "Output vector cropped_frames must be populated with output images of " - "the same type, size and count."; - for (int i = 0; i < frames.size(); i++) { - RET_CHECK(frames[i].type() == (*cropped_frames)[i].type()) - << "input and output images must be the same type."; - const auto affine = affine_projection[i]; - RET_CHECK(affine.cols == 3) << "Affine matrix must be 2x3"; - RET_CHECK(affine.rows == 2) << "Affine matrix must be 2x3"; - cv::warpAffine(frames[i], (*cropped_frames)[i], affine, output_size); - } - return absl::OkStatus(); -} -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/utils.h b/examples/desktop/autoflip/quality/utils.h deleted file mode 100644 index 7285e82..0000000 --- a/examples/desktop/autoflip/quality/utils.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UTILS_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UTILS_H_ - -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/piecewise_linear_function.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// Packs detected features and timestamp (ms) into a KeyFrameInfo object. Scales -// features back to the original frame size if features have been detected on a -// different frame size. -absl::Status PackKeyFrameInfo(const int64 frame_timestamp_ms, - const DetectionSet& detections, - const int original_frame_width, - const int original_frame_height, - const int feature_frame_width, - const int feature_frame_height, - KeyFrameInfo* key_frame_info); - -// Sorts required and non-required salient regions given a detection set. -absl::Status SortDetections(const DetectionSet& detections, - std::vector* required_regions, - std::vector* non_required_regions); - -// Sets the target crop size in KeyFrameCropOptions based on frame size and -// target aspect ratio so that the target crop size covers the biggest area -// possible in the frame. -absl::Status SetKeyFrameCropTarget(const int frame_width, - const int frame_height, - const double target_aspect_ratio, - KeyFrameCropOptions* crop_options); - -// Aggregates information from KeyFrameInfos and KeyFrameCropResults into -// SceneKeyFrameCropSummary. -absl::Status AggregateKeyFrameResults( - const KeyFrameCropOptions& key_frame_crop_options, - const std::vector& key_frame_crop_results, - const int scene_frame_width, const int scene_frame_height, - SceneKeyFrameCropSummary* scene_summary); - -// Computes the static top and border size across a scene given a vector of -// StaticFeatures over frames. -absl::Status ComputeSceneStaticBordersSize( - const std::vector& static_features, int* top_border_size, - int* bottom_border_size); - -// Finds the solid background colors in a scene from input StaticFeatures. -// Sets has_solid_background to true if the number of frames with solid -// background color exceeds given threshold, i.e., -// min_fraction_solid_background_color. Builds the background color -// interpolation functions in Lab space using input timestamps. -absl::Status FindSolidBackgroundColor( - const std::vector& static_features, - const std::vector& static_features_timestamps, - const double min_fraction_solid_background_color, - bool* has_solid_background, - PiecewiseLinearFunction* background_color_l_function, - PiecewiseLinearFunction* background_color_a_function, - PiecewiseLinearFunction* background_color_b_function); - -// Helpers to scale, clamp, and take union of rectangles. These functions do not -// check for pointers not being null or rectangles being valid. - -// Scales a rectangle given horizontal and vertical scaling factors. -template -void ScaleRect(const T& original_location, const double scale_x, - const double scale_y, Rect* scaled_location); - -// Converts a normalized rectangle to a rectangle given width and height. -void NormalizedRectToRect(const RectF& normalized_location, const int width, - const int height, Rect* location); - -// Clamps a rectangle to lie within [x0, y0] and [x1, y1]. Returns true if the -// rectangle has any overlapping with the target window. -absl::Status ClampRect(const int x0, const int y0, const int x1, const int y1, - Rect* location); - -// Convenience function to clamp a rectangle to lie within [0, 0] and -// [width, height]. -absl::Status ClampRect(const int width, const int height, Rect* location); - -// Enlarges a given rectangle to cover a new rectangle to be added. -void RectUnion(const Rect& rect_to_add, Rect* rect); - -// Performs an affine retarget on a list of input images. Output vector -// cropped_frames must be filled with Mats of the same size as output_size and -// type. -absl::Status AffineRetarget(const cv::Size& output_size, - const std::vector& frames, - const std::vector& affine_projection, - std::vector* cropped_frames); - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_UTILS_H_ diff --git a/examples/desktop/autoflip/quality/utils_test.cc b/examples/desktop/autoflip/quality/utils_test.cc deleted file mode 100644 index b10e378..0000000 --- a/examples/desktop/autoflip/quality/utils_test.cc +++ /dev/null @@ -1,1039 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/utils.h" - -#include -#include - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/cropping.pb.h" -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/integral_types.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { -namespace { - -using ::testing::HasSubstr; - -const int64 kTimestamp = 0; -const int kOriginalWidth = 100; -const int kOriginalHeight = 100; -const double kTargetAspectRatio = 1.5; -const int kNumKeyFrames = 5; -const int64 kKeyFrameTimestampDiff = 1e6 / kNumKeyFrames; -const int kTargetWidth = 50; -const int kTargetHeight = 50; - -// Makes a rectangle given the corner (x, y) and the size (width, height). -Rect MakeRect(const int x, const int y, const int width, const int height) { - Rect rect; - rect.set_x(x); - rect.set_y(y); - rect.set_width(width); - rect.set_height(height); - return rect; -} - -// Makes a rectangle given the center (center_x, center_y) and the half size -// (half_width, half_height). -Rect MakeRectFromCenterAndHalfSize(const float center_x, const float center_y, - const float half_width, - const float half_height) { - Rect rect; - rect.set_x(center_x - half_width); - rect.set_y(center_y - half_height); - rect.set_width(half_width * 2); - rect.set_height(half_height * 2); - return rect; -} - -// Computes the center of a rectangle as a pair (center_x, center_y). -std::pair RectCenter(const Rect& rect) { - return std::make_pair(rect.x() + rect.width() / 2.0, - rect.y() + rect.height() / 2.0); -} - -// Makes a normalized rectangle given the corner (x, y) and the size (width, -// height). -RectF MakeRectF(const double x, const double y, const double width, - const double height) { - RectF rectf; - rectf.set_x(x); - rectf.set_y(y); - rectf.set_width(width); - rectf.set_height(height); - return rectf; -} - -// Adds a detection to the detection set given location. -void AddDetectionFromLocation(const Rect& loc, DetectionSet* detections) { - auto* detection = detections->add_detections(); - *(detection->mutable_location()) = loc; -} - -// Adds a detection to the detection set given normalized location. -void AddDetectionFromNormalizedLocation(const RectF& normalized_loc, - DetectionSet* detections) { - auto* detection = detections->add_detections(); - *(detection->mutable_location_normalized()) = normalized_loc; -} - -// Checks whether two rectangles are the same, with an optional scaling factor. -bool CheckRectsEqual(const Rect& rect1, const Rect& rect2, - const double scale_x = 1.0, const double scale_y = 1.0) { - return (static_cast(round(scale_x * rect2.x())) == rect1.x() && - static_cast(round(scale_y * rect2.y())) == rect1.y() && - static_cast(round(scale_x * rect2.width())) == rect1.width() && - static_cast(round(scale_y * rect2.height())) == rect1.height()); -} - -// Adds a detection to the detection set given its score and whether it is -// required. -void AddDetectionFromScoreAndIsRequired(const double score, - const bool is_required, - DetectionSet* detections) { - auto* detection = detections->add_detections(); - detection->set_score(score); - detection->set_is_required(is_required); -} - -// Returns default settings for KeyFrameCropOptions. Populates target size to be -// the default target size. -KeyFrameCropOptions GetDefaultKeyFrameCropOptions() { - KeyFrameCropOptions key_frame_crop_options; - key_frame_crop_options.set_target_width(kTargetWidth); - key_frame_crop_options.set_target_height(kTargetHeight); - return key_frame_crop_options; -} - -// Returns default values for KeyFrameCropResults. Sets each frame to have -// covered all the required regions and non-required regions, and have required -// crop region (10, 10+20) x (10, 10+20), (full) crop region (0, 50) x (0, 50), -// and region score 1.0. -std::vector GetDefaultKeyFrameCropResults() { - std::vector key_frame_crop_results(kNumKeyFrames); - for (int i = 0; i < kNumKeyFrames; ++i) { - key_frame_crop_results[i].set_are_required_regions_covered_in_target_size( - true); - key_frame_crop_results[i].set_fraction_non_required_covered(1.0); - key_frame_crop_results[i].set_region_is_empty(false); - key_frame_crop_results[i].set_required_region_is_empty(false); - *(key_frame_crop_results[i].mutable_region()) = MakeRect(0, 0, 50, 50); - *(key_frame_crop_results[i].mutable_required_region()) = - MakeRect(10, 10, 20, 20); - key_frame_crop_results[i].set_region_score(1.0); - key_frame_crop_results[i].set_timestamp_ms(kKeyFrameTimestampDiff * i); - } - return key_frame_crop_results; -} - -// Checks that ScaleRect properly scales Rect and RectF objects. -TEST(UtilTest, ScaleRect) { - Rect scaled_rect; - ScaleRect(MakeRect(10, 10, 20, 30), 1.5, 2.0, &scaled_rect); - EXPECT_TRUE(CheckRectsEqual(scaled_rect, MakeRect(15, 20, 30, 60))); - - ScaleRect(MakeRectF(0.5, 0.9, 1.36, 0.748), 100, 50, &scaled_rect); - EXPECT_TRUE(CheckRectsEqual(scaled_rect, MakeRect(50, 45, 136, 37))); -} - -// Checks that NormalizedRectToRect properly converts a RectF object to a Rect -// object given width and height. -TEST(UtilTest, NormalizedRectToRect) { - const RectF normalized_rect = MakeRectF(0.1, 1.0, 2.5, 0.9); - Rect rect; - NormalizedRectToRect(normalized_rect, 100, 100, &rect); - EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 100, 250, 90))); -} - -// Checks that ClampRect properly clamps a Rect object in [x0, y0] and [x1, y1]. -TEST(UtilTest, ClampRect) { - // Overlaps at a corner. - Rect rect = MakeRect(-10, -10, 80, 20); - MP_EXPECT_OK(ClampRect(0, 0, 100, 100, &rect)); - EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(0, 0, 70, 10))); - // Overlaps on a side. - rect = MakeRect(10, -10, 80, 20); - MP_EXPECT_OK(ClampRect(0, 0, 100, 100, &rect)); - EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 0, 80, 10))); - // Inside. - rect = MakeRect(10, 10, 80, 10); - MP_EXPECT_OK(ClampRect(0, 0, 100, 100, &rect)); - EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 10, 80, 10))); - // Outside. - rect = MakeRect(-10, 10, 0, 0); - EXPECT_FALSE(ClampRect(0, 0, 100, 100, &rect).ok()); -} - -// Checks that ClampRect properly clamps a Rect object in [0, 0] and [width, -// height]. -TEST(UtilTest, ClampRectConvenienceFunction) { - Rect rect = MakeRect(-10, 0, 80, 10); - MP_EXPECT_OK(ClampRect(kOriginalWidth, kOriginalHeight, &rect)); - EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(0, 0, 70, 10))); - rect = MakeRect(-10, 0, 120, 10); - MP_EXPECT_OK(ClampRect(kOriginalWidth, kOriginalHeight, &rect)); - EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(0, 0, 100, 10))); - rect = MakeRect(10, 0, 70, 120); - MP_EXPECT_OK(ClampRect(kOriginalWidth, kOriginalHeight, &rect)); - EXPECT_TRUE(CheckRectsEqual(rect, MakeRect(10, 0, 70, 100))); -} - -// Checks that RectUnion properly takes the union of two Rect objects. -TEST(UtilTest, RectUnion) { - // Base rectangle and new rectangle are partially overlapping. - Rect base_rect = MakeRect(40, 40, 40, 40); - RectUnion(MakeRect(20, 30, 40, 40), &base_rect); - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(20, 30, 60, 50))); - // Base rectangle contains new rectangle. - base_rect = MakeRect(40, 40, 40, 40); - RectUnion(MakeRect(50, 50, 10, 10), &base_rect); - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(40, 40, 40, 40))); - // Base rectangle is contained by new rectangle. - base_rect = MakeRect(40, 40, 40, 40); - RectUnion(MakeRect(30, 30, 50, 50), &base_rect); - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(30, 30, 50, 50))); - // Base rectangle and new rectangle are disjoint. - base_rect = MakeRect(40, 40, 40, 40); - RectUnion(MakeRect(15, 25, 20, 5), &base_rect); - EXPECT_TRUE(CheckRectsEqual(base_rect, MakeRect(15, 25, 65, 55))); -} - -// Checks that PackCropFrame fails on nullptr return object. -TEST(UtilTest, PackKeyFrameInfoFailsOnNullObject) { - DetectionSet detections; - const int feature_width = kOriginalWidth, feature_height = kOriginalHeight; - - KeyFrameInfo* key_frame_info_ptr = nullptr; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, key_frame_info_ptr); - EXPECT_FALSE(status.ok()); -} - -// Checks that PackCropFrame fails on invalid frame size. -TEST(UtilTest, PackKeyFrameInfoFailsOnInvalidFrameSize) { - DetectionSet detections; - const int feature_width = -1, feature_height = 0; - - KeyFrameInfo key_frame_info; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, &key_frame_info) - .ToString(); - EXPECT_THAT(status, testing::HasSubstr("Invalid frame size")); -} - -// Checks that PackCropFrame correctly packs timestamp. -TEST(UtilTest, PackKeyFrameInfoPacksTimestamp) { - DetectionSet detections; - const int feature_width = kOriginalWidth, feature_height = kOriginalHeight; - - KeyFrameInfo key_frame_info; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, &key_frame_info); - - MP_EXPECT_OK(status); - EXPECT_EQ(key_frame_info.timestamp_ms(), kTimestamp); -} - -// Checks that PackCropFrame correctly packs detections. -TEST(UtilTest, PackKeyFrameInfoPacksDetections) { - DetectionSet detections; - AddDetectionFromLocation(MakeRect(0, 0, 10, 10), &detections); - AddDetectionFromLocation(MakeRect(20, 20, 30, 10), &detections); - const int feature_width = kOriginalWidth, feature_height = kOriginalHeight; - - KeyFrameInfo key_frame_info; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, &key_frame_info); - - MP_EXPECT_OK(status); - EXPECT_EQ(key_frame_info.detections().detections_size(), - detections.detections_size()); - for (int i = 0; i < detections.detections_size(); ++i) { - const auto& original_rect = detections.detections(i).location(); - const auto& rect = key_frame_info.detections().detections(i).location(); - EXPECT_TRUE(CheckRectsEqual(rect, original_rect)); - } -} - -// Checks that PackCropFrame correctly converts normalized location to location. -TEST(UtilTest, PackKeyFrameInfoUnnormalizesLocations) { - DetectionSet detections; - AddDetectionFromNormalizedLocation(MakeRectF(0.1, 0.1, 0.1, 0.1), - &detections); - AddDetectionFromNormalizedLocation(MakeRectF(0.242, 0.256, 0.378, 0.399), - &detections); - const int feature_width = kOriginalWidth, feature_height = kOriginalHeight; - - KeyFrameInfo key_frame_info; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, &key_frame_info); - - MP_EXPECT_OK(status); - const auto& out_rect1 = key_frame_info.detections().detections(0).location(); - const auto& out_rect2 = key_frame_info.detections().detections(1).location(); - EXPECT_TRUE(CheckRectsEqual(out_rect1, MakeRect(10, 10, 10, 10))); - EXPECT_TRUE(CheckRectsEqual(out_rect2, MakeRect(24, 26, 38, 40))); -} - -// Checks that PackCropFrame correctly scales location. -TEST(UtilTest, PackKeyFrameInfoScalesLocations) { - DetectionSet detections; - AddDetectionFromLocation(MakeRect(10, 10, 10, 10), &detections); - AddDetectionFromLocation(MakeRect(20, 20, 30, 30), &detections); - const double scaling = 2.0; - const int feature_width = kOriginalWidth / scaling; - const int feature_height = kOriginalHeight / scaling; - - KeyFrameInfo key_frame_info; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, &key_frame_info); - - MP_EXPECT_OK(status); - EXPECT_EQ(key_frame_info.detections().detections_size(), - detections.detections_size()); - for (int i = 0; i < detections.detections_size(); ++i) { - const auto& original_rect = detections.detections(i).location(); - const auto& rect = key_frame_info.detections().detections(i).location(); - EXPECT_TRUE(CheckRectsEqual(rect, original_rect, scaling, scaling)); - } -} - -// Checks that PackCropFrame correctly clamps location to be within frame size. -TEST(UtilTest, PackKeyFrameInfoClampsLocations) { - DetectionSet detections; - AddDetectionFromLocation(MakeRect(10, 10, 100, 10), &detections); - AddDetectionFromLocation(MakeRect(0, -10, 110, 100), &detections); - const int feature_width = kOriginalWidth, feature_height = kOriginalHeight; - - KeyFrameInfo key_frame_info; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, &key_frame_info); - - MP_EXPECT_OK(status); - EXPECT_EQ(key_frame_info.detections().detections_size(), - detections.detections_size()); - const auto& out_rect1 = key_frame_info.detections().detections(0).location(); - const auto& out_rect2 = key_frame_info.detections().detections(1).location(); - EXPECT_TRUE(CheckRectsEqual(out_rect1, MakeRect(10, 10, 90, 10))); - EXPECT_TRUE(CheckRectsEqual(out_rect2, MakeRect(0, 0, 100, 90))); -} - -// Checks that PackCropFrame correctly clamps normalized location to be within -// frame size. -TEST(UtilTest, PackKeyFrameInfoClampsNormalizedLocations) { - DetectionSet detections; - AddDetectionFromNormalizedLocation(MakeRectF(-0.05, 0.3, 0.4, 0.8), - &detections); - AddDetectionFromNormalizedLocation(MakeRectF(0.05, -0.1, 1.0, 1.1), - &detections); - const int feature_width = kOriginalWidth, feature_height = kOriginalHeight; - - KeyFrameInfo key_frame_info; - const auto status = - PackKeyFrameInfo(kTimestamp, detections, kOriginalWidth, kOriginalHeight, - feature_width, feature_height, &key_frame_info); - - MP_EXPECT_OK(status); - EXPECT_EQ(key_frame_info.detections().detections_size(), - detections.detections_size()); - const auto& out_rect1 = key_frame_info.detections().detections(0).location(); - const auto& out_rect2 = key_frame_info.detections().detections(1).location(); - EXPECT_TRUE(CheckRectsEqual(out_rect1, MakeRect(0, 30, 35, 70))); - EXPECT_TRUE(CheckRectsEqual(out_rect2, MakeRect(5, 0, 95, 100))); -} - -// Checks that SortDetections correctly handles empty regions. -TEST(UtilTest, SortDetectionsHandlesEmptyRegions) { - DetectionSet detections; - std::vector required, non_required; - MP_EXPECT_OK(SortDetections(detections, &required, &non_required)); - EXPECT_EQ(detections.detections_size(), - required.size() + non_required.size()); -} - -// Checks that SortDetections correctly separates required and non-required -// salient regions. -TEST(UtilTest, SortDetectionsSeparatesRequiredAndNonRequiredRegions) { - DetectionSet detections; - auto gen_bool = std::bind(std::uniform_int_distribution<>(0, 1), - std::default_random_engine()); - for (int i = 0; i < 100; ++i) { - const bool is_required = gen_bool(); - AddDetectionFromScoreAndIsRequired(1.0, is_required, &detections); - } - - std::vector required, non_required; - MP_EXPECT_OK(SortDetections(detections, &required, &non_required)); - EXPECT_EQ(detections.detections_size(), - required.size() + non_required.size()); - for (int i = 0; i < required.size(); ++i) { - EXPECT_TRUE(required[i].is_required()); - } - for (int i = 0; i < non_required.size(); ++i) { - EXPECT_FALSE(non_required[i].is_required()); - } -} - -// Checks that SortDetections correctly sorts regions based on scores. -TEST(UtilTest, SortDetectionsSortsRegions) { - DetectionSet detections; - auto gen_score = std::bind(std::uniform_real_distribution<>(0.0, 1.0), - std::default_random_engine()); - auto gen_bool = std::bind(std::uniform_int_distribution<>(0, 1), - std::default_random_engine()); - for (int i = 0; i < 100; ++i) { - const double score = gen_score(); - const bool is_required = gen_bool(); - AddDetectionFromScoreAndIsRequired(score, is_required, &detections); - } - - std::vector required, non_required; - MP_EXPECT_OK(SortDetections(detections, &required, &non_required)); - EXPECT_EQ(detections.detections_size(), - required.size() + non_required.size()); - for (int i = 0; i < required.size() - 1; ++i) { - EXPECT_GE(required[i].score(), required[i + 1].score()); - } - for (int i = 0; i < non_required.size() - 1; ++i) { - EXPECT_GE(non_required[i].score(), non_required[i + 1].score()); - } -} - -// Checks that SetKeyFrameCropTarget checks KeyFrameCropOptions is not null. -TEST(UtilTest, SetKeyFrameCropTargetChecksKeyFrameCropOptionsNotNull) { - const auto status = SetKeyFrameCropTarget(kOriginalWidth, kOriginalHeight, - kTargetAspectRatio, nullptr); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - testing::HasSubstr("KeyFrameCropOptions is null.")); -} - -// Checks that SetKeyFrameCropTarget checks frame size and target aspect ratio -// are valid. -TEST(UtilTest, SetKeyFrameCropTargetChecksFrameSizeAndTargetAspectRatioValid) { - KeyFrameCropOptions crop_options; - auto status = SetKeyFrameCropTarget(0, kOriginalHeight, kTargetAspectRatio, - &crop_options); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - testing::HasSubstr("Frame width is non-positive.")); - - status = - SetKeyFrameCropTarget(kOriginalWidth, kOriginalHeight, 0, &crop_options); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - testing::HasSubstr("Target aspect ratio is non-positive.")); -} - -// Checks that SetKeyFrameCropTarget correctly sets the target crop size. -TEST(UtilTest, SetKeyFrameCropTargetSetsTargetSizeCorrectly) { - KeyFrameCropOptions crop_options; - // 1a) square -> square. - MP_EXPECT_OK(SetKeyFrameCropTarget(101, 101, 1.0, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 101); - EXPECT_EQ(crop_options.target_height(), 101); - // 1b) square -> landscape. - MP_EXPECT_OK(SetKeyFrameCropTarget(101, 101, 1.5, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 101); - EXPECT_EQ(crop_options.target_height(), 67); - // 1c) square -> vertical. - MP_EXPECT_OK(SetKeyFrameCropTarget(101, 101, 0.5, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 51); - EXPECT_EQ(crop_options.target_height(), 101); - // 2a) landscape -> square. - MP_EXPECT_OK(SetKeyFrameCropTarget(128, 72, 1.0, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 72); - EXPECT_EQ(crop_options.target_height(), 72); - // 2b) landscape -> more landscape. - MP_EXPECT_OK(SetKeyFrameCropTarget(128, 72, 2.0, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 128); - EXPECT_EQ(crop_options.target_height(), 64); - // 2c) landscape -> vertical. - MP_EXPECT_OK(SetKeyFrameCropTarget(128, 72, 0.7, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 50); - EXPECT_EQ(crop_options.target_height(), 72); - // 3a) vertical -> square. - MP_EXPECT_OK(SetKeyFrameCropTarget(90, 160, 1.0, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 90); - EXPECT_EQ(crop_options.target_height(), 90); - // 3b) vertical -> more vertical. - MP_EXPECT_OK(SetKeyFrameCropTarget(90, 160, 0.36, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 58); - EXPECT_EQ(crop_options.target_height(), 160); - // 3c) vertical -> landscape. - MP_EXPECT_OK(SetKeyFrameCropTarget(90, 160, 1.2, &crop_options)); - EXPECT_EQ(crop_options.target_width(), 90); - EXPECT_EQ(crop_options.target_height(), 75); -} - -// Checks that AggregateKeyFrameResults checks output pointer is not null. -TEST(UtilTest, AggregateKeyFrameResultsChecksOutputNotNull) { - const auto status = AggregateKeyFrameResults( - GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(), - kOriginalWidth, kOriginalHeight, nullptr); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Output SceneKeyFrameCropSummary is null.")); -} - -// Checks that AggregateKeyFrameResults handles the case of no key frames. -TEST(UtilTest, AggregateKeyFrameResultsHandlesNoKeyFrames) { - std::vector key_frame_crop_results(0); - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); -} - -// Checks that AggregateKeyFrameResults checks that frame size is valid. -TEST(UtilTest, AggregateKeyFrameResultsChecksFrameSizeValid) { - SceneKeyFrameCropSummary scene_summary; - const auto status = AggregateKeyFrameResults( - GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(), - kOriginalWidth, 0, &scene_summary); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Non-positive frame height.")); -} - -// Checks that AggregateKeyFrameResults checks that target size is valid. -TEST(UtilTest, AggregateKeyFrameResultsChecksTargetSizeValid) { - KeyFrameCropOptions key_frame_crop_options; - key_frame_crop_options.set_target_width(0); - SceneKeyFrameCropSummary scene_summary; - - const auto status = AggregateKeyFrameResults( - key_frame_crop_options, GetDefaultKeyFrameCropResults(), kOriginalWidth, - kOriginalHeight, &scene_summary); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Non-positive target width.")); -} - -// Checks that AggregateKeyFrameResults checks that target size does not exceed -// frame size. -TEST(UtilTest, AggregateKeyFrameResultsChecksTargetSizeNotExceedFrameSize) { - auto key_frame_crop_options = GetDefaultKeyFrameCropOptions(); - key_frame_crop_options.set_target_width(kOriginalWidth + 1); - SceneKeyFrameCropSummary scene_summary; - - const auto status = AggregateKeyFrameResults( - key_frame_crop_options, GetDefaultKeyFrameCropResults(), kOriginalWidth, - kOriginalHeight, &scene_summary); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - HasSubstr("Target width exceeds frame width.")); -} - -// Checks that AggregateKeyFrameResults packs KeyFrameCompactInfos. -TEST(UtilTest, AggregateKeyFrameResultsPacksKeyFrameCompactInfos) { - const auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - - EXPECT_EQ(scene_summary.num_key_frames(), kNumKeyFrames); - EXPECT_EQ(scene_summary.key_frame_compact_infos_size(), kNumKeyFrames); - for (int i = 0; i < kNumKeyFrames; ++i) { - const auto& compact_info = scene_summary.key_frame_compact_infos(i); - EXPECT_EQ(compact_info.timestamp_ms(), - key_frame_crop_results[i].timestamp_ms()); - const auto center = RectCenter(key_frame_crop_results[i].region()); - EXPECT_FLOAT_EQ(compact_info.center_x(), center.first); - EXPECT_FLOAT_EQ(compact_info.center_y(), center.second); - EXPECT_FLOAT_EQ(compact_info.score(), - key_frame_crop_results[i].region_score()); - } -} - -// Checks that AggregateKeyFrameResults ensures the centered region of target -// size fits in frame bound. -TEST(UtilTest, AggregateKeyFrameResultsEnsuresCropRegionFitsInFrame) { - std::vector key_frame_crop_results(1); - auto* crop_region = key_frame_crop_results[0].mutable_region(); - crop_region->set_x(0); - crop_region->set_y(0); - crop_region->set_width(10); - crop_region->set_height(10); - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - - EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth); - EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight); - const auto& compact_info = scene_summary.key_frame_compact_infos(0); - const float left = compact_info.center_x() - kTargetWidth / 2.0f; - const float right = compact_info.center_x() + kTargetWidth / 2.0f; - const float top = compact_info.center_y() - kTargetWidth / 2.0f; - const float bottom = compact_info.center_y() + kTargetWidth / 2.0f; - // Crop window is in the frame. - EXPECT_GE(left, 0); - EXPECT_LE(right, kOriginalWidth); - EXPECT_GE(top, 0); - EXPECT_LE(bottom, kOriginalHeight); - // Crop window covers input crop region. - EXPECT_LE(left, crop_region->x()); - EXPECT_GE(right, crop_region->x() + crop_region->width()); - EXPECT_LE(top, crop_region->y()); - EXPECT_GE(bottom, crop_region->y() + crop_region->height()); -} - -// Checks that AggregateKeyFrameResults sets centers and scores to -1.0 for key -// frames with empty regions. -TEST(UtilTest, - AggregateKeyFrameResultsSetsMinusOneForKeyFramesWithEmptyRegions) { - std::vector key_frame_crop_results(1); - key_frame_crop_results[0].set_region_is_empty(true); - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - - const auto& compact_info = scene_summary.key_frame_compact_infos(0); - EXPECT_FLOAT_EQ(compact_info.center_x(), -1.0f); - EXPECT_FLOAT_EQ(compact_info.center_y(), -1.0f); - EXPECT_FLOAT_EQ(compact_info.score(), -1.0f); -} - -// Checks that AggregateKeyFrameResults rejects negative center. -TEST(UtilTest, AggregateKeyFrameResultsRejectsNegativeCenter) { - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - auto* region = key_frame_crop_results[0].mutable_region(); - *region = MakeRectFromCenterAndHalfSize(10, -1.0, 10, 10); - SceneKeyFrameCropSummary scene_summary; - - const auto status = AggregateKeyFrameResults( - GetDefaultKeyFrameCropOptions(), key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Negative vertical center.")); -} - -// Checks that AggregateKeyFrameResults rejects negative score. -TEST(UtilTest, AggregateKeyFrameResultsRejectsNegativeScore) { - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - key_frame_crop_results[0].set_region_score(-1.0); - SceneKeyFrameCropSummary scene_summary; - - const auto status = AggregateKeyFrameResults( - GetDefaultKeyFrameCropOptions(), key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), HasSubstr("Negative score.")); -} - -// Checks that AggregateKeyFrameResults properly sets center ranges. -TEST(UtilTest, AggregateKeyFrameResultsSetsCenterRanges) { - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - const std::vector centers_x = {30.0, 20.0, 45.0, 3.0, 10.0}; - const std::vector centers_y = {10.0, 0.0, 5.0, 30.0, 20.0}; - const int half_width = 25, half_height = 25; - for (int i = 0; i < kNumKeyFrames; ++i) { - auto* region = key_frame_crop_results[i].mutable_region(); - *region = MakeRectFromCenterAndHalfSize(centers_x[i], centers_y[i], - half_width, half_height); - } - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - - EXPECT_FLOAT_EQ(scene_summary.key_frame_center_min_x(), 25.0f); - EXPECT_FLOAT_EQ(scene_summary.key_frame_center_max_x(), 45.0f); - EXPECT_FLOAT_EQ(scene_summary.key_frame_center_min_y(), 25.0f); - EXPECT_FLOAT_EQ(scene_summary.key_frame_center_max_y(), 30.0f); -} - -// Checks that AggregateKeyFrameResults properly sets score range. -TEST(UtilTest, AggregateKeyFrameResultsSetsScoreRange) { - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - const std::vector scores = {0.9, 0.1, 1.2, 2.0, 0.5}; - for (int i = 0; i < kNumKeyFrames; ++i) { - key_frame_crop_results[i].set_region_score(scores[i]); - } - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - - EXPECT_FLOAT_EQ(scene_summary.key_frame_min_score(), - *std::min_element(scores.begin(), scores.end())); - EXPECT_FLOAT_EQ(scene_summary.key_frame_max_score(), - *std::max_element(scores.begin(), scores.end())); -} - -// Checks that AggregateKeyFrameResults sets crop window size to target size -// when the crop regions fit in target size. -TEST(UtilTest, AggregateKeyFrameResultsSetsCropWindowSizeToTargetSize) { - SceneKeyFrameCropSummary scene_summary; - MP_EXPECT_OK(AggregateKeyFrameResults( - GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(), - kOriginalWidth, kOriginalHeight, &scene_summary)); - EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth); - EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight); -} - -// Checks that AggregateKeyFrameResults properly sets crop window size when the -// crop regions exceed target size. -TEST(UtilTest, AggregateKeyFrameResultsSetsCropWindowSizeExceedingTargetSize) { - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - key_frame_crop_results[0].mutable_region()->set_width(kTargetWidth + 1); - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - EXPECT_EQ(scene_summary.crop_window_width(), kTargetWidth + 1); - EXPECT_EQ(scene_summary.crop_window_height(), kTargetHeight); -} - -// Checks that AggregateKeyFrameResults sets has salient region to true when -// there are salient regions. -TEST(UtilTest, AggregateKeyFrameResultsSetsHasSalientRegionTrue) { - SceneKeyFrameCropSummary scene_summary; - MP_EXPECT_OK(AggregateKeyFrameResults( - GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(), - kOriginalWidth, kOriginalHeight, &scene_summary)); - EXPECT_TRUE(scene_summary.has_salient_region()); -} - -// Checks that AggregateKeyFrameResults sets has salient region to false when -// there are no salient regions. -TEST(UtilTest, AggregateKeyFrameResultsSetsHasSalientRegionFalse) { - std::vector key_frame_crop_results(kNumKeyFrames); - for (int i = 0; i < kNumKeyFrames; ++i) { - key_frame_crop_results[i].set_region_is_empty(true); - } - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - EXPECT_FALSE(scene_summary.has_salient_region()); -} - -// Checks that AggregateKeyFrameResults sets has required salient region to true -// when there are required salient regions. -TEST(UtilTest, AggregateKeyFrameResultsSetsHasRequiredSalientRegionTrue) { - SceneKeyFrameCropSummary scene_summary; - MP_EXPECT_OK(AggregateKeyFrameResults( - GetDefaultKeyFrameCropOptions(), GetDefaultKeyFrameCropResults(), - kOriginalWidth, kOriginalHeight, &scene_summary)); - EXPECT_TRUE(scene_summary.has_required_salient_region()); -} - -// Checks that AggregateKeyFrameResults sets has required salient region to -// false when there are no required salient regions. -TEST(UtilTest, AggregateKeyFrameResultsSetsHasRequiredSalientRegionFalse) { - std::vector key_frame_crop_results(kNumKeyFrames); - for (int i = 0; i < kNumKeyFrames; ++i) { - key_frame_crop_results[i].set_required_region_is_empty(true); - } - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - EXPECT_FALSE(scene_summary.has_required_salient_region()); -} - -// Checks that AggregateKeyFrameResults sets key frame required crop region -// union. -TEST(UtilTest, AggregateKeyFrameResultsSetsKeyFrameRequiredCropRegionUnion) { - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - for (int i = 0; i < kNumKeyFrames; ++i) { - *key_frame_crop_results[i].mutable_required_region() = - MakeRect(i, 0, 50, 50); - } - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - const auto& required_crop_region_union = - scene_summary.key_frame_required_crop_region_union(); - EXPECT_EQ(required_crop_region_union.x(), 0); - EXPECT_EQ(required_crop_region_union.width(), 50 + kNumKeyFrames - 1); -} - -// Checks that AggregateKeyFrameResults properly sets frame success rate. -TEST(UtilTest, AggregateKeyFrameResultsSetsFrameSuccessRate) { - const int num_success_frames = 3; - const float success_rate = - static_cast(num_success_frames) / kNumKeyFrames; - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - for (int i = 0; i < kNumKeyFrames; ++i) { - const bool successful = i < num_success_frames ? true : false; - key_frame_crop_results[i].set_are_required_regions_covered_in_target_size( - successful); - } - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - EXPECT_FLOAT_EQ(scene_summary.frame_success_rate(), success_rate); -} - -// Checks that AggregateKeyFrameResults properly sets motion. -TEST(UtilTest, AggregateKeyFrameResultsSetsMotion) { - auto key_frame_crop_results = GetDefaultKeyFrameCropResults(); - const std::vector centers_x = {30.0, 55.0, 45.0, 30.0, 60.0}; - const std::vector centers_y = {30.0, 40.0, 50.0, 45.0, 25.0}; - const float motion_x = (60.0 - 30.0) / kOriginalWidth; - const float motion_y = (50.0 - 25.0) / kOriginalHeight; - const int half_width = 25, half_height = 25; - for (int i = 0; i < kNumKeyFrames; ++i) { - auto* region = key_frame_crop_results[i].mutable_region(); - *region = MakeRectFromCenterAndHalfSize(centers_x[i], centers_y[i], - half_width, half_height); - } - SceneKeyFrameCropSummary scene_summary; - - MP_EXPECT_OK(AggregateKeyFrameResults(GetDefaultKeyFrameCropOptions(), - key_frame_crop_results, kOriginalWidth, - kOriginalHeight, &scene_summary)); - EXPECT_FLOAT_EQ(scene_summary.horizontal_motion_amount(), motion_x); - EXPECT_FLOAT_EQ(scene_summary.vertical_motion_amount(), motion_y); -} - -// Checks that ComputeSceneStaticBordersSize checks output not null. -TEST(UtilTest, ComputeSceneStaticBordersSizeChecksOutputNotNull) { - std::vector static_features; - int bottom_border_size = 0; - const auto status = ComputeSceneStaticBordersSize(static_features, nullptr, - &bottom_border_size); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - testing::HasSubstr("Output top border size is null.")); -} -// Checks that ComputeSceneStaticBordersSize returns 0 when there are no static -// borders. -TEST(UtilTest, ComputeSceneStaticBordersSizeNoBorder) { - std::vector static_features(10); - int top_border_size = 0, bottom_border_size = 0; - MP_EXPECT_OK(ComputeSceneStaticBordersSize(static_features, &top_border_size, - &bottom_border_size)); - EXPECT_EQ(top_border_size, 0); - EXPECT_EQ(bottom_border_size, 0); -} - -// Checks that ComputeSceneStaticBordersSize correctly computes static border -// size. -TEST(UtilTest, ComputeSceneStaticBordersSizeHasBorders) { - const int num_frames = 6; - const std::vector top_borders = {10, 9, 8, 9, 10, 5}; - const std::vector bottom_borders = {7, 7, 7, 7, 6, 7}; - std::vector static_features(num_frames); - for (int i = 0; i < num_frames; ++i) { - auto& features = static_features[i]; - auto* top_part = features.add_border(); - top_part->set_relative_position(Border::TOP); - top_part->mutable_border_position()->set_height(top_borders[i]); - auto* bottom_part = features.add_border(); - bottom_part->set_relative_position(Border::BOTTOM); - bottom_part->mutable_border_position()->set_height(bottom_borders[i]); - } - int top_border_size = 0, bottom_border_size = 0; - MP_EXPECT_OK(ComputeSceneStaticBordersSize(static_features, &top_border_size, - &bottom_border_size)); - EXPECT_EQ(top_border_size, 5); - EXPECT_EQ(bottom_border_size, 6); -} - -// Checks that FindSolidBackgroundColor checks output not null. -TEST(UtilTest, FindSolidBackgroundColorChecksOutputNotNull) { - std::vector static_features; - std::vector static_features_timestamps; - const double min_fraction_solid_background_color = 0.8; - bool has_solid_background_color; - PiecewiseLinearFunction l_function, a_function, b_function; - - auto status = - FindSolidBackgroundColor(static_features, static_features_timestamps, - min_fraction_solid_background_color, nullptr, - &l_function, &a_function, &b_function); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), testing::HasSubstr("Output boolean is null.")); - - status = FindSolidBackgroundColor(static_features, static_features_timestamps, - min_fraction_solid_background_color, - &has_solid_background_color, nullptr, - &a_function, &b_function); - EXPECT_FALSE(status.ok()); - EXPECT_THAT(status.ToString(), - testing::HasSubstr("Output color l function is null.")); -} - -// Checks that FindSolidBackgroundColor returns true when there is solid -// background color. -TEST(UtilTest, FindSolidBackgroundColorReturnsTrue) { - std::vector static_features(1); - auto* color = static_features[0].mutable_solid_background(); - color->set_r(255); - color->set_g(255); - color->set_b(255); - std::vector static_features_timestamps(1); - static_features_timestamps[0] = 0; - const double min_fraction_solid_background_color = 0.8; - bool has_solid_background_color; - PiecewiseLinearFunction l_function, a_function, b_function; - - MP_EXPECT_OK(FindSolidBackgroundColor( - static_features, static_features_timestamps, - min_fraction_solid_background_color, &has_solid_background_color, - &l_function, &a_function, &b_function)); - EXPECT_TRUE(has_solid_background_color); -} - -// Checks that FindSolidBackgroundColor returns false when there is no solid -// background color. -TEST(UtilTest, FindSolidBackgroundColorReturnsFalse) { - std::vector static_features(1); - std::vector static_features_timestamps(1); - static_features_timestamps[0] = 0; - const double min_fraction_solid_background_color = 0.8; - bool has_solid_background_color; - PiecewiseLinearFunction l_function, a_function, b_function; - - MP_EXPECT_OK(FindSolidBackgroundColor( - static_features, static_features_timestamps, - min_fraction_solid_background_color, &has_solid_background_color, - &l_function, &a_function, &b_function)); - EXPECT_FALSE(has_solid_background_color); -} - -// Checks that FindSolidBackgroundColor sets the interpolation functions. -TEST(UtilTest, FindSolidBackgroundColorSetsInterpolationFunctions) { - const uint8 rgb1[] = {255, 255, 0}; // cyan in bgr - const double lab1[] = {91.1133, -48.0938, -14.125}; - const int64 time1 = 0; - const uint8 rgb2[] = {255, 0, 255}; // magenta in bgr - const double lab2[] = {60.321, 98.2344, -60.8281}; - const int64 time2 = 2000; - std::vector static_features(2); - auto* color1 = static_features[0].mutable_solid_background(); - color1->set_r(rgb1[0]); - color1->set_g(rgb1[1]); - color1->set_b(rgb1[2]); - auto* color2 = static_features[1].mutable_solid_background(); - color2->set_r(rgb2[0]); - color2->set_g(rgb2[1]); - color2->set_b(rgb2[2]); - std::vector static_features_timestamps(2); - static_features_timestamps[0] = time1; - static_features_timestamps[1] = time2; - const double min_fraction_solid_background_color = 0.8; - bool has_solid_background_color; - PiecewiseLinearFunction l_function, a_function, b_function; - - MP_EXPECT_OK(FindSolidBackgroundColor( - static_features, static_features_timestamps, - min_fraction_solid_background_color, &has_solid_background_color, - &l_function, &a_function, &b_function)); - - EXPECT_TRUE(has_solid_background_color); - EXPECT_LE(std::fabs(l_function.Evaluate(time1) - lab1[0]), 1e-2f); - EXPECT_LE(std::fabs(a_function.Evaluate(time1) - lab1[1]), 1e-2f); - EXPECT_LE(std::fabs(b_function.Evaluate(time1) - lab1[2]), 1e-2f); - EXPECT_LE(std::fabs(l_function.Evaluate(time2) - lab2[0]), 1e-2f); - EXPECT_LE(std::fabs(a_function.Evaluate(time2) - lab2[1]), 1e-2f); - EXPECT_LE(std::fabs(b_function.Evaluate(time2) - lab2[2]), 1e-2f); - - EXPECT_LE(std::fabs(l_function.Evaluate((time1 + time2) / 2.0) - - (lab1[0] + lab2[0]) / 2.0), - 1e-2f); - EXPECT_LE(std::fabs(a_function.Evaluate((time1 + time2) / 2.0) - - (lab1[1] + lab2[1]) / 2.0), - 1e-2f); - EXPECT_LE(std::fabs(b_function.Evaluate((time1 + time2) / 2.0) - - (lab1[2] + lab2[2]) / 2.0), - 1e-2f); -} - -TEST(UtilTest, TestAffineRetargeterPass) { - std::vector transforms; - std::vector frames; - std::vector results; - for (int i = 0; i < 5; i++) { - cv::Mat transform = cv::Mat(2, 3, CV_32FC1); - transform.at(0, 0) = 1; - transform.at(0, 1) = 0; - transform.at(1, 0) = 0; - transform.at(1, 1) = 1; - transform.at(0, 2) = -i * 50; - transform.at(1, 2) = 0; - transforms.push_back(transform); - cv::Mat image = cv::Mat::zeros(1080, 1920, CV_8UC3); - cv::Vec3b val; - val[0] = 255; - val[1] = 255; - val[2] = 255; - image(cv::Rect(0, 0, 395, 1080)).setTo(val); - frames.push_back(image); - - cv::Mat image_out = cv::Mat::zeros(500, 1920, CV_8UC3); - results.push_back(image_out); - } - - MP_ASSERT_OK( - AffineRetarget(cv::Size(500, 1080), frames, transforms, &results)); - ASSERT_EQ(results.size(), 5); - for (int i = 0; i < 5; i++) { - EXPECT_GT(results[i].at(540, 390 - i * 50)[0], 250); - EXPECT_GT(results[i].at(540, 390 - i * 50)[1], 250); - EXPECT_GT(results[i].at(540, 390 - i * 50)[2], 250); - EXPECT_LT(results[i].at(540, 400 - i * 50)[0], 5); - EXPECT_LT(results[i].at(540, 400 - i * 50)[1], 5); - EXPECT_LT(results[i].at(540, 400 - i * 50)[2], 5); - } -} - -TEST(UtilTest, TestAffineRetargeterFail) { - std::vector transforms; - std::vector frames; - std::vector results; - - cv::Mat dummy; - transforms.push_back(dummy); - frames.push_back(dummy); - - EXPECT_THAT( - AffineRetarget(cv::Size(500, 1080), frames, transforms, &results) - .ToString(), - testing::HasSubstr("Output vector cropped_frames must be populated")); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/visual_scorer.cc b/examples/desktop/autoflip/quality/visual_scorer.cc deleted file mode 100644 index 9ae6120..0000000 --- a/examples/desktop/autoflip/quality/visual_scorer.cc +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/visual_scorer.h" - -#include - -#include -#include -#include -#include - -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/ret_check.h" -#include "mediapipe/framework/port/status.h" -#include "mediapipe/framework/port/status_builder.h" - -// Weight threshold for computing a value. -constexpr float kEpsilon = 0.0001; - -namespace mediapipe { -namespace autoflip { -namespace { - -// Crop the given rectangle so that it fits in the given 2D matrix. -void CropRectToMat(const cv::Mat& image, cv::Rect* rect) { - int x = std::min(std::max(rect->x, 0), image.cols); - int y = std::min(std::max(rect->y, 0), image.rows); - int w = std::min(std::max(rect->x + rect->width, 0), image.cols) - x; - int h = std::min(std::max(rect->y + rect->height, 0), image.rows) - y; - *rect = cv::Rect(x, y, w, h); -} - -} // namespace - -VisualScorer::VisualScorer(const VisualScorerOptions& options) - : options_(options) {} - -absl::Status VisualScorer::CalculateScore(const cv::Mat& image, - const SalientRegion& region, - float* score) const { - const float weight_sum = options_.area_weight() + - options_.sharpness_weight() + - options_.colorfulness_weight(); - - // Crop the region to fit in the image. - cv::Rect region_rect; - if (region.has_location()) { - region_rect = - cv::Rect(region.location().x(), region.location().y(), - region.location().width(), region.location().height()); - } else if (region.has_location_normalized()) { - region_rect = cv::Rect(region.location_normalized().x() * image.cols, - region.location_normalized().y() * image.rows, - region.location_normalized().width() * image.cols, - region.location_normalized().height() * image.rows); - } else { - return mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC) - << "Unset region location."; - } - - CropRectToMat(image, ®ion_rect); - if (region_rect.area() == 0) { - *score = 0; - return absl::OkStatus(); - } - - // Compute a score based on area covered by this region. - const float area_score = - options_.area_weight() * region_rect.area() / (image.cols * image.rows); - - // Convert the visible region to cv::Mat. - cv::Mat image_region_mat = image(region_rect); - - // Compute a score from sharpness. - - float sharpness_score_result = 0.0; - if (options_.sharpness_weight() > kEpsilon) { - // TODO: implement a sharpness score or remove this code block. - return mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC) - << "sharpness scorer is not yet implemented, please set weight to " - "0.0"; - } - const float sharpness_score = - options_.sharpness_weight() * sharpness_score_result; - - // Compute a colorfulness score. - float colorfulness_score = 0; - if (options_.colorfulness_weight() > kEpsilon) { - MP_RETURN_IF_ERROR( - CalculateColorfulness(image_region_mat, &colorfulness_score)); - colorfulness_score *= options_.colorfulness_weight(); - } - - *score = (area_score + sharpness_score + colorfulness_score) / weight_sum; - if (*score > 1.0f || *score < 0.0f) { - LOG(WARNING) << "Score of region outside expected range: " << *score; - } - return absl::OkStatus(); -} - -absl::Status VisualScorer::CalculateColorfulness(const cv::Mat& image, - float* colorfulness) const { - // Convert the image to HSV. - cv::Mat image_hsv; - cv::cvtColor(image, image_hsv, CV_RGB2HSV); - - // Mask out pixels that are too dark or too bright. - cv::Mat mask(image.rows, image.cols, CV_8UC1); - bool empty_mask = true; - for (int x = 0; x < image.cols; ++x) { - for (int y = 0; y < image.rows; ++y) { - const cv::Vec3b& pixel = image.at(x, y); - const bool is_usable = - (std::min(pixel.val[0], std::min(pixel.val[1], pixel.val[2])) < 250 && - std::max(pixel.val[0], std::max(pixel.val[1], pixel.val[2])) > 5); - mask.at(y, x) = is_usable ? 255 : 0; - if (is_usable) empty_mask = false; - } - } - - // If the mask is empty, return. - if (empty_mask) { - *colorfulness = 0; - return absl::OkStatus(); - } - - // Generate a 2D histogram (hue/saturation). - cv::MatND hs_histogram; - const int kHueBins = 10, kSaturationBins = 8; - const int kHistogramChannels[] = {0, 1}; - const int kHistogramBinNum[] = {kHueBins, kSaturationBins}; - const float kHueRange[] = {0, 180}; - const float kSaturationRange[] = {0, 256}; - const float* kHistogramRange[] = {kHueRange, kSaturationRange}; - cv::calcHist(&image_hsv, 1, kHistogramChannels, mask, hs_histogram, - 2 /* histogram dims */, kHistogramBinNum, kHistogramRange, - true /* uniform */, false /* accumulate */); - - // Convert to a hue histogram and weigh saturated pixels more. - std::vector hue_histogram(kHueBins, 0.0f); - float hue_sum = 0.0f; - for (int bin_s = 0; bin_s < kSaturationBins; ++bin_s) { - const float weight = std::pow(2.0f, bin_s); - for (int bin_h = 0; bin_h < kHueBins; ++bin_h) { - float value = hs_histogram.at(bin_h, bin_s) * weight; - hue_histogram[bin_h] += value; - hue_sum += value; - } - } - if (hue_sum == 0.0f) { - *colorfulness = 0; - return absl::OkStatus(); - } - - // Compute the histogram entropy. - *colorfulness = 0; - for (int bin = 0; bin < kHueBins; ++bin) { - float value = hue_histogram[bin] / hue_sum; - if (value > 0.0f) { - *colorfulness -= value * std::log(value); - } - } - *colorfulness /= std::log(2.0f); - - return absl::OkStatus(); -} - -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/quality/visual_scorer.h b/examples/desktop/autoflip/quality/visual_scorer.h deleted file mode 100644 index b2c6d3a..0000000 --- a/examples/desktop/autoflip/quality/visual_scorer.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#ifndef MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_VISUAL_SCORER_H_ -#define MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_VISUAL_SCORER_H_ - -#include "mediapipe/examples/desktop/autoflip/autoflip_messages.pb.h" -#include "mediapipe/examples/desktop/autoflip/quality/visual_scorer.pb.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/status.h" - -namespace mediapipe { -namespace autoflip { - -// This class scores a SalientRegion within an image based on weighted averages -// of various signals computed on the patch. -class VisualScorer { - public: - explicit VisualScorer(const VisualScorerOptions& options); - - // Computes a score on a salientregion and returns a value [0...1]. - absl::Status CalculateScore(const cv::Mat& image, const SalientRegion& region, - float* score) const; - - private: - absl::Status CalculateColorfulness(const cv::Mat& image, - float* colorfulness) const; - - VisualScorerOptions options_; -}; - -} // namespace autoflip -} // namespace mediapipe - -#endif // MEDIAPIPE_EXAMPLES_DESKTOP_AUTOFLIP_QUALITY_VISUAL_SCORER_H_ diff --git a/examples/desktop/autoflip/quality/visual_scorer.proto b/examples/desktop/autoflip/quality/visual_scorer.proto deleted file mode 100644 index 4623e1e..0000000 --- a/examples/desktop/autoflip/quality/visual_scorer.proto +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -syntax = "proto2"; - -package mediapipe.autoflip; - -// Options for the VisualScorer module. -// Next tag: 6 -message VisualScorerOptions { - // Weights for the various cues. A larger weight means that the corresponding - // cue will be of higher importance when generating the combined score. - optional float area_weight = 1 [default = 1.0]; - // Sharpness is not yet implemented. - optional float sharpness_weight = 2 [default = 0.0]; - optional float colorfulness_weight = 3 [default = 0.0]; -} diff --git a/examples/desktop/autoflip/quality/visual_scorer_test.cc b/examples/desktop/autoflip/quality/visual_scorer_test.cc deleted file mode 100644 index f1a3bb5..0000000 --- a/examples/desktop/autoflip/quality/visual_scorer_test.cc +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. - -#include "mediapipe/examples/desktop/autoflip/quality/visual_scorer.h" - -#include "mediapipe/framework/port/gmock.h" -#include "mediapipe/framework/port/gtest.h" -#include "mediapipe/framework/port/opencv_core_inc.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/status_matchers.h" - -namespace mediapipe { -namespace autoflip { -namespace { - -TEST(VisualScorerTest, ScoresArea) { - cv::Mat image_mat(200, 200, CV_8UC3); - SalientRegion region = ParseTextProtoOrDie( - R"pb(location { x: 10 y: 10 width: 100 height: 100 })pb"); - - VisualScorerOptions options = ParseTextProtoOrDie( - R"pb(area_weight: 1.0 sharpness_weight: 0 colorfulness_weight: 0)pb"); - VisualScorer scorer(options); - float score = 0.0; - MP_EXPECT_OK(scorer.CalculateScore(image_mat, region, &score)); - EXPECT_EQ(0.25, score); // (100 * 100) / (200 * 200). -} - -TEST(VisualScorerTest, ScoresSharpness) { - SalientRegion region = ParseTextProtoOrDie( - R"pb(location { x: 10 y: 10 width: 100 height: 100 })pb"); - - VisualScorerOptions options = ParseTextProtoOrDie( - R"pb(area_weight: 0 sharpness_weight: 1.0 colorfulness_weight: 0)pb"); - VisualScorer scorer(options); - - // Compute the score of an empty image and an image with a rectangle. - cv::Mat image_mat(200, 200, CV_8UC3); - image_mat.setTo(cv::Scalar(0, 0, 0)); - float score_rect = 0; - auto status = scorer.CalculateScore(image_mat, region, &score_rect); - EXPECT_EQ(status.code(), StatusCode::kInvalidArgument); -} - -TEST(VisualScorerTest, ScoresColorfulness) { - SalientRegion region = ParseTextProtoOrDie( - R"pb(location { x: 10 y: 10 width: 50 height: 150 })pb"); - - VisualScorerOptions options = ParseTextProtoOrDie( - R"pb(area_weight: 0 sharpness_weight: 0 colorfulness_weight: 1.0)pb"); - VisualScorer scorer(options); - - // Compute the scores of images with 1, 2 and 3 colors. - cv::Mat image_mat(200, 200, CV_8UC3); - image_mat.setTo(cv::Scalar(0, 0, 255)); - float score_1c = 0, score_2c = 0, score_3c = 0; - MP_EXPECT_OK(scorer.CalculateScore(image_mat, region, &score_1c)); - image_mat(cv::Rect(30, 30, 20, 20)).setTo(cv::Scalar(128, 0, 0)); - MP_EXPECT_OK(scorer.CalculateScore(image_mat, region, &score_2c)); - image_mat(cv::Rect(50, 50, 20, 20)).setTo(cv::Scalar(255, 128, 0)); - MP_EXPECT_OK(scorer.CalculateScore(image_mat, region, &score_3c)); - // Images with more colors should have a higher score. - EXPECT_LT(score_1c, score_2c); - EXPECT_LT(score_2c, score_3c); -} - -} // namespace -} // namespace autoflip -} // namespace mediapipe diff --git a/examples/desktop/autoflip/subgraph/autoflip_object_detection_subgraph.pbtxt b/examples/desktop/autoflip/subgraph/autoflip_object_detection_subgraph.pbtxt deleted file mode 100644 index bd2e7a7..0000000 --- a/examples/desktop/autoflip/subgraph/autoflip_object_detection_subgraph.pbtxt +++ /dev/null @@ -1,126 +0,0 @@ -# MediaPipe graph that performs object detection with TensorFlow Lite on CPU. - -input_stream: "VIDEO:input_video" -output_stream: "DETECTIONS:output_detections" - -# Transforms the input image on CPU to a 320x320 image. To scale the image, by -# default it uses the STRETCH scale mode that maps the entire input image to the -# entire transformed image. As a result, image aspect ratio may be changed and -# objects in the image may be deformed (stretched or squeezed), but the object -# detection model used in this graph is agnostic to that deformation. -node: { - calculator: "ImageTransformationCalculator" - input_stream: "IMAGE:input_video" - output_stream: "IMAGE:transformed_input_video" - options: { - [mediapipe.ImageTransformationCalculatorOptions.ext] { - output_width: 320 - output_height: 320 - } - } -} - -# Converts the transformed input image on CPU into an image tensor stored as a -# TfLiteTensor. -node { - calculator: "TfLiteConverterCalculator" - input_stream: "IMAGE:transformed_input_video" - output_stream: "TENSORS:image_tensor" -} - -# Runs a TensorFlow Lite model on CPU that takes an image tensor and outputs a -# vector of tensors representing, for instance, detection boxes/keypoints and -# scores. -node { - calculator: "TfLiteInferenceCalculator" - input_stream: "TENSORS:image_tensor" - output_stream: "TENSORS:detection_tensors" - options: { - [mediapipe.TfLiteInferenceCalculatorOptions.ext] { - model_path: "mediapipe/models/ssdlite_object_detection.tflite" - } - } -} - -# Generates a single side packet containing a vector of SSD anchors based on -# the specification in the options. -node { - calculator: "SsdAnchorsCalculator" - output_side_packet: "anchors" - options: { - [mediapipe.SsdAnchorsCalculatorOptions.ext] { - num_layers: 6 - min_scale: 0.2 - max_scale: 0.95 - input_size_height: 320 - input_size_width: 320 - anchor_offset_x: 0.5 - anchor_offset_y: 0.5 - strides: 16 - strides: 32 - strides: 64 - strides: 128 - strides: 256 - strides: 512 - aspect_ratios: 1.0 - aspect_ratios: 2.0 - aspect_ratios: 0.5 - aspect_ratios: 3.0 - aspect_ratios: 0.3333 - reduce_boxes_in_lowest_layer: true - } - } -} - -# Decodes the detection tensors generated by the TensorFlow Lite model, based on -# the SSD anchors and the specification in the options, into a vector of -# detections. Each detection describes a detected object. -node { - calculator: "TfLiteTensorsToDetectionsCalculator" - input_stream: "TENSORS:detection_tensors" - input_side_packet: "ANCHORS:anchors" - output_stream: "DETECTIONS:detections" - options: { - [mediapipe.TfLiteTensorsToDetectionsCalculatorOptions.ext] { - num_classes: 91 - num_boxes: 2034 - num_coords: 4 - ignore_classes: 0 - sigmoid_score: true - apply_exponential_on_box_size: true - x_scale: 10.0 - y_scale: 10.0 - h_scale: 5.0 - w_scale: 5.0 - min_score_thresh: 0.6 - } - } -} - -# Performs non-max suppression to remove excessive detections. -node { - calculator: "NonMaxSuppressionCalculator" - input_stream: "detections" - output_stream: "filtered_detections" - options: { - [mediapipe.NonMaxSuppressionCalculatorOptions.ext] { - min_suppression_threshold: 0.4 - max_num_detections: 5 - overlap_type: INTERSECTION_OVER_UNION - return_empty_detections: true - } - } -} - -# Maps detection label IDs to the corresponding label text. The label map is -# provided in the label_map_path option. -node { - calculator: "DetectionLabelIdToTextCalculator" - input_stream: "filtered_detections" - output_stream: "output_detections" - options: { - [mediapipe.DetectionLabelIdToTextCalculatorOptions.ext] { - label_map_path: "mediapipe/models/ssdlite_object_detection_labelmap.txt" - } - } -} diff --git a/examples/desktop/autoflip/subgraph/face_detection_subgraph.pbtxt b/examples/desktop/autoflip/subgraph/face_detection_subgraph.pbtxt deleted file mode 100644 index b736a15..0000000 --- a/examples/desktop/autoflip/subgraph/face_detection_subgraph.pbtxt +++ /dev/null @@ -1,121 +0,0 @@ -# MediaPipe graph that performs face detection with TensorFlow Lite on CPU. - -input_stream: "VIDEO:input_video" -output_stream: "DETECTIONS:output_detections" - - -# Transforms the input image on CPU to a 128x128 image. To scale the input -# image, the scale_mode option is set to FIT to preserve the aspect ratio, -# resulting in potential letterboxing in the transformed image. -node: { - calculator: "ImageTransformationCalculator" - input_stream: "IMAGE:input_video" - output_stream: "IMAGE:transformed_input_video_cpu" - output_stream: "LETTERBOX_PADDING:letterbox_padding" - options: { - [mediapipe.ImageTransformationCalculatorOptions.ext] { - output_width: 192 - output_height: 192 - scale_mode: FIT - } - } -} - -# Converts the transformed input image on CPU into an image tensor stored as a -# TfLiteTensor. -node { - calculator: "TfLiteConverterCalculator" - input_stream: "IMAGE:transformed_input_video_cpu" - output_stream: "TENSORS:image_tensor" -} - -# Runs a TensorFlow Lite model on CPU that takes an image tensor and outputs a -# vector of tensors representing, for instance, detection boxes/keypoints and -# scores. -node { - calculator: "TfLiteInferenceCalculator" - input_stream: "TENSORS:image_tensor" - output_stream: "TENSORS:detection_tensors" - options: { - [mediapipe.TfLiteInferenceCalculatorOptions.ext] { - model_path: "mediapipe/modules/face_detection/face_detection_full_range_sparse.tflite" - } - } -} - -# Generates a single side packet containing a vector of SSD anchors based on -# the specification in the options. -node { - calculator: "SsdAnchorsCalculator" - output_side_packet: "anchors" - options: { - [mediapipe.SsdAnchorsCalculatorOptions.ext] { - num_layers: 1 - min_scale: 0.1484375 - max_scale: 0.75 - input_size_height: 192 - input_size_width: 192 - anchor_offset_x: 0.5 - anchor_offset_y: 0.5 - strides: 4 - aspect_ratios: 1.0 - fixed_anchor_size: true - interpolated_scale_aspect_ratio: 0.0 - } - } -} - -# Decodes the detection tensors generated by the TensorFlow Lite model, based on -# the SSD anchors and the specification in the options, into a vector of -# detections. Each detection describes a detected object. -node { - calculator: "TfLiteTensorsToDetectionsCalculator" - input_stream: "TENSORS:detection_tensors" - input_side_packet: "ANCHORS:anchors" - output_stream: "DETECTIONS:detections" - options: { - [mediapipe.TfLiteTensorsToDetectionsCalculatorOptions.ext] { - num_classes: 1 - num_boxes: 2304 - num_coords: 16 - box_coord_offset: 0 - keypoint_coord_offset: 4 - num_keypoints: 6 - num_values_per_keypoint: 2 - sigmoid_score: true - score_clipping_thresh: 100.0 - reverse_output_order: true - x_scale: 192.0 - y_scale: 192.0 - h_scale: 192.0 - w_scale: 192.0 - min_score_thresh: 0.6 - } - } -} - -# Performs non-max suppression to remove excessive detections. -node { - calculator: "NonMaxSuppressionCalculator" - input_stream: "detections" - output_stream: "filtered_detections" - options: { - [mediapipe.NonMaxSuppressionCalculatorOptions.ext] { - min_suppression_threshold: 0.3 - overlap_type: INTERSECTION_OVER_UNION - algorithm: WEIGHTED - return_empty_detections: true - } - } -} - -# Adjusts detection locations (already normalized to [0.f, 1.f]) on the -# letterboxed image (after image transformation with the FIT scale mode) to the -# corresponding locations on the same image with the letterbox removed (the -# input image to the graph before image transformation). -node { - calculator: "DetectionLetterboxRemovalCalculator" - input_stream: "DETECTIONS:filtered_detections" - input_stream: "LETTERBOX_PADDING:letterbox_padding" - output_stream: "DETECTIONS:output_detections" -} diff --git a/examples/desktop/autoflip/subgraph/front_face_detection_subgraph.pbtxt b/examples/desktop/autoflip/subgraph/front_face_detection_subgraph.pbtxt deleted file mode 100644 index 3b2d410..0000000 --- a/examples/desktop/autoflip/subgraph/front_face_detection_subgraph.pbtxt +++ /dev/null @@ -1,135 +0,0 @@ -# MediaPipe graph that performs face detection with TensorFlow Lite on CPU. Model paths setup for web use. -# TODO: parameterize input paths to support desktop use, for web only. -input_stream: "VIDEO:input_video" -output_stream: "DETECTIONS:output_detections" - -# Transforms the input image on CPU to a 128x128 image. To scale the input -# image, the scale_mode option is set to FIT to preserve the aspect ratio, -# resulting in potential letterboxing in the transformed image. -node: { - calculator: "ImageTransformationCalculator" - input_stream: "IMAGE:input_video" - output_stream: "IMAGE:transformed_input_video_cpu" - output_stream: "LETTERBOX_PADDING:letterbox_padding" - options: { - [mediapipe.ImageTransformationCalculatorOptions.ext] { - output_width: 128 - output_height: 128 - scale_mode: FIT - } - } -} - -# Converts the transformed input image on CPU into an image tensor stored as a -# TfLiteTensor. -node { - calculator: "TfLiteConverterCalculator" - input_stream: "IMAGE:transformed_input_video_cpu" - output_stream: "TENSORS:image_tensor" -} - -# Runs a TensorFlow Lite model on CPU that takes an image tensor and outputs a -# vector of tensors representing, for instance, detection boxes/keypoints and -# scores. -node { - calculator: "TfLiteInferenceCalculator" - input_stream: "TENSORS:image_tensor" - output_stream: "TENSORS:detection_tensors" - options: { - [mediapipe.TfLiteInferenceCalculatorOptions.ext] { - model_path: "face_detection_front.tflite" - } - } -} - -# Generates a single side packet containing a vector of SSD anchors based on -# the specification in the options. -node { - calculator: "SsdAnchorsCalculator" - output_side_packet: "anchors" - options: { - [mediapipe.SsdAnchorsCalculatorOptions.ext] { - num_layers: 4 - min_scale: 0.1484375 - max_scale: 0.75 - input_size_height: 128 - input_size_width: 128 - anchor_offset_x: 0.5 - anchor_offset_y: 0.5 - strides: 8 - strides: 16 - strides: 16 - strides: 16 - aspect_ratios: 1.0 - fixed_anchor_size: true - } - } -} - -# Decodes the detection tensors generated by the TensorFlow Lite model, based on -# the SSD anchors and the specification in the options, into a vector of -# detections. Each detection describes a detected object. -node { - calculator: "TfLiteTensorsToDetectionsCalculator" - input_stream: "TENSORS:detection_tensors" - input_side_packet: "ANCHORS:anchors" - output_stream: "DETECTIONS:detections" - options: { - [mediapipe.TfLiteTensorsToDetectionsCalculatorOptions.ext] { - num_classes: 1 - num_boxes: 896 - num_coords: 16 - box_coord_offset: 0 - keypoint_coord_offset: 4 - num_keypoints: 6 - num_values_per_keypoint: 2 - sigmoid_score: true - score_clipping_thresh: 100.0 - reverse_output_order: true - x_scale: 128.0 - y_scale: 128.0 - h_scale: 128.0 - w_scale: 128.0 - min_score_thresh: 0.75 - } - } -} - -# Performs non-max suppression to remove excessive detections. -node { - calculator: "NonMaxSuppressionCalculator" - input_stream: "detections" - output_stream: "filtered_detections" - options: { - [mediapipe.NonMaxSuppressionCalculatorOptions.ext] { - min_suppression_threshold: 0.3 - overlap_type: INTERSECTION_OVER_UNION - algorithm: WEIGHTED - return_empty_detections: true - } - } -} - -# Maps detection label IDs to the corresponding label text ("Face"). The label -# map is provided in the label_map_path option. -node { - calculator: "DetectionLabelIdToTextCalculator" - input_stream: "filtered_detections" - output_stream: "labeled_detections" - options: { - [mediapipe.DetectionLabelIdToTextCalculatorOptions.ext] { - label_map_path: "face_detection_front_labelmap.txt" - } - } -} - -# Adjusts detection locations (already normalized to [0.f, 1.f]) on the -# letterboxed image (after image transformation with the FIT scale mode) to the -# corresponding locations on the same image with the letterbox removed (the -# input image to the graph before image transformation). -node { - calculator: "DetectionLetterboxRemovalCalculator" - input_stream: "DETECTIONS:labeled_detections" - input_stream: "LETTERBOX_PADDING:letterbox_padding" - output_stream: "DETECTIONS:output_detections" -} diff --git a/examples/desktop/hand_tracking/CMakeLists.txt b/examples/desktop/hand_tracking/CMakeLists.txt deleted file mode 100644 index c3c94fc..0000000 --- a/examples/desktop/hand_tracking/CMakeLists.txt +++ /dev/null @@ -1,48 +0,0 @@ -add_library(desktop_offline_calculators STATIC -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/flow_limiter_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/gate_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/immediate_mux_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/packet_inner_join_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/previous_loopback_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/video/opencv_video_decoder_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/video/opencv_video_encoder_calculator.cc -) -add_library(hand_render_cpu STATIC -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/begin_loop_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/end_loop_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/gate_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/split_vector_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/annotation_overlay_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/collection_has_min_size_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/detections_to_render_data_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/labels_to_render_data_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/landmarks_to_render_data_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/rect_to_render_data_calculator.cc -) -add_library(hand_landmark_tracking_cpu - -) -add_library(desktop_tflite_calculators STATIC -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/constant_side_packet_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/merge_calculator.cc -) -target_link_libraries(desktop_offline_calculators PUBLIC desktop_offline_calculators hand_render_cpu hand_landmark_tracking_cpu) - -add_executable(hand_tracking_cpu -PRIVATE -${PROJECT_SOURCE_DIR}/mediapipe/examples/desktop/demo_run_graph_main.cc -${PROJECT_SOURCE_DIR}/mediapipe/examples/desktop/image_sequence.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/begin_loop_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/end_loop_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/gate_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/core/split_vector_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/annotation_overlay_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/collection_has_min_size_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/detections_to_render_data_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/labels_to_render_data_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/landmarks_to_render_data_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/rect_to_render_data_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/tflite/tflite_model_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/calculators/util/local_file_contents_calculator.cc -${PROJECT_SOURCE_DIR}/mediapipe/framework/tool/switch_container.cc -) \ No newline at end of file diff --git a/examples/desktop/iris_tracking/iris_depth_from_image_desktop.cc b/examples/desktop/iris_tracking/iris_depth_from_image_desktop.cc deleted file mode 100644 index 928ebb2..0000000 --- a/examples/desktop/iris_tracking/iris_depth_from_image_desktop.cc +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. -// -// A utility to extract iris depth from a single image of face using the graph -// mediapipe/graphs/iris_tracking/iris_depth_cpu.pbtxt. -#include -#include - -#include "absl/flags/flag.h" -#include "absl/flags/parse.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/formats/image_frame.h" -#include "mediapipe/framework/formats/image_frame_opencv.h" -#include "mediapipe/framework/port/canonical_errors.h" -#include "mediapipe/framework/port/file_helpers.h" -#include "mediapipe/framework/port/opencv_highgui_inc.h" -#include "mediapipe/framework/port/opencv_imgproc_inc.h" -#include "mediapipe/framework/port/opencv_video_inc.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/status.h" - -constexpr char kInputStream[] = "input_image_bytes"; -constexpr char kOutputImageStream[] = "output_image"; -constexpr char kLeftIrisDepthMmStream[] = "left_iris_depth_mm"; -constexpr char kRightIrisDepthMmStream[] = "right_iris_depth_mm"; -constexpr char kWindowName[] = "MediaPipe"; -constexpr char kCalculatorGraphConfigFile[] = - "mediapipe/graphs/iris_tracking/iris_depth_cpu.pbtxt"; -constexpr float kMicrosPerSecond = 1e6; - -ABSL_FLAG(std::string, input_image_path, "", - "Full path of image to load. " - "If not provided, nothing will run."); -ABSL_FLAG(std::string, output_image_path, "", - "Full path of where to save image result (.jpg only). " - "If not provided, show result in a window."); - -namespace { - -absl::StatusOr ReadFileToString(const std::string& file_path) { - std::string contents; - MP_RETURN_IF_ERROR(mediapipe::file::GetContents(file_path, &contents)); - return contents; -} - -absl::Status ProcessImage(std::unique_ptr graph) { - LOG(INFO) << "Load the image."; - ASSIGN_OR_RETURN(const std::string raw_image, - ReadFileToString(absl::GetFlag(FLAGS_input_image_path))); - - LOG(INFO) << "Start running the calculator graph."; - ASSIGN_OR_RETURN(mediapipe::OutputStreamPoller output_image_poller, - graph->AddOutputStreamPoller(kOutputImageStream)); - ASSIGN_OR_RETURN(mediapipe::OutputStreamPoller left_iris_depth_poller, - graph->AddOutputStreamPoller(kLeftIrisDepthMmStream)); - ASSIGN_OR_RETURN(mediapipe::OutputStreamPoller right_iris_depth_poller, - graph->AddOutputStreamPoller(kRightIrisDepthMmStream)); - MP_RETURN_IF_ERROR(graph->StartRun({})); - - // Send image packet into the graph. - const size_t fake_timestamp_us = (double)cv::getTickCount() / - (double)cv::getTickFrequency() * - kMicrosPerSecond; - MP_RETURN_IF_ERROR(graph->AddPacketToInputStream( - kInputStream, mediapipe::MakePacket(raw_image).At( - mediapipe::Timestamp(fake_timestamp_us)))); - - // Get the graph result packets, or stop if that fails. - mediapipe::Packet left_iris_depth_packet; - if (!left_iris_depth_poller.Next(&left_iris_depth_packet)) { - return absl::UnknownError( - "Failed to get packet from output stream 'left_iris_depth_mm'."); - } - const auto& left_iris_depth_mm = left_iris_depth_packet.Get(); - const int left_iris_depth_cm = std::round(left_iris_depth_mm / 10); - std::cout << "Left Iris Depth: " << left_iris_depth_cm << " cm." << std::endl; - - mediapipe::Packet right_iris_depth_packet; - if (!right_iris_depth_poller.Next(&right_iris_depth_packet)) { - return absl::UnknownError( - "Failed to get packet from output stream 'right_iris_depth_mm'."); - } - const auto& right_iris_depth_mm = right_iris_depth_packet.Get(); - const int right_iris_depth_cm = std::round(right_iris_depth_mm / 10); - std::cout << "Right Iris Depth: " << right_iris_depth_cm << " cm." - << std::endl; - - mediapipe::Packet output_image_packet; - if (!output_image_poller.Next(&output_image_packet)) { - return absl::UnknownError( - "Failed to get packet from output stream 'output_image'."); - } - auto& output_frame = output_image_packet.Get(); - - // Convert back to opencv for display or saving. - cv::Mat output_frame_mat = mediapipe::formats::MatView(&output_frame); - cv::cvtColor(output_frame_mat, output_frame_mat, cv::COLOR_RGB2BGR); - const bool save_image = !absl::GetFlag(FLAGS_output_image_path).empty(); - if (save_image) { - LOG(INFO) << "Saving image to file..."; - cv::imwrite(absl::GetFlag(FLAGS_output_image_path), output_frame_mat); - } else { - cv::namedWindow(kWindowName, /*flags=WINDOW_AUTOSIZE*/ 1); - cv::imshow(kWindowName, output_frame_mat); - // Press any key to exit. - cv::waitKey(0); - } - - LOG(INFO) << "Shutting down."; - MP_RETURN_IF_ERROR(graph->CloseInputStream(kInputStream)); - return graph->WaitUntilDone(); -} - -absl::Status RunMPPGraph() { - std::string calculator_graph_config_contents; - MP_RETURN_IF_ERROR(mediapipe::file::GetContents( - kCalculatorGraphConfigFile, &calculator_graph_config_contents)); - LOG(INFO) << "Get calculator graph config contents: " - << calculator_graph_config_contents; - mediapipe::CalculatorGraphConfig config = - mediapipe::ParseTextProtoOrDie( - calculator_graph_config_contents); - - LOG(INFO) << "Initialize the calculator graph."; - std::unique_ptr graph = - absl::make_unique(); - MP_RETURN_IF_ERROR(graph->Initialize(config)); - - const bool load_image = !absl::GetFlag(FLAGS_input_image_path).empty(); - if (load_image) { - return ProcessImage(std::move(graph)); - } else { - return absl::InvalidArgumentError("Missing image file."); - } -} - -} // namespace - -int main(int argc, char** argv) { - google::InitGoogleLogging(argv[0]); - absl::ParseCommandLine(argc, argv); - absl::Status run_status = RunMPPGraph(); - if (!run_status.ok()) { - LOG(ERROR) << "Failed to run the graph: " << run_status.message(); - return EXIT_FAILURE; - } else { - LOG(INFO) << "Success!"; - } - return EXIT_SUCCESS; -} diff --git a/examples/desktop/media_sequence/README.md b/examples/desktop/media_sequence/README.md deleted file mode 100644 index 828d152..0000000 --- a/examples/desktop/media_sequence/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# Preparing data sets for machine learning with MediaPipe -We include two pipelines to prepare data sets for training TensorFlow models. - -Using these data sets is split into two parts. First, the data set is -constructed in with a Python script and MediaPipe C++ binary. The C++ binary -should be compiled by the end user because the preparation for different data -sets requires different MediaPipe calculator dependencies. The result of running -the script is a data set of TFRecord files on disk. The second stage is reading -the data from TensorFlow into a tf.data.Dataset. Both pipelines can be imported -and support a simple call to as_dataset() to make the data available. - -### Demo data set -To generate the demo dataset you must have Tensorflow installed. Then the -media_sequence_demo binary must be built from the top directory in the mediapipe -repo and the command to build the data set must be run from the same directory. -``` -bazel build -c opt mediapipe/examples/desktop/media_sequence:media_sequence_demo \ - --define MEDIAPIPE_DISABLE_GPU=1 - -python -m mediapipe.examples.desktop.media_sequence.demo_dataset \ - --alsologtostderr \ - --path_to_demo_data=/tmp/demo_data/ \ - --path_to_mediapipe_binary=bazel-bin/mediapipe/examples/desktop/\ -media_sequence/media_sequence_demo \ - --path_to_graph_directory=mediapipe/graphs/media_sequence/ -``` - -### Charades data set - -The Charades data set is ready for training and/or evaluating action recognition -models in TensorFlow. You may only use this script in ways that comply with the -Allen Institute for Artificial Intelligence's [license for the Charades data -set.](https://allenai.org/plato/charades/license.txt) - -To generate the Charades dataset you must have Tensorflow installed. Then the -media_sequence_demo binary must be built from the top directory in the mediapipe -repo and the command to build the data set must be run from the same directory. - -``` -bazel build -c opt mediapipe/examples/desktop/media_sequence:media_sequence_demo \ - --define MEDIAPIPE_DISABLE_GPU=1 - -python -m mediapipe.examples.desktop.media_sequence.charades_dataset \ - --alsologtostderr \ - --path_to_charades_data=/tmp/charades_data/ \ - --path_to_mediapipe_binary=bazel-bin/mediapipe/examples/desktop/\ -media_sequence/media_sequence_demo \ - --path_to_graph_directory=mediapipe/graphs/media_sequence/ -``` - -### Custom videos in the Kinetics format - -To produce data in the same format at the Kinetics data, use the kinetics.py -script. - -To generate the dataset you must have Tensorflow installed. Then the -media_sequence_demo binary must be built from the top directory in the mediapipe -repo and the command to build the data set must be run from the same directory. - -``` -echo "Credit for this video belongs to: ESA/Hubble; Music: Johan B. Monell" -wget https://cdn.spacetelescope.org/archives/videos/medium_podcast/heic1608c.mp4 -O /tmp/heic1608c.mp4 -CUSTOM_CSV=/tmp/custom_kinetics.csv -VIDEO_PATH=/tmp/heic1608c.mp4 -echo -e "video,time_start,time_end,split\n${VIDEO_PATH},0,10,custom" > ${CUSTOM_CSV} - -bazel build -c opt mediapipe/examples/desktop/media_sequence:media_sequence_demo \ - --define MEDIAPIPE_DISABLE_GPU=1 - -python -m mediapipe.examples.desktop.media_sequence.kinetics_dataset \ - --alsologtostderr \ - --splits_to_process=custom \ - --path_to_custom_csv=${CUSTOM_CSV} \ - --video_path_format_string={video} \ - --path_to_kinetics_data=/tmp/ms/kinetics/ \ - --path_to_mediapipe_binary=bazel-bin/mediapipe/examples/desktop/\ -media_sequence/media_sequence_demo \ - --path_to_graph_directory=mediapipe/graphs/media_sequence/ -``` diff --git a/examples/desktop/media_sequence/__init__.py b/examples/desktop/media_sequence/__init__.py deleted file mode 100644 index 6db73bc..0000000 --- a/examples/desktop/media_sequence/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Copyright 2019 The MediaPipe Authors. - -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. -""" diff --git a/examples/desktop/media_sequence/charades_dataset.py b/examples/desktop/media_sequence/charades_dataset.py deleted file mode 100644 index 9c85405..0000000 --- a/examples/desktop/media_sequence/charades_dataset.py +++ /dev/null @@ -1,519 +0,0 @@ -# Copyright 2019 The MediaPipe Authors. -# -# 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. - -r"""Code to download and parse the Charades dataset for TensorFlow models. - -The [Charades data set](https://allenai.org/plato/charades/) is a data set of -human action recognition collected with and maintained by the Allen Institute -for Artificial Intelligence. This script downloads and prepares the data set for -training a TensorFlow model. To use this script, you must abide by the -[lincense](https://allenai.org/plato/charades/license.txt) for the Charades data -set provided by the Allen Institute. The license for this script only covers -this code and not the data set. - -Running this code as a module generates the data set on disk. First, the -required files are downloaded (_download_data). Then, for each split in the -data set (generate_examples), the metadata is generated from the annotations for -each example (_generate_metadata), and MediaPipe is used to fill in the video -frames (_run_mediapipe). The data set is written to disk as a set of numbered -TFRecord files. If the download is disrupted, the incomplete files will need to -be removed before running the script again. This pattern can be reproduced and -modified to generate most video data sets. - -Generating the data on disk will probably take 4-8 hours and requires 150 GB of -disk space. (Image compression quality is the primary determiner of disk usage.) -After generating the data, the 30 GB of compressed video data can be deleted. - -Once the data is on disk, reading the data as a tf.data.Dataset is accomplished -with the following lines: - - charades = CharadesDataset("charades_data_path") - dataset = charades.as_dataset("test") - # implement additional processing and batching here - images_and_labels = dataset.make_one_shot_iterator().get_next() - images = images_and_labels["images"] - labels = image_and_labels["classification_target"] - label_weights = image_and_labels["indicator_matrix"] - -This data is structured for per-frame action classification where images is -the sequence of images, labels are the sequence of classification targets and, -label_weights is 1 for valid frames and 0 for padded frames (if any). See -as_dataset() for more details. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import contextlib -import csv -import os -import random -import subprocess -import sys -import tempfile -import zipfile - -from absl import app -from absl import flags -from absl import logging -from six.moves import range -from six.moves import urllib -import tensorflow.compat.v1 as tf - -from mediapipe.util.sequence import media_sequence as ms - - -DATA_URL_ANNOTATIONS = "http://ai2-website.s3.amazonaws.com/data/Charades.zip" -DATA_URL_VIDEOS = "http://ai2-website.s3.amazonaws.com/data/Charades_v1_480.zip" -DATA_URL_LICENSE = "https://allenai.org/plato/charades/license.txt" -CITATION = r"""@article{sigurdsson2016hollywood, -author = {Gunnar A. Sigurdsson and G{\"u}l Varol and Xiaolong Wang and Ivan Laptev and Ali Farhadi and Abhinav Gupta}, -title = {Hollywood in Homes: Crowdsourcing Data Collection for Activity Understanding}, -journal = {ArXiv e-prints}, -eprint = {1604.01753}, -year = {2016}, -url = {http://arxiv.org/abs/1604.01753}, -}""" -SECONDS_TO_MICROSECONDS = 1000000 -GRAPHS = ["clipped_images_from_file_at_24fps.pbtxt"] -SPLITS = { - "train": ("charades_v1_train_records", # base name for sharded files - "Charades_v1_train.csv", # path to csv of annotations - 1000, # number of shards - 7986), # number of examples - "test": ("charades_v1_test_records", - "Charades_v1_test.csv", - 100, - 1864), -} -NUM_CLASSES = 157 -CLASS_LABEL_OFFSET = 1 - - -class Charades(object): - """Generates and loads the Charades data set.""" - - def __init__(self, path_to_data): - if not path_to_data: - raise ValueError("You must supply the path to the data directory.") - self.path_to_data = path_to_data - - def as_dataset(self, split, shuffle=False, repeat=False, - serialized_prefetch_size=32, decoded_prefetch_size=32): - """Returns Charades as a tf.data.Dataset. - - After running this function, calling padded_batch() on the Dataset object - will produce batches of data, but additional preprocessing may be desired. - If using padded_batch, the indicator_matrix output distinguishes valid - from padded frames. - - Args: - split: either "train" or "test" - shuffle: if true, shuffles both files and examples. - repeat: if true, repeats the data set forever. - serialized_prefetch_size: the buffer size for reading from disk. - decoded_prefetch_size: the buffer size after decoding. - Returns: - A tf.data.Dataset object with the following structure: { - "images": uint8 tensor, shape [time, height, width, channels] - "segment_matrix": binary tensor of segments, shape [time, num_segments]. - See one_hot_segments() for details. - "indicator_matrix": binary tensor indicating valid frames, - shape [time, 1]. If padded with zeros to align sizes, the indicator - marks where segments is valid. - "classification_target": binary tensor of classification targets, - shape [time, 158 classes]. More than one value in a row can be 1.0 if - segments overlap. - "example_id": a unique string id for each example, shape []. - "sampling_rate": the frame rate for each sequence, shape []. - "gt_segment_seconds": the start and end time of each segment, - shape [num_segments, 2]. - "gt_segment_classes": the class labels for each segment, - shape [num_segments]. - "num_segments": the number of segments in the example, shape []. - "num_timesteps": the number of timesteps in the example, shape []. - """ - def parse_fn(sequence_example): - """Parses a Charades example.""" - context_features = { - ms.get_example_id_key(): ms.get_example_id_default_parser(), - ms.get_segment_start_index_key(): ( - ms.get_segment_start_index_default_parser()), - ms.get_segment_end_index_key(): ( - ms.get_segment_end_index_default_parser()), - ms.get_segment_label_index_key(): ( - ms.get_segment_label_index_default_parser()), - ms.get_segment_label_string_key(): ( - ms.get_segment_label_string_default_parser()), - ms.get_segment_start_timestamp_key(): ( - ms.get_segment_start_timestamp_default_parser()), - ms.get_segment_end_timestamp_key(): ( - ms.get_segment_end_timestamp_default_parser()), - ms.get_image_frame_rate_key(): ( - ms.get_image_frame_rate_default_parser()), - } - - sequence_features = { - ms.get_image_encoded_key(): ms.get_image_encoded_default_parser() - } - parsed_context, parsed_sequence = tf.io.parse_single_sequence_example( - sequence_example, context_features, sequence_features) - - sequence_length = tf.shape(parsed_sequence[ms.get_image_encoded_key()])[0] - num_segments = tf.shape( - parsed_context[ms.get_segment_label_index_key()])[0] - # segments matrix and targets for training. - segments_matrix, indicator = one_hot_segments( - tf.sparse_tensor_to_dense( - parsed_context[ms.get_segment_start_index_key()]), - tf.sparse_tensor_to_dense( - parsed_context[ms.get_segment_end_index_key()]), - sequence_length) - - classification_target = timepoint_classification_target( - segments_matrix, - tf.sparse_tensor_to_dense( - parsed_context[ms.get_segment_label_index_key()] - ) + CLASS_LABEL_OFFSET, - NUM_CLASSES + CLASS_LABEL_OFFSET) - - # [segments, 2] start and end time in seconds. - gt_segment_seconds = tf.to_float(tf.concat( - [tf.expand_dims(tf.sparse_tensor_to_dense(parsed_context[ - ms.get_segment_start_timestamp_key()]), 1), - tf.expand_dims(tf.sparse_tensor_to_dense(parsed_context[ - ms.get_segment_end_timestamp_key()]), 1)], - 1)) / float(SECONDS_TO_MICROSECONDS) - gt_segment_classes = tf.sparse_tensor_to_dense(parsed_context[ - ms.get_segment_label_index_key()]) + CLASS_LABEL_OFFSET - example_id = parsed_context[ms.get_example_id_key()] - sampling_rate = parsed_context[ms.get_image_frame_rate_key()] - - images = tf.map_fn(tf.image.decode_jpeg, - parsed_sequence[ms.get_image_encoded_key()], - back_prop=False, - dtype=tf.uint8) - - output_dict = { - "segment_matrix": segments_matrix, - "indicator_matrix": indicator, - "classification_target": classification_target, - "example_id": example_id, - "sampling_rate": sampling_rate, - "gt_segment_seconds": gt_segment_seconds, - "gt_segment_classes": gt_segment_classes, - "num_segments": num_segments, - "num_timesteps": sequence_length, - "images": images, - } - return output_dict - - if split not in SPLITS: - raise ValueError("Split %s not in %s" % split, str(list(SPLITS.keys()))) - all_shards = tf.io.gfile.glob( - os.path.join(self.path_to_data, SPLITS[split][0] + "-*-of-*")) - random.shuffle(all_shards) - all_shards_dataset = tf.data.Dataset.from_tensor_slices(all_shards) - cycle_length = min(16, len(all_shards)) - dataset = all_shards_dataset.apply( - tf.contrib.data.parallel_interleave( - tf.data.TFRecordDataset, - cycle_length=cycle_length, - block_length=1, sloppy=True, - buffer_output_elements=serialized_prefetch_size)) - dataset = dataset.prefetch(serialized_prefetch_size) - if shuffle: - dataset = dataset.shuffle(serialized_prefetch_size) - if repeat: - dataset = dataset.repeat() - dataset = dataset.map(parse_fn) - dataset = dataset.prefetch(decoded_prefetch_size) - return dataset - - def generate_examples(self, - path_to_mediapipe_binary, path_to_graph_directory): - """Downloads data and generates sharded TFRecords. - - Downloads the data files, generates metadata, and processes the metadata - with MediaPipe to produce tf.SequenceExamples for training. The resulting - files can be read with as_dataset(). After running this function the - original data files can be deleted. - - Args: - path_to_mediapipe_binary: Path to the compiled binary for the BUILD target - mediapipe/examples/desktop/demo:media_sequence_demo. - path_to_graph_directory: Path to the directory with MediaPipe graphs in - mediapipe/graphs/media_sequence/. - """ - if not path_to_mediapipe_binary: - raise ValueError( - "You must supply the path to the MediaPipe binary for " - "mediapipe/examples/desktop/demo:media_sequence_demo.") - if not path_to_graph_directory: - raise ValueError( - "You must supply the path to the directory with MediaPipe graphs in " - "mediapipe/graphs/media_sequence/.") - logging.info("Downloading data.") - annotation_dir, video_dir = self._download_data() - for name, annotations, shards, _ in SPLITS.values(): - annotation_file = os.path.join( - annotation_dir, annotations) - logging.info("Generating metadata for split: %s", name) - all_metadata = list(self._generate_metadata(annotation_file, video_dir)) - random.seed(47) - random.shuffle(all_metadata) - shard_names = [os.path.join(self.path_to_data, name + "-%05d-of-%05d" % ( - i, shards)) for i in range(shards)] - writers = [tf.io.TFRecordWriter(shard_name) for shard_name in shard_names] - with _close_on_exit(writers) as writers: - for i, seq_ex in enumerate(all_metadata): - print("Processing example %d of %d (%d%%) \r" % ( - i, len(all_metadata), i * 100 / len(all_metadata)), end="") - for graph in GRAPHS: - graph_path = os.path.join(path_to_graph_directory, graph) - seq_ex = self._run_mediapipe( - path_to_mediapipe_binary, seq_ex, graph_path) - writers[i % len(writers)].write(seq_ex.SerializeToString()) - logging.info("Data extraction complete.") - - def _generate_metadata(self, annotations_file, video_dir): - """For each row in the annotation CSV, generates the corresponding metadata. - - Args: - annotations_file: path to the file of Charades CSV annotations. - video_dir: path to the directory of video files referenced by the - annotations. - Yields: - Each tf.SequenceExample of metadata, ready to pass to MediaPipe. - """ - with open(annotations_file, "r") as annotations: - reader = csv.DictReader(annotations) - for row in reader: - metadata = tf.train.SequenceExample() - filepath = os.path.join(video_dir, "%s.mp4" % row["id"]) - actions = row["actions"].split(";") - action_indices = [] - action_strings = [] - action_start_times = [] - action_end_times = [] - for action in actions: - if not action: - continue - string, start, end = action.split(" ") - action_indices.append(int(string[1:])) - action_strings.append(bytes23(string)) - action_start_times.append(int(float(start) * SECONDS_TO_MICROSECONDS)) - action_end_times.append(int(float(end) * SECONDS_TO_MICROSECONDS)) - ms.set_example_id(bytes23(row["id"]), metadata) - ms.set_clip_data_path(bytes23(filepath), metadata) - ms.set_clip_start_timestamp(0, metadata) - ms.set_clip_end_timestamp( - int(float(row["length"]) * SECONDS_TO_MICROSECONDS), metadata) - ms.set_segment_start_timestamp(action_start_times, metadata) - ms.set_segment_end_timestamp(action_end_times, metadata) - ms.set_segment_label_string(action_strings, metadata) - ms.set_segment_label_index(action_indices, metadata) - yield metadata - - def _download_data(self): - """Downloads and extracts data if not already available.""" - if sys.version_info >= (3, 0): - urlretrieve = urllib.request.urlretrieve - else: - urlretrieve = urllib.request.urlretrieve - logging.info("Creating data directory.") - tf.io.gfile.makedirs(self.path_to_data) - logging.info("Downloading license.") - local_license_path = os.path.join( - self.path_to_data, DATA_URL_LICENSE.split("/")[-1]) - if not tf.io.gfile.exists(local_license_path): - urlretrieve(DATA_URL_LICENSE, local_license_path) - logging.info("Downloading annotations.") - local_annotations_path = os.path.join( - self.path_to_data, DATA_URL_ANNOTATIONS.split("/")[-1]) - if not tf.io.gfile.exists(local_annotations_path): - urlretrieve(DATA_URL_ANNOTATIONS, local_annotations_path) - logging.info("Downloading videos.") - local_videos_path = os.path.join( - self.path_to_data, DATA_URL_VIDEOS.split("/")[-1]) - if not tf.io.gfile.exists(local_videos_path): - urlretrieve(DATA_URL_VIDEOS, local_videos_path, progress_hook) - logging.info("Extracting annotations.") - # return video dir and annotation_dir by removing .zip from the path. - annotations_dir = local_annotations_path[:-4] - if not tf.io.gfile.exists(annotations_dir): - with zipfile.ZipFile(local_annotations_path) as annotations_zip: - annotations_zip.extractall(self.path_to_data) - logging.info("Extracting videos.") - video_dir = local_videos_path[:-4] - if not tf.io.gfile.exists(video_dir): - with zipfile.ZipFile(local_videos_path) as videos_zip: - videos_zip.extractall(self.path_to_data) - return annotations_dir, video_dir - - def _run_mediapipe(self, path_to_mediapipe_binary, sequence_example, graph): - """Runs MediaPipe over MediaSequence tf.train.SequenceExamples. - - Args: - path_to_mediapipe_binary: Path to the compiled binary for the BUILD target - mediapipe/examples/desktop/demo:media_sequence_demo. - sequence_example: The SequenceExample with metadata or partial data file. - graph: The path to the graph that extracts data to add to the - SequenceExample. - Returns: - A copy of the input SequenceExample with additional data fields added - by the MediaPipe graph. - Raises: - RuntimeError: if MediaPipe returns an error or fails to run the graph. - """ - if not path_to_mediapipe_binary: - raise ValueError("--path_to_mediapipe_binary must be specified.") - input_fd, input_filename = tempfile.mkstemp() - output_fd, output_filename = tempfile.mkstemp() - cmd = [path_to_mediapipe_binary, - "--calculator_graph_config_file=%s" % graph, - "--input_side_packets=input_sequence_example=%s" % input_filename, - "--output_side_packets=output_sequence_example=%s" % output_filename] - with open(input_filename, "wb") as input_file: - input_file.write(sequence_example.SerializeToString()) - mediapipe_output = subprocess.check_output(cmd) - if b"Failed to run the graph" in mediapipe_output: - raise RuntimeError(mediapipe_output) - with open(output_filename, "rb") as output_file: - output_example = tf.train.SequenceExample() - output_example.ParseFromString(output_file.read()) - os.close(input_fd) - os.remove(input_filename) - os.close(output_fd) - os.remove(output_filename) - return output_example - - -def one_hot_segments(start_indices, end_indices, num_samples): - """Returns a one-hot, float matrix of segments at each timestep. - - All integers in the inclusive range of start_indices and end_indices are used. - This allows start and end timestamps to be mapped to the same index and the - segment will not be omitted. - - Args: - start_indices: a 1d tensor of integer indices for the start of each - segement. - end_indices: a tensor of integer indices for the end of each segment. - Must be the same shape as start_indices. Values should be >= start_indices - but not strictly enforced. - num_samples: the number of rows in the output. Indices should be < - num_samples, but this is not strictly enforced. - Returns: - (segments, indicator) - segments: A [num_samples, num_elements(start_indices)] tensor where in each - column the rows with indices >= start_indices[column] and - <= end_indices[column] are 1.0 and all other values are 0.0. - indicator: a tensor of 1.0 values with shape [num_samples, 1]. If padded - with zeros to align sizes, the indicator marks where segments is valid. - """ - start_indices = tf.convert_to_tensor(start_indices) - end_indices = tf.convert_to_tensor(end_indices) - start_indices.shape.assert_is_compatible_with(end_indices.shape) - start_indices.shape.assert_has_rank(1) - end_indices.shape.assert_has_rank(1) - # create a matrix of the index at each row with a column per segment. - indices = tf.to_int64( - tf.tile( - tf.transpose(tf.expand_dims(tf.range(num_samples), 0)), - [1, tf.shape(start_indices)[0]])) - # switch to one hot encoding of segments (includes start and end indices) - segments = tf.to_float( - tf.logical_and( - tf.greater_equal(indices, start_indices), - tf.less_equal(indices, end_indices))) - # create a tensors of ones everywhere there's an annotation. If padded with - # zeros later, element-wise multiplication of the loss will mask out the - # padding. - indicator = tf.ones(shape=[num_samples, 1], dtype=tf.float32) - return segments, indicator - - -def timepoint_classification_target(segments, segment_classes, num_classes): - """Produces a classification target at each timepoint. - - If no segments are present at a time point, the first class is set to 1.0. - This should be used as a background class unless segments are always present. - - Args: - segments: a [time, num_segments] tensor that is 1.0 at indices within - each segment and 0.0 elsewhere. - segment_classes: a [num_segments] tensor with the class index of each - segment. - num_classes: the number of classes (must be >= max(segment_classes) + 1) - Returns: - a [time, num_classes] tensor. In the final output, more than one - value in a row can be 1.0 if segments overlap. - """ - num_segments = tf.shape(segments)[1] - matrix_of_class_indices = tf.to_int32( - segments * tf.to_float(tf.expand_dims(segment_classes, 0))) - # First column will have one count per zero segment. Correct this to be 0 - # unless no segments are present. - one_hot = tf.reduce_sum(tf.one_hot(matrix_of_class_indices, num_classes), 1) - normalizer = tf.concat([ - tf.ones(shape=[1, 1], dtype=tf.float32) / tf.to_float(num_segments), - tf.ones(shape=[1, num_classes - 1], dtype=tf.float32) - ], 1) - corrected_one_hot = tf.floor(one_hot * normalizer) - return corrected_one_hot - - -def progress_hook(blocks, block_size, total_size): - print("Downloaded %d%% of %d bytes (%d blocks)\r" % ( - blocks * block_size / total_size * 100, total_size, blocks), end="") - - -def bytes23(string): - """Creates a bytes string in either Python 2 or 3.""" - if sys.version_info >= (3, 0): - return bytes(string, "utf8") - else: - return bytes(string) - - -@contextlib.contextmanager -def _close_on_exit(writers): - """Call close on all writers on exit.""" - try: - yield writers - finally: - for writer in writers: - writer.close() - - -def main(argv): - if len(argv) > 1: - raise app.UsageError("Too many command-line arguments.") - Charades(flags.FLAGS.path_to_charades_data).generate_examples( - flags.FLAGS.path_to_mediapipe_binary, - flags.FLAGS.path_to_graph_directory) - -if __name__ == "__main__": - flags.DEFINE_string("path_to_charades_data", - "", - "Path to directory to write data to.") - flags.DEFINE_string("path_to_mediapipe_binary", - "", - "Path to the MediaPipe run_graph_file_io_main binary.") - flags.DEFINE_string("path_to_graph_directory", - "", - "Path to directory containing the graph files.") - app.run(main) diff --git a/examples/desktop/media_sequence/demo_dataset.py b/examples/desktop/media_sequence/demo_dataset.py deleted file mode 100644 index 93ea56e..0000000 --- a/examples/desktop/media_sequence/demo_dataset.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright 2019 The MediaPipe Authors. -# -# 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. - -r"""A demo data set constructed with MediaSequence and MediaPipe. - -This code demonstrates the steps for constructing a data set with MediaSequence. -This code has two functions. First, it can be run as a module to download and -prepare a toy dataset. Second, it can be imported and used to provide a -tf.data.Dataset reading that data from disk via as_dataset(). - -Running as a module prepares the data in three stages via generate_examples(). -First, the actual data files are downloaded. If the download is disrupted, the -incomplete files will need to be removed before running the script again. -Second, the annotations are parsed and reformated into metadata as described in -the MediaSequence documentation. Third, MediaPipe is run to extract subsequences -of frames for subsequent training via _run_mediapipe(). - -The toy data set is classifying a clip as a panning shot of galaxy or nebula -from videos releasued under the [Creative Commons Attribution 4.0 International -license](http://creativecommons.org/licenses/by/4.0/) on the ESA/Hubble site. -(The use of these ESA/Hubble materials does not imply the endorsement by -ESA/Hubble or any ESA/Hubble employee of a commercial product or service.) Each -video is split into 5 or 6 ten-second clips with a label of "galaxy" or "nebula" -and downsampled to 10 frames per second. (The last clip for each test example is -only 6 seconds.) There is one video of each class in each of the training and -testing splits. - -Reading the data as a tf.data.Dataset is accomplished with the following lines: - - demo = DemoDataset("demo_data_path") - dataset = demo.as_dataset("test") - # implement additional processing and batching here - images_and_labels = dataset.make_one_shot_iterator().get_next() - images = images_and_labels["images"] - labels = image_and_labels["labels"] -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import contextlib -import csv -import os -import random -import subprocess -import sys -import tempfile - -from absl import app -from absl import flags -from absl import logging -from six.moves import range -from six.moves import urllib -import tensorflow.compat.v1 as tf - -from mediapipe.util.sequence import media_sequence as ms - -SPLITS = { - "train": - """url,label index,label string,duration,credits -https://cdn.spacetelescope.org/archives/videos/medium_podcast/heic1608c.mp4,0,nebula,50,"ESA/Hubble; Music: Johan B. Monell" -https://cdn.spacetelescope.org/archives/videos/medium_podcast/heic1712b.mp4,1,galaxy,50,"ESA/Hubble, Digitized Sky Survey, Nick Risinger (skysurvey.org) Music: Johan B Monell" -""", - "test": - """url,label index,label string,duration,credits -https://cdn.spacetelescope.org/archives/videos/medium_podcast/heic1301b.m4v,0,nebula,56,"NASA, ESA. Acknowledgement: Josh Lake" -https://cdn.spacetelescope.org/archives/videos/medium_podcast/heic1305b.m4v,1,galaxy,56,"NASA, ESA, Digitized Sky Survey 2. Acknowledgement: A. van der Hoeven" -""" -} -NUM_CLASSES = 2 -NUM_SHARDS = 2 -SECONDS_PER_EXAMPLE = 10 -MICROSECONDS_PER_SECOND = 1000000 -TF_RECORD_PATTERN = "demo_space_dataset_%s_tfrecord" -GRAPHS = ["clipped_images_from_file_at_24fps.pbtxt"] - - -class DemoDataset(object): - """Generates and loads a demo data set.""" - - def __init__(self, path_to_data): - if not path_to_data: - raise ValueError("You must supply the path to the data directory.") - self.path_to_data = path_to_data - - def as_dataset(self, - split, - shuffle=False, - repeat=False, - serialized_prefetch_size=32, - decoded_prefetch_size=32): - """Returns the dataset as a tf.data.Dataset. - - Args: - split: either "train" or "test" - shuffle: if true, shuffles both files and examples. - repeat: if true, repeats the data set forever. - serialized_prefetch_size: the buffer size for reading from disk. - decoded_prefetch_size: the buffer size after decoding. - - Returns: - A tf.data.Dataset object with the following structure: { - "images": uint8 tensor, shape [time, height, width, channels] - "labels": one hot encoded label tensor, shape [2] - "id": a unique string id for each example, shape [] - } - """ - - def parse_fn(sequence_example): - """Parses a clip classification example.""" - context_features = { - ms.get_example_id_key(): - ms.get_example_id_default_parser(), - ms.get_clip_label_index_key(): - ms.get_clip_label_index_default_parser(), - ms.get_clip_label_string_key(): - ms.get_clip_label_string_default_parser() - } - sequence_features = { - ms.get_image_encoded_key(): ms.get_image_encoded_default_parser(), - } - parsed_context, parsed_sequence = tf.io.parse_single_sequence_example( - sequence_example, context_features, sequence_features) - example_id = parsed_context[ms.get_example_id_key()] - classification_target = tf.one_hot( - tf.sparse_tensor_to_dense( - parsed_context[ms.get_clip_label_index_key()]), NUM_CLASSES) - images = tf.map_fn( - tf.image.decode_jpeg, - parsed_sequence[ms.get_image_encoded_key()], - back_prop=False, - dtype=tf.uint8) - return { - "id": example_id, - "labels": classification_target, - "images": images, - } - - if split not in SPLITS: - raise ValueError("split '%s' is unknown." % split) - all_shards = tf.io.gfile.glob( - os.path.join(self.path_to_data, TF_RECORD_PATTERN % split + "-*-of-*")) - if shuffle: - random.shuffle(all_shards) - all_shards_dataset = tf.data.Dataset.from_tensor_slices(all_shards) - cycle_length = min(16, len(all_shards)) - dataset = all_shards_dataset.apply( - tf.data.experimental.parallel_interleave( - tf.data.TFRecordDataset, - cycle_length=cycle_length, - block_length=1, - sloppy=True, - buffer_output_elements=serialized_prefetch_size)) - dataset = dataset.prefetch(serialized_prefetch_size) - if shuffle: - dataset = dataset.shuffle(serialized_prefetch_size) - if repeat: - dataset = dataset.repeat() - dataset = dataset.map(parse_fn) - dataset = dataset.prefetch(decoded_prefetch_size) - return dataset - - def generate_examples(self, path_to_mediapipe_binary, - path_to_graph_directory): - """Downloads data and generates sharded TFRecords. - - Downloads the data files, generates metadata, and processes the metadata - with MediaPipe to produce tf.SequenceExamples for training. The resulting - files can be read with as_dataset(). After running this function the - original data files can be deleted. - - Args: - path_to_mediapipe_binary: Path to the compiled binary for the BUILD target - mediapipe/examples/desktop/demo:media_sequence_demo. - path_to_graph_directory: Path to the directory with MediaPipe graphs in - mediapipe/graphs/media_sequence/. - """ - if not path_to_mediapipe_binary: - raise ValueError("You must supply the path to the MediaPipe binary for " - "mediapipe/examples/desktop/demo:media_sequence_demo.") - if not path_to_graph_directory: - raise ValueError( - "You must supply the path to the directory with MediaPipe graphs in " - "mediapipe/graphs/media_sequence/.") - logging.info("Downloading data.") - tf.io.gfile.makedirs(self.path_to_data) - if sys.version_info >= (3, 0): - urlretrieve = urllib.request.urlretrieve - else: - urlretrieve = urllib.request.urlretrieve - for split in SPLITS: - reader = csv.DictReader(SPLITS[split].split("\n")) - all_metadata = [] - for row in reader: - url = row["url"] - basename = url.split("/")[-1] - local_path = os.path.join(self.path_to_data, basename) - if not tf.io.gfile.exists(local_path): - urlretrieve(url, local_path) - - for start_time in range(0, int(row["duration"]), SECONDS_PER_EXAMPLE): - metadata = tf.train.SequenceExample() - ms.set_example_id(bytes23(basename + "_" + str(start_time)), - metadata) - ms.set_clip_data_path(bytes23(local_path), metadata) - ms.set_clip_start_timestamp(start_time * MICROSECONDS_PER_SECOND, - metadata) - ms.set_clip_end_timestamp( - (start_time + SECONDS_PER_EXAMPLE) * MICROSECONDS_PER_SECOND, - metadata) - ms.set_clip_label_index((int(row["label index"]),), metadata) - ms.set_clip_label_string((bytes23(row["label string"]),), - metadata) - all_metadata.append(metadata) - random.seed(47) - random.shuffle(all_metadata) - shard_names = [self._indexed_shard(split, i) for i in range(NUM_SHARDS)] - writers = [tf.io.TFRecordWriter(shard_name) for shard_name in shard_names] - with _close_on_exit(writers) as writers: - for i, seq_ex in enumerate(all_metadata): - for graph in GRAPHS: - graph_path = os.path.join(path_to_graph_directory, graph) - seq_ex = self._run_mediapipe(path_to_mediapipe_binary, seq_ex, - graph_path) - writers[i % len(writers)].write(seq_ex.SerializeToString()) - - def _indexed_shard(self, split, index): - """Constructs a sharded filename.""" - return os.path.join( - self.path_to_data, - TF_RECORD_PATTERN % split + "-%05d-of-%05d" % (index, NUM_SHARDS)) - - def _run_mediapipe(self, path_to_mediapipe_binary, sequence_example, graph): - """Runs MediaPipe over MediaSequence tf.train.SequenceExamples. - - Args: - path_to_mediapipe_binary: Path to the compiled binary for the BUILD target - mediapipe/examples/desktop/demo:media_sequence_demo. - sequence_example: The SequenceExample with metadata or partial data file. - graph: The path to the graph that extracts data to add to the - SequenceExample. - - Returns: - A copy of the input SequenceExample with additional data fields added - by the MediaPipe graph. - Raises: - RuntimeError: if MediaPipe returns an error or fails to run the graph. - """ - if not path_to_mediapipe_binary: - raise ValueError("--path_to_mediapipe_binary must be specified.") - input_fd, input_filename = tempfile.mkstemp() - output_fd, output_filename = tempfile.mkstemp() - cmd = [ - path_to_mediapipe_binary, - "--calculator_graph_config_file=%s" % graph, - "--input_side_packets=input_sequence_example=%s" % input_filename, - "--output_side_packets=output_sequence_example=%s" % output_filename - ] - with open(input_filename, "wb") as input_file: - input_file.write(sequence_example.SerializeToString()) - mediapipe_output = subprocess.check_output(cmd) - if b"Failed to run the graph" in mediapipe_output: - raise RuntimeError(mediapipe_output) - with open(output_filename, "rb") as output_file: - output_example = tf.train.SequenceExample() - output_example.ParseFromString(output_file.read()) - os.close(input_fd) - os.remove(input_filename) - os.close(output_fd) - os.remove(output_filename) - return output_example - - -def bytes23(string): - """Creates a bytes string in either Python 2 or 3.""" - if sys.version_info >= (3, 0): - return bytes(string, "utf8") - else: - return bytes(string) - - -@contextlib.contextmanager -def _close_on_exit(writers): - """Call close on all writers on exit.""" - try: - yield writers - finally: - for writer in writers: - writer.close() - - -def main(argv): - if len(argv) > 1: - raise app.UsageError("Too many command-line arguments.") - DemoDataset(flags.FLAGS.path_to_demo_data).generate_examples( - flags.FLAGS.path_to_mediapipe_binary, flags.FLAGS.path_to_graph_directory) - - -if __name__ == "__main__": - flags.DEFINE_string("path_to_demo_data", "", - "Path to directory to write data to.") - flags.DEFINE_string("path_to_mediapipe_binary", "", - "Path to the MediaPipe run_graph_file_io_main binary.") - flags.DEFINE_string("path_to_graph_directory", "", - "Path to directory containing the graph files.") - app.run(main) diff --git a/examples/desktop/media_sequence/kinetics_dataset.py b/examples/desktop/media_sequence/kinetics_dataset.py deleted file mode 100644 index eafe18f..0000000 --- a/examples/desktop/media_sequence/kinetics_dataset.py +++ /dev/null @@ -1,478 +0,0 @@ -# Copyright 2019 The MediaPipe Authors. -# -# 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. - -r"""Code to download and parse the Kinetics dataset for TensorFlow models. - -The [Kinetics data set]( -https://deepmind.com/research/open-source/open-source-datasets/kinetics/) -is a data set for human action recognition maintained by DeepMind and Google. -This script downloads the annotations and prepares data from similar annotations -if local video files are available. - -This script does not provide any means of accessing YouTube videos. - -Running this code as a module generates the data set on disk. First, the -required files are downloaded (_download_data) which enables constructing the -label map. Then (in generate_examples), for each split in the data set, the -metadata is generated from the annotations for each example -(_generate_metadata), and MediaPipe is used to fill in the video frames -(_run_mediapipe). This script processes local video files defined in a custom -CSV in a comparable manner to the Kinetics data set for evaluating and -predicting values on your own data. The data set is written to disk as a set of -numbered TFRecord files. - -The custom CSV format must match the Kinetics data set format, with columns -corresponding to [[label_name], video, start, end, split] followed by lines with -those fields. (Label_name is optional.) These field names can be used to -construct the paths to the video files using the Python string formatting -specification and the video_path_format_string flag: - --video_path_format_string="/path/to/video/{video}.mp4" - -Generating the data on disk can take considerable time and disk space. -(Image compression quality is the primary determiner of disk usage. TVL1 flow -determines runtime.) - -Once the data is on disk, reading the data as a tf.data.Dataset is accomplished -with the following lines: - - kinetics = Kinetics("kinetics_data_path") - dataset = kinetics.as_dataset("custom") - # implement additional processing and batching here - images_and_labels = dataset.make_one_shot_iterator().get_next() - images = images_and_labels["images"] - labels = image_and_labels["labels"] - -This data is structured for per-clip action classification where images is -the sequence of images and labels are a one-hot encoded value. See -as_dataset() for more details. - -Note that the number of videos changes in the data set over time, so it will -likely be necessary to change the expected number of examples. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import contextlib -import csv -import os -import random -import subprocess -import sys -import tarfile -import tempfile - -from absl import app -from absl import flags -from absl import logging -from six.moves import range -from six.moves import urllib -from six.moves import zip -import tensorflow.compat.v1 as tf - -from mediapipe.util.sequence import media_sequence as ms - -CITATION = r"""@article{kay2017kinetics, - title={The kinetics human action video dataset}, - author={Kay, Will and Carreira, Joao and Simonyan, Karen and Zhang, Brian and Hillier, Chloe and Vijayanarasimhan, Sudheendra and Viola, Fabio and Green, Tim and Back, Trevor and Natsev, Paul and others}, - journal={arXiv preprint arXiv:1705.06950}, - year={2017}, - url = {https://deepmind.com/research/open-source/kinetics}, -}""" -ANNOTATION_URL = "https://storage.googleapis.com/deepmind-media/Datasets/kinetics700.tar.gz" -SECONDS_TO_MICROSECONDS = 1000000 -GRAPHS = ["tvl1_flow_and_rgb_from_file.pbtxt"] -FILEPATTERN = "kinetics_700_%s_25fps_rgb_flow" -SPLITS = { - "train": { - "shards": 1000, - "examples": 538779 - }, - "validate": { - "shards": 100, - "examples": 34499 - }, - "test": { - "shards": 100, - "examples": 68847 - }, - "custom": { - "csv": None, # Add a CSV for your own data here. - "shards": 1, # Change this number to increase sharding. - "examples": -1 - }, # Negative 1 allows any number of examples. -} -NUM_CLASSES = 700 - - -class Kinetics(object): - """Generates and loads the Kinetics data set.""" - - def __init__(self, path_to_data): - if not path_to_data: - raise ValueError("You must supply the path to the data directory.") - self.path_to_data = path_to_data - - def as_dataset(self, split, shuffle=False, repeat=False, - serialized_prefetch_size=32, decoded_prefetch_size=32, - parse_labels=True): - """Returns Kinetics as a tf.data.Dataset. - - After running this function, calling padded_batch() on the Dataset object - will produce batches of data, but additional preprocessing may be desired. - If using padded_batch, the indicator_matrix output distinguishes valid - from padded frames. - - Args: - split: either "train" or "test" - shuffle: if true, shuffles both files and examples. - repeat: if true, repeats the data set forever. - serialized_prefetch_size: the buffer size for reading from disk. - decoded_prefetch_size: the buffer size after decoding. - parse_labels: if true, also returns the "labels" below. The only - case where this should be false is if the data set was not constructed - with a label map, resulting in this field being missing. - Returns: - A tf.data.Dataset object with the following structure: { - "images": float tensor, shape [time, height, width, channels] - "flow": float tensor, shape [time, height, width, 2] - "num_frames": int32 tensor, shape [], number of frames in the sequence - "labels": float32 tensor, shape [num_classes], one hot encoded. Only - present if parse_labels is true. - """ - logging.info("If you see an error about labels, and you don't supply " - "labels in your CSV, set parse_labels=False") - def parse_fn(sequence_example): - """Parses a Kinetics example.""" - context_features = { - ms.get_example_id_key(): ms.get_example_id_default_parser(), - } - if parse_labels: - context_features[ - ms.get_clip_label_string_key()] = tf.FixedLenFeature((), tf.string) - context_features[ - ms.get_clip_label_index_key()] = tf.FixedLenFeature((), tf.int64) - - sequence_features = { - ms.get_image_encoded_key(): ms.get_image_encoded_default_parser(), - ms.get_forward_flow_encoded_key(): - ms.get_forward_flow_encoded_default_parser(), - } - parsed_context, parsed_sequence = tf.io.parse_single_sequence_example( - sequence_example, context_features, sequence_features) - - images = tf.image.convert_image_dtype( - tf.map_fn(tf.image.decode_jpeg, - parsed_sequence[ms.get_image_encoded_key()], - back_prop=False, - dtype=tf.uint8), tf.float32) - num_frames = tf.shape(images)[0] - - flow = tf.image.convert_image_dtype( - tf.map_fn(tf.image.decode_jpeg, - parsed_sequence[ms.get_forward_flow_encoded_key()], - back_prop=False, - dtype=tf.uint8), tf.float32) - # The flow is quantized for storage in JPEGs by the FlowToImageCalculator. - # The quantization needs to be inverted. - flow = (flow[:, :, :, :2] - 0.5) * 2 * 20. - - output_dict = { - "images": images, - "flow": flow, - "num_frames": num_frames, - } - if parse_labels: - target = tf.one_hot(parsed_context[ms.get_clip_label_index_key()], 700) - output_dict["labels"] = target - return output_dict - - if split not in SPLITS: - raise ValueError("Split %s not in %s" % split, str(list(SPLITS.keys()))) - all_shards = tf.io.gfile.glob( - os.path.join(self.path_to_data, FILEPATTERN % split + "-*-of-*")) - random.shuffle(all_shards) - all_shards_dataset = tf.data.Dataset.from_tensor_slices(all_shards) - cycle_length = min(16, len(all_shards)) - dataset = all_shards_dataset.apply( - tf.contrib.data.parallel_interleave( - tf.data.TFRecordDataset, - cycle_length=cycle_length, - block_length=1, sloppy=True, - buffer_output_elements=serialized_prefetch_size)) - dataset = dataset.prefetch(serialized_prefetch_size) - if shuffle: - dataset = dataset.shuffle(serialized_prefetch_size) - if repeat: - dataset = dataset.repeat() - dataset = dataset.map(parse_fn) - dataset = dataset.prefetch(decoded_prefetch_size) - return dataset - - def generate_examples(self, path_to_mediapipe_binary, - path_to_graph_directory, - only_generate_metadata=False, - splits_to_process="train,val,test", - video_path_format_string=None, - download_labels_for_map=True): - """Downloads data and generates sharded TFRecords. - - Downloads the data files, generates metadata, and processes the metadata - with MediaPipe to produce tf.SequenceExamples for training. The resulting - files can be read with as_dataset(). After running this function the - original data files can be deleted. - - Args: - path_to_mediapipe_binary: Path to the compiled binary for the BUILD target - mediapipe/examples/desktop/demo:media_sequence_demo. - path_to_graph_directory: Path to the directory with MediaPipe graphs in - mediapipe/graphs/media_sequence/. - only_generate_metadata: If true, do not run mediapipe and write the - metadata to disk instead. - splits_to_process: csv string of which splits to process. Allows providing - a custom CSV with the CSV flag. The original data is still downloaded - to generate the label_map. - video_path_format_string: The format string for the path to local files. - download_labels_for_map: If true, download the annotations to create the - label map. - """ - if not path_to_mediapipe_binary: - raise ValueError( - "You must supply the path to the MediaPipe binary for " - "mediapipe/examples/desktop/demo:media_sequence_demo.") - if not path_to_graph_directory: - raise ValueError( - "You must supply the path to the directory with MediaPipe graphs in " - "mediapipe/graphs/media_sequence/.") - logging.info("Downloading data.") - download_output = self._download_data(download_labels_for_map) - for key in splits_to_process.split(","): - logging.info("Generating metadata for split: %s", key) - all_metadata = list(self._generate_metadata( - key, download_output, video_path_format_string)) - logging.info("An example of the metadata: ") - logging.info(all_metadata[0]) - random.seed(47) - random.shuffle(all_metadata) - shards = SPLITS[key]["shards"] - shard_names = [os.path.join( - self.path_to_data, FILEPATTERN % key + "-%05d-of-%05d" % ( - i, shards)) for i in range(shards)] - writers = [tf.io.TFRecordWriter(shard_name) for shard_name in shard_names] - with _close_on_exit(writers) as writers: - for i, seq_ex in enumerate(all_metadata): - if not only_generate_metadata: - print("Processing example %d of %d (%d%%) \r" % ( - i, len(all_metadata), i * 100 / len(all_metadata)), end="") - for graph in GRAPHS: - graph_path = os.path.join(path_to_graph_directory, graph) - seq_ex = self._run_mediapipe( - path_to_mediapipe_binary, seq_ex, graph_path) - writers[i % len(writers)].write(seq_ex.SerializeToString()) - logging.info("Data extraction complete.") - - def _generate_metadata(self, key, download_output, - video_path_format_string=None): - """For each row in the annotation CSV, generates the corresponding metadata. - - Args: - key: which split to process. - download_output: the tuple output of _download_data containing - - annotations_files: dict of keys to CSV annotation paths. - - label_map: dict mapping from label strings to numeric indices. - video_path_format_string: The format string for the path to local files. - Yields: - Each tf.SequenceExample of metadata, ready to pass to MediaPipe. - """ - annotations_files, label_map = download_output - with open(annotations_files[key], "r") as annotations: - reader = csv.reader(annotations) - for i, csv_row in enumerate(reader): - if i == 0: # the first row is the header - continue - # rename the row with a constitent set of names. - if len(csv_row) == 5: - row = dict( - list( - zip(["label_name", "video", "start", "end", "split"], - csv_row))) - else: - row = dict(list(zip(["video", "start", "end", "split"], csv_row))) - metadata = tf.train.SequenceExample() - ms.set_example_id(bytes23(row["video"] + "_" + row["start"]), - metadata) - ms.set_clip_media_id(bytes23(row["video"]), metadata) - ms.set_clip_alternative_media_id(bytes23(row["split"]), metadata) - if video_path_format_string: - filepath = video_path_format_string.format(**row) - ms.set_clip_data_path(bytes23(filepath), metadata) - assert row["start"].isdigit(), "Invalid row: %s" % str(row) - assert row["end"].isdigit(), "Invalid row: %s" % str(row) - if "label_name" in row: - ms.set_clip_label_string([bytes23(row["label_name"])], metadata) - if label_map: - ms.set_clip_label_index([label_map[row["label_name"]]], metadata) - yield metadata - - def _download_data(self, download_labels_for_map): - """Downloads and extracts data if not already available.""" - if sys.version_info >= (3, 0): - urlretrieve = urllib.request.urlretrieve - else: - urlretrieve = urllib.request.urlretrieve - logging.info("Creating data directory.") - tf.io.gfile.makedirs(self.path_to_data) - logging.info("Downloading annotations.") - paths = {} - if download_labels_for_map: - tar_path = os.path.join(self.path_to_data, ANNOTATION_URL.split("/")[-1]) - if not tf.io.gfile.exists(tar_path): - urlretrieve(ANNOTATION_URL, tar_path) - with tarfile.open(tar_path) as annotations_tar: - annotations_tar.extractall(self.path_to_data) - for split in ["train", "test", "validate"]: - csv_path = os.path.join(self.path_to_data, "kinetics700/%s.csv" % split) - if not tf.io.gfile.exists(csv_path): - with tarfile.open(tar_path) as annotations_tar: - annotations_tar.extractall(self.path_to_data) - paths[split] = csv_path - for split, contents in SPLITS.items(): - if "csv" in contents and contents["csv"]: - paths[split] = contents["csv"] - label_map = (self.get_label_map_and_verify_example_counts(paths) if - download_labels_for_map else None) - return paths, label_map - - def _run_mediapipe(self, path_to_mediapipe_binary, sequence_example, graph): - """Runs MediaPipe over MediaSequence tf.train.SequenceExamples. - - Args: - path_to_mediapipe_binary: Path to the compiled binary for the BUILD target - mediapipe/examples/desktop/demo:media_sequence_demo. - sequence_example: The SequenceExample with metadata or partial data file. - graph: The path to the graph that extracts data to add to the - SequenceExample. - Returns: - A copy of the input SequenceExample with additional data fields added - by the MediaPipe graph. - Raises: - RuntimeError: if MediaPipe returns an error or fails to run the graph. - """ - if not path_to_mediapipe_binary: - raise ValueError("--path_to_mediapipe_binary must be specified.") - input_fd, input_filename = tempfile.mkstemp() - output_fd, output_filename = tempfile.mkstemp() - cmd = [path_to_mediapipe_binary, - "--calculator_graph_config_file=%s" % graph, - "--input_side_packets=input_sequence_example=%s" % input_filename, - "--output_side_packets=output_sequence_example=%s" % output_filename] - with open(input_filename, "wb") as input_file: - input_file.write(sequence_example.SerializeToString()) - mediapipe_output = subprocess.check_output(cmd) - if b"Failed to run the graph" in mediapipe_output: - raise RuntimeError(mediapipe_output) - with open(output_filename, "rb") as output_file: - output_example = tf.train.SequenceExample() - output_example.ParseFromString(output_file.read()) - os.close(input_fd) - os.remove(input_filename) - os.close(output_fd) - os.remove(output_filename) - return output_example - - def get_label_map_and_verify_example_counts(self, paths): - """Verify the number of examples and labels have not changed.""" - for name, path in paths.items(): - with open(path, "r") as f: - lines = f.readlines() - # the header adds one line and one "key". - num_examples = len(lines) - 1 - keys = [l.split(",")[0] for l in lines] - label_map = None - if name == "train": - classes = sorted(list(set(keys[1:]))) - num_keys = len(set(keys)) - 1 - assert NUM_CLASSES == num_keys, ( - "Found %d labels for split: %s, should be %d" % ( - num_keys, name, NUM_CLASSES)) - label_map = dict(list(zip(classes, list(range(len(classes)))))) - if SPLITS[name]["examples"] > 0: - assert SPLITS[name]["examples"] == num_examples, ( - "Found %d examples for split: %s, should be %d" % ( - num_examples, name, SPLITS[name]["examples"])) - return label_map - - -def bytes23(string): - """Creates a bytes string in either Python 2 or 3.""" - if sys.version_info >= (3, 0): - return bytes(string, "utf8") - else: - return bytes(string) - - -@contextlib.contextmanager -def _close_on_exit(writers): - """Call close on all writers on exit.""" - try: - yield writers - finally: - for writer in writers: - writer.close() - - -def main(argv): - if len(argv) > 1: - raise app.UsageError("Too many command-line arguments.") - if flags.FLAGS.path_to_custom_csv: - SPLITS["custom"]["csv"] = flags.FLAGS.path_to_custom_csv - Kinetics(flags.FLAGS.path_to_kinetics_data).generate_examples( - flags.FLAGS.path_to_mediapipe_binary, - flags.FLAGS.path_to_graph_directory, - flags.FLAGS.only_generate_metadata, - flags.FLAGS.splits_to_process, - flags.FLAGS.video_path_format_string, - flags.FLAGS.download_labels_for_map) - -if __name__ == "__main__": - flags.DEFINE_string("path_to_kinetics_data", - "", - "Path to directory to write data to.") - flags.DEFINE_string("path_to_mediapipe_binary", - "", - "Path to the MediaPipe run_graph_file_io_main binary.") - flags.DEFINE_string("path_to_graph_directory", - "", - "Path to directory containing the graph files.") - flags.DEFINE_boolean("only_generate_metadata", - False, - "If true, only generate the metadata files.") - flags.DEFINE_boolean("download_labels_for_map", - True, - "If true, download the annotations to construct the " - "label map.") - flags.DEFINE_string("splits_to_process", - "custom", - "Process these splits. Useful for custom data splits.") - flags.DEFINE_string("video_path_format_string", - None, - "The format string for the path to local video files. " - "Uses the Python string.format() syntax with possible " - "arguments of {video}, {start}, {end}, {label_name}, and " - "{split}, corresponding to columns of the data csvs.") - flags.DEFINE_string("path_to_custom_csv", - None, - "If present, processes this CSV as a custom split.") - app.run(main) diff --git a/examples/desktop/media_sequence/read_demo_dataset.py b/examples/desktop/media_sequence/read_demo_dataset.py deleted file mode 100644 index c057add..0000000 --- a/examples/desktop/media_sequence/read_demo_dataset.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2020 The MediaPipe Authors. -# -# 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. - -"""Example of reading a MediaSequence dataset. -""" - -from absl import app -from absl import flags - -from mediapipe.examples.desktop.media_sequence.demo_dataset import DemoDataset -import tensorflow as tf - -FLAGS = flags.FLAGS - - -def main(argv): - if len(argv) > 1: - raise app.UsageError('Too many command-line arguments.') - demo_data_path = '/tmp/demo_data/' - with tf.Graph().as_default(): - d = DemoDataset(demo_data_path) - dataset = d.as_dataset('test') - # implement additional processing and batching here - dataset_output = dataset.make_one_shot_iterator().get_next() - images = dataset_output['images'] - labels = dataset_output['labels'] - - with tf.Session() as sess: - images_, labels_ = sess.run([images, labels]) - print('The shape of images_ is %s' % str(images_.shape)) # pylint: disable=superfluous-parens - print('The shape of labels_ is %s' % str(labels_.shape)) # pylint: disable=superfluous-parens - - -if __name__ == '__main__': - app.run(main) diff --git a/examples/desktop/media_sequence/run_graph_file_io_main.cc b/examples/desktop/media_sequence/run_graph_file_io_main.cc deleted file mode 100644 index 06212b0..0000000 --- a/examples/desktop/media_sequence/run_graph_file_io_main.cc +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2019 The MediaPipe Authors. -// -// 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. -// -// A simple main function to run a MediaPipe graph. Input side packets are read -// from files provided via the command line and output side packets are written -// to disk. -#include - -#include "absl/flags/flag.h" -#include "absl/flags/parse.h" -#include "absl/strings/str_split.h" -#include "mediapipe/framework/calculator_framework.h" -#include "mediapipe/framework/port/file_helpers.h" -#include "mediapipe/framework/port/map_util.h" -#include "mediapipe/framework/port/parse_text_proto.h" -#include "mediapipe/framework/port/status.h" - -ABSL_FLAG(std::string, calculator_graph_config_file, "", - "Name of file containing text format CalculatorGraphConfig proto."); -ABSL_FLAG(std::string, input_side_packets, "", - "Comma-separated list of key=value pairs specifying side packets " - "and corresponding file paths for the CalculatorGraph. The side " - "packets are read from the files and fed to the graph as strings " - "even if they represent doubles, floats, etc."); -ABSL_FLAG(std::string, output_side_packets, "", - "Comma-separated list of key=value pairs specifying the output " - "side packets and paths to write to disk for the " - "CalculatorGraph."); - -absl::Status RunMPPGraph() { - std::string calculator_graph_config_contents; - MP_RETURN_IF_ERROR(mediapipe::file::GetContents( - absl::GetFlag(FLAGS_calculator_graph_config_file), - &calculator_graph_config_contents)); - LOG(INFO) << "Get calculator graph config contents: " - << calculator_graph_config_contents; - mediapipe::CalculatorGraphConfig config = - mediapipe::ParseTextProtoOrDie( - calculator_graph_config_contents); - std::map input_side_packets; - std::vector kv_pairs = - absl::StrSplit(absl::GetFlag(FLAGS_input_side_packets), ','); - for (const std::string& kv_pair : kv_pairs) { - std::vector name_and_value = absl::StrSplit(kv_pair, '='); - RET_CHECK(name_and_value.size() == 2); - RET_CHECK(!mediapipe::ContainsKey(input_side_packets, name_and_value[0])); - std::string input_side_packet_contents; - MP_RETURN_IF_ERROR(mediapipe::file::GetContents( - name_and_value[1], &input_side_packet_contents)); - input_side_packets[name_and_value[0]] = - mediapipe::MakePacket(input_side_packet_contents); - } - LOG(INFO) << "Initialize the calculator graph."; - mediapipe::CalculatorGraph graph; - MP_RETURN_IF_ERROR(graph.Initialize(config, input_side_packets)); - LOG(INFO) << "Start running the calculator graph."; - MP_RETURN_IF_ERROR(graph.Run()); - LOG(INFO) << "Gathering output side packets."; - kv_pairs = absl::StrSplit(absl::GetFlag(FLAGS_output_side_packets), ','); - for (const std::string& kv_pair : kv_pairs) { - std::vector name_and_value = absl::StrSplit(kv_pair, '='); - RET_CHECK(name_and_value.size() == 2); - absl::StatusOr output_packet = - graph.GetOutputSidePacket(name_and_value[0]); - RET_CHECK(output_packet.ok()) - << "Packet " << name_and_value[0] << " was not available."; - const std::string& serialized_string = - output_packet.value().Get(); - MP_RETURN_IF_ERROR( - mediapipe::file::SetContents(name_and_value[1], serialized_string)); - } - return absl::OkStatus(); -} - -int main(int argc, char** argv) { - google::InitGoogleLogging(argv[0]); - absl::ParseCommandLine(argc, argv); - absl::Status run_status = RunMPPGraph(); - if (!run_status.ok()) { - LOG(ERROR) << "Failed to run the graph: " << run_status.message(); - return EXIT_FAILURE; - } else { - LOG(INFO) << "Success!"; - } - return EXIT_SUCCESS; -} diff --git a/mediapipe/graphs/object_detection/object_detection_desktop_live.pbtxt b/examples/desktop/object_detection/object_detection_desktop_live.pbtxt similarity index 96% rename from mediapipe/graphs/object_detection/object_detection_desktop_live.pbtxt rename to examples/desktop/object_detection/object_detection_desktop_live.pbtxt index 3762d01..ea614d6 100644 --- a/mediapipe/graphs/object_detection/object_detection_desktop_live.pbtxt +++ b/examples/desktop/object_detection/object_detection_desktop_live.pbtxt @@ -64,7 +64,7 @@ node { output_stream: "TENSORS:detection_tensors" node_options: { [type.googleapis.com/mediapipe.TfLiteInferenceCalculatorOptions] { - model_path: "/mediapipe-lite/mediapipe/models/ssdlite_object_detection.tflite" + model_path: "/mediapipe-lite/examples/desktop/object_detection/ssdlite_object_detection.tflite" } } } @@ -147,7 +147,7 @@ node { output_stream: "output_detections" node_options: { [type.googleapis.com/mediapipe.DetectionLabelIdToTextCalculatorOptions] { - label_map_path: "/mediapipe-lite/mediapipe/models/ssdlite_object_detection_labelmap.txt" + label_map_path: "/mediapipe-lite/examples/desktop/object_detection/ssdlite_object_detection_labelmap.txt" } } } diff --git a/mediapipe/models/ssdlite_object_detection_labelmap.txt b/examples/desktop/object_detection/ssdlite_object_detection_labelmap.txt similarity index 100% rename from mediapipe/models/ssdlite_object_detection_labelmap.txt rename to examples/desktop/object_detection/ssdlite_object_detection_labelmap.txt diff --git a/examples/desktop/object_detection/test_video.mp4 b/examples/desktop/object_detection/test_video.mp4 deleted file mode 100644 index c706232d319feae7ff983218c7be1032a80610ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4367585 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 z zE1A~Dfh5&GL2~N&bku#kBkD(#>v%_$uyJgn_}r$0uhd8${sHrndYr#2M_ZIPDcpQf z$9@MGOCKocdi;1~+jOMx0tnRg>SoRE^EzUMj7xYWZffOipOO}vcw z4piL}VcHRLUI~E)k>OdTG~k??>F6Np)z>(wIs!N+?2C1U+q+#DrGoW2uz&JfKu%v6 z=FeG{+5^}ho2vG$rUFTPx0Z(_#is`_Z;G>P;2w~Ux>i6fAy8>_oEgOddS?6|*Z)^V zM|^c9Q;1!rZ1UPv$VB5HV76L2ENMEaEJ0}r|pJmktsvi4T==|q-qW?&f; zu$Ep$i&L@k$|Pi`V<5G#>RSNo=0wDsU2dAxe(C{_hOJVPqFSeKM9a;9|J$44UaVdo zn&|4Ij6d!k!*&%&Re+cyR+Z4etO4~^@g0-uuK52pb&9s7{gnbXtg|SDY)JWuM0UrD zTG0?FGLR9CC^kA_{5lmDDaF%0l2ecF(^BOOr;?*55aX@aIgZl)?DMCu#rRM38X)VV z23Z2K)}(&Rheyc)S^4+9N0*8*zebyzTFVmlFKw{AR5d@D_X&z~&?zF%|m zi_YtJPA?Z2z6+LAEfx^dD*q^SO3oQw!tYi?C%Z|8cFV|kB+66jajO~fT-X(<^ z{3F>OYO$`P)M8}ky)}spo@Ab1e-~~`BCbCoRsv(4Q)I8qNRHR8hu7%p{7%HlSU;y&vba>G$7 zC+jw2)$)zF??(WXH+j3vN&3vdzK_PlXXC!DqQk#fz^I$MEE?uTE(G50s7WNl6}E3y zS$yT=tVeYGhLozu-Ysq?F&+aYJ|B!cN6_h7C22#A2O>k!w z{S7QJdtXs^Uv&9d!`sy=7jkY9y`~*JQNZ?@H>^kqIA;j5la`92BQ*8yj{a`x zz>8MODqF>k^NVeabCwoR<8Vm)N}F|WX{WbS-~is;_MgO}w`l9Vf0ba~Q=|PB8(GWD zwQfTEyS6P>Yw&?2$=TGDW@X%nww4I6-TkwK0IhdHR6PFG8Ww=f6n6%0}MM*jFyL>MP7d zFv9e0Yp@PFWr~Sbak&bdwNMwEy(F7P1>g<%u@$MWwsF48f4ZVe3saVdifi#^_DAO^ z{17;_bv;~BrR}W+2F9!|s&l6cv_Mc|cA`2J724W}2 zr)AGB0ppj2@W);su9N`HX~~mHAaPHz1f?EYFY?x-i=u!!mPiC-yO5=Iy|L z@=(|tn9*haTSCOPx9p5<_~9Z_$zp1p*|~nT*TgD8L;S`cYxedIY8E*ulo;yQfvEA_efPi zau7ZnYll$hoRwExc9l7alpB}N*k64ffyG4?a;yL&Gw8cw)vgQGlny;BD%exgx@7X2 z5L+qgoLPGevQ4ZTOww&72it=+)S5n0p@v;noWp1mJCRRUwm7KaRvrS@)^ZE&3*-_4HD3i|0v75JH5&A*^2SIaiiZXV6UX_1)jc~q;*6*4 zs796u6PW@ySf5E|sAk=pmc`dxK#bWKn5}aXz2tlqL=;Uc3d|ULJifmn==Bt`+n4-> zJs`&hVEKX)3OC+gBs71zB3&`EpuMdq1N6}ZD+-RYgm$SWpHr3~KtNv*>A zu9fmtVEV3$qW?HFH?(C4Cl4H^=mS=zlxnBY(kr{NDPRg1PpB}I{C9bjU38PRMV1Um z_Yd6Cer6-N7rq&p)GBQEnOIW7b+bBa39fE?%CB>rVH|K;TPUSfE&(Aw=J;%YhIv7e ztpvov){OIAeXSHZD;J~P3mIC)9em#HYXEk02IY2XV9m*Vz%^Hgc37n7Ud_&J@Bfm@ z%$Sy3tm~CKP7AeGNI*>*(tQ21Vo7B2owRRo3Ep7TIg!`w_VIY_YI39X6E&x^ zkWviih3pbYRxu+u018!`lLbgrJF9%cN#*6{n93$tvTJ0bQ=0rgT*QsZr!UJwik}Ba zEC5z7Q3|r1QHD4^8->sm>{4+l1?P}-IdzR7kVk7kamnCXEo%UgGZ(D7stXyV4!G7D zZ#b0bn7RhE0kQt+AcPp;o9f#idOnAq`L?)bx!uiPTvRHK=I`2Rb*K7TS4-9Xp$Y)| zT&4LT4|MaOo@>S7IWqAj8&@DM8(^Vk=Qo-30Jb4e>o()aA;!V1bC42@mYI~N;YqM9 z^wz|RtdvkP28rtO)a`4*f#puw!|BoyFrw)qub)Mg?E*Bv+uMN>N@!zes z8T(MKyTKT{fVP?wwIhA(XZR{jzh&i-ZwA~VIqut?5^vFqLNIy|5r!SdvL+z(aZq0z z{5_jjsmOyVm#8~U8}(i`)!~Tm+tytcsKIW}pp>dVW~ z-j8LT=4WCQUQ#*Y+2R|87eJ$8_Re+u2dASQshDWsb(yTxztqXnI71D_0T(>}DbD)bDODZm-ssC{N9WU$^*IF{6Ejyju+>PB#qba_=_H*1JkTzOmWT+dfusxBmHd=Rgcd#R z3|K$n=pv2WBD@|~kfY3;4bIwTENPRNSNAI}#vBY^$r)~B4d++TMmW=$+ry!x&|)5z zbU-{T%mf?FIf1#-4vgAg8XA%=rGcQh9pQ(jHY^x52}g9JH@>)JBu!xTs*1c=_2o$_ z4P5L*VjKk*GkkH4`^ybyVazyzQU>;{90#H;y=eDNL}V*GFzUgX$5*h5uX=+YtA{JJ zb3sVlP~;wt(y#=kSswWo7$|;$qgIV{oicMce1pmL8?75JDrgl&?;0d{>dqu?y(JLi zNdOIBkZ37Itb`_>p$OpP00<_)mj!QK=s1U5;aHNDR)bw-u4p8ZM~e{cpDDLW@c%_8 z1ZBZvhKL*#XZ0_-< zVvB!)R9Zw}sY0>B+h`G^#2<}$F1AgV9K*AHmj$>>EaQDGd6=`X%-~K-Y|MGP2rG;q^fi^%i06Psrr2; zh?LN`cG*b}7VFByIy{uF(vXJ6l|c6vGqO*IGRKwd)AM`HJ!__bl>>}-bK zG64EezuBG=+rATja}a~}V_?1|Clumdf9zCq-Q+JwLehDlX1@ z-ec?>yo;3wmC;62Kg#$hRBOuEiBy6(#C|;<)blMiW5>bpV)shO^Tci5+!T`-lh6QS zIUks*No+|yN9M*D@+I;8zZJD}X;OG5ND>T?+dzOsYXE*_2=~q~$lr#S8+|qHNWbg& zh;dr9<2=D1$hN){+dwdyOF{1rz&uImr5^P+oDj#eXeO~!#1K{3@)_*jZjMxXWOf8` zO<#YkT`M8KxeQ=)5toQ%eG3q$xA*utI$<5l?xYV2$>F9 zdIi<$kIG%!-RgNX6{Sh1=4@!2tW*m$9E)7<`N)_VGmA#_(oEVau$wubvRO2@~ zpm41?tPIRxTwzosna2V7QGs|%udb{v#y1~bR?I5}NSrDWSC-B*`Q49t-{-mmvuaelwHee7X+GG+Ll4t#;NDK?&_Y?wX6`TqSRHpl1*vs!s z;2GBrsYvrW7wS^s5 z#m4-vzh~&9OaDOqyQ~W-aOnz^J^26HUDw1-Wy$Z zOB_p$*F`Eb92$Z*-xnjtXfGhkLqrXl9U%?>g)X#}E=X&621D#~G_9f+>RlSTJ0~XT z)f7A;g+QU{V`T1oOvWk{ush7EMY|mzORE@jlS|{MabuqO%X8Iz$tq$SP2(zw`|_yd zUQ6drA{fmz;Rt{%o)^IO6tHS(f`Re$$nIWFIS16=x?Y1&&rdLIvWGm6-Icm znc{^2^g)6_EJPPPY=oYl3qsNy_yJ?N?sab}m(Zdep;_v#!HB%+d1!mzyv%*s+DAYa zws(ErUbp#6Lv`2Jz_T)1>4o!X+>**8q_ zPZ1JNzikb}Vk_CzsOQ)vN(9+~cfz9-hovMX9(iMRt+WaQgNy}g{E`X(Npf=#R7Ji? z6G`|yxw~dEx0&p5jOiTX!%f0UxC+SeDR3q-@1wsR%m?762F8o8*(LT^deUebv$iZc z2SS<4d{ezSFaEC$O_2!I&L^yVgzi3puKf!$ zWSH(J$cbLj8J85F93_C;OS0Uw&E%&>^9+q4YJpnT+h&(J$Tf;3|1$|fnK{Epgh z-`GzLP~__}v@Own)kxgfTysmYE^6ZBWp^z42Z5veBLvSG+V7ZMC`_Ma6N7Y8@0D;Z z%SDs37tVGgq^LpJsuYqpf_@T&*r#hm%0eiV=(D)aZAFqD2bK={I@I9paQU7(qW&FqU7<%ut z-x9df2qAB{Q`q6|%rV@M^cqq(`bb=Kv{x_%*#mrIO)B)c1(-t{K>ppe53N4+XPp^W z5Rh5y3zTBNQL`Y`6hN{3Av>R{zaWMrIY5r|^k2s)6j0R#(bX7`~!DF-}%Azc~f?|2LJM3J(N z`6YWgj;ZEySCq~VGzudbzh4QmN`k+02zcFO`5*J8+I$h!KS!?r>|M(EGj~Bb#f@ccKfj9mpzvpi z-8gJc?Lx)3usg`3d<8Mlh?1%={aYa3<7`3+MB zwkwrTL+TrY^R$FBf8+alt_r39!y^Us_sh9ORsQJ8{%PJ%SBD}W4hEo8J`8k!EAVl;^o3}_ORR8wCTWC))Z-ttZxPL6SB03+O z-2RHQV3rPVSHRYqMUsA9Q&>>=K)6#U556USBNYNWc93Hq10+3UKbgL?i5jnVEVj;a zs&F5f9`Wq#LSVmg&Of$*fPu+J@#@}UrnH4&L~E=%C2YhXgL=~x+%1!FY&U;P zuN$;;lq;d@tS;#qe$?eNk=DP8gJ?w)Pz6yBeypW7}BBp%y<(nR*t9 zCJT|hyQ1_P3VROIs8`re*W6AjM8MhTV1VW1_evK+4@1J(Sf zgg7=?NLb~g*r!%Jc|e5wA0}_iIa$YCroKNvFgNWE$N&H_NkN`Mc!fXxaEY54vMS;e zKWc=j+`+@-W>Ytaf(sFg_H72p`u(KD3>Qx=F+%MS;f5po!h4C)K^OuTt6rdin>&#~ zfZ^59JHS(!kc5>CFDH~w7kbBoa3Ju1xVsMGd$^A(ku!5Y%U-G4W(vaJ8FWg;b^Yth zb=d)3@lhh9N_tPTN3*TWQ4>daxWa&JI| zZ|?u1EtQ@=+x(LCme&QHX*1ceGh2WA0RBj@U#5JEm+obC%Q^dlAqNjv0AF31r1!0( z9;I-zAZMXHGSB}O_K}Je(XJvj`d8v+mHwz<@fHHSI1_MrhX%y{p%arL?b|n5&>`7_ z3Xil7Ff2&szSC)~dY3#8^JlK?GuxtRP-+u?wbb!?>0Do4Jjd5TF7%5!4k55de>AE9 zL~5ykXW3RgrT-vs++(LN7=7YlH=ANTEvEA2G#0&NE7>wTlzwv1`1R^<5~vdeH5tNj zud5sEIvIIf>^PX)6m_|KO>KspGP9c7w(?P^`^QuAAMY&Nsv)n9dgCZDH%~VaDRoY> z3_e}K{VLhjFtk)LR`5L3N6Z(OYxaXib(=$qT)juYYtneJ6mPMQ0vpimNeTAPz*GXE zglV;B_*OgR97~R@*Qme_;rID%IrN1wy2`uTEexl+z6GvtR#Y~Pdy?WS# zm%fx7o$DwE{{_Pcc^7L zCG5fNDu=WNZdmE|Mg`gEiuj7zFO*k^4A?XtZc=n>d&D+i=6aLGw~fiteo;y;EAXXd zA|Z=Pg^@|z4~0+Af1H~4wPZPhkF^77IcR~j>)?m;9Ak+I1}Y-;BN#sVRv>5H)WaOt zYyTJOR=U2UC^*XeNCw(Gcx-^s1-Qk89)W#_%iPgJ*4I8wx$($Yfm`m7LZ~yW8Z^AgVd`x^IipB?KX`-S`Jom6y*yjJy(C~&+ zs3kZ(s7?U7Q-GO$n(WyNA0A{bsAy4W>-yRn5ikDmG|y#ffthtMT2TV~=)Uq}H!vVX z>7Cbz`$e&6j#>#{-S(|o{e7v}gBzju9Gkr>Q%w^7SCJ0MUH93)<5+=~N-mJ0`a!s_ zvuxNNO9QsQiLZDBA*!o&n9`rwU{#)DVS}!bI3^EcPXV!g(AR>{yjqZ~KfX#4HTVIM zU4Jj(H3$<)eTqckb~iR(1cXBxK&>>rsN>fowdg$BPYXNPVo~m;>Q%n^)eM6SvPoN` zd68hnRx`$ZFibI@AnAz2G&RtC|MLfKuX#vU7ROWhfvGa1w_r&I@g@3!EzC)cc^Rr; zeRfo8GzN`+BupAouKkj)1cjOfCI`<|hYl+_%9KjY9-@~f#x7O64-1~Q*cDaP-~u6F zv?6=NMJWp7z6mVFfX#%NkzrHYrzzbKWf>RM9!pQQLT1pi#gp>hX&y$OFOz2*V8Dpr zTi_M4r*apS5B2q$O8d35)v&HdHJeRbF0(_QXLba-m0wj1&J|F5RMYx%&NmM$uAT~3 z^78+qy!X8|T(oVG&Y4xr&X8IB#yOie*fax3>P?vD)}94I?;`Pr2ekduFejSC?ocwt z+o>Xg%NW(Jg#j^G+AZ(Xdf(_Uyl$Eh(3xv;iWU;*LS9*Wbb{H@B=9z#o3v@Z(CJvF zKWIUpYhx|nN$T~xmr8#7p{R+dzlmSG@K54^Akj7Y9>He{Ix9M~bq6D{`YWR=>GOxz z(JOnvdk&pHFU43dN_n5ai5+4k1JiW{5DRaWoxQJhvVWTWYk}i^{ev7@!gHyWn%L(=c?qBSTXIn^^CcaDfD`hp62omIGX!a>SA(CKF}t zodnYASLH|9%nash_{=fnJ~a<&iCI3Ou~kCproXncuk-{VhWOxVssJR4+zZ`p-JARW zMd2+q6Nw^@xzwXItV9h%DoRd6{IVqpSN_iZ_Qf^%%jO@FSIttP7=TT326%k z;xBx^80%tXDNVAy_pXP`p@5Iss~PNbap z{zradlC({mX=R(N1HtLNzIw)KpnNHJ(JG(g(Z>kdqlTkFE&ievswu27nq>*)61L$8 z=x{q(L4ExQZuOSgGYct|Pe_>xwC8(6s-uyR3#+XC?5<(W;ws_M@U`R|y)L=4DVq=> z=@*LC`=|VqRM};R2Ny@J_rrQnb(-)*tEWHYUEE7HTkomwN6XMU%t9b_gcs68iSz6BEko5KxNepaNcbWA1@VG|&P2>fCi=~`NLSmZw@f1To+5NKUg;4PAb|ra>?!RQ42iX1G+m=o$Akv8Q$En_p!Ma-F>A~v`>c`QDh=tA`u$T=oAWTyL33VYfux}jngzV3kn zanUGsUjlzYx$fLzn>KQLOmu}dA%$QaDDw???WR0xL2b~;3Tu@$%;)cgZ?hybVWt7x}liich zQX%Eht%WcAs5M*NzCo?*GH6xffp)^HhzsDP6eGy5OyhU(QO<#_jyL~6C^|@{DBv^n z1?ndQi#^TC&-odYyfnX{=%Zy5xvIEc{Ho(E5ZEdStqM#`!Ru&Xpb8^Ii~`ETuxf15 zCvE~TqIIa64w`RIAQrHK?CQ!7&uVbpu7aHHvJoXrQw9!Mvp9DU^*sust2U(G3CX(|}By))dn@ zpeWom6{5Pxq=85S@5Vp=E5YQ#p6x$U1;BbUV6a9r_|Zag5pAoyL!T!k4Ey#b+fHg=(MeeH-bc6wKEv)`aTv!C#p6kw1k;Y2BWvf@=PGFOhEr_sm>pBu_%AS5O2neNBrMF z;b$+O5Y=roy8~%jk3buK50zzb5C~$W^gNZaHUl5AXZ2KgG9{>kN9n|zJ9IiWXABv7 z(rS)$9y7o5fIOGQ7;cTlckOH7+%V6Wtb%{C;E>F!H}IxC=cfPWpC?Cfy=fszCin*> z9u}MzwGq!`JmC_?+B0|ob!R9UOgLL^F9D%D)_W&Y7@PGA9aW1$;|j}GG}kZJ^OaJf z%A#5O%i{YgTl7_2mfCcr0BrZk%foW4%Ep^0Sk74wwR&;VtUI|PJlg?8imAepEo~*@ zd4T1ahz1mw>VWfamHd2g*!kUg1rI>7l*Loh`_ECFU*&&@10CeNn&YY22%|0D)giP~ z7tQnOc{|f3%=X@WH!sQfprmVWATze7%+NHYA2}KR0uOW9RrV;M1>FHCz`f*rtn4i~ zVFj(QDU?%yw9{RVj_ea0=pH8+_MqHrhkf2u%nUmiNlV!p;wyL%*fM9nZ_k7=AVB^y zA@K4M9oJJR^{GDo+QEoMq7bsh-8KxwYujCeM%({aTwjs$FW5l57wwMo9vZVXK5)>& znQFa0;FrMvW(bmLC@y}V9+aY4B9og*!IWWUvEp5WNUICyMd@u&Yz02sj8=>~eLYBx*@^KIn0gtzM^|J~97Ad6BStaE$y@XZzE$FJ3 zivi)7zm+fN7BBftpMVC=sm|Et#W{4G~?iZ!}f2Ev_^T z{^Klu6ZpsihTH7fo!rEs`F!?Rj^TWCkQ7K$$w(un)AbFg>LP?|$R`PJ2(b)6soEpr zaj1#NM2^1XxmcWWgKpj+C>oLmzx7h9?zk( zUSiUOzMIGHi{)_9;so5lH&)a|{Ed5COpRX^9n=QHq82hT3FMjmG!5HGT)Wt*2&C^XU}dNCUcOz6jXnqnXuX)-yxjuC0+vRssh-Adx- z``qdTCP=KR$<@w{>je&MV4^+n{7qUSvf`na6NI(2e1TiHXypG^0knVs9*boQUHxXS z9s(eV7TDRD&^LxygSW#z!d{FX6N}gF?B%uDQ($Iz;cm+Fz2wQrF>_RGCF&QBZaUIc zMGcG{Bquae__5<(%)8VYXwxXvLakzWU1KiyP_taI80PmzYuZX2j}@sLSyh%#z!QXL zp{O>N31oJGQ*+qmV!@YW_CFZCa5)IT^w8$C#7UCnF|p#AG{rn#USIGc2tgeYpt*4j z|2MD!jmGx$`<$G22vu%&KtID)M_$dR@R*aPJ$AtoqBTzh+FRXya5O&U#mG9St4KS) zDHgxx${-0|((p#4s0mK%G)RpGE0!{*-OAVUF7kA_&Twivk6sqc2`Q&gYc}GtvG#%4 zoHTk$8}7));Vx4Qy2%|$agCyI*{LCX)579!g}Am7k|-H)lFdYT(iV$0G%)?WpK zsvvBRd6XnQJ~ZX5b46kV?{elCH`r-U3K$vIJ#tU~6tQW1jLIB>nAhD1fdHM`IZ3S{ZQ6Jj*g@yp-&bvRSZhUP;e`^Go@iJUNi%n?+VoevYbw; zu3HPppUUn%nS$ybs&n}?j;6gs3#7UV6Pg2|4I}#5*lO5e1atUXq`E%@52Eh zI+Q_96!5j8QVI7b1()?Ie9)Hf?t6Mk0QU@Lp7hl$ znmAI*f=?n?TaEfpvgVds)(tqA&1{OwU6h;#9%%+|-{A8QPr`j>HPY{nE!CUbb4DR{ z7s^U3kS&RJeH~?#Fc2Pnd@dzDt!?oC%;%`dF*CwpwlzpGn@3phtfnKIw)w~+YRNP8 zVN;5j!98E`6x)4;R}Bu|x}>CL`Z>rrHaPI-k5p-eTb$wEVN_;a!)Uco0SUVgf1*AP z4wej&iZOF`M$E~Vhp|F*RqiUNSUPdsv+xC11ZbkbL&$hj$T@1Ug$* zj{g6yO8AqW(_et=MH1lr8Oxh=Oqtq4#~u=#-;`f>#Xy89E3p1{@UKCt0mp4 zDqJC9dI=nilyt9hqw#o0=&S=!jLu2`*cb1_HsMc^(iqYLLN<_m_1I=D=BLxEEiRcd zLfE1v1BJ@~sL5HnhpV#Mdb zESlnkhy6Y8+oUZ;@7yVNx_SbG=F@tfGsxh$pbxXJUjp-|PAei9f&mX}qgAB3co^U3 zau&@Lz{76TaE@hYB10u@pI~{tqXjG@KOE1F{Givr5z=k>I1a77@7^n;Bws|Cm^>+K zBb`mtNJC{qIwa8pndWfXefd`{QHeJ0Cru5o9v8%bnT?$Sp^_%h2R4!SkObXj98>cy z#DD54KYNANo5SL_$d6%bTSNdL&WVD(GEm8X<&<(q;#}3eZ*RD{5_$ikIo|91*b~+s zLdU`pO)gZE225&k3{29}CB#(Dc6E1TN(;pBLg+u;C%dS5y?-8`iTSunc@J3ItQIx}A3n4%7D zEJpC}A{^_tl7AML^2vt^dw>FtD_u}+$xKd zM9kQR(2AVsma;*BkV7P!xedw^53Pd#tyKU)$iCN-p#d~DgdZ+Rn@LJA&H>4kUS-)!N|F7wQz&v6Mf%iJ-_Y0U14|@tRn{}AFx#&2z)hC zIQ&lm$bMG5K3&rUB^%HBxrECNG3gt@NNl!%xAkIEr&*aEmbO||z;k8D zV`gNuyBnKCFH-8<(}#C3!Y~TF&X(fh9VJd@brVWQ8w^XUhgc11;QO|Eaf~^e=z=&E zI7FoBByOI$(JtBKw-esFf#8b^ACUI%YGf^~ne1jtC!-6nXYr-( z;PXK1sW+6=s*Cgq;~AHrj+d-$P4s+ zW8gQ}m9UL(984Mkv?)*e>wXg1iTJPq)Yh2GY)Ag_7I!Ua9m@bIopwFyhBNg4LAv-xVt zlQ&j*DBbf#zQdzMRq7u1H8hKhT(e|p5)w-*vA8u#7Go`z!y8_dlQ_VO0ERV+k z>HH6ES`YZ(h77;Cbo^=sou3P$Laqtx++C-gLqTcjlcNZ_qh%?}h+!{jUumoGEXqA4 z73#{HPLenTJVsH4;}bnDOp+-aDn8zFB#PDddDhAGr3!aF{aAuON!JOLcnY#>SlL35_^ z9IS0G+d+%tNS$b*^fMB2>-v>p-%PNRBFdBnXB|3>ze`!Uaqv`|p(zF`iyY16&4Ia& zEnjP4ABD&S(xsR75m@0p!uGcPaS8H)YM!#xz2@4<uoy5ZJpudO* z#K#Xe+bJ}PA0C>gjSzmcJI&9QN?w&EMJaOjgF>wFHc^+QcLlv$KC#?fEP8w~b z42s;BF1dmu+d^F`l z#(ZegXVQ|ZIM#gni}3wj7j1AZsin!oB4>gKf1Zm%wIh6YU)`|_a)Rz6E{gK`-%R=s zc$7z3wE+<^0)R~IoMI&--?;CA$3<1@IEa?AvD~>_zvQU3hkZx@q&VTun8OhCy0Q#f%k@$!5jJ`?yUzq#LrJXj0b!j13Wi!m89|f;=R_7j z5uJv^Nui9U15@rtylj(Su%;a%`?!li8m~COmZxybh1Dc(523gJOo%RO1(k*qR zv^-;I=M$oi#I!A%pTPRkF2w)9_uB?yI*4mZfk}2?qE}Ck0`J$dNuK$1C7Ija)HM4k zI~5OL5Zlro(bzp~dDK6{^Ib4D9X6r^;6!f~ipVKUdMCiuZ^@cGA|nW?jtdo?r@~}7 z^%1GwS;wM{?mWC*&=KqnFe9YSY{|Cxg*RlXfZAsny*o-2#=0WRwH4H17gOH-;tBS! z=zxJuCJH^ev4DqXJ&Wf4rs0*j@&l-y@vV$NPX<6e$^!stj_dV>ZJT*Q}1NV=j z2wX0QsNs$dp70|R%WN^pV>lUeqGjAyfb_oZaoUH&g`4b9$=&O7SVsC+lJZlewSTN3 z)>vf0XP5n5f83u!!>~Ijyf~`bHy_o_>PEbc>u-+G9@FW#nF25-1aRy#8@2&^G8?kR z+r1nb<9k{DOX#?Y2m|dnfmw}@!G~S+`x`UVea@4;3C9{91Ur*s+)y|2K8qG z!S)hUv)1sq0+6`?o`{oXKN|pD%_i2XIEY{utf*4gt?cy3CdYMH)`Ktn#79GEdvq)* zO{{pJkP}NF zv+KSa+OSH%jO=#I-{i%bY5ek4YUG@$_Kw#t;_x88q+H z2`Nt8)`kV?DK+R1!QTpv4bI3L>%sI4SW_k7X-|Oi)7R|PuB`m%0sK&MV5Z65!_F6v zEZmpStV{TrLwwCL`;NHR1YUI)zO3FSLuAE)NG)J+2t7+4oHO8Abv@grw}$-C?lY4q z(O%x!NLOYY55Dz_=}6v`A8Z58BLV!wvk*8nV=6-%*#;{O+A#$nryC_%*bjX)#I*71 zW3zLQAohX>fbMTXTWD!9eo4Q}ZaN^h!i(>2j!iwtrh-3IMHqApBN>#GMQ30*hGkhn zGd547J$H`^bI|3tAEA+y77MZlk6W10*YVR}+9!W-fL=C-qoJcjn*kgWGKv8=J2g7n zV2K(PGqVMuNpf4evXA~eLsA)5{B$Fxn0Dqa_oxLu{*bHPo-86UWW=*Ovh}pt?kE!Fy{ZHT1VDn8gv_m*0vL0Zz9n}Nv(=J(Us zJ)Gw&+HIZSHq6l?2Wo@SefAF%(m49==KgX7VHheZ+-xVopwHq|H$#%ip(GZH&o_;F zFVpx$F}+OD*f=#iaIbjsi_YZtQFB){s6|Dm)Hu*JMVyN`okXp2_RsXNm7!$D{<-=xi{)7DW4 zZN4?;+}DCY(2$GFDNCgN1Z|X1FeML|z(t2^#3kG zv&Ovf$Fc*J@d`T>8f^!z54XS{K5=n2yR?ZJmK$jH$?r7zPMHbSG2Dm>&)jUS$A-1} z!7QTu;xCjQB_hO}n)A4jUHA-1lqAhwK)b*-$R?o)EOMpgtyGmhBO0Rtmb4)(dji~D zk|k^i+aQAqr@196VHm}UMIm;R!1l&0OTe27J}Q>Fa=Wcg87Ks&wuCMo{@`=&|1iTVwSLM9-UWHSng+z_oe9CknDJkBajiyH@r^~xgM)7oh`>2 z;IvN9dXUB2+&)37u|S$Vz&;$L$R5P}%!*Q$|I(}!7|SSV)EcbL4U2d?@|^QZKjYQ5(Mk+xF$l}oimV43`yQ>FMqKCGgM zGNCD&7}Zs3VCxfxn1xSYtB%?-$?SQ;_DMJji53`PXZ!sFt~!5*8J_G)haDscdGjHM zvGkAc6y?M>>D+Tj*a($~%MKOMe{ z)YdhzavlY-z-Uwe+fs|05wED2QOac)J)~xv0qDO=F~iLqjGgX{1t=frn&mM1%f!jU z3QFcvCNxAO##i=u-od@R*=#*mO^0K9aEQp4-uL)kO=yYLr3_RiO7VdOT|H&F7ALW^suNCHSRUe7 z8vH$qY#%b6*KKAlcZWt2m0`|Up%P2V>uTLXMmfUGiZqc9Brbi5I>w|1ymn{QYdY(; z&K!4c>)cxSTR4EwRMar}5hjCo#&0B~wxijZCZ!m-o4k*#vb7~Ltjp0hkpv@(R2bHJ z`Y8l%^%~yd)zoICvg8~de1dZNYYHKt4~vIS$`=uqapo0%iE(dVltP}Az&U3eO4cq4 zorN?o(&1d9@pUbU^>N#To)vWAnmc$vaDp0yX67HNOQMjasD5t-0GaK4KabstnhvLh zFF|)2F!rYqwH2H)yU*$q0E#phovNJCM7&^XZDFSsif+Z5;yyRnU==(NrxhC11B7JzVR0_k4S&)CIOxEX386`3*UqD{=1S5>sY$bt%!)hf zY`#~3?Oi~K0lIp~YZ*MU>&D1}Z`CKYx#Y#2*&KL9DcndBx#iGr8q?~Wv?_r;PO3lC ztQp6Ahs8%+mF@wV(ksxe-QvvgdoU;~d%wJ_{nOjg!9qO**_ps$PdkD;lMU6vG+?@8 zgM+6$tKqyp(tC|Mt>^HD2FwAyzWT{SZB{rZolQDO%V(S|nj6m4bK=f$Muk}Fu&G|L z5j%n*elwe|bxNKF!;)@(pIj2n@R4L1&b_Ae9;$3^!gVCGdHN^Yq$Jd!;I39Msba)w zkXS7wAxD5|qOG7jO(D}O98XvkXZhB{{$m{9C4}X zyzKo4uTo%Wj=8SjL|9h{ij=6S{imB}lyY_1BCd||O2$l*Ug+9e$i|6)* zpN@p0gn6Wbm1_DQT-2XS$F`!?c0o^Nj^cy&wi4*csLANCfWdm!he$_5pw6|o?=3v{ z4;R1yWnPp@Ms^|~3~^Yxhdy~bwm}d0M3R~-&amq6X`6;Zp$*Qc5$Fl_diHXP?dpu@ zo~Y+4VF?;mz~kw;!IWM=Q4}g$ZHu`Saa&lp!yx+Fu4{JCQR`s4YkyMpT9im{_NoTe zY2XxHx8%kKG}B0pc{z^Dx<0%)i>X4N4%8d3rm7>D@HxW7hkNTlEhQf4dInWwJm3l| zk*XhQU{Eo)a(gPsP0p^N8S}?YVpY$i>eA)dJPU(#unz}f67aL8UO;@^hi8ddlS-AY zjTH!Tk~(=4vh{d0K7ER@>(A}@FxRhjIjiv2DpO)DUm<}s`-Hrt0ZB1hkkchK$nVV! z)?=)Fs`H>+ON-6+>LXhH7G^6d6`5} z<>FSo2!Su)$@H2bv1Kc}=2E{^EAH^83^^h~bW6A!d`pssmK|@yp_FU+`Zb+}A95D_ z8*5`dOHN6&J;~V(0M3O}g;)4%)3rzjwSLRKcrIoSuRWI7^Oe@J)0(EZzZwP`cW-R; zw~`YVSRru((!>?#&83PbIe+kzKSCN1{<*fsISPz((51adghK9)H$Dk(A|B~kO?x7HZ{oJTU2#d1H3Bn)5X2u%X?Yt5?B?R&D9l8hd~w-!d-=QA zrk8Ygv04%?JcXu}%B^PjR?A<$0`w_@qaf7Uyh}`Qq31Bvi46B_vdZ2Zcrv;d(Ckb> ziYZ0%7(&g$APzGnMHsN7mKcYA7YcVj=bn)&GLRtR(NxZX+{oCGIDnoHuW-4@635_T zdw)G^m+-O|EF#r&nP7q#m_^17fwl64n?BUPY?-Mi9J+MI0Qo2omHP*Tf{SoeAzta4 zzrZq>sLaMc$HBO-wY;OZDa_J=aH_>@6W0BT!@V8{LF=6&Ocj__B(Ssh5{L2q+*p6< zp-2kQ?D@TN|1}(Tjj2)9$=!da?ZOr|s4oJ7b;^^oN83oP8OF)WHxtF;Qy4V2R5?-4Iq2_f#`K2~73uX9c8( zp@|qm^vihKZ4!u_CfaVRdAE!j3VzR-Hb954CK~>Qm<7*yNyb^Gak1wi56D^p)EzCjC-<#?*iSLLV|H&U7V*@lCOZQqUX-gcv5$G zC19}OiQdCSNEbBVW&R>Y>GY!x#^a=zuOM71Ag;RWzOqW6{P1p2CnbfRXWq)*N?snb zq<96|*>IQfl9{rtU}W@x2zcjx1n%bG@pm^=_1H^UT?l62J27*{v)v38W(Ngn(9W1i z>R)`2fNIty8NZ}VuaMZZ0a%~L>mCcs6TLtUlxG>wdjLmCo4*-2%H>(9VuHJe0ky?$ z#I}8m_uv}O9Gfu56%6Ub7Zy3yK>@fY_;Q2EUh3Ti0~`!kEJd)8spw*sf|h;y!30j+ zYTc01T-X(+lPLn-m~gBDfxdVGdWTXkoS1s2<(M!9C%0*}S+3e;9g7*4inj_OJnrrF zw?-BR<^#ym)!VPn_l8?0Mc?JsGGVZ9wzX(dc8tK`gk&oOe)@2VI59E^9>A)PwL>{Y z7nzsnX}QDtrY;w;f_DlPW>bUc#IKjEl< z(70!){2>bDP@gd{Dw0rA?rXfl8Yjroj3RodU#@41D_(jh*R`FG7rWnsT4r)(SJ5xC zreC#3#HGR5swud)6B0bEO7UnO>Wki?_hp{~hm}=K&aa3WhW2@sc{4NAWIWSp{5c!b z_&Q_b3dlR8+Nyp)J5QvKjRq>TnS15Ej)bBMH}^T>T}wk#(h_)+daM2G#t$_F)ia-3 zIQ*a(xBR|Wu*Pr8HKbYPr}=IC6kVc(NbP)*`p7bd|4ASUgDtAx1V-fa(UB|vR&L?k zuol1gz}XNNLVG%B<95K&&0JXWnZ7|NtZFR2MY>XCe`UKTXw^vIL6nuhR?IC$U6I#8 zUw*7F`T;_?S}{ZwkQ2FGax5wlPnerxG4E0yC|mF;_A5vcl53WEklwX**db}rhYt2` zS<+}vq_ri8@rN1{$*PG~*{6$OlW2w~0!W+DqTJldK(y_Zz)&*mJ&|-3V(9$96)@2- zMNe6pH{GQTl3H~NA3y9#|IL<}uH+-3e=lolo}Jr9rva)GqRo!&x`#mQ*=NAL(+6c^ z0IB2nuG+knLrp2J>hW?P7qf>G6&!D>P()V>>c!PBOh9o|TpmGF8hv`5=>{t~LYUhk z@~)bW!D4RL7e&T+dgKv!n0C~H{75oN=K???qNaXjO~)srA4!(my-1NZ#_?yDRtgiQ zLccsyPcW!*xMbO7mSyl_tI znz*8ZQ?)^q-^b0N#3caK4x)4=8hJe(YLsE0?2;?16es_!<=wQBaP(`Q%Q_ zKAy&xqtNEjYd`KY&t*_LL{#@C{27bG4VOlH7L+MaZcv-^&6nJmggPK)xXep5|7Lf& znsS{3OWZZouhEbDB#Y)gO2#FsS_M^i;!pV({HgOvdv7B}!H46l(;P zcYAQ}^@f8v|5wN*`E`x#AdwS=y2?F~i-TLs5zH&%Xf!%F@JL550*)6kxrME;MX?!pJuJt0Ko&)(6}8d);sv8!2fI|iTA&7p#NWMy+ad>GDXxh9%8uk`Su+(umr~LFpk{p z9${EKrr1#CTszVQcgb`qM~W@}X@70sa7^ZBJ}D7f$FPn&b=0mva9{QNQR-lXpyW28 zl+J*XwL5E@ri4$E{ge<>n*n+8mVBqrv zTkz*AbKW5;S2=<56eV7M3K;9)Q&nmXdF0@>mxqo4lGlG0&9vrkh~vmp1%NSGB-{lKDggiEzXdgfSC#Y}JerbyFC_DtyA4DB zhB7P+UOmTByy>ux>R>(sDHAB}&I_^E^UUa-U$f+AcBF-SLU!Zd_^ajcT~Z#W&Yo65 zX=37wG(K)XhFu#gAjQDUVYMDy*gz;2$QYcB6nIR+@5Ai8AEP4IY9Ite?Z1<+lk?u? zAA(9Uc=Xn`KE#aOv$67tRHX3gmE|FkJ;k9~edZk5cLZj2$pD;qNqB9GerhrF{y>)I zG8xq2p_a6aTKxv|Tyx0%5X8N-OXSwN=Vdso_P6EP^efVkB*q6RGcV`fU0YXVAqv(L zCRqQ^6(>ndYpQ8UQD{s_Hazo*8=yc8+C-eu!Qnr2Lv?0qzav!X!wz2~hjZ&OAHVCG zqyuO(p&fK%=B00Sd|TxM1K#&$tKba3Vd2Rb-gVYwlXns%8Us9vf&8e78b5+39tUd1 zwj5+byC|X#RVAXdOWY{nMAQ3-T!v2gZPDuSmM`PFyFJJUM09m{9aOmIQwN=+hq}A{ z;`J9C09%GAfC;?b72lZsjXqLbSJ8?i3_l3xqdcwwIX`-?>EhYza*h>!5V${T3Q+rm z5f8g3M~jVfQj5=ki2^=?V>GQIarK^lrkc6ZnYTDBg zb#Y!KN-;8q?7e8Yd7~c7KQ0O#>SL!D1R_xnT(KCg3MwVJ!0ecMT@+Ygi+yIYu@hd+ zXl(GL8l2YeP>d0n$p6)^Tnvx}FXqbxsuTrU}8{!HYr$EtX%v5783ikKz84w)GEGX15~ z3Z5r4A*^xee_233(q@q&+G}l|r#fb#Dhny93|~L1f^Q@?Inx;UJ+T}>{T3^J=DK%C zP@VTMv2BJKO0dx-mTxybv1gIuDF|6MU#Nf(Obh`{*S*eKuCkqi zAf6)-Zu5U#Mk_Zt9kKCe@CF4HpQ8^?cGxM^Q)uMXijn-jiZzMr#5r|HZY;@-fy6xD z40L@4bZtLB=+;A%uvv9&@Rb0i&nQ!-^dF5L!8#xNNiYgH8}gin%#YwsU-n;^zj(N? zYv)v+m13qGg*;)AxVJ`87LJwL;EJ)clnX0gw_P^po7@ zOU+Z3lM#b#9fSq^F2}+4cH>d%HwU^#N}}o#KtGXgfz9;!@nL)cdvd4>&hFYoxc*f< zS$z}W^Hc@Ho(h;xc!S6B7UcAaWqw;IyQ^qgPHq^?=cgS zUx6@}9fa((cmRHSMWpMjEK^N@I)S|}Toy>_4lB#SYi!cGR;2m7IQ>!QB4Yo&^Cp9| zL}(K_l5XW`_S(Q|=wpox+V48QveL*+gPA{geLo63{Y!0W3&hznR*RKx0L(L9{Tv@-cw1sURSw*LuXQ(?{6%9-57eFo>CgcA*XGp>Gsx zf6n>;^uv|jHPdp?*Nzh?%K))^%~~k2@s>0J(0o}!A#U+ghopu)D2@oI;TCjgh!~&yJHMN z+DqPewkCHwxL=TnFw`oY`xQ1EUUR#*y?y5?y45s6vlk>VOV1WI?M*=D6XXGKx0czt z`uJZ5?(6KIWl&epU$<#~Ao1h>l6ZMhcGJMbd$m`IVF4ET5(ba?YsKix#I9KJ#*QH@ z3zMA|%CJ7qP`imSl3z zVBpx8UiLF^_H5%8m{@BD$BFcb<@Cl?lkJ2u%9#ke66qW#2pn-+tHmzoNWRc;7H@!)>Y`p%a3XWQqBW3RyY zP`>jpPZP7p);g6fyt$pbWE)3 z+=J{f7d9)7E!~<$LT<&p`p1JSP zwN5YLL>%F3P1cPVHC;Ig^!2q`t0jRHlPn`TI+w^+sw$2zwg8VV;3EyI%vm##d{lmM zlV0(4dL01leHjHFaKOl*XH9%1S#$@Sd-2`##T>2JR<-V&F`K+0?cHRv$Q5|^*!Byx zO&2RdC@0_gc}4Yw<#U;T&|h6Y@RvC@$Bj>Y+cISy=*9?uXYFMA+p&f{3>RO&UM$V#%VG zv%{ggEa${*=I`ML%5&5pUIS+n*4s`i$U=6H?osE{Rh%62isC`dw$x^e!}*@i0PW$s z!m5sjdv8Pw9G6KMtL>}`f(CuT-fu^HzK94u2hQArvY$v5zYoMC9hJ`z% zg^-u23z+l2U`^;d8wNoyPt_I=>$Q=Wv2rs(7#5qRevY1-u^2-QDaVvKf3Ag36EAzN|rl|O?akBf)-d(VsVL3i}^U@*dq^g6qxYTNb} zeC&JC|AKolMg7venIE0)^l>-^e&y)wgKd*;bdB`vj6Oxszo!9So?Li0EcN*ZG&5Xf zP8ZM`pbp3|6reKX!4LxeMvZQSuM5V%cIdA}WGYPnak9_E5cKDa2cSgcLjmKmA=cqK zgIp9ZRMPTYiWae+-7SWfv2+EB_j! zcL5m0X~r(ZBWwV6HrV!;ms80=mbgMGXatU3b6GYe+YqMvr0#SURjL!xT&Jo}M1ew0?3&kK80XKG~}{MQ4?)NC(Zz`6nS#DSjQF!4I{=uQ8<} z#EC?GII2+Q)a?5>-fu3X-~`%ux&UPl&D{~UIZbzw!1KkI@MM=_+)Y=3 z9jh4YoY@=)^WT=JsW3&$#xOxBIFvu_kB)8EHcQPKEdb*6#tqDyN8bI!t`RF>YEz#Q65=ab&X|7O#7 ztp%|nhgL?<=!p3ID^Jd`>R#Pj#wt@=8-nMc1kK`Nwyzo_@^sJTA&)9$!2O|^pt#4Ycvaf!QyYK?+ zXk|MwiarKhH7n}b;^S3MR|1dC*4B1}YKMxWz@ZO-uDK(u(aW9t04j{Z1&6PRT+6%* zuPu}Ip{F>ayv53xIep9fxL+3$V`( zI>cxJ5t^j)lGIJSZ1>;8g0ElJ#F$#*sQ!+a)<#~yXdk~lcMj76toPK(o7Uztb%eoA zYHGi`@{bc+9V4xAE6kd+B|UkT$G6;KwD(t|=#C)qACiLH3ON}fmP2POx4u-kq-&Rh zY0SmzKr+tUW6%n;kcHGr=nPtFnPy^5iV-_*2O$hut@x(kitdMucw9=d{ro&1zb0Qf z=(u{umB5@>kN}COVS!DL*yuHw`Gv(yX{xY5u<14yow+=aBmSb8`^LmO4rxF`gIxkf zZpO*}#l#pOLDiW-cpoRaqk8{HG?)xdmIkb~ zXs=av)w%);zy)S(=gg{Hj5LjuBDTblHyjxiZ3Sk@?DLNc+pFm0(&)PQ-2A`9E2rEa6M?EJ*2ObEoCp>~z$wstPn^>;Yq0c$2Thk@&MroG z6zgd-R2RL$kI@otUmQV6qi0o#88Zf&I6PT^iyms)sWM#I`=t1s&izo`80>ztIi5M zPL7X>tc72&@k7w}lN$iA4?aVYdeBR8@#r#;02eCVb_rRygKqPh(jM}?S7+ZP(s}0L zeGxbNM$-IV>k$GQMxCud2^FCa06;*$zvffO`oPiJwbYkObwb-{1Rv0VW-a5)+YjJT z4X#FLd%Fe6bTz+ezRvJ=r&TCeW$b`&O;35kCnNpcQV(t^%)`Sta|*=j&R<6RyC^T6 zGtb!yR$Lqm+hzZ(OmWT0H8+~#%1gi#ywuyzfLAp73PHUZVybVjxGP?+2CT4HsA9iR zlSIN&=T3fGMnm7-&}0E#LWOUVqzU|BJWjiD+fUS8`=?`C^)C5@K+YNUqL4&gHr>i% zr2GY3w!vKS>Xf+W^q;;|QT^Bb3AHwn_NuG~gH*LPrHl(?$J!I&(M3jB{9H~)5m7u` zPe}r#vR-C^H2gd^;VeAd_CUof}0G_pJ}P} zrf%4lfHmgLP!ebSbx_N?v+s!z+3mwR(n^tlEM0#NRSa9F3fMkTb z@4+$;A@Nh3CK&9V%bU`$ef{;crogvlXuncy8$2 zhOZbISketEp&L<69#_W74u;?g=Xio`Ql1T5x>rrvPt*M?ZPgp*N`Mv%-~2%%og>cR z3x+7B4YAMeE4K^jy{eM)HqwgQ54uX+=xJ&>Wt~RUv^n5}Pk3(0324 zY8)u1eUKd(j*Du|j4_6-lyKIQW(i;B4eeZ=8Q$~cs~k#!F2Z(xjcFoE2xS*rmoH<9LO{3uO%CKXf`^WrGs}qX=r(>h!GbR`X)SG zO!QhTp}U0=hMKx7;@RuFohA(zCDjlSYp#4R2c^f=cwc4sfX61Urg(bOrG=eo6fBOX ze0Jr0o?b_%^{s|(|ADYc$pZ72gYg4u|Kx`HDT~8i>hGvIzCAoh9lzt(81a$q=cY0k zF}+#G$eDQOlge4%6nBJ-no;_aZSTQuxSW^pBV0Ah^npZC3SRnv{a;0z{t!iHl0uAg-_5OH%{qos;ZUYlTnhUGoAe_ zXo}%REA+@u=J!|Q4kS^P7IBolABuydeI#BpFUr;W(an<(?)l@YML1^T?=?>!G(ZMM z+LcIX5R8VTuaHy1b&Wb;YMk||I{BQ@*$$Vt$%K-#I!L_s z*C!g(7J9eeYl)u6Gu*4o4r*3c6kRB2#9&-IQ-;meQCrQ*=?6Bhgo%i?#B@dp4cvv6 z{RG9x>T9Z&jA>>(O>eApa5=25X8c<(BPHJdKgsu6cWPgmC=zw3$1sqEc6APZPd#h zc5RIb7!_6qS{kk>TG#z4OZp6s?)X#vfguZnLpu=Df-L`pOCR&<02NdM2e3!E6OlyU z<+*kbMs|^Q)$TOMm8%}vfwL;rmj;)l>u7M&=oel-zpJWkL9pF}g59)=efE-?L`M(+ z%CCz%3{R+L)Fq9ODj>WD;Ha(i^;mdk8Npcq!P(mSrX;&U0U0bgtx>fs!I84@csPRt z$V!8f=NeR*X}<6h1IHY3=fxaxc(oOBh5H}pXBAcX44NT?GJ{=5X$ZRexl0;s+Q=qT z8G4tVxh=X5N!(G3K}*$@22zHGTchzrW+DI7#kcj-$UOwyN6$*k0xPN%;}GzM_mYEn z0(9L#001An0iI)OLSOzuNuoZ+P?ThDZxe`mzL-P}Xn+y*=9_OC_oDRf41)FAW2yW_ z24tfHKhrf2c=y&}T(9K1Avg;?q@$#$dp)a81frta!mDfQZ!b9`D_0QZjr{uwM0gtm z$G%ByTqAd=ENfEj3|k$QNX7rNrJ+c4r+9gokg!o}xF&othCm(Z{P+n3rEJj%dg=K8 zJnfPB{+wWnKe0QM?uTm;k$97m&3G8Fk#6mPn4G4n|6>$TT z+;DUnz|{1sx6NPf09MD!XzPq1ZRuZp@rUeWnn~u9kl8dEz&yuvwMxO1!!wYtJGdGj zmY}Q7C!xe#{o#3}`W@`6!@uEg^O5>|wvV4U(uV+~6@8DiJJIOH&;e5gzzADZ1K(8W z?YyVwSZ&ABRf`3ojKLt#w>bQO_j@ot{I;2(paA!H?9wd}nE~EGo%W2N^)Q zKL$^k!%t1|8uc>6`^|X5)d2aN>x|2ruo{C8%YV6fQUzoVCz5XXl>(aik!iXmtZ!iI z{*&Yq8*k25O3Mnho}9R=c1QMK#=ei(5=TI*_~2-{h0YCD>3d)bNk-b8IgZa~daYut6lsv1M`7Rp0h zpaWrl)JrQGY?aP-K>u5m*&*9>W;^2^9~)D4XcRF}6dVW|glbB@mXj!&9b?C%DewxE zmb(NB@KmIJqOJJ3g10g+HPA!W_MY9k00AE^*+{pTrbcY#x79~)x7zf#j zIcBycg}wzL0?L^`R#4!@`|J`E4B$;CGGjM0qC}3vNt%C+m6pxh&Js4{f=>Ol?5JC6 zU~nH1t|d_vh{4nk2=?yYD^-1_{V?2J^RP4I9cq)Pfz)M8;W;L+%Ue-HvDSyZi&%Fq zA+H*uzzX)wBZhlu7Ag=j;w6X{WUJsyPL=#RMfkqtX$?f@5(T-NLv{&P_DRa8epD_wWejITjT5)^#Dr+KbA}c(hby6B}LX|$qR+lGXsryfAFsu14!apgd zaF4?g9S|ng*Z%3Lt)w;wEJD|Bd%*uKJQZ8MamBtdCJo%Mw9_`j3H>o6JnjdGQH~x< zQ$~Q%WhWn3HJ9Jy9d!TKHc^6b*yUg)pc2?Am=5~Z7$kP*5=b?|-<5zBPp0oMoAv51HCqzQm92TzL>acvxJ za2k5a-wa9-FmX2W#C3$4sa^%Qq@Q%DjF92K@gl2MCK8S+J9_zz=X&;n+(1HGDVW|pYFxhYbWG$&?BRJESaizcI zzzz^m%z`idk`VTIRHz&v%pWni98Fj&KRwCFo@^0R7?e*T=#F}qfr11g-+o3V)E^<= zzt}S3Icg+ED8d11YAyoW8%4J>Hw1=ZuLHBooyG#n7-tP|Sxv)nGRo%I@)uP1#^oIn zatwrNZ#DT<33Cy{4_M5&amXBeX6~RlW1vb<-y5bZFv!s1#y6q+tD*$-t_$Np9B0CJ znfattixw@Hk9de|V~utHz2%O>36@kd(b3i(J|qP#qr1z?o18mdHF642uUNp`_YNX4 zFZGvY@#d@~tcw+MC9{S{1#WR5#TI2HlxX#gzz9|@SWbD?$0J`gd16km5+1j0sv}XT z7c-37*CflMnZ?Nm#frK}F^klCM`>=4D(b&6YSVxOW6yzelMvfRAK2pV1bpU+v@=ia zJk7+Sp&e#m9?7p0rLJ9%@Cqfff_<$`x zb}u5In}xrHgq?&PDN!W-F(Ar?S~*NKnunwZq}i1vnMHhIxhpSE9fh56H+UkzeH`F-h&sVyG#AS$BBb5LsQu>wwoxS%|jx z3#)2wc#H$^d;%e0_j(?zYr2I%P))rnE$@_b=5vUiL zKzj7e0WoyB`4?xTp^mKukI(`!Lv`L;wjyuq=oS!5S6SPxl@(9Du2Le8ru!@X4V;f% z3Gd~HSI`w);HGL@Dbv7?%O_}mwnQgLb%ApcF!r5wzZ!-fq)~3vAudu@&g_NeFZ9WSEDd177Px!RiMNsDJ(}j zjwS&ziS&$D4g_2TAu!~(2yhPoZo!!9-#j;rpC+eo-kxE|9xT9O?gChqc|=YX`H1I1 z1`Q!CUb zY!YDv0&$NaH^_ZLHo&rov=gnnLdVG>1%J3LobhW!uv0?FL3z~sIt~w|^%|2rYMS9y zQR1J!W2>%WbY|F2YpQQX^6n|TOtKG_0d~*`dBnSyap{bim%)2EVa9HtwRwTxS+QaA z4@112`j8&ilcQ(eep$B75haFK3^BD{!rtoeWp;Ov-jYK zHhrgvP#ucLQ{?aE>)2(M)zvgCOh)O`9F6SItUp^OOZo!MM9!e1qs#-Y>M_sagPS&u zAeX~E`ft~mc;nn9peR;&@yA06ULLG`n+nB_({xxHb^|Q5^PCdcGT3X`S19GT%HfnMBD}zY+zxfVZ@bz zJz~WNM0{M`r65zH#6FFAsN-lsU5MZh#y)qe8#O>4S?brOQ&J?@ei=&OUS)0XUGNe6 zs>f*}`TrrN_(ozk*9dBxntjWP2^+VgD>>C34T>Ar&MadSouMIsB0Aq)1L~>9{~%^qIZdKy#%+Mgv|hdIZ`n8V~v&^>001%5#he&+Hy_)zp zYpA0rrEHFT)`@dTFOT>;dUe_fc>Ht8CIvB01Z#z!Z;k$jEb3#!dUsU3O_36NzIvkL zT6^+TLcByhj`Rg)8f>hdA|_P+zoc*-JVyC&yyKP|sSqvQ5@U&mB4-MH#M;Rd(Bxw$|OjN@d?ac$ekEcqt=_cE&Csxky06+Knp(Gg77Z za7}q?_tbHwQ1eK-fjX)Blj>%Wa$X^mjU4T^G4Y&N8ICk1p0dkuD3Rk}$^u9_M_Prw zI3BDEkD7h;VG-G)p}O=vy@|FI=N?GAjN{(qWL1OjC#}86BC7Ti)X&Z7PWgB;)Fk6? zIi~=flYhjD0kuO+w$Hmd#g=dp76h>w{i{m|;Kh_aquR>?@9@y-Na*%OH5Px^{t1Co z%-xs9kMsK;$>jdch}4nQZNHfXTMNQxn_WM1l!EYxA8!1np8hOLeZp_KWVW>Z1^H$G z((Ny>33{vm1bk%|G-*S&gDL9sLj>WiBGMlvSFWfhEEQ!}J63S;B;rW;x6M_iGaDt#vh*bh^sI?9-7jNlY67+uWV@4*})rgK|-ou!96 zC_Oq72V|tHRXrrr!!wKQik?lGlMu(;zCrPN1I$3xJwqM)D(juv7ZqH;w3Pjd5wKxJ z87OOCUkO7n&)KODQKL}$$yufW_Dh>x@Q&9>%l;q>7c3oQTXr2wd%2_Z6>n_Bj2p}b zONBCl>jWGz+w?4ch3%V-rrcMn^mfqc;C?FJRpuVp^Z!dMb#{}j z-7?Pd7f!GAHALN7Z1%aIuCDeMqukeK5A!t7n3|dZ>F!!xnm2peUfpOFIX^G<&^X=f z0*F6!_BiR8f!U!vIAc>BOnP4S`rt=4h1@758OJ>WGAO@>pMoP+iTT7*P^M2n^GS%e zJ9t8!MNWv9sVBXvKFp*`l-93clsLMeXJ-+WPAw2s7I+1Jr(Dbpp<8tG2J1Dm&)f(& z>H6dy*p7}C3hxSUJ(Tv!)~7md`wu9X-ZS|ZoQD0Fw=MnC2DQE&JCG)RvP&8r=YHPt z?qr~AiOhcS8f11fZ(KpDW@_Lsz>&iE=JuFmRvG2}yy|H>fN(OBqYxpwoj6Hx4sNeh#CD$p_n`M_nJn;sIxZsV-=FwNLV~ z>{|$LQC}*H1C?bnhcfIC3dg>9H1hv%Y}f^xxBp^a_K!}MjyB44sZj+Kk382V;ATHE z;g0gYgzzbf-B~gIt0RLtc*p6ofl$tw$j1^RvyKL=B(H{8XMXD*^bA7cEY}g`ls>qn z;P%ZX5T9^YXvKXxQ?YH6?LCzbx18ZhX~l7HPf{4Pr%YizUMlC)^KS2XB+=z=MX3r;HhkY=eG!6Zx60Hs+!50!jwm~m2?S&O9zI`>kcSNTv5W_Pq4j3w-laisn;^Ah!EAK|7g)`B=`&PY^BjRlGj> z%yy39vpiVe8EaA1HgKND0neI)I&1UTea`%B<)^|g6G?XiAwpA-nsdtKN$%x{KmJ4p zYnZsNa6gBsVO8JBr>+WkUe|r7V={xkt;DnH3NsO3d%4xfP_{i+Bj(>;O}5DD? z+5Y07V3OGp<5u5c+I-|r^d&Vawt=8#)Ic&ZNUHX-f;bq~mkmN_850epLj1JSSk~6| z+LC^YfjWnK16V0p{L!Ct)f}klfit^UV9PVD&uA1dH0!tPKTtg}2n#qq^zJ0b-V!AA zY)8)d@lE7?KmD_H>kBj^FRwyh`49VV4hF?9v7rB;w@)Q#D`udGZs!Ne8NDOF9hO{n z>td>&3i7dufMyk`h%#NFjI^GL^uvp^uVh~g0qkd5ql7yuKaEqzEw;_-KXu;cB;_LS zah`uS=s-oit$>3d0su4O9S|kZAJxUpo*@Q6PgQ_sz=#Yb_1czT)*7J@oF?DXN9si z>6{hb0Md6-F1UykqPl`_uG0wbHsF?H0rK3!dVmNdmlHRm6?|337 zDslSx%4L1|`(Tx5TF{>WJ#`AHg`Cde7qF@DFi?s@gW>2`CRGGj3;>?M`g8wCHqXeW zlH^nJ2Ymf)%`K7nnbh2w3UL5Bpb~&#A+ft8kY%rXmrrSlk|bXUas2JV>`|ABnYyrP zMKc&@3&SihrJaL=scD;RjeClV(e+9N<_Emav;Y_E-tWziXz22tCsPf2Y8w?|Y$F`e z>fk=y5vVoY8b-SsfzMwKOITbet&L#GO#_APH3+11!u(=G(LN_J;vZcY)C&b$EFSZ@ z-P8_>aam!IDC>lGlvfAKd{J-~5|7E`7KyRSH=nN@ZTPnDTJaB4*X^0Q8j#aF-CijR zYokSiGibM+fVyg3jnS5JMk%kA8(d;X8%a?w<#*P(p#*H0b9nHBEU{h;&TRP^HQ$0s zFmMNlXJ5a>1rSCpLDOXF5Pg)BMOz^@G*gFZJpKtrwfrBjAV;{)7e^fcjQ+{c#hV@C z3NLXdw)W*P6`BT9%^eYsV{=EL;*SY1eYN)*b!yitX*xaFY-uV+PYu78{+@V3*T?O(fSyqcK^9dHAXze1fv@wO}R$=UxMaSU#4p8Rg+DBPa!yt*M{C#>bL0g@BIKh z#kSuU>a?{OlXh5}5>yWEGcyYL5qbApGx0F$i~4qd&7DV2p=)0W5GJH=lALyPMTu$k z>bSXPC)y{B7lm|m5IIrgRkWx6a^HudPAS%u*R?mTrR^b>Dzy1hW)Rb@dcF@Y2@s7C zS+pi!5AVJ`l{2Ywe2LZJiK-TAui}M8uH~ zs#r}W*8yOPJpMOK2omYZ_t>HRlpD`C^>iZ95al-?zh))SUjChQGi65f zxl5SsO(d*~yc~w@hdlilR-Kv88Cc{j~FM)J6tivY;%KjiH~)2nxNdyJ8YcLT^4vWR;*~Ko@cC)u#J9Y>T+TG)oER}bXAoc zry9vKdz|HQp7wU6<%5V}|K*IpVmN!N5PKSK7yNbXggntuI@!;nNw=wEbePxlv%Hh7 z^pRx`dZYTr%8YAbs>(3aq3onn@7*GTr(IPbGw3_U5!mn=Hb1FW! z+?H^uB)q*yWa&)iH%=(iMqeI}a0Vlg`UwMyUi9Nr12T!Im1|OEO^0V2=9^>BRv6Im z&V%A)vr0E`OEw*9;-CmnLpiHc62VL5NQbFC-s#^$?FSjuL-4bap5P@Q&@1uW^z7#9 z^1qJ(DEDw&gBf}3ts=vIRD#;gpH3lm$HiuyyY?7AKF$O)CM3!9q-^guiDJBpq><6ts` ziX>w1!!Ng3c*e?Yy0lRdZg3l}`v5&5H&aQDTx84{8TJSawM3J)Y7So}@yq=;z`qv! zE|-))t+6lHo?G269O$iHu9FT@HR@L3w)$U&J6_#XsURHWjag}k&Mav_t9~*acO?+6 zv(RZ3d)mqqY~ z_6_!1Q;DTpZ3Rmsk_?VGp=r^EmHO^=wNI7dy49_cdCu9`j!(EZ&hj;5Rp!gRLbsV! zXI$?%?Pwuvaf|sAZ~m7^RC5U3FepUP>gnmK0yz83rId$(l%TpqB9@Vh)tuqNOlv*6 zxXHaoX6TU5Jqcb#_(Z%QNyM7qM*b{@Q5q*p7%^xz4Dm-%7> zI)j%3%57~WE%*jE{_FUE&rN`OVC~Jct}{}q)kSIrx@TSO^vhE=~Ij;kiG@v})>E6UwXoT4d<%ptp@*>@pNes$~D zrJib#i`@F8e_E!9sjpXo2Ri24v=JXiUZ7`Jr=XTpb4N)QNrsASn;khZpaYSXc#aW= z8*z#=1&}0e(L%x1?F-?0!7N+PbcdG$I+0R=_BLEh z#f%Tad@}5jWcAU39&YlA3Ie;#<+u6~?soC5GD0{o;j!xxz9Gkm30YC$K_Htkf6c(PM_A*x}hbP<5(=2lXs_NWdz*)@j5ll z&Tl<0t9Z9n)vTJmDBMhqB^Tp_dW+WE+N_+DB9+~p(_u@wLM`PXNicH5YhEqAbU7HS zaByJI-Tgv*X*aKybXe#*@7$Wq7{`Q)j}KzmZligVK5?+PlsX%;#-EIxZT8yAoET0; zWFbEFn#copAUSqbBOpXWVWdHJw%(-W!k{hkndG~jTYzg{Ue;VXlxNl?-`QoZ>TPNC znN6iw<_qT&_?n^CRp&;if_4B+_lcL^4FZRaZ?zs@=!zS6z_r08XVnN(UC6DdBn_1n z=E}@pn~!cbDsu zH;%zO+{FcAW!lqgmWz(D0dW}?bW5~$Lwp{D5ArETX`>@@laSvI37x+RHtY=Urq|;M z+8CU_e=+JOHNK;_w~f4pxj3BVDN@AwLC;m}y6hUzy`f6@iZV3eYvxWCB1vvO_jzLv z?ZdH{9ZwB=&-x8maYRd6JU1QM5Jqsf%p)I)6M&ucPCugZV#{L@oD_*Oo8BOcSnpUb z5Qt>Rt4hlr0-`K?kdT(6mVKqbA_vMpn8?cAi_Y)me8&n)r|a8e*k89Is)9nb63x5? zNwHrx1d-q_fz%b$vn#De>Tnxi0|8pH9yml(uPm7*VCy5R!9Mp)a{kp&63tDDkXK(; z1LHxk+gUT!O2X_xg5{4lwil_5eGnZC!nA%#ZPfr^)@G@OSs7loVIS`(`ono4^49|# z`n630izG-_TRRk96DsQNmKUn!G5BROM?xOS{H&d%ddgF9r&%S_D3V~FNDAl_f>g;p zPm^;s3I%nb%2?7kXnUaR<;Oe#2&{UQYDR?6Ajd8PK8Zt?Y4s@!73sF{m)$&J)3Zxg zjH;z^pvVWe@fG2C8WY*(wpX5Nr})5C<--|8Vs_zZZ67%BhukrVrqhaGo3y3mMR2Ib$bW5 zkskQJ$|giUx1agA2_CY|EM;B9u|H?ZGI|wCf#9=K!7FK@nsotQS$<%-+Zf8#K-k^O z4}c4Xuc~e(s;sj1GMyY^M!sU`;r`oGkm+6ygz=faw6PO#v4;?Lrxe=WX~66Ma>GG5 zK)8m0P7S}MSIZ0#X%}CfFu>I@0y>)lH(w!bz}jTfx~S9D)&~YRQE{p+7seN&M12ZC zddupcz8|R>Tq{y;ydD@|h_lfxye7FhWe?w49<@G~1gFoI138AL9@SEsR9YohT?p8~ z3+JVoQeQrX5`Fri8Lzrp5XUT&L=x-;5+?^bXEr`7my;w@iRHR>!5Y0P0z&v8A}_|( z4s)4QV1?*8R9X8B8?5s+ijuvhJ+z137?|>eW7b%>KOBQ~vC;UfX3w{3g979jzY=O=gj%5>M0XcF9GrG`dE(= z$_;x#T27<1KqzuyFiVg)$Xwj;PI_(KqeJ^dJM@Ie{OG0)>obOaGQwE)ajUWpwg0|I zS8j0qecTHK9gwGjcjw0Zy=M_(?8u>X1kY@GohxK(fgo+wbd%;yo`kPK+p=>-{fDMo zF`G^~l2(T(IL)LE6kw5@nd~>De^Ypq;Lera#0L?%UUmv$(`r(!L4J=or93U=$O#{aPxXidMvfHlgXimvlPJbUt zsUlPk^5zFnd{*b-*4H$fC=XP6U4&K~VWp4=pMagu(v?9nN6*`WKjI4EYZBi-!xbTB zhgu|Mq|x}LAuz2ue|09SsbCNiUIaY-W-e;L`1gvw45G33{c@W#C{?HeK4mZX!m#@J zoWW$TKLyHJK85)Dl>o7&^M6^4>LDgSTP$?8$OYSBSe}G|Q6QzW?dXw~)Os5lRz{@I zTq(;^kF_||1k9H+fdTnmU%AcO1|}LN5uB&bT3<04EkV&4M~Bd!=*K0HMadM~qRGS$ z7aHxkJQQTsAtHyes@eq80^2SLDJ~4VIG9(f|+Zs8o!?7BytNal^rh zxsS~W2)m(bi+!lt1WEmT+U{HfH?mz-)z)4$*XL8S61#rW5yg;xs)Z6c55OjN8cbES z9%5)w>5>go%MRKDxQ08iloxOESODv~RlC*xETYLV2xi98-EAalb#s1`Pw41)q4HZ2 z2)~k-#-lLO^BMvk)O&#MKCp1LVc2OcW~4bbk9z z->{#Pnpf2bDl0-Y;+184$Td1Fd6U_s6&H`}T&%iO9SD~65fA`T=qwua&Cf=eO;q4s zikTx;gfD6ekbQ4>G@ex`_aQr7t71wQzgK4a>p;-CjwYm#+$>79(vQ6!|N2fm^h11+ zp0QhqXK)ypBL8M%&t(1mGnz!#wqlJonn5jWcrpxB!~X;q1UZEuFh^l{f`q9R0G2$$Y`}_` zs{StY183)hxt{Jc!~&An=yRC~L5rpi&qS6fC{kC}7`Iu4nVfNv_P#e7a9zuKR|DsE zI-AZB@pUo)wScvd1Yn3MH7s!f7hb%B>eM3rIA4{cYRz2kup@gHbmPl!&{~EG@4Gf| zfw+O#Yz~HN6@h|lOJ~-Ir1j<;z1AF~7ZtXdkdeB{ETZH2+qUz=S_v=YcAJezCl6SD zZ}mnE>eom5zq6qgXi%H)3pO7BB_|>_&5BxI-0~;Q{8LX_+KsQ%8JQmU1 zQy>8h|HBUj@9E}1d6|Ona|Cm?gjLL!O=-2^@0|m9@pbHYzMrZUqu}Rn(cxmKmP#_~ z%^j|{4~loNi~szRf#|#nvnQ-HM79VmjafDIi#I#4kxY$DRgDvZZ3sns*9C9g>I5FK zF&TlCq^*!CanHvH)-owrjQb8vBL5=GFWVUutWjR%q&+WFd{eOtB>%w;DUduS%YI?= zA5>BsW-k3;mkk@SDiKlVZH%3gfy%Z?4tx_?|Jqg4R5xfE-u zQkvT^%dvs;Q;^)6ixsJwVwF5*TmG^3VCb^{|L43{z{G&I6o4CUC%?QUXg-nXLS~2t zfxXK24M&pIjN+tkd(rP!<>B#7*f!n=(-PzWYUA)jEd!c7Cx zSih&=A%~^gdl%N^eIzJL&ua68&TNI)jrPWP|CP_fVaY>KK z$iZ_F z_L~)&nL<5=W0n%jxVT6;tyM2QEwyn;x349y30!!=R=73{-4(ZRZ69@}d?5sK5iwQ} ztJ0L|x|+>03=!41-b6h|bIQH&H4uassQg%ytcFz%#Ir-?45lO7oD zmqgcVZml17Kw(8@4!HXEKrf8cFAqMe)d%i-FC&|I4pQ-H{Q?MV=4yY>7|JvH4)BWU zc^f*SUP6pIJfZW&U*@~DcVXcidwok4Xja7_z0m?rRfSc6uchNN%3mHPv9_VO@d(zd zDJ!>0>xaY_wc)m-TUUI*L4?}VbK%41nQIt}SmKl2C+A?hZzK!k_gn}(bv5^#-apOq zQ%?*55)N}-5N9OKadWe`tlzt!%Ne=PTTUPSgbIbxw1fE({ot!R$+2V2 zS$J65`+81!KNmX&ky@FC(sh zxltCXK}JPop+eCPgBBAq;W4j}fyEcV@h|k;iDX&dW4r?WW$~HuSHP3QGK146srk4Y zD{@jY{M`*F2Y&}Ca2BjicrR#^jCrT6gt;5<=z>s_aP_9jj}1A0v*ym8bebWQn6laG z>c$>eB)@CeduHfCEv2{^G=sGv?-c3US8mV4JwK*kwuD}BzUg!pC?(sCzOv14oodq6 z&p2$JwvMP;H~o7SyW@=6Os?6y?RS}+pG;Why-44K^bx{&cRx5Ui#QaDl!79N%{p+t zyoijCw&dMt)Xozw@t@(jP=LUh4HG3XEuwt9@NFDtf>qik8v^n*d z4=AIT%!4DGs0O2IgX!0)#OkUD9!RTYhv^$8N5rqA$-qKT>&G4a<}k0sjnbx$>Zx8MS88H-KE-vaL3((7H7N5eRM}E%jS}Jr!W&5|bQ-hc z*oNhV&yok4GtXfUQ&Q0t&WR|<4}AwUUF7G0sT zZBv6jkdT>e-Uvx{dMpt^qj+y0wD{)INa^5s1GVagS)*Fv*rHgnpy?sakNJP;V(xM^1Mr&;&GF+MqE2!{8jL%dpLOe{5b=fdY zDZgTH^LCHwd&`QPgir?Vz-p&*VaiDBwJYZ9nT?KwZp zh*!1fxwuw4l$YOT{fkcYU!xN(rTtpvsx zb!Cv1q+cKs#JZnlK)yeLgFfIh1Z}i6lscgGhnw&nm#%oS@0adDeP<{lNOG5YF=f9Uey?W zmmlY84FDyPYkoEmS|MWePG^73n^LB{ZVuf5Z@=A+JDb3%w#FIr1@NY=1zYrw~_M zK=lZ;-dnadU{Z8QQ=*+p0c}7f@1p{1!`|~+LI&>f7z@nPn}gY>b^8h6p0ei0F>@JL z(A~&1?ovpaN7sF6=@5@VhQ{tqDCh7s*Tz`H(FyHREpH%lEcpb1}!7@czj= zhIW$_obLn#zn=`8*dY2QowfNzhO@Tul^8u!IKkJbPz~QE7SosY%F~I#Xk?EFI=Pm& zJ5`$`>m^9r?aZm*0cDK#=VMy=7NavOJgb=M|c2=&G+>>s+kCt+fuV`1$4Z(r-qrO39|+bo)R5| z<%O;({4R_JF07fm&?U*TyWz$?d@7`=hOW}%ARmp|ku<7G(2HH&Sq#!Z+>aN4`1av9 z#Q@(O`4qDX$`)Yi^i(&=Oy!H#D4w$iK1JRNYZior(>1`z z?Ong==#x+WL=D5~m!dlgM}z}}1!B*R60>zH)|FTF!Aj~nW$hN0cwg`t?^cW%-;n3s z6l+TRD3{M}uaTCOfa`w*<~!d1SYWg~U}$V=9g>i4YuY6;{^RGukk@<^?vUP5;e7m+ zfV33E!H`ImK4t~i#*;kvcGhhy22*w|tE-t|SIwDF(w}NJwfSK#meGvR_@u2ACjCa$ zhY9)E9x7=e#N6aR{^&{>2M#fy4&G6yHU=5GhhoQSK|g^=-*v7tG|PnXt?E=dI{+4X zLiqvW**^IpuI0S0nh;8?slDi^DzXIwMlz30!e2AZW_$9jL4U`9|-F8C8jb@tp2qH zhoSeOaw{GdVfR+3$m7#FLyQV&fkB?aGY5qJu0EFMf*;ZGG(8@^ZN}Pmu%iTTlGAr6 zi2pV+nFmrfo}9k*AF{l?$9A#!q#A@MMq?rDb~RA(m@^3zfJL9}*cU=u%6>#}19VTFPwd&7OFuJy@;nMf@HVb>f@V&47Hmnm$AWe)rU@ zTZmK|0z0JlPr2Deri$BaHnfPm12b5N0{L0<|)twWj!5VMoNr66?=jN zX=`l6RvOaJ<3zkY(+ne-gSxAkB0h5Y{iO#2){*p=ot{RQLposW^XbJyG$f3^;OsgENh5~4 za_UzA?A3h)Quk?QS?Z6&k>+>#yYukDP}q2%X+`npGVFYQW${PBsjL1MjaJ|k?MlmT zV-I(ts918uc^k}R-DY#}bM@vNt_G}z4rpRfK!^{l%XAbT&S?b&gVYn3J6RVTqr`=O z6s-oh1L}!g?*=3(j$T7DEN4DErlMO|&Ax$#6g37-1B)}l$EqW`4fAuTwgAS>Gycoq zba9YgN6VH=q&bDC`1ETKa)hsZ4mL_<3&|+ssw7}JQNNSL(=qTK`h#%0C8ZKkjv*9D>BsM%a-egdD&-}VsEr@fb+J8795uADA z6M|&bZtf5;99ud+*nDJ;ulE!9_@xQY{g|u*=})>e8e9))&3?_-R>d%Z^#uUuMvF92 zPUPPai6ZZgi;OJk>db;4mu66OrPHfbw>fIvTzBugcc5#UNHWJO>gu`z?iSwyP&8`O%w(&~Fg|r}S@)F@vkHxgyqY zO?l}9y$?47OIFb&U-`!9tb&EIC|FGM4%H}biEn8L_HQa7sY;;vC{!h6aTJRsuK#u@ z(-HizX=?2%zsCv^*-I;ji4Pr*w5kGEQx5!=V>fF=QMUc2zwdHj3WulQ;r*(*#7a7f z?xIS_*XX8iVnjPb;wGIZS|5N!0obk2sD#R}W)I+by&*Qsmv%NhvTn8<&5Q%x7|neu zOvn;S3(&D84rAQzohp|2iQr^)cBf% z9j{>+=@c5B8WmEnatr|g{A&d*a8Fcp9gl&b3*$#FyL)2m}Qjr3epWzJ6Gcrn}oy*U+w?b}u5GI@SXY`1g zwJZclTsI!$+7=M8OpDQbZgsNlMJC77GohSbP@_DI$>i|1I#J}6#62Iyz zDR`}GF62g~s+kzSw8)CZDAm0OxP70AxDVk*`zfUBLY4n_@~Qg4SMT=6Xo!bzy&(Sh z{OwgHyM@1M>AWz_qDYz&!>ky_hSFgH#g391RJ5&kdbq(x?nS>KijgARI&(~4jM)2z zW?&Rv%8zpYb&SC_Xn@}5=kW(24^KELb7lH?&x-&{nnrDp0XSmM+m&}=qq&C!%AeoJ zm?AlgKJxgCN$0RXU67KzW_bSAJ%R8NH@gmn`RyN>m=*9?N(9od=Gkc&#t6cScj+2i zdn`1PF0_PYkuG=-W2QJ~!OU?<25JX>!PuWNkQX^|pR2M+1QoyPl`ESZX{JU9R~<4X z%T9n=U})`KXI{HLW4(8lKH_t_;q z6>6zDuq9m3n3tGEjeF>+-3jOG+`eiY_IU%ZfoBhzR3oDVj_~vWT?a0;(JlutO_#zb ziPP+R$@EBmi%={1%rRou`{Gv#7tV7AqLB?o#|2jVjR!1js0y~?{x=)cosRad<}*Jm&In{5AzgL%d{h8oIhXvEhdgb%$O&fSG<##zucSN2Gp=d(W{=?nZ`i0p2o_>3-WhZ$>nkf>ZWIUyKZeNa=e7e`PT^VWV>nVkY*Y%We*pU=}%5 zm=%{cs|_H`aa5W-A!N>r5X`R-RJ@ble@(I*SixbS6K)*}FE^JW#+CZJ_Fh!xLr^8q z77M?nXztRL1lMt{=$cl8r#h`GS9$;#%nWCc^S{?>Vxzggc7zfG{INs&TDWMk0z z;H&7}+95QNRIrl$XB|8cUp8u396g%O(^me6HX*J{seO1&H?lp6<^~}syvMvrq(BpL zctuN(9+AsKr-c7ui0Bw}j9LaPDh?^^Vbef`hx1Bi;yQlEUteUsQuy5Tp+w9kR^UT~ z9#Webce0%|(xZi11?ylbC^!pf<7+>cZrg)x1?-Mv#&caFg2e`dGAh@j1RPy)9SU;bys&e*L-xd{9KzA(B9L4ojt`n4a9 znca(b9|m+rjCOk9)Mnc1m1_{4ESY2ydzdi_1{O6`Yt-1Kn{?Y{}+UoJBI5Ya6&Kv<@JUMVf6SMk35Bm{53btNb^ z52Tn)>lo+&pEkV07`>DWj@!FCiNA{0O9V9I5A6TbnrGvZeLKb zb)zC&t)yskgJ%#oa2eWi-VWG-z52WPXQ`4mCeTpvo}WixV&!_gKDxd+5^P{NUpFyx z76YmpNOZl!JlJsEK)j$}JbC3o(<*<50(pK5FOrw&7iHW~?=l{PRltTZ8ZneP)cPRx zwZTR;t%5?wY%4qLbP3Z&cnan^E=PApwQHX1wtrTQ@Uh-<*rdptNeWO;7M z*h5n!gcVj1-Uwf*gpZS?+zUyQazZ>N4tvanPRg#LTi7_K8_j5<_|4qR)giRpy%>>} zQ1VRq_yA+YL+}e|eBUVtQqFWom05PHVfFFUxN^z8CmZ2LZeN9?&ImI73%IhfE@7M^ zOw(vHM7fwnplz!#L(sqV#I6fP-{OJ?fT3eK=oVXss}fWtMrp4%woyintnVvenxwW< zE{@j9_M%O93v=q`%2YODbA7XO&y&l950i15YoItMwOIBu$thYo3eDCEm&4JD$)gSz zrYd(@kAsgvTmG=m1tSTlw$Q<8fqwZUBTO3;Rs|ZgD{GacWA!IfCZ$w}PWa$VHxOZ!y%~KgdgA zJZru*S<$fuJNAKBSdPF#;M$ctglgfY5#+1;%;*B6$`pSQ>NKYZGS&c-mFp?l5rrNy z{R1lef>FhA#V>PXYjB~R&}DLDumQZA3|HktSgeQ>(ptJ_m}oPAfsM)c(YCO|sd^bl zn#vM@?!Xd(3`gWlBLbH^0ku%0Oycruh#i#`RU=8@0iF8JWXo#Z1$kmOoF%cGWBA!G zWYWg9T*z&mrfH)|n7cOldj0SoLaQExXd;%z+cpVOFTZ>@iG}{=vT97H;KI zwV_?%>{k;M9`6QW#+GBA-q9lUC*Ko|&gryrj{r;Wfu7Kf~OO8ULVS)(=WEEKO%Y*B)y1@c9AJff1aGMxYT3 z7Pety+-94ITLC_LadAfM_cbq5KelSZATS8Yf-1iFKL7MkK6P-FO>gw2e%s^M$8R%< zyHL?SSIUeUSEiA}=!RW`wu6p#8lK-jgxd@vVJl>45l z5_qFb5HB7BGLL>N7-v6R2giLjDew!)*2K7|=CYZ=@HG7Z*6HfVIEav!)9%zZ0C zSO_b5W~0;Cr@FOOHRIZB9q=`tMO1=>loR9KDS33<(+R(mi^~OT%T4{ ztS9d5pcnrg_%)x%^pqV_gAXNr6<_L&sb#=-lXb;8yt}?2^@hy})GU3;N!Uiw{k70h zl2s0}49Vf>-GVUmSTH(4ASvnmL0?M!$*()fj)wejLyFH6cqOI|Hc-r{gGdmFEjo8i zuvCUmI4V}h-8I8Njn+6~*1i?}95c%qKKbI-k0ba?G?X%002YZ$|JB8~0{vvbCB!%2 zL$r(O1tlCw83WJ&iyDSAUsVi$0^gdWgX!jAdw1BHPOiHPhfL)vyY5dVL-Ym@1r2XY%FBf9kz?2f)S_JrN&k*b(bDRNnTi%6MEgXQcR(Q{8mhl(63*`X0 ze%Y1^4vf+WJ{zfa?^(_s{jI3VqNTZ=?$^#bSk;yr`~!ysgkQRAsaS?EghvsU=QYSP zA+rKj9W2T~PgD#MRARuRna98vr10D;WI_&mrzRZV{UU8S@N6l zBc(vjiRg7#=gn-q^p(u!>3&##x9_sA1G3N4DobM~iZ4>$IE#HD{S?MGZd@jL1vZ_0 z#Vtb;3uqr1PS-fN5^lr||p@8Kd@bk8Xuk$9IFXxtIvPbvl zB!a)*DqxRMX5VHJyxwG>R95qPe`z~)L$lXF!FBV(oMavCWE+{Cr2{&413wr9zn-g0bO#`+Y6 z4LheL9}3`G)>aBKAZ2LMr-xJDN0z#7eow=Z0DQ;<2^uUkqIgCo5wDEv|*(|%vS&%}0Z)&>%uCE{gTc6=@Ra!XlOQjN=zfNi6K)dcII0x-mEa)SG1R>UC`L$fkSlOg^gb4OeU zb_^rIiivk7b5pP`8e5YUvy1O``Jjug^L+=>W73<^N}UD(2XoK|Y{sc`II8}xl@?ru z_xxspVONP!Ihv#&ViVe)R5Z4q7k}}|SBY$nAYi1iPh%*~th4{XM}=8~nDsy$t{AtV zwi62j3}Zo8ywdPbi96jfY}xg31oFMhq%?km4rSd7DC*T{$wdG=0_8L(?z*VC8K|b6 zgg!NAA=22kn+HBO$~n}{Ajsgw-A(s+#`(+5G3Z3WTSe6b@CNtI&z?Xk_NVLh*!4W&=4=W0LZMJsp|bFQ|wtN zw71R1XlTU63K0p{t==jJg-@o0l^Q-vVn+yn=<(FwkYA9vZwdomQY=he?ybgDCGV7beJOa4kK|WFb%8HOKO)mHET<<8ya#0 ziFH7}Qr*%ANs&jgDlVBUznFO#r6K+gsbVVvfjvfUujs7o<%2tnQZ(TZpmb?j@{s)l z6_apbshe?p40T5OI^+B~p7af!$6ypfxVxG?4)oWq`IeHEzG5fAB6=T>zXnWtM_n?B^Q61KB%dPJCmCtBz;_%eoD_!=cJ z;T%P6s-~2~LioPEq1@eOfg9D7Y(vgzLJuR66F-NTZOHM<615zSq7 zG&{qMVZ;Qp%O;~KasE;MyB)+HOHdK&#T4+;>pH{a<@nlDA1m@wkS#4Uz>cSEt?a6l za&{pDbK`d%Q8KME#p_Xw@K>iMWSwqUe{i=8l?NlPNmP8cs3H$3y3;T{oxX$y-x z!^b?m6ZKSG-%X*fhq}Fo=V5@rJ%Xj~uTp<^Pjp8B&o?#A!Xa~`DB#E#+cr13)*}oe zNgzGi)XvtKAB=OU2cZ3W*PUwQ4u!g5DuGn+A}5V#zcy99+KYq--|4J$Gwm^)P|VTe zC81m?eSg-cw(Q-ffJRY#+P61UU9hjrxu11>%om->2tZCX<}jb0ZsXpy>`#+J9!G=l za({x&%%?;r^gVrp`^hk?ITVgpY|Z_{A+#Y3;}I){rLCN29u6kkF3F6!?v#layN_JJ z;O9PT-#`JqVu?7Bp!!1PqR+Oc@q1ylEN9fxxz_gUm1l1wr zFQ3D(VUgU|pNr>phjAw%>sSZsnuZOPnAbD_6=Pho!)eqq)zh$4R40Q~)*+8V<&H)3 zV4O(|yeh6WvQq=K+_QPGhJy52l(*hOvG~4Ubrpsq-ZI@&d;cy_9b}+MTJ7$y4W((( zTaZuzDXG8LBaH%-$%C5-}8O!3}IP?SJc zYo;bklr6{w*hj4~A0i<^BqAuOZT6bmQ@W@e&jyX1PIM>YJ$5bfu~ruI{@X^B|vUa2(y)uc)w zE$-8jC0~wep66s9OOb6tJ~d@!j(5!7ohx&W6R}80{f<*~R7{tG`bc|k;IP#CniN#5 zdH*?-UFC=Nx@~dS+ioZPC7)qF;q8n(*{8kASp?9%AZHs{SrjyGR2f5Q{hv2JwO zPv^E(b0aXLl?;skNXJ{QB*0*ucc-yka*x&+Y5Qx`l**L^wsv5{P3(q4Ub>z_f|X{1 z6pH)pf$2c%@f&4{$nzJ@(B>g_-4(e8H{0Jr!|lOW!QTD*rA`U;f%UW+movOC2-YRc zcH=xfN=HVENAGB%ja57~d?UMu-9yxSvvU<5%GvyqbQPBlz~a@`)9^Di7<9)ef__%f zRKWYb$TQD+IrMajj0~#O-?_wzW^OT7{21T&h@9jIPa&~DfNcoU-%6wW+>JYFck{&0 zB#{3l1Zka1ERwLjn+}b49!Z8DDsk>bLNCLKMApfv({tU>gLc@&v2_nNP&x=-tQ z(vY379krmYW1|JHr+_vpvA{Tb-r4;@>y52(lE=d@h%6{>j#PZHwu81t(_=QKBJ*h|mrT!Uj=Ka;QWM#W!>L{^r%7kftgu z$e6(*OD9=-hy;~m3b%+vi184IcR%H^QmI-VEZP77x{ndyKrdX^i0(S-mvef44t^g3 z%y+bqbAx5-|8*Q2!||Eu8N+1-0KBzZw!{gOn+EWY`17_aVKcG2x0y(kw-=YQ?09l) zj?%`tNKNrteT!#>uOfo)!{k?|BCd{*YV^GeOadeyEfL z?{P`B%d^!a6Db!c6wZ;>UL`5no^;nkVuQbK$k2FWr@$%JIB{khu2+!-zht))#_bag z(=MEZd5HNjn65f@d)*U!yh@DgqSQBwMd}0NFO7YVbMh6%w@+cxg9M`IDzHuELHGKhvl4`XNTspc~=-nF}Yf z&P_A8dhzU-iqPRk72^roLDS!-P}$Q?I+=XW=yS=;>K{!sA+}$Ah%h(OLMma;Mx{X` z>S{0oMofJc`KYV<{1$@xVf8T&>VcVX(Zb7KhN}|g_5V3#n3RF_Vow~MMS>XeGP_YU z%>|%+6USIqT$0~AH9~m~+jwZ&Lo24CnBY};U~Hc5^$fGe$XX_vDrFTrg8X{Y6?@ww zY}C2)4njbdxk0?Z{A_YKdA)ywiE6IqU@7w1SJn)k6X;j#r8YEKcLSk7? zRsN=o#P?#E=LZ3#%PD-`l>Z`;D#8p|>Tda)^(e-5+)(ha8_ekd)bh6kedmV@q9W+o znp2t4gCN|V!HFBo70H*rlh_C0g7M#1_#P56)GbUGC*BKxOIqi+a%=B6pGhXk zs31XKU{C~WS_tQtTh@IgU<*qFe9b@Ot~^kBcNB{r`) zF}L(l!gDz%l1FkdubC$c8#@pHrlNy5aTI0TgDLM~I45Fz*j~&7gVbUk;$YJSCTqZ8 zHR+oJ{A5-dk`3LHZ>Uk<+2_A8w;V-=k_lZC-dpf!%x?%k zYVq1WO`p@L(>eLwf&|e)cHy@!NrobJ$+1nSPqpDh19{F$VHgK^&j{(P*#i?(b^x48$^l79 zV-99Kb%*CXYEkb>brN-xV_NEeQ`2id1D2}uhzVD^q82z~-$gOr5?3rWR zXnk0WRD9&EI*x7I|45J831! zbAazK`^*k;y4_p*Hm*MdEPsGd6w6cD&mK7+_#9Z9PG#%U2pEe(b9RFctazsJ4~jE; zo!F4Acr8~bfc0z7%!wWPdBt{#XbJ!i<4>Je4dFp{O*V~dIEEveP>eIGsP1jH!0N8D z`2Plu*XtUq+-n27JVvjlcgM$Gc@JeA5B-b?TMtDG#N(gIjI3TlGz3XR6fPszelh7({_5Ca#QA(2?&>?dMp02nKoh%QrSBi53M9ZuhQ#AtFyMFqx$ z$QKGN>^EwTlBtrZMf)1P!U5LHr0*JzWw^K$+C|eN-9YqHq1inP>4Tk+xI})^{b}b; zsweszrT9RsDS)}He)G?=fCG`^fN1`ph2vYzmG0kVF^#Lx zL-0Y8nQW+>J8P3~p9JVO0HEWlj_iA&bxurks5gj~bMVl-tv!9Qat+4&Can}Al`A&K z2gewFj|bIsiO=nb=*i12S8Yfj9w@3q^eFb+u5f!^iN55HCXO8uXcJQ8pKK&KQPvcJ z=-&_n+ZU&9oixsQ)C>{NzDVrrgTt#Op5XdoHUNJxqoLCUNY%b-TBxo3Cx6ls4C%Mo(2j6#CuZl_l#R}-dNFT8CW*n0 zVEzM^uNd|3K2gWEGK2j}m$oHiqEuLnTh0Ew#&VOh^uT;o4?<|O{SulRN5{X_uYw%X zmo4VW&tNt%rXEVA1AvP}Wp~oHY-5nM?!^=QfA)WI8Kn@Igc*I5gQT-YV5fun?ce&X z>HNt56pLjrID)&9LIB|2LxOH;wcqI$4tf;B~S)!Wq5d%_(eZ@=kvb!#$Jpe8Ntv zAkcY(8NSH5u9^P`XvycnBbn=uOy40pIbkVayh(Iw#xZ9d)CDP2Z`H^vDzHPF0}~~)&>-gkL3{; z3PzH+BnYDvN5Ahc$EH?+H6hLqa2x!lcU;0l(dpRJ87QZRj~0Z9FJ`hIl~hwXXhQrQ z*1c02#~@h{vYwpb#=JWTU#{Q=hwk~C_>vP_(v}qUz{V0I9Y9USVYa*F@A0)u{FQ47 zV3c1g;)VKQ&{#2c^~En&Afxkn?!u^d+v%@#9br$CKQamQ*e$+Ix6prw3!wipZa8JLMtvll`=f!G>~n6tF40c&`e4 z*J9_f>3`$I2(Y8)F$OJ-{_*#e>|?RTb-q49^^&MdT&YrXH|b<~J8Y#D_CttI zxVEP~x%Z~-40kvpWHf^o_V{&ib{y{xP@BLR3#9--ZnOYn%g3;94>-Ek#<5fTaNR9! z3re6v;XLcX|ZX&tsq;XE3ro?%bmYz=TK!xy=m)9gBV(7D1H2)=k=fYV*wAF|CT(OX6jr2x* z`u@aA3YwT3E5kQ9rH9Vlkez+TJzC$c9voBHD64I!0P`PhQo1%VfwfvtUaWr<2}tOX zb~nIarjmB@6Ji!x&@AZ7iUWFnwT1}cNPT+Z&I2hTR1;ME5`L&M@dfLqouDz`X=G@N z_@*NZ|4y7l`{a@{AXk)J3?`?VMB@40e~(H6rWTQPMU7<^S-szY)Ci5#;L|5%_?sn= zJ6Ig;Lt4r|8Bj=5@rn(60UiY`ZM7Z5EOL2C?NR^WHlvG4LJ%S&Mzmzb$&xeI;l)ZQ zplNger;YD%jwH$Gy_O9TJsb$7qRZo2wG#UHgb2QQ>1RX|Zi}`N``7$I=HulDfYa`? zxqNGay|Fl-=QjaPUx{9i=ys0BRl(Bz<;{6x6KEMpf(={st7Po80i|VXVWB?7KDv9Y z0+EIx0F9$ebxB&;BW`7FC3>l4qgNq{G#;iya)m*MQR0JP(!%`R zUWP+_AxYq|al>c6naz*GcO>3GXQNC}i$FfDtD_{_*rWWv^FQnTFr9vayEsAJRJA zq~hDnslWOfzHDT==Ud8;<5p;R|J%k(Iy7pn5GLR2z#|hZ8TN&!HR)jALibsM}%eA!3Yy>wEI!_-Jds3`D)n07 zt}Rt%!a{&dl{T0*?1^Dr!ThNJy{qnM>{3Uz!LbH*>KAELOAp6~8=a&b$yW>< zJ+WS`3H{_Go!@OyMXvzS1Jmz-nWWObY9&Sa(2Xo+IH|9(n||TNG5&&>ISYDFei6~0 zsy1u0$etoQjUOSb#)H)FLFCW`Mc(`hK)6XfvVybh@s*^xtod8rQz_d{8F&EZvRDjx ztwH`?rP`T`MqPXf$yXIu$RAH9qC{L5vDoWKL}=&9~>SeCZp z`-xO~e++U1lX?!`+8LLD>&hmH{AMRc-xZg+%#F-`Qi0)Cc6+F=7eVMkw47F0M|Szc zDdxz%j^L29r1RP@0P3IJn3uEuq0@cYeT_w-Lg?M!Kdo}YWtV&dY7{?}o$`abvSG{(SNrklmIx8mK=j^A?WeQ1Y zfTRNzD<~YDJh(E3(r`5{(tANA>VIyS@X`~C@t_~z9dMt|=JrDF^OB<9*HPfAJ6#q%iO;aL5-2eq>06O?7SuN(= zjQu2{?n-!+>^|Ag5i{UUVw_0CwLjfQx6UX7&$X`>Cr%BRX6ZwJrLxI&r~=c&MlaKP zg$OXCb@tv83{)>)NLgDu?J6i_4`NC9?tz-*^L*!rAeG+_n~I#K422}o$)&tfR2%1n z&Yg!<;;cTmiNdd+oml*XcVp)MhU#}H2#CQxIvpi-gAku6TlxiFsMy#T2Rh@6qh&3m zg-)@n$oYt!6Z8YCzptIeXmzh~WhV$lLk3V)?yZJ;rx|73yMC++giWn*tdX&`KC)9B zCEdp*mdIHzJYW%{JB4KkOPCd;PlwR2gG@4%n-w2ifqGtardH=_a#hkxK z<3NOQ-OxUN+LAk)KXkH7l@nsXp{@o=u=xW(S@&D~9hJ|#iyM7&9Sug))w2?j6a3TM z(A8b34#9rAvS5`L%6yAKBSK~7P8x>OYnN9w?P+Us29osT8^?PM1mRicRcd_geV-Bw zF${{o04iZzMGyXkyjpx%1m!ucHa_IVyED`85y$jQur%gGOeI89_~>ql|43w|6Bd8( zoOpDI70tOG%Ag63V-1Kt3#0W{Ds0I>30Hq765+{yRPpc15}-T=Mz1emV^JTrbJAYe^-fHc4JtFmX>c!4d~Eu@HFy5nIQkX#fD|YP z9DYxVncF+F0MFd86$AcL!u3e)VB6o^gFQAq1}!rStJqeG*}bA1DQz!~VJmz=I(7vd zxJt7At16qk-_r7GDx{WS_^EU;oXTfmy5VxW+shr|22`wILu2=L?uS^fGQ{Z6-uff5 zC<{s`vO2_(X2emjJqdV17Qs6>jjK?F@2R7|;LX2bPr1(?*jRwKMpt2`@c*z0@mKdvbfau z55b^ioW?J|SoW3*M9A^6@6~ftb}KnGlRdi|Jp8bbJ8Ag^H@x-#Z4pdIJ$(IFxyQyG zhlcxGzZsmgN^_n%#2_I2y#>4*nMnJ2PSe~OlOfF(rlfs)Kh^DT{}6d?B@`P%3LZj9 zZT{6SnAZxIa3fC`m-`YSIBV*wBMn#&HXCml;(>YYPM;aj#nM9{`Vw|oFhIp>jh?OR6K=TH z{^my4l4TztEv!1#UJC@EdO|uGX{{20ZL%zsdu^-`kBs!eV)}WuhgfI269gPk2FaSA zXR7{;F*7No6;l)91BszA-OUARRmm@w$&gP~*9VU>by~NHUBll^NKZip0O$*12~~F0 zT&7yR<6Uiyu*-VF6OD^M(p(d3Cvn5nt2HXtwIrcnj5bDI5?b{6F{Z75ja*nXMb`os z0D^t@u<)PW($30FCoF^q@}xYB29!GsZ1fnWH?B)(XgeW{p>(IStnuPI)^gV{4D#mY zf2BZj?Qo}U9jdAzQofL~hDsq(Myb%n5z6G@T@XWx%QNEPYv>hj<(crR6oltTF?I_Y z)bje7@@(8BcyviwOo&h|k$H&u?I~u_>98O07dK^bQBH*(egtpD$qt+n*4;3ljluU- zzq*R`PBT`)=iR0XMRyur=4k{^S|5-W8i8_OF5sE4YNW6)Q?N>X3Yu19LWflAt`{Hc(T>dzy<0X{$v>6+b!Y;rmwK#WVOz_whi6$EOChA z7m7JiK2w(SpWLEB#Uh&>uX!Ad=Wdrd%(S*K7dWDRyD zWFAsIj*B*c(H2$%Qr)n5s}LxKpd3d&(2P(n$LqXH6WMpTNMuJ6rcWAW&gy;EN19*^ z0U3SimT5b7Q?|Oy=Er&=i^x^0#uuqs9$UesU|Aq`8u?{qKTEd)!kaz7Olk`#cUxR%6!7np0VW7{2Jtm^FP87y0 z-ot8b-gci|N=_)g`=h<_zRP?{SQG>Yd0oEJGnb@*{Xdf0^Y2wb(c6U=ec_r~yUB&K zW3j;^5LfNbvsbKh;|{q_OsiH&jVd^}`d#$VZN%RmQl}K7BlRv;kg$zj5Oo9Yp-{V} zN5mg$b!k<{Jtxj0rdUYW=3@FMdK1!c_1O4hIu5Zguldq*2>UECilaqrF2|rGo4KkB za@l0(`T-MOHj|F6q^3juWI^Ly3yPTHEzdD3nHAsh|Q_5 zRW}>QLf`DPhP21W&paV{8`nf1npn^HZqF8hFq{YLx19QMDJ!z3zxv~{pYfm^Lm6gc zlQ0i5I#riw&zZP6_F~U%vUdXhpaDirotKuIxA0@6C!1oeYtbuay?dCDgIkiuN-^lc z!#iXUo#;D|A|THW)yz5V3@ee|+V@B55XfFjfDDM5r@O@pdP_ns=kBmt;|+AjdN80l zL%S0}Ne_Dm?GM+BL}x%O z{7m%l=%j3Qq{IclH0>M}>>UpYbV`z3p7^r&#m%UCX_Qs8QYS9|A%R60CH2b3s&|@8 zmIVbX8Bsfcc)c60c`8jL9ckp;2l+dV+h<-te~A07uf$<&32xxCfS8G4UqVk8yGP&< zC^XQcA?-^8M`TiEkSqa;K$GB}d7?y_#ECsOwnmxihMdC5Dj6!eom!JjXW(%n2!hELO21iquFp$7;U|-aa`7^sK8D0gSrPeHT8)>D3xw#36+DjT>ac3hK0L zP1lwnrR*8Jb#9cnh^oFNFJ2ss0Os*lk7`kvgeBG$=_WT!b9gszilvW(vjCVXoxRp zZTPeS>^iw^Q#^eIG}$%wj#WaY6kmBg`1o#?VLLQtX@cJUVQ*%LW?_<~Qv<~B$y*WT+z z$^B$-tkIOU>gKkR4eNv-UI)Hdg&KZypgSUV?8#un!?LO`N#e5~tbG8|v(`3(Fu zmwN(|tO6E1eWwGLpy7la?Wn9Ch(=<1t~{Fd&P9QBIk!YC>QPw2ZV?TJ2CG`>ZvP<6 zciW2pMaPIpov-OdyBKXIe66Rg|5PKkvw=Sd*%@X=Yz z1wZp#XySIHasnaJV~RYT)k4RG+0lPA7{Gq_pyQCrL@>EvaW^urrQyR@wKHlUz_xMS zsS1O_S={Z8$4gxeVAFx`LnYNHf&9|*tWP-&xoB%z5|vu|B&xfaVYopDGt7gkz$_B* zh%9bJWpGno#gpGCht&YflWe5!ow44Wrj7BD+k@91(p(&R1qLj3-PVh6O_RlSRqT6N z%+bTM)_sIqiHCV*6Y2ibon9KG2&>1Sbb3DjSb?mM=-uA>p#-!8j7+JW7;I55`1z}i zRI993z2I;cl+b&8=e+4?@RAuuZ}moK1}010#`AdF?>8P*p`Hz&;IOpUz59QZx8jP3 zx%5-TY7#64g`_UnKo>qhgamM<76PnN!YH^s9kRzFj6qq7o7$RDT2sm_b;>B zfjGBSIY)y3kGVZ$3AJEsRx^^v87~$u+j5Pb)F%)i_q6x;XzjPRne{iej;{=kIsA5* zi>L;r@t0PP6mxd?9bbBTp|g@QC++p2sn(d4oM`fo^F{mCeldj7^jQ2?F-5?EQvYEk zx3sfC9;HvhFIL;eB$**veJHehXE zgg6^bz1srWW0sRso~8TYj-UYL0z*^`6DPuNaOzdbHU-Dy()bL;qEq~6eik|M?vAIz zcr+)OjMxV}e^S?mz%X?9;0Ao<;{fc8?(5fqxvL{$KA?4KdI^pICdrR1J_m@Xf(xci zOk}Z_VXCs;J4q;dOXFXpcBUt9#4@2SXBZ-Wlss8- zj4C#!YGmj*DfmuFN`N!0$I?eX{ofpZC)IWwUr8Hiq9%;(PCxjsApxL8_Uru319)n# zXzUoJD~jT1#};QQftaF!NguOqP}31gJ5*^%s38nBw1Wlw!)R!tfu{A^mo{pCq8A|N z{~u>l&we?@GVU=rBhEgxKU`jAaE4WY0u9s$} z24PypQ8q~Gl95%3kWQ-Rdx)`e%bm(vbBL3kyK8^_5}u%k@2^_~?wT4L=vx|tvDOLDDSzAgm&lT0{HIJhaUY@TdM zhoILVQueHM)D&-~P#EJ1J+@yOAWAFa5x=^>riB8HOVDcL>g7bG_|YadLSNI1r&^#` zB`P1cjrk`Jz@4hq=A+gE7x+$PDz6v~d>pravfR8|C;#;`@S37g3Afwe=)Mz%C8mJ) zFQ z;{ix-ey}2Te*^#lSdsp}j)V}FR0Ol`XQufzm&v-%W4kB?-$~HAx+#Ft8~@{zmrH~EoOJjoXHg9?dSO@($Qz(w7H#o8t7OYyEMiZ`ecKco#7J=; zEK_{%9B36;c2S;yf9Db=sY=(SFG*dTIgeiySgsN|p?O~q_5z4~%UVG zjGLj5(;3=Z5P?cCr8IfmBzp1x8AM~ma0kjBHKQS=xPBXrWtWu2-?lPttl->1h3q%+ zsWo!*wl{M+4GE|HrK@TFx=b0llDA0JWrVLZu&qYP)<&K+xM&o2l2$$^Qq*Pcp}m&s z`LR6f5(xd!QM_tVcnMcx<*2cFzh|7zTQoiaZRRP|V%m#(bg9UZZ5h&xN-EI)SXND} z(?0>VM*qhz?b0;@C1HI*WgQa+8J^W;PBlJ{M3jk@T~L(|q`?cLC%2|8quj>5tXm#* zUOfeN74y=jHU{5%(vV$HH=KW2Uf!yBGJ$zkH)e2gpW`NajK9J_@!E zFRX|6VPH|ec`n$)Ul-~#xm3TxDRN91pynolov!p2;LT|L0-UZN{$=+63gRXalDB;v zIlpHkBV_zMUem-E9sg`^!IZ>28Pj!(@ae6sOC);5U%?GE$2qD1_`UFLdVeTyjRXbl zl`?&;0KWx0MDexKN({psE@YArG`;PYIS6Yx4xLbj)hFr&4Je1vOs>IJI|{CZxp|eq zN>;Z(LI6)du)loZRG!BhYR2FsymF%E6kSi1C-P8SIw;VxvwsaH-8=fr@Rzl|8JCUr z@Xg}8ewj_|ABkOjeFk+uVc_EV8_a;Unh|+`WGmp#qVhi;*-M~{#tYLAfkWgX8J#~G zBh_g+91DwB5GHz;${Kw1eHIbenHVE?~D;bBzu&j3*&ixmL}e zhc)U0c^e0vHr@oSA|9_BE>^kt4B1%NhaQ#!Rb_6~`m|vsEq)u`*Pk&oWh1&0kZgc` z!k8kB&UFh$sA5Z@7wx5rwu|z#Le5^9#}*3v44V9LCrr6xWa7#P*Ly)Mq11 zH1+W`>p-Ona_JacNq7H4Kx34lXO zZC4EyWH8xvtgm)7FD>3?hNH<_Uz1LT!|SVZp5~*Lp2M^8lk~>jbo4{`^XU;`$)IT% zHB7>D3GT9ilz*bge}(`ujf%Ute&N|X^P-VHXUS=Q^9|UeDEQ6^ytF1N1kHZwhwmGr zovr%GYGtSwsuIO$Og!=m(!|+uI`>AHtfBT%0ErK(`a~V`LFPH*L(O8qY1sKk$@o6n z9Cs|m0A5xIfI1B-fpUse`2O_qNB!Qt3o zCfo-2@lOlcl5>eQa3YSW6qVgR3m2buhyx%|E3rV$oz+t`9l*9lwokB#lQJo)=w3nO z@oq!+w6d7oHF+4ddMPzIC?c(95ANf`iNw2IVPH}F$_$h@R&qMq>uh3u9|n&9kFg3! zZ1?K@@;oD?hEmI$Fre67=~RB4Y!x$e_`iG25Z*yy(+o72e8cCQoFv?PUpD>;jDH;% zdb9Somq9VpcmNXsnl<*|_r;DS?>P0Z-Q~lJg-f&+n#g7QvOSKTox8afYE;?Z7r~I| zqfXDK`*v`!@r6)4#n>_g#RfQqxIf-`)`(LZyiAxozGayRJ*~5`#xOub8s;SzzygdC z6E~}DX!9{evR@C0$r#I1jME=8%jFW35x5iV5B|T($~;0z?_Fs&P_X&VPN;~%VZH|& z3Y18$z#UQ_UR%x;>QH7w9p-IW5FEY5;(}bB!*@^n;QPYAkzWl|#+#lCq2lpoe4rqG z>De><7rBZrIfh6CBbdadzL#T+dnft}1{72revE2sm>M@aX?{e~{kHxkL1&g9Aibr3 zj)jd`XPq@;3};Ga_l$$q-?#V}wW;vbIAETCE{5CU@^rlxN1yuNPBUcd0(0+UNH(*%WWKcZ_%k?(!teJGkz`(cmaR#<9?6O_k6o;wm*aK$aNiqbf`%#Q*NbOfA|fS{sV@p(`maNj-?mNLvur2mE1g;U4Y-x?V9VCI*{doov;nTj*F%f-9(rD!Cetjo2B5N5$t})!@ z`!PU-zN}0%p+5Id+F=O@l`aUEp^ojfV0M4v&Dzkhz)T3QVbmDVgDpaa=FaX~6*ynF-eoyY zFp&Fo0JL7WgurJ^^nyNksT(HYGIPKBni0xK6F+xr%v_{KWMwqpfXqF9YM70hSc!)? zfBiA0Q>z^|CT%S;uPmQ$^N?!YXWdA$Um!V2T6w`(&(=9$!NVnVTY_>B8J;mJuF0W* zpmaXNST5V7cTdOVm=Vtk{`tEErp_xu4tH96JH$%M%r5ekKim(*KXK6S`6Co&a`qM~ z#U%0nFQgv<&n77HAlJbxdc;FQ{E^fTvZf-zm zJuKQ)RaoCf#FZ_m?b5B#_E-JhaFN@D`O)2M&QEMljoFM^BOkjN4F?;-o@Lmx)OnF^Ex%vuDJ-kKF+B1 zCZIYy5OSG*vf2zn#-8#Fqg%dmtso6+4@g`aus*a?&L6IAFU-}OX&4%WwT5~Gz;4jY z3$h=uCVbji{|XF5i0u6vnr$f|&E7GTz`om1@!+&?!Xh#7P~-rmS+|33^7}jlpX&>& z&nVuTYyP*DVB3gHE3!5-6owlyb`WLpZ=yB%%et^>GL;HPHgYnf8i}+S)MZHxYl3bc zxsjkMdRLkI#+=ci<2jpCVN)tthR7k* zYoH3zx5aq6l6DY;3}JNXyO$j`zhl>nD*lO>zGV?nDN_8(?q!bc9N1y~e78TUhba*!?L*l7cL0%zSa4fE&rzA?z?JXmGJkX!m z#5`m75iZtkjk=f+o~-XSOjLU$TdNA*Zek~=_ry?y|HF|!pF=L`Q?1&9>Sk#tF>s?`|YOs__+uL@An zJoww&l81#&Xr+|@RR!i~c;oE8U8-)$1K#IGuTq2X-gjk5YtVkhoQiQU9ZkIK3(QAv zx>&d5wodV904~~t*O>MSa+)d$Msxd(?$Mu?kn`eccNjhg$$`5cSuw&@enW4T6Gp*2 z|AWYh8{RiG*L!1Q-7t2|x{=oERiC7Uw0*#v@SQ@^bd40+u0!NLAHRP9BezyL45x!y zwvwx}_V|;8fB09ys&^$*FFflS)Q(aLMKM7Pd~}n`m1;&26Wph+uJb)}_;{Bg`v7-- zXfs2K^-T$J!TY9_;d0Cl)aLAgwzoF=PzC0FJ#cMlNpLyQEgpji#Gd9gmYYcg5SXvv zVhiBTr797iI^_EdSea%E8(|=MU&HR&Xti3|>W^*HJR-}En*gTZ~yggdM_0aZ`7$MbSvA<3hYX=KlNi?u&Q8kx5N98RySO$)*+at}_K@ zNF%XoeelBU+(P03v#O@Q@0d*W#h%tV5Pi^MaTb@YpXgqe(y~_iWY6yPezPZiXVIj0 z+Qv~LPiPmWDr@N7YtqKk5mIGg9QXs9rb|G~JS9-4$Pn7@7xThN?2p9NQ+M&^F20E> zmVJbIlc)%4G`4^fNdIR5TXVrL_+otWxK!OpsCq()ow?TWY9#d-J>}g)&P(#H?WZ}( z25;Ll8kl68%o_xueL+}<0W|&bYSeH`-z#O=4Vxt9Svr@9b_2fMP~ELIp|Mb+iqX-W zY~0?4h}{%JK*W27^mr%Zd`lJJZy`^ z_d{9MJM?akyp1lh)Y2sJ!pa=Xx&pPoiM6cO{?wzJGH;n8Q_=PwZ|$iy1#TdmjjisR zPNED(BaiQ+uVJN*%JmOM$gKK-(y*Wr9X}pkAH+egsaT zhzPtx%aq-b$39tTUBMWiT`{*14vcw#1p-a*T7C4xz8tz^DT zQOnnU>RM9tuJxtJAnaTxXE37llnEq) zp56!TQ?$D!=9Tu-|h8&-NTQTa&Aw2yxheFE1y zUDuWZ@b7m=9tlp5xa?Dt=?ZjU_WBkPmv$&VtDAVd9tbB*mmmZ)rN!EL&-#-4RdK8< z`$O^aVN3I;9f+tUY;l+nqrEgD*|H;)p?SyM&wckuaDg-W@R|}4tzeaR9fE<7f!Y={ zt9VSa!h^N~Iid7|;0o{k=t{ghzv0$)y6=EiV{Ov+_8}n$DbA;$VH5(^1EUoDfg%`N zlfW0h!kKVa>WC+p+el`-VPifcD7Bc?&gvTT=o<%B-Y68+a>FjyM+yRYqY%lEdF1!m z^B&M9QnXtYH*}Fjg^yWw2k?ezBY#U@duD#||` z1>y1YjtaCW^nIf4yrahqbPbPYb&m{-8JRLWa?dj)+7_;wMJE4?3s0g7Hp*iEB>PiOad-*_+Gqw#I;cu(HS-R)3~X# zdp_1#da-9X2@<|HjioSIo|)VH*cS+8kou$^n8=z%#*;BlKf=N(EAYc#`|Y@1eRRIb z@OGKoWFgyXceJrI<|(;MOEX>r zRH>zOy;98{5KgHCqQ5X%3PtDz zKG_3&{uxrrVp_%psKz>sWirB!VWV@0(!%-_ruI}jVtm(towJU4T63WT7W%k)q<~u_ zBP5J`&X8(+9Vz!k5x~CnRgtfYraEBujK^;2|3G_BPn+Ulw(SB0GeL{0)OSDjd={No zVgCUFRAIMu>{(pip_H zF841c1BjtpS@b5Q^>{?qaO1vB-~KpXK~cHFpar*01h>JPyRM^RZCIsVozS_KefWzDq36Zr*!Tzp&nQo2i+avyOVL ziE^-dx{=u3k}?*cl$5XWEpW4qkxDzt>{ru`;~j5$MV^_;>$z$*(X+3!Grnuu8GY6l z31d_$Z~|sQM9v}8?P^QeY@n9_8H@sEU)Hw_4=WOx1d zQ3!>^97o}Ebf<0x)&VU@C3xLUV(y=uivii>vk7dl@(*?lT_lTrjo=zOQn$*b^25L*yQ$+3ihy2V^m3sm zslgOi@iVaqFGuDc!co)T#WN;5{fIyllkn7OS7>n`!?9iAUWIMLJ-sPfdF3Xt11~h$ zKPvX~$`4H3y^LJ6g_$7SsKqCg=pPs_|F7<|T?tw)Dpvr<)4?94xUfi40(TwGtMWS# zMlggFS>YWty)%u+#@_rvHjr*KjH6mF8#XK}Q!RpOso*tqSUahlIhj)>Af2q#=G?~= z-2ghail=ab^0~1&9N_EW;y<(m78;IfldY%0lFQ9GEt zaQ~>hr$7k3J0NI?I5b7`0D6GU86&)bAx5sjv2}0bNw7`dR-FnKGJY=61JmZX+w%Qi zkqZKG5rRa4Yvq*Ms-!v)o^Cxw6p#1{&m#|Xi7nnI;brr+It{j5To6k~4*q2YbsE3| z$sRXMP*u`Fu+Vlmsy<9fBZt+U@aekLHCa;*(8zc=9hm8>EX2XK^ubk=OjsAi*6S2t zZ^9AzK;{XKIn;m-+>FstSXaaFsvCd%;H;x^3cqpEe#mb_z;cBO*OK)y_P2@)&BlyY z9xQnSd!D4rG0}bT+V{$g;DM?`T9It{SO{$19d@TeK*J&j&qlP0SRxCwQu>DtW{td2USe1h7ukS)f~<#@ojM+NHYAEr@JbA-M$cDcLQsZfGhjGdn@cUkvL%JS(+>i3=+2><6K zz!a!?O%1m)R?beGLX$ejL)xhLkbj)Ab2AJN+}$8ZqX(GgtvVLy;rfe1k*+zPKC#Iw z0Nx4(qM6Q}lKZF|D2XSl=eytXy#H6LaS8mpbma^2>IvlRqE$BPNV_pEeCFK@gu9JW zjY`BAh|b&^?=(^if*9`ztz7e_q0l-}vQJ_;ltL{jH5#9@FpSvAd{} z73C&S$l?JIRv!MrtV*jcLf-CnFt1G>&8?Og03TPDY^}LsqBY7|_-{W6!-dUzWhBO?>S*XTjAEFP!FaJkWipMSS25sS+c6-qK^dRZ6)AYfaW=>kVDEgB7teXb=?pKO^fo{YL z#I8DGF*P1_#{@7Ss6o%aJbOjf@D<>o$&l&c4Jf{L26e3si>{q|1MzY(c5>^x{#UCn zg36$S7AOj4wdNzR1iOUWHpR0d(?j!3c3{}lx1}jl|<}|1=1N%>6UG7B0>;!wf{I@zlE$AF%UJs zjo_6G^j_rDP%DTR;bqJPwL1Ez*uR`P=5Hu;zK}0BS}`yhQSAQ;6J@DNKr(8ng#HW~ zc&pvMR<%km2Sk>BI7B^MOseHrd$H?w490sRMrTO_tbH$MP#6)DN_H{t_!fwS(#Y9b zgE|mhc^8#eOVF;uI8i$PvbJ6rV)B6L=s$tZFJYV)qJ~${OlOnsZTE#@;NoDS38Cbb zc+s5(q@tctZgITF%6Vl>q&F^-#@+qBj9(c>`j0{EO?|Ha@$6vU`e}1^o((MJW|I97 zpdRzs0xZOb!kUmL)E8NGcJJeNEZRKMYatio%S8G7E2|7A5+(^RqE#c3m$j~+jK&$L zN~u8~PvLZOIjmNMrA{!#5(d6dv%-d`9krr##?q3BC?1HL#&l9Wi|IR4XJe_*R#kB|M_rO>AEF4|l=r&|z?IUmnkRj*P9<*SM#xq0g3nmskFjAFENn)BZ zxXWf0k{An_J=m|xbtXO{zAqnTsf8fkmN0D9TI}V=J%lBYVV;*6Y{_bIf@v)tvIHCN0So3;?|W&bxLKTtFIW z4PYeB09MXjnUt6J<9LUN?y1N=opvrCy zka>t0s}`oa<&QZPj|2G2txjb`P5+2|kb)d+L3LaF{>8-uL(uTRJ2k9Idny4Jg$X*Z zvV!Ismaz>kz!(j!k(=-Qu;!{j{t}#MmgBZCU_gEVD*LWqk&;QQ8ie7IMEqgKB3+$v z!h1!0Z?V%Jbf)RiP1_Q?S$8I@?5}VTcSl=_mmRdF{s~|3n|>5qDN>3!UYAQ~O8-%4 z2JKOq8=uC_>>I+xp0Sd{cDabDkag+q%bh){?#%+3(Xrvm;ZdJR;~8@}WxWqC5b&JZ z7b&uUa*EiU#R~$&5*F^SBdi?wKIJwtZx-_ez`p!xy;(~^oJ>d0P~k<5JEx2BcVwHs zT>7TK6(ZM82wZ78!Gv;1DM3LKgRpzG>zK=MNTJ6@n(B(k+_mC31A~egyyK$X=qB!Qhpy7jQ@c3%&|Mc=_t zfHC?Pv-|8MT~aZE7QxCZ86{n*B-Hy)0oTy&F1m}dg(F|4=5S&*bLAU8nFX3i-O3=M z)e=;4kB^jaLuC+Otcf{5W~_7}IVTOsXY($e;yqwKhZdmCIu+--M%u*}H^A<+2X1)E zGxKyo3x%(HatTxtDR*n;w8AA+iIr(Dlo17DGp~oFGXl!ms$!6e06{ds+z9D~g^iu= z>*{@^w6S(%M%;P;MSaK|8Y22U%9V)&&7tNv<50>nbQG@;%p+_eb5R90tPanl3N`}u zbw|B6g!ni`@9p`guZMyi4<|N>4>NO$a(ayE`9v7AHCESm0}~DkUHjx)`)ASG5=WXz z2}S-h4b-%;kcmrFK#G{>_M~-S$){%7+_sh-KI_1-O`DxcWGuE2Q&2XBO^^w79l$aG z?f&!LL`BKIw$!TX-zNEVbMbEr82Dx-++^@nERp-_nx^U>Hk*a&s#R?imF}2c$mPzj zkM;EYsULDs1s?^G#ELYwXFZucu3~ulN~q2knJ6#kWw?r=!FOg=C|m7IP%HlmC}S!y z3&_?|E^c&_6%fyf`90WWoAcx9vOkpc_P}Tpu)Qk|jg?tD=YMV^@KwO{-R^n22UmNp zh!eC8eD(@hXm6v&Me<Ma6g%;sn5M@ueWOD&`<3y2_B4B+2jEGAK;! zbW#**xw6Z7SRzP6K750St6_LM3jnbUK_IN9XiqUbP9cz?=0@U=XU#Bvlm>rDdS%As z?~8%W!gr{49K?^P=)AdAdJyb4{lS#tq86x=V}ksOjnJ~2I_Dkh+zuozG{m)YG>f&y z=DBj85`LKe&3dvamj4WMSG$3DOUYP=590H}VFoU+7ka2)Fezs6o8;W#5krO<3-l47 z53N+s644M&AmzTisaiwqCs~N4yi0aYv0+zBWT#j1;<5P-5ngDsQOQ(3Dv)ABTF_IR z{3^Mz@K{dSMd>3V!6jWHo5;rviSn!?v-NuI7bv$TQp1CQwuzXKhpPVp`4PA;qI#+I zi??tmZ3ob345YI?YAZwaSNoI(2l5B&D4+@iKWN}dX^{4b8Y{7Y+pNycrK12SU<0>l zvL)h&EdP&=S*gquq&M+fpcFx|-#5ns<*q_LQ@_3JuoyyjlMg5^T@#Tj`v!|5S5kkm z9XH$^_g;GcBcROlJmSdjxXLt8FC&g(_KZmjICQeTGf-9*CiHOjXFVs} z)TwOkt5cN=*9xDSHgAncIV)&k5crnaPL)T^kl`G2^A}8R8Q0_ImC`;*^J!|F9->eh zy>rfimRLxfR{x8XM>%>lPXOt%oHz3aO<(dKqqaJ-dxaFH$I<5o#QPf`RlqiYN{1_y zcG@KWR`+^)+LOZ^<|iR*6aBua#-4ZI`_r=6>F}wQfN59v#eUutIg%FL5%qyftG92z zRt8{IX6`SJ1*QFD zLh`szX3>CrNV3lz^wkaeb*S?D7Me(Q)P-*m%yQQGxR9jL*6|2pz^)O-Z z*<~eAx(VAxMN)$9vo@igi{Sl^0kEt*Xu=NfBnKt>?6p5!=t9nBHl1HE$e^Q9RfP>Y zg-o!u^5+6_BUC1txTAWNK?rkV&g-rfKhfequjUkS&Yd#srOBoHw z3TOAIPhhjCF_y_(8v(=tfEvDo`ckb?}iU2t-G^h#ZAurvafN)i}9~tZqKG_ zlY_ZK)dXkskSgfz0Tzg06g41u!3ZUP^hdO*d{I-9IotlP6Xd0O=J-M#~THLu3(+{$8SGKY@MLsyL#8lr# z0c{n$f%M#jJmJ5f{~gc2#{C5xHR8^^#OT%@nOj=!6<8wtRGd(rCmI5r zZI1Ws;PxLR9{Kr0xnQ&v_zE+ho2EmY%4Y>xe_yk z=W7+1_=j-T1~&n@KfhyZGC#^A0)yvxFSSq^6H%B5>Y|ykM?Q7c)CP^r8D zUCUdWzoU~Kj?y#Y{cw%}p(!BJPxa7-=k?0{=#ytNfA@{Kbbda~=qQEqn8Zd!d-l~c z)hoIl(N8;)t!42cVMfrY_vD)EL@Lb6VF;L{Y?m_;r$sjho>LG?(jExj$D9H@s1rKJ za#xaW&>1hFALhcX$)si!{gXm!j>=1|_^z^r^ZC25s;RxSBi~LfhUYt7T0jyCfDaY; zbD(~jk$i)8K-gqv^AC&}>HuyxSqtv5LsiUijyMvvPL2eh9PJrEXgO}+vUW$JossAw z><=(zIFIbBZDQ&wiw4TT*a4ri1#~k@)O1BuErx$7OdhE+?|(+G6r1=7A(EpcrAMi( z$*-oyj1Ye4qX5JVrNwxQ?E#Hj9w0Mz?e=yN zjz4O_zCK6&=f{a)ics2TlDqM@fe2bQOg!#MZwCwAj?1nFgPIVAG!?-u&fbxO-mDZy z{?Cf7ATc!>3P@%3HN+uQ+m)pSVR_h0INd>M&mf2{hKY9QnK%V#>h=SmGcV=AQaq2_O;ZfmQjoJ!82*LO<6UjBb2?0~w&J_ABCCb39k+~M z`_z9L0{CQiy|A{Tdt(3WxBX{pFM~iJd>z}@-1>j_NUJkt5(;>X%#&LE{{U8*f_GEI zIf~k4b|hc)R!Ync{C=8rrG>3^FZAxhzBJA>PQWjLm?7*~9dz?f;#en4x<@0XBWS1O zwrf-IigC8&v6vtiYyZIcslCmM_8J06V_NG>$+L|x`B0e0wHV@ce=Ota4{8nnP9ukG znK25a$*cZKz|e|}yTP`|1H&vuq%D=d+aUH!EhR-n#eN{bBn=~Wpo6PEnk=}UG6TUxx}MHBjZcr)Bv~`cO#q4MA7ppx$4Sgs%B64jRht4mw6Muu+kD4vSYVcxTCm0Y+^*9os zG%i3VV&Tp(^~k~M4^PCgNC+)HgtYDeTDyKg8c4_o=}>;yad1WL=yXVp`P1t|z=hz+bsCT2iu z0e|K=1gn0F<*b3oOXdN0rATX_$#U1`8f6zkY-JYNN!n@1ue>RB-DLSyhsWl&kRM?R zrUNc{;y|5n+2b12vgdoIgUUw)boHR>gV(atG23}0cESIRcwQy7I2`>cUSUF}i;4>q zcM9}pIaca#bP5(F)22X5{cLENBGvNd`7fOy@bI%^0Fj{ZTD3yl6IBfNl1L);A5VFr zz!PX>q@iWYvnoLR%8AckQ7#BKtrO*s&q99OIh7#bJ-1rlmKJhF$bkAk;31k-fhm~1 z3P4S)ZJI6u@o<@Qlr7ux=C&h9*FYNkXcu`?XXMYhLZSog-k53)fmos4|aoiztDdirK!+##_uVAVN8c@M7scXZ2`bhW+0(wHR&AeVG(@NOTp+j(x zVsjT+8N*I|3lRRE;q@HI#J6W=MR|s?)^S_8CL15cTv#P-sA@G}Y?O6Y-@^8<0W&5~ z1sLMzo%XrOU$aS#)Pe;QWr3z`{$4iMY%uU1>sFs3LgyW3i_Xw2!E+PifvL!)*CvDM zaKE>VL#||iA93XSC<=zI%D^+Zd}KwYE!U4;Dwk}XP{^ABeH$(`td~o@B+eW2I_v&o z!VN~`I30{8DRAWGwc!qU+>e@~?*!a}!l<;4cnKZz2v0}b6eobvY0`fxw|PgHm{3DT z_HgS#quI1Jc67fVw7I*mY4B6|pxOptzSv)2<&Kl0K1yPBT?*wRliCw2jhVkhUvMC2j9}^I?-UINpgq9LN76r1WTHuFvNUVS* z%7Dt929hh%XB;uV*N|V^=|;iBmkpFUaRnx}!t3kO%}EcDl<-}#RL+FPa@UuAa)$7S z9J>IL^q*?Gm=Dt2iPNz$0b7XU^7w=t0*Pg!t)XJH4cl4b1>_eC@Z+h3EhhM1=tPzr z$jEVCFzdL1NmrIt{t~_3FPIJ|C;SZ}8lj0Dx z;jhq`IasLMajxpsJdDSUK)rTQc^G2hvU*WNiguCt(H1SlQDwNU+rAj9UC$dm>B;A6 zQ&)gZ`((SHuipqv?O=olG}z0r=!c$|y&YYKbw6YTQvsVNUjE-^jINTPDJG2e1V4V; zxeNoH+Ph-lvtVfzBo|3ePxIcp?jQcC8)d^Ue-6s>@=oF8baqVwmp3Zn_e>n@K}nRy zLENrip6jczbuDK#0FOr=|?o?$%LRk|ds= zrPfu)u5A*r5lj@m0jAjcDk(1uQufQ>8?|{JJf-%%Nq^b}b2rNjWy$lY^B*e(-4fw3yST4~;VcH-XUpAl-U9|iB0iMg z&vx*Sf&u(G8i>fuD@|qh^2>Z0_gveCxMfQyrI+t_vqdnn!zOX$hc8&I`_vP}mMdtp4nw2H7rR=R=^-rx9}a>H|7OfLtKTg z9nZybU=0MxMHbG7Chb)qsNOR*8xmi>w`e5y9$JD=b6U|ebrc(j6uH1s42c^i&E-?O zb{NgdMe8ufO!1fi!=t_0q^Is98X0b1Pzc48cY#n~9rRBg>rq@-*3f(wr+fu6cP-RHBz#GJY?skJa zbk@mA8+O2cTv$A6tyMkUxpkdrq4nUsPC_o6kMKx;Rl;jiGou36ib-NYg)o$wW*Va6 zY#%hg}BUd@@-ThA}vMZ>ZgDwuxOl{;5;xOe0{I8h&X z0PlN-eNfuIsg<@K5ZT#0VNwEaO4VjuIYg7>H1!yaYc-QW`fuL4EJXpY!*PS-=I2&y zvVU&z*;owD*zo+*Sz)>l!m!n;i0Tb55C5Tsig&=_n>y2qC|sS|^4-;6VkfC$@H580FYhayk(UN9%%(6-~vn3QY;m3txbI_t;#@ z&(#u-rcAv47onn!!Gt>FDx`oWFl}@`hOSm-`Rq%JelH;i>|BsC)%wzPHB>yEK<}T* z3Jbj$@NC$p=$T0;T}AN(HVq^>jv=Fhgpss_Qd{uXee26ZZphxy$d?Zh~9~{ypj9-;gP! z5&ZhiTQjSUHMUTl9XSd*K?`egaS2aXj{$)NOijm@?J*8>Dm)%pR!(_UGFI5UU-tX{ z7U8g7cbpv>ax?BUUcx?Or4A&3S{$ES$tNoA#Yq~0Pa;lu7Nz)06-apa8nvDJ6J1qh z{T6a3L4p3;DV76=D5>%BOK*-2H}J&t!~=oaDyM8ylUi&J=1rW z2omTm(U;YpbqY;!5vHA&{Xo77-JaN$J<`7%*GwbfA@H5)&Jw8^KnT6I*#qrr#oJxg z5vRrxor58BeV)PCpTF&=Bxq;G(phqp31;h_Cbp`?m}YB+%__esBEK;)){~gYtV|>V zP7V$4SaCB69uyQ;Tq-T_5bxtOu9+hZ(yNv)%;fKUYdaKskx!*!r!N@%z_yo6I|d%S zC0eO2mp}BsAVHVNu#ys^^DvIlP$ewM@Lq724uOo z;K_du1xLql7^)VdKC0F!5fDm+YA(K@8Ru_@%PUwi4FrEm4KjH$5M8zGTGPrcNe?e_ zxdhjl5)2K*sE^KWw8BeP*uS(i*LE=#OSq6^0rF9NP%sLACyLEjgsMe<>o#iB)DKBT z-|!qE^)+G%MK&Wr1?z)DNitsLpx)t;n zS1NngYfLHc>kxM>&>({^nK^hmnH)?K286nUi?JWMNgG@VBiFULU+04FDbjORb8x1i z{yX!z|KaJJl@Ij;z^Wnu$aA22$>j&oXg|2;P6aWHzwH(;krLT48woL=fa^eR&N!F8 z>RcY$w65WlF13Zx(6x>ylkS^`?AV&UQBYhSYY3Rm1RU(3aHG4)LJjE3Z@zyb_N9b4 zEZJp}+_ALQe+9~H^KB$$t!!mgPa6*FpAlb^7#WdEk=ugG6Oec6D$l$?z{cQ!a3Ll} zwicf2EcsYe=g}T%S8^4RkyXH7Il-3)jIYKs3a7{`1dS`7ED8+H=&E&U^SRS8E}vWm zu{tNhqZyIONikZ=rn)?BID_)iu1WbWW-;MsfXj8N{T@djD0>+_!uM>)uUG&79K)(BZF#?BAB%!7RWX+oE&wN*-wV8 zV@bq<<$^ZZfeg`Z5Aa{fr`8)iK)3r;#LslWY@!|5O1p4GH?xB1*+N_2u^w#5UhisvDvEOW zb6b|#_)@1jbM*ftw}4V~@D2)z-(r1S_=C@a0UizOpc7(zVxB{UtpA~1e^%2Vxa_zr z6R|c8A-#&1m(g2Rg={KeMyio2&_O&n&2?qd5dxVrSyBwbNpTCDW{S;EQ>*;C4#-Zj z3#ZX>%I=RckgdJo0nkW}nysgWXjTJJniBIwgW`>3&<1olrE-5st%tw68*i*Ud>^$2 zn(2HXGBHKAXah33bA;jC+XS1pX{1^)AK9h)7+_Z3S zj}v~nVe}7d@I z5I6k65t68h{*bb}#|bB@RSjw$0`ML7;}crOrJiP)2bzzi=OaJ#`X2XUNPjHP(T zNe~(cStJ(Y$L*oed-0&fsFrn4bc8zO$LY$l>px|@M2+w9w(sM%d|er9#ao%w(lY{8L8SVERk9h*|Sq zist}V1pkgRNN>1*^3qiz+V|)uq6Knff(d+XlCOv(g-H0?9^M_b_a~ z68e$s%(D~cph+{p;<;3Wr6Ijj44*9Tub9?sFtc^2u@%q|xACbzxI3mluo5 za-61EcN0>vo^YjSYUZEzHAYIK5i?^~HRH9@uEN&4d)Ck(LOO0het2g+r& z+E62gm5JN0TbwIlc;NdP}Uz`tFULHQ^S zweoRiJT}aPlg?anGbI+U|2|(bZ&`(f$A#Mw;6J@X`gym3vZKDIZgA#fs1fW*N|X25 zii)}cON#d&ReDh%mYAeXb@nF({M^D3)iM>Iw=pfJc(<-)@}7*vM*v5Y5xmgU?$w^; zkT`{PGs`Mtv?^{sDVjIhbTC2&IGkvQyAO8K?H&y0l>!uyLM1(xDU1xr(|d@jm~9oB za>MH!6EKp&wzkRkx<%~lMC!BPjS_4&|4pTTy?0HaHKuZQi`|P=r>9zhpJBHo0TmU@ z%=w%LrEk3qjKdOEAKoy{(&!a8@3K#ah~xJl0_dmunVZh&4P@r-@o+?+-X|+`pd1nf zw(sh;HCCGPjCoj#-PNTvxQR6-sVfximzR7`@!Z1r;_9wMMA9!qgM*lyE*$Uo1>BUJ zGnq8Mtl5H?frWIqAIyU30(Q}r1tvTCMwS8Ugwg%QBBN>FJra+@YO8Gun(8*w0}>NK z-kt`jslUBeDs`3CTpEZ^s5~Um4{?{Haf}O%0!6&fH1oIYczd0$*IdFdQi_=Rt6_?b zkM0BLxW;aNhe&OtW)mDM<0r;zhBr(6=`X>SR_eOs0JG`_`cEIB+%GR7$%^>Hf8mvU zyM1t|RdUaScnC)RR@eAJ=&LBrEt%DIJsP`9Hn{(J7PWPFKt{BB(u!x=1Xdg_`Plbg zEUzvZ@N=YfFf7yzDAVxC#zm=Zh-5Ynf`!mA^Z_M6IuM-qyU;6IbTG9M7XCLLxw+%q zelv-6kgvIo#qW}_+90Y|K(XLnF~D(Latf!@t~HUGsi39W?s`Obh9L+YkT?qsLm zHHF}+;Qg#@FSoZ{5YB^esMk1Vu4lW-xO${L-CGFr750;A@^(VHps%M}&=G8&Gd=TK zr$IZw5x>1x`IEF7y~< z3&MjMvK|^Qe284-SgBtqn@u6&#aB!9b-et_QuqJ7^6%$igZY7%v}`a z5(lS(jtsPLXTh>jwr>~kNgbVfe8~OtL3SY_s@|4le|(LQpq_wyI2~>xL3;|aElV@+ zI9rEVZ$Q`%)_(bm)wn^!SwnWa$4Dh}H1sNX#NQtB;5g{df|G<>T_tISHrb}bUW-wx zkE7HoSHH#00i)tt&sGCBv&vD z4)1MjsAV4S?@GbFNT}#n) zhuLnV9f=Zus7QW{Su>0JyR6D8KeZ@IBP($MZ&Tu~J@^Ou&b)w%Bmj|B*EJe0<7MC? z>O(lw(tSPHq~a?9HyKoHA&;7lLEE`#0PpAFY#oH^jkrgy<2-R_(P-L=y8NzPYHg** z7Y8AFPtoApq>3KKQI2zw1DQI^49BPKey5_a%hy)6FK2bkOZuAN$T72MG`(~mjV0@6 zBe>2|Z06&%UNdd~L4jfpPA`CwVMdRwAjW@Yv588mH(Aw&MS5A=bHEnh#G?u|u$OchP^J4h&k}#xL z_6%*sl7)M>_KZST5CP85!(kP9GaPO~?`?iZd!$r8)awBXqiaDkN%9)%#FkFL_NQb$ zGIAth6Xmo<32yapxfG9uYS&6Xi&0wdX_`RCX;2JsbUOW-aXVAS>?w3sl`OI^ucMff zLOTgDqd@n;pF<>bt((Q)w&Yv7JPw170jgI&K|Bz_hfWxdk<;n@8)MQ;Ax=IG%Wl*KSCw!DeMV$HPGeGnB~Jn8<~i%*b_9ASh@yjSot-XnHjdM;~bo^LX2A z{fR=9$-`^zEZjz@sA>$MUW#jcxJd#kpmuX4oal34KzO}SkoSag$`gMPrG$Bu`c64Ebi})COCcIeQ{>!}XF3R+ zFml#)S|e6eZFGX5Ic;F%lS-nJD>?Kr%pLO7d-MF<{hZ=Zu^#U6#3cE%4@oR?Z}l!T zls4pyVMi)r?V5o1t0sy7^=`@%kd4!=!L=f+53eZ@tLAEcoyR4~)LxMbrn0iV%HL+dh&+PUjoa$orXQ}fbdN=xgqRlXYjw%Q zjO)XO+!42N*@72(Nw{71znf&h1A*Y})frCLie5rvliO2+j()M3n4aBMj`x^1KcIKL zWr5i-UjH%S%VQ>z=Au}K07?ZG#9KM$Cz5^9_!Z3kU>U!31@ny}DMbMpYm=>MBIA)* zQLz0q#nqM&1bq&xQE~ATo-DlkHezUcE;{yXT;(0M8xCikYyc@n?W*Wqa{xC1w}XU- z80mfyS|+u!MIqfc@gpyfW|#v{oY(MQHQ&y>EZuaZ1ECJBRX35rzbEo7n8TIxqXB|D zXQmT?l`8s2dVQeWtp<{P*xRrQ3TlH@7!0fTH!|7N&#y)y0mRiYhv~9ipxI-&3Flgp2yW-AX3!lb%kd$ zILk$tv^XB9epuj&XXKAf!@iDs;eSPC_P=0e6-p--sDZ7lXqanrqpG66hHvWiuJcB? z>Y!Uas3;-6Ua)C+kcHr78^tI_Tu0a((f+_z30UoRD#l=$@Ah5}mM+l39B$u?3Mpbm z)AOVK=EjwSfTTPsqphety=T$#L~ap4e-H{yH# zrfIG?VR*yexq_pWMQl6V{>BM#*EOFGLQXVgx8A42LAi|iYJ|hfC)6v@ zI(r>^TKlIc6lO9SIh3yk#oI2M%Db5+$lNg)u%SyB2s{KOVQ#Zd*m-=bnsI?Zll?TX zy_%y%yb6j54!K5C;>7`@8}4ch8J&ess|`9|qI7@S*oFw-OR!>UsT1+|WP$F+|4RM| z!NDB}hRTdRGXVO5xKV)+sSq05DEGpoK6^wIujf^NGKZFCv}>!(jc5hi#nmbV;Jld^ zAGAEPo_b|95iEiRbeaDp^MKK*2F(DxfFM3uawx2ONv2_#E@)l(G+`WeB6%WcUfYEH z!OiLaWc}a}s&jpvc$&D$94_y4`haNQF&Z>B9(+KffN1C%4IZ*^kGvUdX*k$R`tJ^WO?bZKOphlj#2VY zrefEK%9IQ{Y?rPZ$|GM#wB1Uz1ZeQ;<_rgYa{&QGxzGP#@R+!-8EbWtTI!9h$y|Jq zMCqb3V-Zph`hG+oD+}Ji7eQEGk^&Z(14Q*(W*6X%M26h+Do8M)F*m)nEN?z=^1@^i z-$#$-9c9}$Z?Hi3Ik54^x&x_rWcCr9!`!}LMylzqu7xk3eIa_7+!BD=I{n6z<9xVG zQmzMic>WHPi%z&F9HtS#3uQ5SZZCl9Zv+xtDY*&1KInoJ! z+D?R4_qp`k+pfUTjf+Pwhx?G+DOolG?Q@rnhv%b5!`>sKq$yDi4GApy#Mf7fTo<(z zU=7Kjwz;#{;31Dqd&4Mt??>pfZOe7JY_UdDvop&+CEx@jyqTfSgcaxOWUTKS#2|u?s8}3SP{w1+!>u63&Q}zy{K)^Jfp#i~c^($+c~*HJk>el;FCH$I%o#JX z7%sc})zBzlS9c*!?DIGQ>9|5SAl#JheQ;@Tgy%`=&%WJk82KRCNH_$$T0cjYA;nQp z%@PJ^aqbzVp&H>(-sa83NEpDCqA$~f%b*2ThKx(+lS9s{?GbyqaIt|RQ;OY4EcVJA zP7{*c;eC};Bc-uQwhVAi`P$8259Y{0wVDgui*9X8F>GGw2OW0;@`@bCmS;-+&?R~` zDrq8td%-ntdUUZue#}(b6C-^mG^dipet4ldxm-feESepUNyU>$E+s1U!P}<^*Pk3sm}LQfH5=u zrCunIoEsamZSh;ngdqC`XBhwvze&rvmyY>iQ*weM#a|_KsFS=>++)F32<3rClq-#U z>thaPFX!;ikOK{;xR2nog)GJY5*2=f%9ni`3wr&Uuv9|8n6^-=gM|R37I_3^!_s+B zSW*0Iu&ze`Yer2gahSmD8G>54nwK{*SD;|j&cO!XoI0Ta3eHzbvtXk>k`YLasYpNV zteSTTjt@&Oc%h;254p$T0AN`)+tm}C`4yHi#MD6QA5xif+iBRqJ-W9`U?G=spBf$-b*n^B#njlBmsp z80*_ONqhpmN?$lopU2!4rU$HT^lE2k`YVY@p^ZZ}_@+e)J+2v!o7Y{jZbZmMUe2{e zO08b={C~yaxg-N?%r6x2caNyV401UwqHKF{HA6CO!5!64q(R5LAdB*$zuqQLHVJBO zm+>cbXWg8lr$$Y-f-=vb#@Wb($QZe*lv%|z1MpRv+BLSRl~KOfX@Uy*J+3y!cZER>on6SNF!w~09w z9LSPt#qD2pnRR{H>VxwT@TR@gJeqTVZxYbn%ITJ7Q`b>szK#65YaU+ zApig~<3XN=MG-8Y%>9rg-+Y4o8qPGaRNwliYprj(N3xUG>W((pPr8&)g!xyrCSp@D^dO8?q2}UVI3^LG{{Sy2j%8LJY1~~sw=K4z zp^YBLap}A1$-LBm#^`P72g5D7>mUxUrLz}u$tnP$jr&82?L@tZQ{^&N!MLi;B+@D< zM6o)hM&)7>@F2OyH9u7p%;g&AJAzjjA?W^R+`KO#zZGAVXQ>jl?*rll$MS7EknvKZ zu6z2>!7cRMtE@{(kIUo5x0M68YvQi;WZGw_iE4)Kq6SGm)LBpDlK9o$dtcbnQCmYQFoout$dLzAomo zRpEY&bk?=pGpHBgbwM(=%lc{8upc`+O=00^7T`AWvL< zf05mN0ktrrh*UMU>s&+x4=Rdm88NQd;XYsb%b+bZN?o3N|lk0{ku^N~&#*RY~p9Hp@ilnW;nLCx<)NXa?DCusSL1l-(M3rTkQ4 z%*cbuXjqWTkoD zlj`Bam%Ad-IPD+2z#><11|tXG*ydp)H@zMu`w(pI;jB0gstZYhOTYT)=|jK+wCCcz z&6hB%@VdGrXe|s4f&t;H6#ZQHu>A5;_@M0ob$UX^tEa%} z{f^;)9sVz#2)^I!f<@cov8g76E}rWt)e+{vilAP+V z8qKRC#o4%TI7zM|&L71}P3ZKj;Iq&~Z#t{p#KJy=`!-0Dr3Pxv`8mcc5Ec&8XP7or z$%67&pNhyUS`RzH*fMHC5vy9Zk&5o+i^H6&)Uw+yMu^#|3bP(!2PD=8P9v=Xb_YQt zXzRtiI&CJo!%9M`bmO(>tw0CDwD}-r*C)5^|LT?uH`$1h)u4=EmI^u!r(q%Xavpzk z$;X{`u~jK-#D&|pgE5VVdLgO>znl!dNe-J|BFMu=?kAm{*E#mLCJNJK<|D2&B&63QK!9-~cra|h|E^mea& zlz+z1iwOKozKUx68py1{#$VDJkRu@)GV2!=z+QHR?fHM7;Kv@3N((WnSZ)M<(<`F7 zTF*1gWAKQ%XXhSbk{C8$Br-&T<}vlBlasPA-=nEW ze&{V^K!qgs_*TU(;ZVJ%AZWgPycUa^iMS%K@8)e%C+yR{x)MSpUsasKOPmn@C$wZ=KB(f~MTp$> zmTA-e`NS8hhkigTX^g)hetMduj(G0u7vo#+G8r*S;NfhXIIXA+($Q?(b2yu*ihAS5 zj!xsM>Mh59Q_&6{Stu;;!ERvki=BTD5VbMZQ1)h7e>`6^wPxsZ_T;6qINkCa?Sb*5 z5L_e$%ia}-r{tfe#frh-bwQPtB8arH4MvOiJ-c48P?QIi(cfxl!n$wX@~i+=;|LGXysdhS2uGu*ryg8W~fB`psijNKMLEg8l$hrJh!IS=jxo}5X<4e8tA%W9IlO865q@4}jRg&WB$Wxq z<#elI3aqjb#MQNH*cG*8CX>hvN5>Nn@KabYAOtGtc0zt&Xw9}Fvb1# zq|q#ZihlT|(5o|jXNs%@Pn9zw-KVRXl23XWi;@(sen361=*EK z)sAq3sd*9IR0aHBabfsm_eNB@ZI+vq8iP-acWAHIMxGA?*1mo^3SD`N2+%R+29PhS zZ{GitBi4^@UQOEZ;@^DAd|{|8M!9~?Q2y^kO|@$py=)sBi=~w{DMAq#5NRRhG!iO+ zpa|B_F(T`RE#(ZhEaFHQF^<~AT-4Ve-I^lf#}P(&{i?%pCgqHk3Qb7z^<5`w1qFGb zwsIW6QcS8z6H;#U)x#Mo_d|c%v3J%zbpU!(-E*<@o*A?76Q54wKoVG5GH=&?&wCT2z?hsRzS6rc5NM9;Ju zwg}O~Y${^tTYUD`JJV1d1H=mZhKP{SMz`_* zv=WJ^plW@DGgGiF8%}DQ*W-5tWzko+V&Oa}-&m@z1-dA22PQz%wQ+ChbULV1YJZjm zQ;eMro@ovZdy*t%a*JwBSna-t`_G5c`cPMs)-d+S<1E)NdmCQplEeb>I&eP(Wsj1r z+ysOF2f(NZvthnNg>KW;Su>o@gxH{IT>8>fM}ToHnqQqu3P=%5Q-2(SNjWxmTmnd! zTb`~x<4N1|U0^@8T(fr}*bMhu>Wl@bk5=k)+XF!djxS}2Zt;9j`^H^W}X9u8!_XUwI7NmK%1( zM3$_&i}Np-@dK2t?H2ulW0KzVIDdMED~})F7P+7(%HzwIUeM-C9dIR(?#2!juKJOE zN#q^5jp_G>jvMem2UWebeP|7;*g@2Vtmt?IuT{dm9~Xc`2Y+FAAni1A!`W5oeXo5F z(B;=N(eTYNYW#c!Vocvx)-~Oa&=DqGc82XOT%q1b^YSfRRuXzj^WcHFqRmRRvwNl# zhsx`)Ki(uVndxaOVyC5Sit|<|C36Sq=*|BsJGy1r_^du^oLbU)Q+u?S3~SapCn)z1 zB5z{L{Ss^YG7d%M)(cI^ZN2Z?vduj0CTmPjf@o&Y=ej>QVGn3xFI z?Y;zN5}hG!9bV4Bw1gAKO>u?b{ac`ocP&S4*Ol_=3+8yNf=UVLGkkp$`&Pot$85_j z@c9EIWm6z*r80qqHtEeJZ&tLZ+fwT|{W5}uyO}=inzi@$(vY zH~7e+ac3NUUMfoatUI*=x?Dq_oJH1d%!Vn!H_>V>)l0x57p37sgPS4$0N%pLxNIVI zRB;7s{(50xt;xu!0PyQNLzfTl_8|ttk-`}u#`cD!Qti1KG6+_abJTj2_L!1A_M4VX zvQW@9S11pC8$`{(jaZlcu2u(1Q2m;$)97GA;Uxy8eRB_I;hf zp}eKCNx!i;zm@~U_N=gLM%v$x=fUMW96VHt&C7vw6xEeOs6%9)SMurADWE%~DP^M3S#%6?PT8dBms# zeWLGs=Sc#Z8pE`|RYg~oE&upb(>2Hp`4F~I02m!a4jR3o!f0?mifY&TKxm`dYIFK(40ANV>_O!D( z6>tULa$8$tT${k;T+3cLxLBM<+E8dp`G0M%DK6HZ|5Vhc{G$U#Bf=3>m?j3`~cUrlS8`l*qN+0 zBvry6U7#)zDAuSBlj9z9LC2A+RhassgpG~V~iZV=_w`K1{DiIga+-9MnE65i~q zmQ{bSvdA_LE7cwJ=$o4(8Q;6=)4FIY!||#DI=L@9 z>x-(ctYRev1}TB{!h=~Kw1HWk(Mt9Ynqa-?7q%~MpbVV{bk-Ewcpm8k*rkv!J$=@X z2f2#d2!*FMBnkTS%+!o1T}|Kv^-pun3q z0{IdN4PRumLl`M|{I)`Ju1w;_BiC9M5vk(cUS*$G3cYS8a5Sa&!}4U_3PRvd3Alf5x1 z#rZ`s9v%l4M~{tWpY?`L*DU`%A)jB)cvw@4j0G3T!mC{1FJx>wthLGxjV?F zG}t%nIju6%b8J{%AQG&)qu_xKI~=fr-xLyToh7dlwi0?8Nx5B zIi~!)pQ{}bc|1pd#vi?l+d~@vgU^nwhi2K0h>8-)?#bFQ;`b+(q8j+&!St4-!2WZX zRwbRjlp4t)Kt__&BcodNK*AwlcG}CscP@(@4pUHk+mQRSpvrlC5@B-ZOPv4u;5ZU` z(5Qk*cqoF7?XP}M;==ckua zH7g7ryD$vd3m|hI25Vv#1}ee+2fEc#4Uuu75n^zuD>Jh#WfmRCTFdgHO$jw%$)N;n zD!*5PqMsiva;|i`_^E}T`6&)Evt!-SFKVG9(Q}i<6g-Q~3brPqjV_}OC41qx#p|E- zemz^pzS#Ge4D(4Xccof`EHvkh6|04*d}4PHr5r29Ii_>by*7weWW?sQcEo>2<_69+ z8kPF=OEDkyL~&*3YzL;7A3nS({jedsR-_9Phfwf|sDQ*#&eHX;Z!m!Sonh)&(COS1c_IYZ-(I!!BY19rH~gFspM;f z3x&hk@fPTXx`=->`Kx>;q_5|IbrW}kw1g7Rj^LaWgG`~&$^-fXe!32(K3(T2d;WfC7h_{ui~ zih4tFxBBpwWNDVc>g6(; z{Au@}mk*e01AM~D15%^HH?6|xb7(FLwL-V)B@-=xflaWgJ4(st-!E5#?p6 zq5jlv--ym-#|Y`f5uXM9D+GM`Zr=uhlZZsYOfueMl{E1Cn zv8-fj&C4(I?nHCk4;bXoqay@!C#P&L3gC@^>>(Smd>KR~nZv54q6PFFR>9n9W8+8L zu1C5pAeX5qjGNjCnjW>4H??OEy2>zN$Zgxjo}jDynbjGH6|5dUtQqoNQWdf0nP|pknNXdgw;Um4kJZHySazvE-+S^JQL~S|_BI$) z)|X!Q$Tj&Opz_Pl;Smzxk<34aV&6JfX`KFGJn%ipCF)7(=o2N=lmG1V^7=Ot`N(L* zg=5&{Q9|#Azu;YzvMV=+k(dV2wGI?^ucn@cYc9(2?X6pxhP?!QI_gMozRubuqHk47 z5yir3;!V6xnTnPz9TZV7#uCU1sg=I{aZUNYp|2|JUE)H_l?+zW4rouhB3vkG0`{A( zyL^slxY_&Bylpsw|Lq{xSlP8Z$kMXU0T~;E*n3f#zyr!8DyhSy%Zsh4&^GG7MR{ROXSCp^=x%tY#@{XQ#`Qg2&@=oYl9K?_yd+$bO z9-YDB8GdS!b8KVQb;`%^8z^-m;aK(xrPk?n%@ADe%(sfVszzlhI8m*@6qD?_=*pT^ zezU1K8bzz+XQubsU;InToA<^fjWZB7lQkf--j5(`!_wI;Al7yF`AOqGS4z}zP3%$Z zj(=SK^dITmAvkgslm~)38d-qyxl7zA##$83_*+8!p=%Blgd8qEG9}DFuM}(7%EQ2Y z;}1H~ibSzi^_b0^V?8>N#8Y`5`0jRNf?rGc7v)}3ND1c@E#dRq8I?!+Cy4?KuRPsQ zngDL^N_n~wy7(VK9Na*=&fxL4Z-eyXS8TGi>0Vli&g{N)Mti53()0}NLG&^F=1Tu} zxIH>&{8&#!&hcNKl4R!jWFinC{}W)`hxRU%!%HdM5!1aM_Id07M~S;h`Jw+Y4?5!3 z9-!~vE<-v-9v)&6u8H*Zn+@m*v6jZJF1hsBz&etQp%RtC^z$jgw-4xBXm-caJA}HS z@#OdUdYWvA7Sen4Lks>Pt<{CLinN{Hj1sZ{KN_Q>%Xb(Vn z*qyeg8k+vAd?y5ZEpo*N-&9sg=FgW%YqPQuWeh$@%S>{@R%E7dMn3>;aOjG$e+!F{ znFUu<%*`xp`QTGYkV>G+fq z)eO*$OtKSN3kyjbshKfcC3;kvASEzpe^%u23=_sb7lF}vYEp7m1Ss)Dzdqwr%eoG| zR4LA3l;?{ab${47`kF@3XFA%t}cbu6R!*tq4f z(5#lss?eK=R$m^`Eybz%N1r^6Zm2U8TN)WSJU2$!AOAz_`Loy>t3*6PXo=ncF9h;5CIOen1MyAuK}$O2P8lCet5CTX^!IuH zU&C+Pu_D%%QYKZ8y3*$}H}}_AS#w5$tddC?RUF9PLG3T5%U0z9yL4W>W6;z{k;H)JgIHE!bZ)B=6II-8#lYzxG24&2F@H1IfRq7Ad z95ONq`f8d89&ndQAUdOgEB6#z`P|w$2CtK|P%AjY^wZ;(pZFxHtf2`30v;!Gl>g6( zsSt?st)2lMlj$chxG`_2&w8i6GS_kwB1Fv7?Z<`HPsqDE)F4jhGC(4^qUrTXFJb@f zVwf6LvpWTwuvn*YP6woj@Ybd!=6^xKJVDEjv|l}czm>XD{|u>e^@%JAA9Cc$kN}pf zTVl_+x8R#it%TNDz&bGQY+pub|LeNBg0+G?Ic$QpedjKICa`EO^!7dF&gMYQ zsQ^Dod_=QVR9XDE!~C-0_r9v0h|*WYwV+6-5GkZMz#tJ&YDJ$LoN5QBV@uxHQ1fyx zm^q2uTl04LqpL_vOX-dW#WBk~1eatfN4#7S=dD2GToT>`P?=C=$&c>-xIKT{+5{2x zzOR5}K#i$lRz~iEBxdx8`jAPAl(Y`anhEmz0at=M6SR37;AR8aK)ju&XA9u)WQq0J z)c)?tki^`4lD7bC*du9LwE1IL#Bz_1ysyoH?W4UVu6|)q@u)=787O8;^8x6~;)<7O z)O+wf=!ybwS~b8JH9}*hQ7*@bxe?a@EDakXsP}0>!UG+8GexJSMm${d3^w@;nn?>a zB1PERWQuPjoW){NB3)8QqC$4BwlL%ysny>g=(;vy_@RBLCL~%8i7`#j$*1VVj(qnMUJ(g*p4p~ETn2Sil5sAmJ{Nrw_QsJSD@w)N-?5nP0*cq4E#0Vd3xcflJbiL5YCTdI;mO+BiXD7w zbf0wN2?*`2MoLi5*MLJshqobG=!&JAkKwPregPC}y)&OQ_el-zQ`oRq^CN14QW-^i z>XAf6X1umwc$?5Zz!NfC53%!obf>$uH(J&wmi5Wuii__WE>)V$H{<#cg|45?(dCO_ z&xx{$yY6CLDo~PNNOrW2MsnIepQ`6u2YA59@ClfHR`?lVIa9~GNd z$zkc)$=h)P;XX&pi~%7|C?mhV(E3IwRPrUQ!d=+MD^q?!F1AeH)lojf0*1#&v zx`Ca!ZA+kb>&A5>E|G9dyR@u4lk;_lCu!UjLkFZ(&3hO~O5XOezvuxJ(z=o4mk^FB zrHLpBnza^}UMV~B4W4U{K1L=d{biK%x7Zo10{T+DRl zyUnMJpi>VvNUWr6tEytkGH+PSd89-NU(yM_TE&^vd#P@%M`gzuU^~|q4M^XS1!=vF zDRm~fILBDro|PM+iW- zL8s^;IV0cT0nmXJN!$MKnpA z1#V~$BG>ELOM|5mm5AEg+Q^%Ok!8K9rKhNDTVc19^la#nRswVTK~-;nR6iv(8}SPz zH~Z+2>|#YUh;_Y3vx%KO04aB2;eLxZJ>RNLv-Q9K!e&bf%~wW7W2Kll*x9WBXnEqt z?y9nH+Q?Cu0}IL(+OS?dGG?xPWAbfxW!nTI0iw~hcO9Dt%-UJ> zYh_6mbH@TO#`2-y3^oIVbZQEc9A(mlhVqes_JMh3G@;s;C77ci)iRR!t#B;O8~>hT zxfrxD7*9P7$2QURB}W!VA4sb>0C5@0w0~nC{g(`-S&sttZs)ETj{kz@-Y7v`BCqje zpG3Ztin@-Jd20xP;n1bl`WnxD@Mr^NoV$ff-LK03 z`K8ZBJ)Nr&gxVZ5QKlHaIQwMUPlgK(XMzevwF3<%-4tsC(g4AN4Fa;0ii|PfKrBo# zh>gzQswCkfxT+(AjTZ?dvvuxHCMQs)ZMS7~bnyhLcyZKxMD-f(1{q?`S(lr2g#ZMI z4w4i1(~da}i^J#%DTY{M>c2GLlpAuMx-)l6VD~^K;LcUFdm5n!;X|#Ex=rwupxK$3 z&iV&i4i_2qDI8{c38jsy`oN&Dx#rriTtc7*oTOfCDA#~Ydy$a-#Jpop)E;kz0{)sN zyK1}f#DgHzj*+VqWIu{KhJ@|L8ca55=hi?joqA0USodL}Z}HU+Lj5Hnd7gOcy;uzH z-Dz|~X>2&1Bj4Pw`!GCZ_DSqq*>fW`!ku&V|k%a^-TNG3}ip$Zh zL*ld1cPc0`!k~A+El*B**&-`(+IHw0#XHJa2gl48na*sJK;6t0qBJf&n%o%wY;#OX zy=9u=1Ns50Jdlv}F1@aOPG>J`gS3<7YEe4gb1U%fN(0;w=w8d^D7eOI$PeGO{ibGG z0MCDe+9AD7dX6}4J{oo`EWcP{FY^SsN z)TqGcJI9T1$nG_tYg+0qipFz0Zd%`r%B5i8b$C_*fZx&2>***mF5IdPa9#6NqB_hu zD%5vg|I8aF8f*wgmOefv>{W7}=LnQ75p8htq=2Fh+-}|lPu^Eb@kTol!TOAyj6~Bx zHf$W(m>vk)8U5YxOF88UwczJVm@$D;2w7@#tvAA`;Oy=BM0uxsv(1z>Fst&F?MA6T zzRMR5c^9p*A%JXZe6`Hi)$_aD;}eE>HII8CZ?uvIE=Tt{ViJ8_+Y6xU9T-ib#Y-!b z`p2W$=hRPf<52j!b|}m076DzA<B)tkr`=ZOBlVKlO%iupsO;^Z=NS(1(G=m^$yap3(3 za;a)x*nS&xf(4NMmM0jc_N}8CrQ8AX)=8;S#O%RYoEFKSy;e{(L$@(@`bJWFC+szu!V8z ztEYwyazc=j1ZfR5YBOR%n>n!O(*^fd`0Ri8GFej*0OrV^pa{Dldej?xD`$zmh+5;tt?;4?_Npbd6bu$ z551aFLmG6R6%Pz9u3RPmQl0lSd9ky+GPdE|C2?lgfGxg6c#whe z956zR1KzI6wI3+2a&M*$%0qh;!`Eo1r(_RN0i{^6leX z7|~xCCB~wH`3v-i0hfWwR9d3dwNLFG(G+*XYpD#tay_AKP-!sR`%@*J!U~m(OpYS# z+{GoC>Gsn{Y|pT0%|QOp&)cC=3ZUNdz?oLcs+b`rC-kw(KNpSW^VBhyW@M10V$>>m zF)cGx!2#rT9+ttG00Z7KLUvT*9>haO95!U9SjN==c&>cm=tU2Qy9bmGI#u2hYy{_y zVj@AD-}eNw0WULpkIJ6;z`6Ol2&roqK52E4Zjnm7dAHj~op0>pBSO6Y&)Vb6iAZ&( zo_X~lc$@L%A!$>h9A|4UaqzP7fVBrKTZ#cqj9s4Y^xR7sFJ`cWPSH&H;99Ha)F&*) zd}{u#sX8jRB`)lP{mQSlQ)vtJI$9qJlVme23uuorunTsUWz_cEHLhBHn@f9`Kg6|CBXgQAuxbPu!l~giZ(EU0t z7n&B9M#A3N+o>v-@*&r~-s-uTB8!yw^UVr$b9ceS~ugdQif_!vFIUXVNM=mt0UO9@aECEi z5LD%49uVD)F+x{+UKFK#H?=^|6)pfndw_Y=;b!OjN26|%n{44;Kuc3`td|irXjCT( zHLrQt!;Xu>0*4OJ?-&oF$lzGt>C1~QwhY};p z>`hNqJ(VLk3PCiwp6yPM39+q|;R8s{;H?dL>$9oO?aN@`xUWg>_WR^I@4UBrDJymT1L8-1=I-*QivS|S zC_6m1AMj`Kn%)zs7sA1cW}WAXt{NlpAsz;Tk$OJA@rh-%o#%}5cg?bTxv`q(jq_kK ztfxS^iMg9Qi+-j%JAt$wzf0XXcEIanK+0$qNqH5>@W0>gV|-(eZb9ZD00=vDw78SL zLJYNkiW=U#GMbeya8$KH3Ambb@44kirpHwEzB#tI1&fK8td*Pm?fi z`G8yl_(PPtkR@~~PMrADa_&G8i%Y8lUjNp<$*H!fG#4ws>HL2npud_#Tz>HKDwVaM z_rwtt4zQmL0n6Zu1qhr*1apw-6J*1cO3l=#lOr~%wF+td;0+(PQZ-T8yJMgaz3@+` zOk|D;iK+~(qc<>pqU8^(@D9ReCH>YDlCXpw-UDVlZ`wjN7tH8VN>E3iPPU{3wxIGM zONy$#QWw7F8cmJ667m7V%qwj#dM&?(T`0ClOnkV)2A1FiPQ5HwHot_<#AvKL{qxY` z9TUIb9JYcOThd8BU$ifRl}{mLKA4PcAH+QUzfPIQ-+sB>6F0f#Rv?LI+(##rSbSTh zWy)xK^y;vZcluJOd)1Dzaf?Q~w7)K&j_DN#^WMww_VoWcZ;0`uS_sg2K@ zA?rex6gLOGNAW=`?HCAY#&rj*vqT82Dou3f_N^GTe1lVxz@d+v`-ggCB5X!5=+?t9 zlsbOA*=d>a;Q%*4$iFJmxTg>_d0n6I{%q@8C9IZfaGD=D5Rsb|7wI$65j z3xL^O`6-w4Gw(45oG|^Y1?RplGHO%xR!F$0HV8|Ppw8AI1atsrkrW2qLlFBA*8=0h zDO&ir{qBe9cDjZf&#P->oH(4@7t04gbZC7D78dW9&m9%B;pL}OIdYXgR_<4f z@xe_^$VrFr0I^GFU}3I@E^SSa1qY3H+A2`5I&tEL_xGu!U`{s+jKc%2@6EY+L9oB&>MBbK0IdlE26m z^pchDwv959KLAPjdrTW_ss5gS+&S^;V~#c!DA$z++IGM&cLhIr|An^;eHuq|+bf59 z=~yhh=bnzKbK33X1J_JjIKNTKee=J96uI@te*6LMIy7 zLhkHgX4m^t`G$wKQQ1ecJ=0I%{G1qqTyCl_4`-#e$N8<+%GdR8O}sR^kcwrha&-bvmvh#zti>*H2kwa$b6%y9*oIM%&g)s~F8S=mo*L~XVQP1}l4)mz zw{-`d5uO6Vx;b_z*D~tPl@aHRw4NRA zsmt~|w0CtZMN+Kl5!jMU$lP#re7K@g8-iwcd!jB|8rBzG%Y66XA>`qs(7>D0(XoWF zfMxIe*Ey1pS#(LSZf4|nguC4D?R*0=s#~XYZVRgYv078cHf3!Toh_ddEdPCuu194c zbHI4XBpxRewuJK#GITho(z;ptFKBnr`|aJj!bW=+sQ|@)_@+TcG0|I%h~=Y17O~fO z6_9gSe!TvHBh<)ro3L_3s}T60N*}vJ3dRMPq>{PNTD-U32%#$b;TDO%Lfby-VUur-(uhw8(uX%AOle?B&TmZAe$p+6w@F{iLWTRf`J z3IC)Oh~o{H^v1z*0#KRwiV-Izh9JBPPaWbFKorC83LKF9+9nNJ=LQgEczDNQ009rOy-=>s&i8bagoc~AY;T|2$v2V|w9A2SP{}J7w0Y7)~scF~D1y@vpE69W0 z#vQm|V}(mNH0Xb6Oj$x}U^=ezLa%+X6^krSRkw;!4x`nh zYZiK9z=t_~e2)}S<|f%7=tafiBi*r(NEz6`r=Z4D6;lj-Q)C;~*CNf{r<9J&oUFMa zqPEELFMm?rhcdc>yGuDhQA@fOfE;t{w*|E-Q*!zOH-c}2A3Nq4gyD8j<2mKa zpXWvCd{J!g;H;RRl7~;e>h%Uv9??Jl2Oj5%4HS-R5a>oOBEOmtANiywV=%0#Lmm>w62*en@m-V)S3OEzCg`Gi)2{KbQ%|_HX(jqX-7(&i5=_xwjPE=>{jYQ*%oq zxjlNR>nLZ9wve=UcI2CjX^ZCPyZPhI2t=Vf8|y^bLv{gKXBY7S%ER$#m3A6V$0Ygv z7N3H|yvqsu78TD^tnM>k67 zVygW|dL4+@#hTh@J92UA+n_WzIZ!7!wh=6Ormg1Y$O4H4^a+2CRx&^cu(m#8|HLY41pxqn3jyne< z7S(>073N>BGE?z_iCJQrOp?^;14cco^qWG`XuySsA{gOr^cfHqw)y}?sQI=_qg2p; zP9)=zqhUFM52aPzZ7v`yLZjMyYk@4SQF;>Isq7fNg_qu<3M)+*zM`6d23upG$zH0f zR&;c6yDgqp7TGv47-nySNc&-M`w3K)iRrHdgGjrq`1xFGZe{n+OQd*>A=~&piUZA@&f>VmiuIH&T7dZRXGBRVT4VboLCf zINGyxl*bQ%Hn2&lGcWQ$T!PMd0vpQiK_hmh5Gq1jQhvh?-&CK*1=}cm)!F!V44o<3 zNH;l3DqcTT^Ndi)vKKaCEgXJ4wE|_wklgyEbIR^|V|>!tXpC5~Ul!JaG4hKV%R!LxVRc~yzhPZ%toC`>~OcQIcNhj z(JYIpddmZUw56$RzN(YVOdS&3s=2dAAv?84{OCi?e|~RVr}Zs7eF8V0c!;RB9wyRq z)WC9M3HI@IJtx}*FdCbMa2^LgA9?Ex3tlPv3w)mi4b$b1uA?p*1FK&#y0t5f~-fM@P^OB(1yZ4%brm~e@Izrx=2$SXMi?#n_Y0meV3 z$Va4}zg*ZW-ks@|!M^06QV)z^>17!n!sFWpj?ZA{s8O~7paN7FBZX*W_C?a>C6k$b;4&7c0+psk`duPE z*ut5-Q9|k8l_Gckjl9L>&+TuTYHwGyi{?x65vestTt{+sjXJN4>28y>B>{<=N(F`i zx8Lk9xDHEtqc39T6nbLu%SC>rSY7=O(+Hu=d_H zT=!j$;(VbjH|78xOs2AdA~M&HR}U3f{Q~shAK(raEZ4!A*~nj2c5aF#gN{TB)nuMy z-+%MM3#7unwO^}5hYh=KD;`kOhCU>c2vP^w*eGdB!x6TAGHhY#OcIjo>lNVA_TSZn+!+Nb zhyLQG;>2jEauizt33K*|`l!|o4PvT&M~4(2#)x_<;QZytyv)dJlU`PFKa60U*nmAM zm&0QDqmM83+-CYKYL?Rn((7VK|K8IGJs>DE)3e&50dIf}_5Gg9c;m_>?|!!(N*+{x z_11gkb{g|eA)FuD08gin&CH>}!Q|T8g9_Ro3Q0b3A18L*ib?Zbv|_7GnEfE@m5$#s z*ng0QHSQ4K6g`2U3(0TIJ_DSmN4Q#-CBi===?Gb&ig~Y09%DJa-O3l)d8f;?9`aMt68VbR{=df? zJmk_Vf>uXmj_v-I8VZXaq#pEjjYM+blydf zo|_#90(it}zbn*1TzZr={^s2;=J(uf!B^p`D`AspgvA+{iy$LF<{~z6Da4cU$5{l# z7MbbjfbppIRKDEoT}3S{CCwDDM`u5h;78C4Om9C~b)D;cbVq&DBZWK_k)z>! zHo7kf%0n752b_5cjJx3-X1v$eUj^(AKY)L}W3K*ju(Rjn`XodNB4rdJBM1Hsh$eyi zD)IQwEB+352ZDw+Sv?CLzA0doVsz%Dy{_R+VIF-yA8M*Y`Pf+CM+Yb4Fa|p-bt<~J z+7-qK|sPiuTE7!hBj3FlWfT3XyZ+1NJfF+nj2 z$^q`p5TDd8f5I-;e@5uU4&4u%z0*G7aj~~R1BIkOLK#XN=hVE=BbP$3R-LqbWbY^6 z6>Om80+yC1;G}zEOg|EFf1j^oz-Z)qGD=O!kQU8c>^kC;#?RFdc%ioU^5%FqX?* zXm*&MR#B@UCPz#Jv%jkr9E#k#aWlQkq^4@yz8EsUY>8izO|HEckh0kP_aOp_`-9~8 zo_3MZI~BI8D95acRCwWDAHNMt)en*O@Y7RL5iY1^!M=7dGzvT!cJD5`PVgL^!zK4| z8=UiT8pm=u%nh;k5TuGggN5M^=IIHDk+OX`YkF)iemLHX@Pw^y| zDzD-h6>4Ub#R3P4pf&p0de5T8w~B!vW61zmaFbR{f~n~K&Cf(j(z2Vm`o|k%V1;=S z@E}TZ&yWt6kUDbNJ6;BQ~OjCS!rr*d|V-ntd;6`TTLU@`3$6gII6B}H`S5_ zbv(9<>eaVpev1iL+(=>J{a3=PdGAr|IHcF|2**s@96%$5s#f@XC(0V6Lb)Gr3J)#s zJb2v|d8MAF(u^>kT^?cB{fI*YBiyKl)fgtVj}JlQp7?;_R_x$SmDP_8Lgw21oCaxa zBJLOHt+%M%T(sCcb^iXJmEp!NG|sz6F6^!gN z@GQD^u)YM#4(8_ShV$?l36{`Zk45wceKwR)vIP@J4$ysw=vpgS+S7WKaJ1!AQEVi? z#=GRp!wT?fv>hoSGf`q@q$FA;yPN7o^9ak7qr~c3@yjwrsdf0Zw)KXX|0@KOQ(psD zbb2!vba_c<5CpwX^&u}Lu@cH(2=}KaaO>Axc=7wc(ObK_@`8{_ecwI*{FznJEu^vO z1BV9+ax3wnPul+gOMHQ>H!091LuyOt{$J6g><|$~k&U>~5{iI7^H?`mms4E^!!{#H zXr$AD&pkdg=xMKkhTz&npiijba8D(c{Ur7KU%I!DSwupJijH~&XdxCgfEpdrcZO5trq3+GVoZ zelF};)4p_dklWj(7HhFRaY!|=p-@VDUu=Ue2z29uIEB-Lof~R1La{5|NU6@=YleM=LSxiowhLt zK=)0)F8ric@{!Yk+@bX)fG8&BwUVEXK9RX+O*xT)b(q*Cwe$UKTd{V$FKh zoC%!jkK#9=xQK>_9HZVkG&vbfGyj24Ot#;KYcfTU?wj`=Vx5SH8F4z<{z*-a`prd2 z8(GWL_Hn7NRsGqyJk3U0aI56@0Hb|-kqFinW`luNvBmn?WtSJg&n${*jC(A7=|zK# z0~cp>{@$4W0V<6*taSiZYb7HO0^h`c^V#(g=Zk+`Z&_=w88S$}{HAMC%r&IwMk+OT z_yCF(C!*f=U|S$*7Gn5H1LJyqz3Vk@Ua$;PyhyXP*w;8pUws==eOjf5mykenl+SmY z*AkOE=+25-^v*IGJIpCS)x}M^Zeh8Zr9z+nj}$)V)XQ{aX|kCxrMtr1US68Yh+UWO zTDX)4Z7g!(^(1~CftMZw*Z;>x6ptrfsQToTiE6vz7(BA_Ku?**Q6hG1qwveLG8DVF z3%Ii@DkQkr=N>w5FNKI`NCa$2oc4h;0HC0T(@!?b|wVq%69drDT1=5*z}kf>JTzw<)kVD~AZ{6P4L3#(RDU94UxvgUhz3RzIqF@d`8Z^xm->8R<(k3se5xKl6iUIU0 z^4ybm8+T=HnGe-v{AQ)ys_TZ;Ra+=lG88D+wbq`B}E8{dsV*-7dUoYIJVo^ zu9sU%zEZ;e=H#s)yQd-xZY?08RHSw+Pp8yBYKs&iE;r`BfwC(s3b|EEWXpc@tCPMW zpCcrfO*kUt+EJ^$Bxtya9S25kD_a;5!hk z@?K-PU0%ZEs~xHiAOvDFI}+zt_gyB0#V23+Z_l;;*%X}(pImL z14wrtmKL$EicU(aT9Xy9ATGHrBmZ1yH?xUsb5-8^@-`UP5;~AB^0HY0zbv?^% zK>rB`7_u#TdhnzBE;nVKSJ~VTUSA_eS}WN&pPZR3J#Tzke3HZ6kPl`tVOjq>NV?(D z$SJSQQkl2L{Kfb$fKixmS?eb_G$A@Sx{xB`)ROgxMV&t2gR8sJ2C_Td`LDt%4%2SY z>dSHqTFfPP1bAIM4lPkcV7OI`y&wgC;6E^yPcm6+DLzS{nQO%zPNLUPw8~aaBM9jM z=TTB_H%8d94s@^NrmLCxaKal-+dTgN4GGoZ0bmQ%>4N;uFP0^73Ow%kYv1P%pegR- zHivzp=TqB@Gdvqj*T;Bv+zyp6faD33kXtK3E+$${t6LKm^NxCWxl)I0-RJe;H zU?Q~#iheiujaH@53+_HUxfjBz#lfH;8Ljr{- z5&V0XN%@s3YRO%WE(bo%nP;x9n2X}Hc&!aC=lu4CF6N8n7@N>=K$yi?J_>+-BkmI_ zl%^bSH}r=PIdbY>YJGrY5c4J9k0~FPz{(9moZuUy9DEj^6Ze42{z+vxl=sfMOwgXM zSwc=W9^KfT!`$doJX{+ga#pzV&h+CDg!&F2SOAo)?_m?kkm!P^G|?n{$n`9Evrl3( zpX%w{wOT<;@-Q~#uVIM_;z=|r@J}&6FonPyYo5u;0=#_5kjY4b`z4$52qhx*iGvp% z^rYE;-nW2dLsTu!{f0w~wkl$4r@Igi<{|heo6_@jZ8C#6&GLEI1tcvSmgpgkCybP^ zF~3%>cXII?PlcX_e~a>ZPiQNTBDN@Qx@Kc*wW|lGSwRQFwCnv|H^Dp%G9lerEi=*g z{WUoCZUN*O3CTUd%NLR_3v1DbKBHo*aA=ZYi#A+s8O53U;}hpav_*tyUx{1oHs=S0 zfNUEEUK_CVyO%-1)KHcMkJ3Y~{^LjJfa{;49v3(Q?aND78nKIsbHf8JZ&5AlI0IBQ z&G_TOiWdmoLWJYZUe{}t$ghTD0VCr8wm1}wG*OH08S0CPzT}*vrnhmWUL!=YY(?~U z`D^BsSs4f2Qv60B-{+gb6l}a(4jz8#G2dmse$k|;DqDNDYOz6aNRm>+FcfIXV;7JK zZ$s7X69>>|(^*p8yb_!c)oh2sfUv}kqMR!4!%_e<-#6ue3P6GbNYEo$Oa-s;Fk$?N zma&ay<7><=APB>|>7nIi4iZsa+41HfNe0Z0A9M|#7?ruDxC&AOS-gjk4(S^V4(cfH z>Tl4^>Mjo^zsXa0C6-xbx^oyWb&7QqV9C6|qJXz|Xjg%pT@+w=^lZz`NtGYF1}JbZ zUy~ZZ85YlUhf?8@(~ys$d50W_kiX29zl0c)c+FM-k;d@v?EJLD`wM5$M6`M5Ge^Gb ziIR_=;3ZIM!l-HEM`dn$k$V|soH;RYH$O%+MCu^IJ(B|H>ba?KL%v+&D_*2}fU>OG zYMGWQLSM?Z$-s*wzMc(uUOqEPF03+NA5k~0UW`4H4VM*EU>S1}E3O500GP!?TBsoZ9YPlR#KJPBp)F&#ovggxo3R!*#nd;0gFxYFwt zlH|mb4O${=FupYNP?+5pal%iJ^a{!Vw@Z_7sKFpfFbW}tR8-D``{)(kJr*}(7jR9O zfQ<}0m)pk=opjypG$RW;iAr1D94GXiRM!Xb3~+kRB+F-pew8x7)m?MD1#aSaP$M^X zPI`9KZ9@w3;$(f7$=_LaZg8dYV-qNL8j^O>xKcUZ_nVLa6x_^M1;Gze2TvmR{5Vb| zYA}5hc6Jy_BwNfhn!BsM!ka5TXSE|g?>I8p_VT7K$8b5eMrc?4#f&k-I^qO*tv+s$ z7e0rfd+!E|vqn;9cHRo@mLK|o8IkfpKYL8cxhqm`D|L;`vq)2iMpn`HHTY&&AJV|D=>?H?q@3k}n^mS;qc$BiH*WS4DVG|ny_l_Z1sc~#ozL^4 zD>y30jG!7qCII8K3FYk=rtVR-%eR$FA%tOtWnf{*rG9db8@H^ND-4rsW}gvEE3or8 zAI)z2N%^2ej!SBwclCE|BPXQF6~2i!jG^rMau}_Dru!3K^MD5yMIgE7&lYO8Y9&h~ zkjtO=^SWQtk<*pcM&XO^XXpYa#HFn`0tSxz1==X3M4Kr%FL#3uXB1H zj-C~tI8zmf!Lb!-;=LDikZ2*shmuR2+LQL==0ZSkmT3=tHS;&AxH0o>HIge_Yjb@= z#keIGRBbdmnq`-SNjzt;ay)h^x2yM{6>-q1BCg@~!ekxnk@D0wAtt^s|8}fmuWBPE zk`7E?CT2I^=S9i6_%8?H*FvPe<%Pvrbc$8_C5LioqV@+xMUoXK9xh+9cnd>yap{cD z9LiWl*)lF&67;f+FC*}r9w1{nzpZhzA z+n8O+7&uHb38-}$;aqSB)MCIN7{r)Kv>$AVA6eiVv%zlp#}=?z@oM^9U>rQU$Cu9O zW55A*%ruU}CEHCY?dVeo{P#lYV>gUCuk=DBC(1Jei#_#11TLJN8QzFf5Tg;O%o&q$ z?eTjvl;C=o$C`%?q3@ff%9P8#ZmzQ9#}t?=LJkh-ZL z$(&A;`&yO?=|e~Suzje+&Si?Y78fGbt724AmIy*VcYQlLVPdd%P+%ly5xU06V;IpP zs4{x-6+9RyH~t-Ftc1)|Pc?#{BeG6Tel7y=iZHjIeS_ezxP|pstFV_)!akXI#o-lo zg$&&pR~ulyJke#?l=rlyuU#Z?3ZQGYTsTcI?_`8M-Pt7Q+;pn|ksk6+!fkB;03hK3 zo~LS8s^paBP!urXcH3t0&GR81=l98pfnVdk6{2TXy(F0AKqsi2x3VrZA!bUdx4#}< zT%h)GYtYhW%d7NegOu9kgzG(v_gDYaJPsnkWF=An7e%=3$~!(y@RH^Ob+j$umFA~7 zv^#9thl>j@uRY>+ln1xce}ph?wJCj$CWKY`rxzT*SiTsAIg`j>jr5*p8KCnkpt}k= z_hLM}0{T0G+fXp%Az?v~XXKYbY0723;6U)W;;n~wnP6srUbqmQN{0P%!#Q0OrF@H)_B}N!^U4MKI zG1+uU%m>21TZ|`^X24X~7jKp#1^rhG{;61rKxby@Gugr%w|g1pmw6un2u{dcWD(Yb z=puE~jKqPQo7=3{D)f)D(dM!W4+JWlO13D!)Q9=6q4fIna zqXE1a2>VNMUjxz!*}_Xjo+ezp{|FysnLWads~1%USLVlbo(^P_d_aS3^yNy0)AlKvz9hg)Er^0s}QTyWs0&21=p2yp+APuY6o0z9-GQ>EXcLsFt+v z(KGTdZTQV|Fp-;*UNqz%##mp8mm8IhG3Z2}(%Gkv_mabrCKb%-6-h z&aFCDwL`@j*7qX#^w~y{vDBX3fxtxdG&%EasA2Ln+p8xTrd9l4dpy{YhFN; zv+hz{#SlAyNdPxY3`mraRQ_en430VtH;KaWiVvLGKT4?bbBHdz ze=1Af7y(iF0O-cXb_4T;h>lSYOHY~J1i>h>+2+YQS^tnixeXH!faNUq=4)q{xS2Ah zI>bZES{md3yW5pqD;~#tAHu2f@brj`?~cI(Vsna54X&4pgAlyLcZ&y^PzRtRQY*0> zi;OQjreFohi5?sl=q<>ol?nh^NGgViYFOd|fL=xtXl+7rAyd!rwC0k|1TI{~+_)|H zb6JzDVC^Y5T9UIG`OK`OsHu>w~iMuuP3f7BooFyVTWo z77aZ(GX9p?ZK%^G=VyQVFcq5%U>VK=x0|#-`~|TQM|n0*;!hZI*|(3YbUbS5vQRcm z#q?S?56=I((vZ&pNZIcB{<>Z*n9bK`5>t&cz%0v&_Y9C%V+EjTo#cqg%%C&-i_vt* z>Nd?lbv26{O-PA{3TRZI7F3h6enD6rao_;R94w9=!~9w@5z|^{C%!MpXN-vaob^8~ z#|fu%>7!1pFK+lASjhZP=zb>Zfwy85`G2D2+X!IsITNtNKD0vscUAR|)A8JBAjupU zbNmUk9+K8hl?nN}dWOk8>Ez@)dV{vP6^SEm7gVB4CCm2S$Ju#(d48(}d8J&<(urBd zcJd9md;!-Zb%2lu==~hG0eQh4sffQ!#gz_Rj9krgE{|T3jA_@1cymgEV2r;~Y_+9%=rg(@S>LBNvs6EY@-Aw2Ugv!S zaj@YMn30t780&*LyR(?#+Amh)?;8$Wad4(8yqc{)1}L`aGq)+=M$+QQLvE14qh3uPf=SJ)hb*$=}~akIQHG*W&7$XKwvI$_EM> z*gN2Dbb>M)AS)}fn|)7n{Oa`b^RZZ3%N=1F&9%BRS^s;f!P~UnIaVg~q)<18ALu@& zU0brYggtPa1Od9`g74K3$Q!Xw@SYVA5&3hCNE24AY=x!Vu2coC{QAs+uI3b_^Q78< zkYjYl%Xd@X7omFv2N?x12-?rI0vic zhaDS`D7Hz&Wre9**iypy%?1Sq3AX}~Ov7;v-ZJ$mS=kYfHgTGSAEN_Bg(X3ow1T@ENgRi)C%H`#8j zFTdtpK6>ebsH*KIi6#w?BYk zEH4w&0TB9S+C{g>p*=o!^_Zm3(jk!&rHnTUeqB>qo;TN&e8gq1LZVDV&lbB|?U%a1 z9wF8QYA%Ko5Cbk=D@X7LYC{1Q7xa~l*^$H$`0^}#57in1ZDXIa=V*DEZU+GvU|n!? zKig|V_{JY~o!aW1q=(Cbtp&6EYU&4=pf0!UX%vQew3LT}qChr2ta#0aKq3w|+&icm zc5i0~1&0_>uI@&2B@o(KQrVPYY)Wz5E@_xc1&N2~v;v>qIK5tRbcco~@nTqw!M;Y+ znpUcG;F1#uuHK;zSOKm;1}D1udBn!2nHCSqEBR-R@e3yDQSWVZT}z{mt@qBq44^{4 zxnJapY)%F^7ah9Vy6Kqc!{K3w;N!9h$jgq_m|sChssj(1l!(GaG0A?SX`AW(9VJK9 z&r!Siwp`7{L#e$@h(k!`6Uw?v0z#Hk8vK|TYGD|{vydQ|7J$kuPTXS16!AYgj9WBW zU?CMXD);b|iw&eh^gC;0Wg74Pyh2rGOUlt*>lxr zM@gmVgd*`nEnSgUjOaOAlf_pN#`$fbBNMOj<&?=NGFqvGPsH=|b5L!0^r zlD2*ZE!jbnbjE%^S=#yuz$$S%R_5>5tf(KKel&y7a5KZ%`Q%p8-GrdSOH*wJrHOr22Xr>%fgaV#i6qiC5TF5 z#M#hOfEZnCS9st24Qc?{R_m3P0P>v&{LS}T#u8_ zvm54`S7J%}&-u6sI94?7*vXNn*4d~?gS_qbDpkOWWEUVdIkkcdDeS{7=XMmm z#o*QrUBoW8{Nr#rP>kS^Y}FaOBIgt@z1l+bv?5oP=wNwr^TTh(KfDc_D08cj;lFWR zB(yLBBHp5m+(9o5#O*B*t`F{i5R+SeRFQx26~>Fyg=y9HhD3d3uzb`~wNf2Q&ggyy z+voQhu#>$o5m*fOFVcnJL_c-+mFaUnbPD*h6w5y|iu3*qG0uN}O0Z@)f2$q!FsG4N z>uzj9v!=}PS>}G-8HqDX<|lX3aI)_RX6DQoaS@uv2Zhp$)hla*lFWCI|f z2wb6K}?4S*?VAFUrI3^5lFla1!95Hf7N{;6pD z(lZo=kxwXf=*)aD2G2hku*P476w;S>$NFegm84Lpt3AJia_cntVo;O&M85$Sk~Iw5 zrNodC^H4_bz#_=2A)65cdltzHnHAAwPuo1gNn7>OhICi#&H5~Swg!38ny_aff2rM! z4G*L&FIM@6T&2J7IAi3Ov`*CRV9qj(_?0wIt*PF}k>9kf1ydzDA7egtEuX91%qoUC z<7bacx#e-s1M8v*z(#cWPBX)PG49qPqr{4`9tS}>+9$291~xvY(@J+22s91lh2>)K z%ThzYxoEg}gYRpv?|01T{XwB2G1GMryc|&5WdJzRB+Z~5(Fno~sGII{W7ez%=0Ks< zR0kHtmLOS}xLfEv055NKVrmXse6N?YufzVN*Ue~KT?X_;G!@L|HldA#W>&bKFmILP zxMt!aX?+LpQI!a{m)uyQ^BSY^qO@mNjM?pC=?n|J++!B-Qr%%2o-hZnjes%6IVHHC z;~pGMqG#a$5$)XTjGIGxW&z@9mft^Ih^V!uw$K~{qMpd%LhD25>a@v@YN|x5u`Zl4Cl8T2(SnJhyTYm3M5Wr> z=z!QDCPTDK2_RzbXHrJ1>javhQbV;zgd`NW55f(q63H-yy0<-TxT5A%C@bh8yu<8@ zqbS(*9S8t{lORK}-X1uz>}5n|-u#h( z*5$N|O)y8^S}eE)2>M37dMD{=*2fcY&MfQ|+s}_>S9M`Z8cnt!aofrk+H_y32MfGs zl;g4L8KKN?80fQF`7GGUakDwB|Hy9FU;!buOcils!~;(asnl9mP5Q1tqRke_Jan`8bZ4aR}UD{@qcEO8Ac8l=s7GzjOFXF8c|NE*V$!Th(Z5_ zw>C9Hq&9*L$5-YSil)BLy?Ce09Tn8B7&?kAXuCV4>+ea8N($6L4X5M)U{N7n-?-#aYr^7=0iUrF!fxXvG?1Co$y7sfapmAv^e6Kb{)$n& zGF~_h3}!7U6=b~g79OM%4(1z`$CgVorld}I0%eW8Bw~R9`>nK9;CB{V3t~w!3d6MC zoLJtRdO2A2Q@Cw)Ei9@57?*L=q)QWJ?5Epor> zd?c5L#`kRk;9sLN79fP>j!<^^X?!1OVUSwWCD&4%I`^TnIf#-hOrqq)MX<+H_2T{+ z$fX)|wlA|YaJ!!cGtc0ZP){Iigv4$R^S#nNXL`La>*F!*rZcR-29miRsPc z$IHFG5|y4QZiydqrPx9H#r<+WWn4%dSCs(q9(1PO_uM+cBJv^bkn!>>`_^cCCng9u z5Z)u*1yNZ8IS>x&I_0=OQ>%es<*A5u%HZ4@{3Q%}>0@p^LBllJpMLv`%FC`-xOXD^ z`Ad3ez@VO@kAGNk6n2X^d9z+@2)VT)If)FD%#!w6haI^M`G|E6@~zfpB{%LGk%+kF zQm~JgppAa|i>vrU-Q>+D(Ilks_gq$a$t2ZZ<%IxZEnS_jna@pLZrkQWfvU9zl) z+vw9#?+Wm5gbN9P_%hSCS@OdNcB%||juNTldGJP`p8_~1#C9`%o}YQq=(QPkQCyZb z4E!crO=G=+vx#_%+_iF<>(Eygxc+VC98G(Du-RuD3_#R?mP`B2NY7NT4$7^N6_67f zk8oH3X#G&HRSMZvJFNFZtv>dXaa9YK@C3;!pag2q*NTKuu6H-Oj!UvCzF?ZKd;o|L z41Nods(r0eFBH~>9{`wQIWAs5r6Sq>RK&;-L(VBteHFo+nI0Hx>$38&JXE^bHj;g* zU?#`Z3n)HMiH2y}QZb=iU8jr`{;8lVylBuu`m$!wbWOcnc%& zVkP+OH9jNQN!C|76-TVU)(&Z9kO33?^7wd{nK(5Kb|a+uzdpNfQ)%T86&1TnG5(kI z&Pe0uWJV>^Q>$78i~omZmLorXTO-TlXX|vrH*+K$oAd9OSh`l4$C~g6cEd_wCNqRF z#~yUCLDLlaHUN>-JJIMOS>;5ZbQcnmtqXa;K_OtY16K3CwH#ymbtxw}mgKp4o=@=Xt=lTCJ3ZN9 zLOUbNWR#UYebe-wQAl5uuoC{&fZ>d~cpy~BtQf6Zg-!Bn+`!JTO{ZOvP~$uV00C#v|>1ro4rf_n#_AT0paq7?Hf2X1qa-*=LYgiY|uyS|obl$DwOMx{S1uC(2kr z^5ze1G`lCa7$qHMD69(zyb(+oUP;h+gmmk?U3Kmjrl-~S+$tC}Ic;4t7%wKYwHc=~ zh$5766l9|R;yUZ3n_K=K;yf(8%{8^*x+(N)KvG;W9HaDoK#^|=32pHYdPD&P7MjqP z6)4*PDD`Nj?~}-g`$jfg@();|t%fT~D%ed25qLP!3T;teeZjJ`gj7`Aak%65tbLjw z9F}nGN^>flKFcto&KdN>9C<{6>vA9SBw#9nZ=roMDY&OUFz(z~T zP;v&HGw&hXLhW=5NWtw=)C>i@WTY*QhP98+5|poxw&NCcbt2yUGUW#qQBOFF2Jl;m7Orf3swhKQRMHvxTd^-!skhND_pC zy<(5J?ljn<))DZKaw}-xFxmY;R_;G$G|>_g>Dr{LxTi~6cd{0rczT}QZEZIB>sj%F zLNnO$s{BNcSxX{v-fGQe4UR@HJ)XvCLn53H^Gi(@ddt!tm0w^cXZrBs$QrWeTBB|t z2+*nm6^F+__2+3@d@IaY#xVv>OlRvA&THg^W`OLeuE!OnN)g)W09=vfZphMP!(p64 z_!F9}V}W8uTwFOzI}Ca2y6rO~pHkt4=>bqc*yE6oL|9Y6j$$uHo2quF3(i|*R3cEv zg}5_pgC*Pmu0VOO=$2~1e^839tduME$6ma@pfbgvO^ueQ*(85u!BnDBFWlcfVHQ%8 z%}&e-j@+z!@*fZq>sAOFkGce?Bw+Hhu|Q3cAor5qbh0B0q=LRrneYf#8nH}N5*cBf z+je1HiLj3Qr;;J0CwJt0v@k!Q-&DV1Kg37f5)eZ1{lumtja4LB zT)SqZMC@pQKMJ}}sZsNUT|<+#%f}0Jt-xBQ3A&`r)8wu7a@bzop2_h!%0>#L0yDR_ z#f`Lakj%@-=KqZ)Ix=N25gO-Mu+p^wjANG7(8YABk{)Ab8_|Eq=79#iSPQkQ^}(tI z)1*_3k5{&nYPO`(!ILGH`=Jjrhgo^7h{YFcj-HvF{k6Kw@o|FtHmhl5E8+%|lGjaSL?K4%G2wGuh0A zk-NNV9N`vYJKk$Hi-~mR0^8VcbF3ET0D4)uT0bzVm5MPM07B+`c1jU$KiL(?^oMXq z5Jj^(kVWor!g$^Q3vc{m74+JWe5b^2gC%PMTSGPQWvvB=aRh(ll5<)Eg$kE1W(feR_UlE4+wIC)j18Y6nE2ub;kcA0AGx?77)#jc*B$#i~#-|47ahA%L`% z#BvG*;W)YNfjOJ3U==)Q@~G_Mqo{Jq#bXbYmA4_<}6} zfmFcsi#(}Dl~K?;GY+r6%vpCM3rWhCD$stX1_RZb$>0wAmQ<;~hMXD)iP$tht0^_8 z2D!WX;uOVQjMOvH@eG8)&LD}m`~Yhd{Ij!dP+ege+&!}f+jxD<^)4qDLa;(pAZ^qo+$K+XoQ7?sccMRvdSiJJD~B%yUN zdn2yT%o~bQEZuKSv6@HV%LaN;`H<5|4BJsOOme0(sZ)5MqV;W)8ta!#dRR2K>whP% zKaa}E$E>uM>l1*^h@g4`tg_7=jhN<=*S4TKX*$Dbv;cZnD~b3eM-JELt1^)4hd$~n zg^hi!X4zclGoutjSmqiY(1fvO5C`ExhB6!l`%)eq-q(xmKw|~5Tit(ub@M@T=!q4` zz(&@=e%|B!>_H7HSNm1F!AGcM+hsF$eqz3ahhUz8j!Aqns~1)K0=}nfV-m%P(-|Nh zI~fU=QK;rXsVK_PsJ9f1Ib2wYilYbdI>7lC$)srcO9$4_JB$bT?Fa%TRh!wbwSBY{Fw5JNg0_Y zV99w;Du*aJB0~hBacCBGMLsQcJL=85 z-}%p|L#s#ao0}OJP8rX}$nNX=T7cs5F$OTotR7n6S1s+1ki8vL4(wx}^zy6(x4yDS z4N=zxtJAW|na>F!oJld0S#{8V)R}f=a++uR#Z-A|$7LtfO@b8QwMD z7hvdvk@ zonhgM&$!}*a#=U|vP?R&h{GIPGLhN+c-q?co2!$|r=0=Eg@eX;K{=}sKo$eTiOYN& zgp>7OYrYo_d)pNeOaPD5o$MZtB7~&#@^)DX2X}zgsNw1AGadJ}Z|QkI)(ByL*|;N$ z5tkv_qozku#%nP*cl}VVd$|PkzA6d(%1{fCOh)#FOU+_{6R+h(4Dk$exRxy-oB#ky zu0fiqNvJ_=nM@1+h%P@w@PW9Ww0!vFJ=~wm1BJlh*WaLH`sujGSP`9-^6r!F;{0>s z`MqyPZu3f6Q3HzQS*faJbU4B`#OreyiiNp*7OE9K>8uu8*)W8lw4oU+=C9nC?B;=J zWIQ_9A#>q`^*G3t?ec07lKWw3zz-;{05t$}{YdBknWQQD|07NM)NVqMhmB_BxF&RO zNjtJpPHN)7gzf!86q+CvJiMlS4^leuwGA;YsVu^32R-wb5ZJY5JRmF{cJRVDd#h#X z)WUmE1q0n_iD(A}ni{?filHv^3t~(uA*27%^*RWemf-*Y2 z=G)(%V~Y7eCC5mlsrMEcy&d0t4{DVlcbJ*wL+M+8IU3#3P-2aP%B> zH%C6Ufn%~gb!Z3af5Ew7^1|M%(23rm-VL!aUR<6RG@A>M2>%;*?~}^ux^UxLX+XZ< z@@`f1z&i(0Biu zKCX1^#GU!Z`0|`3f4(GP$UO8^5l!C|&|?;U5u)#{d*wlnPWQ;PFi62|hV1)egfaJ4 zsRp`C)`z=Y8pw=M`+XPV628Jvq)k_4XS8DW8nKO_XA1d&186$t1mqV}h~uXg6S0|m z0{gFJ9$}&)9`L~q8Ns>J!ak6U1zUnB z78%7nm{gkxFcuApUMeSsfh>U{41e&fKSUyOv9q-u1%dFSua!RKG*)nqK2FNfDU4wh zMC1Ol4+;HTlz(m+-j2$*vYQ8?Zgkv3Njd&oRgi7Q(rZvl?|Q^iGf-rygZ3~$o9kYo zSn--G1A0Cn$)WwLI>+M`SAP*NOUjVe^YO$N>v0QxD*+T8id9^oF@d?yx1(~6tS;`AsMK2!y zVDJ{yy)$)*c>PQCgo!sxv*B^rEhx{;J_5B%#UI!ruj|)cHX82JZ(xbc6e;| zp>&;8t3a4Q$K=Uoi^~%OuVYV{>4yY-(Xn$%XM>|4>f-BX6>QK+Hn}5;8zAz5Ala8^ z+=`VGyzma#lw~IRo|>UyO5-k;uXcWc2I&kN@V4nQM4Tq61@ z_8HEb$dQKSuZc{RtH>_=Jsqp(OO*0zQ-%}4v(N7UHC#>l(r~k3JjjzE5owrW*+&%W z5mhp9iibJ9l*7`xVp8n@?0y(mc_#HbeUOAlU=q^8Rm{hjeTEVkQ?SE`Wjf{Pw=roy z{s%IUTnO#sYh!H%CAlgS8`J-ma9V-#_vZfn2J64QnbYVH^~Nnyskdcjuw3(YlE({YgsUW6ZowG$2n!6Bx`Q&8|0#p1|UCX!v%YC zVFH~qJCs(U771!m_O)CC-JV)UdFuFKwz_i>gKbRq2XV0=RnF~^cG4t{ zfpj78R9Q_AnWZ*c@nvuEe1bv=4gyW;y$$^-=F0(f!uXuXcXEJNl=|%(XfWNOgGd_w zsFnLl=Z;dQN-?gAi7G*po`69$*S)wkxNLW|kZMUMTYYh~T+X^WRInF+Y*lw_?oCNR zghTDo@_&dLef=lWxh7T@I!N#XooUmH^ss8-r?J9KJ2_h~Adm zTk3q?aA?i&iL++9SX5gR!3(TUhnot>dIRYPtz^EA!#)v>Nmb0HUbtQJ`(K!!nh4`ey3+3-ycq&Bf*WMV(z1cZh^TWe0-Mh*Mv=)$@=<=^k{i9DnsOK=& zqQ}o#)8l-kWFz@CiW}Yi3keJH!@Ogs<+iibepcDjw9>LFO`v(Cy6XyMQSYdUNu?m( z;oOytc}yAOnLuxQPki;Dc@vzM{PI{FUYDtD;NTN>+_HyYFa2A1>auRwjn6<{rC6yD zsyPNC_d5_kkKnd<YDl6`)cr6 zLy#yV?DQ&b2GR}5l_)J*>2Q_ivp{JF?$lSho*b#Rt$92!EfwX(hNw3KDNGCn=(x7> z{LTI&Ip?1pfh_o)ALCxB#9J$xOT?F(PA(#0r>%(WexB9MHw=3z{~Wd@^m<0Hh;dwZ z0OT8m`lrk=KLhKm)oXl5arp1K@ZLHW}1GCuEB}uv1k0?ib*$mK| z4(65255sCzeH10&ycE(GZADz~Ih=FEZTNMv>&?SdYPwbUUo$N;n?IhuDLJC5_=Vwn zTU2R{Kr<(IpPSUNU7fi%XAlMCTAuKC_(k&xqwYB!$MY?|0{n$#6}dMZrG-*;fL<~Q zhu=BAMyE+Y3{BJ7gd)_9%5WULax5n+`)<)?aY@{p;hCjE5Gtjf+s($5Qm1!|d?e)T ziRKN(77PtQ7en@Ofc&vauESt1Lo4-2oJy<1vg{m~O?6K@hAUYDnX&*tq}Z1DbV=-&zQ8DwU^Me3|q?6RqmGbIZfh$zt9Fh9vTm7rh?B= z!I*lGVuD>CjA<_3fd@KKAW@`p8%LtFVF;A1Y!>>TOV3}GyyV2uCSX(VAv^f2%4ov3>dd=fw}}?- z)JAmsQx!VNetT#a?8uhANACz)eNrb0?tPx$hV=FJ5MCMf7JHs<)HpFei-I?}C}0Hs zq0p`SCA2@H$#tnxbO)pMUwFLW0=x%PwiU))3?QZ>Rea5gLGhkX0LK-+Lk1Mx01U#W ze#2t#$jmY}ykcUE-vt4`siv%rsM;v!U>PhD4Y_8$vzT&GE8O`OouLPb0%?`lvqhMd zh>|y)b0rT7Em+|ZYe$n@0t|B#8ziUI1ELe+pei7kdg+gC-4|iH%T03bG z*ARAXiEyIZ%WUiE^;{%EI**Xww67vs?)nJUu!pviUCK{ z<*f-N4P8uPmVJ)Mk?P^G>5vXX@!JLk^&%u;?931$?N#ZNOGbE%?k40W(Gp{VL@0GknU-oR>vS@49KDrMof`gP9&lMt=!ZY3LBBGP~GTE&Bs-o%wX} z>?o*(!qtt+sfXRN+CV{$(2Q#{#l&?+{2k_^~y6PzwzRf-|bUU<$CKd`WkG5?QIiYAr>kP0`Ybx4o z9&{Y2G3Ts=F*;MzJwl=(+`$U-rccXELVujy)wR_LX?q?i$XLNl`4J)|CB~wO-gIePC+I&?}v{3P~`nMRVy@q(fF)GAQ=0@}? z;D<6xLTjv#ywo`Z(AsI@92+C*pb|kJy+w7oXoPAT1R1s?YVww!wG4tpcKW#r4*A&s zXt(Rsl6@N5`4G7e)Bu8%3(X?PN;&OOF1P4S)xFq1p-h(+v$yC)9IOqQy?*dFdsoMO zahd&GsJVeTMNQz4HTe|*7Zo0H3pWgQs=76Imul8^cOsTj2R1<&03nQ4Zc6SvAhnk( zMLKy&A>;iNbW$XZ`o2=)AO+5Irj{cz)Z9d26s2oPx`KjIvy?6^G||+h1s&XX8w5HW zbx@i#c%IH($eaNWBPb3=X}3gg7?lqNL+axIrEX41C>vOHnu>4LUGennst$zXbQxEi z#uURGb!7JNcK2E>0*Sy8rRiVMQ6@U6^wOac9(5T|Z+$L9Pmu{6PC%B0%{72R?N~b4 z4qWEU>Ep!lh4Le4N7rlFK6T6J#w1=ECfj=hEKV9`s{f`XPtk@hfUdm^VTKD_$$Z!! z_oTQiWSHq;+cO zEQjq9hZeXddQUw|7;% zO2Z%`(QHX^?=_+ zsMN6BZNmsD4Z*Jhg3EF1ba|3_6qS9_&i`9fO%FbJDH^RIX?;e`I#*$u$xvvc;lWby=k9XCZtxfU>WG zq#XH32aa&=08Refb?#cdth2j)R9C4z&{E7cIdSI>f?W7&d+~8WgG^LV7h>NpkyA>o zep?qd+U#pN<@o*?zNcO(`U~KHbK``ca)mJ8LN6@RI;^4-&f(CeSIGH+mde(%WKJk@ z5bY@~I@-I0jm86fEL)|rjK-v1&U<_$449Nob)@VE1xYuMO92;d$FZsJc8E=D#LV(UlOjmK=IppPgcc|G zlc64<{bmd1ly_S(eqdzC z4~u+D&9_LVXwshq@*kT{Bv;ON*!<1jmtWmQbIn$_TGcIg@nMdh zACM)QhL21f*b}?;1n+sf`}Y&b-+HU8zW%EAzUQ4LyRq}5JQ6}CY$B+6-Qyd+WrPKgBK{@SE1YpxH+@0#bML^HA#&?acg%#T%%0_J-0LSvF7QA(p>4zu$h zSM#LvfhT33FqeTd%d^#AH6Kt(fOXdTIEyo8iG`jYbE|@!Fnz_qy^4e4uGz)q0OALH zFMhvhm$b{zPr{gsaSD(in68QoqQJj?8pE4b1~G767v+JaWp)bwX||pmaF{W?Sc;r& zy_anLKIgi0nal2jxWPrn6}?2XU)|XmfuVIgODKzFP~1^v=0cEc|K z$s=hpUlhYzub$8VUc*4zJ4#k+VP?PGuichXZmLs7*6d3p&vnlb7yHsJ%)^{k>j|cv z{9W>nQKrpuoAjQ3c-g>i06%WL|HO$A(!1rRHnBB{z^?6jhEyLAlCn4PZ|m?s+vr*; zsrsuN=3!ds1gt(fyM|SQCk~6Ga%n2yA=b;yv`s*n11zU1M9*jE_q?)t^Kb_AHmJNA=VoOlO=LjI>?gKH2b(mu8*Q&d^5 zQju?$>dp)-Pw%@wsT6(XXRED0QbgxV7eDulk#B;RscmY| z7o1HfOXB5rA)w&43E;9VU63k`AJ2wtQZT}@`%$?`89^=7Tdg@K&t!cr7>5UxF@&lv36>73dGO2zdab5s8YCuP^gC3 zW)gqwOW@Wa7WqE}rckyNM_+oBy_BWAmH~d%LEgjo%@;1o|4^-lYX~3zn12VYQ^tr1 zfkUev;q0h{)T2HMhEooFjZ0GTON&Z(3`uzsh}Qo#l}r)rWSWEC2;OGxHJR<{zgzym z=*&v+4di}kwuOPdoB2@GU*>){AgFxQNsJ0G&5ybNoxqE8wk5Y}vLVF4o zVFH9&V`l+uqm-=aB+90V9dkzu;3DGlN4ylCw1OAX+z7J%QV!l_zaiybd$nWUF*X}K zU0tJ)-(&I!NC78%5B2;)E5d?YA5;G~CQlP667^%lH~vXdKwg!$%%LU^eN`!Qhq(;T zRX2!bpG78IQQ^7eCBmsgBWg)2VcW1NH5m>iNPv&1L9C6P-MUB)v z&C}i7S`lz6X$GKh&GW76?&aY`OMv4}*wFwP zjRO{d>!s?U(WeD4jjV$x8j5@lM{PDyEUEMIrPP*!6p0#g?@u2pufg$}8Pg*4jyDdr z#ffCcB}4!b`RV|;;tT{OJt1l)pnVht#$){D7qaJsZMaD;M)0u6tsY5V56<+f`5)e_ zDjAf*yb|B99Q99Zr&hVM7B4y2J3J8rYRLP1d^08k|6Y+OoK9(-K@eX@+jXx!#Bh7q zJwE${3R=ZJHp7U>rF%>T%z6tN$WV@a$3u*2C3Bn?3_Aqh$&}+g-Tzzx2g>}BDXMNz zZC$SFblr~GIC!zD#v~eOu7s-a+}FjdO3Yms+<6o`sWT4OCI=)mvAyRC3lo&25;lY zHp;?eF0tdc7r*eYXpT+RwEsumjXskQQNjIBh_-|QJ8z6|KcIO;`N4j*OAV!N$Yqip zn@;8Ts`PvsL}NRYtT-`H;7VngKO$1HO9l>UCbScS1EMSj_Yjo3!ydj21y`4^wMIzE zs0re7cu&Z#N1|^w7`EImOd-AC;P$_}c_=*}CfnLf@AjPrA}7e1+p{sPdar>kTulMK zl$4P%B@hxj?Vk)h>@NA;Rgnnd;-jqunsJ>vc*tS^+E$EtrjF7q9uP-D5*(aNjsROb zZz^1KI#xq>7nhK^SUNDd??fw&mV!4>tv>A;8>NP|vZ%tyi6TR;DqmC94pw;D^5NhFeAV=h~X?L4nRg zcH|@ST0zVLC<&hCvQ9IFFrD1C(?hbk%s)-jpmB63c42tOK<1>Gm z0zz3TM13`%4tY>BB|Ca9r997)7@h5$U#LJsoyK7yh1~kL@B60|?I2D#4F2CALAA6A z2A{Jgd1cWOJ$_qqFBPiYW&3lmxjQZTdQmxcTu-XRJrwHl)w|ASy z&&aa;VUKA2_S%e0;J^NU*k9i@OS=WMeqlBCU#JxyhDkp#9ORRr&H8~mRTzqa)y_I% z&Z?c@eDDGE-&)z{w4X=r*gofwvg;rmwtf)_l+mzo1yRJ2*_9EWEt2LnE!seGotz

    Ky%5QVH_%*gr3% zO0=h~ZI-}quVi?BmNq9(uM?toD)Z0mlrDVJEB`TUVa;vDahNV-amdg*UNmxJebn{F z`tRA3vNQ9Qmsn@M|GFt7j=r>>IF62AKpVo|AY|BSJ_2E!CwWK#+z^13%QqRDz!`BZ zc1vv@;weIyk5Q@~fRWMGz*sF` zF&MkqnJc(S7HGBu5)Na3)$9ee7>Dmok#nXp55nXKgFL<6mUJloisN!$9UZ@ILQDki zgB$-x3`KPAb&#zC(nf!ZrtRvgOtEmme9yiXrN9kLq%Wp=}ohFP^uZy5cnYdcm6Euz~n@SKph5kN?)J`>e~0Oiz^EZ%jZ02;`KEF$u^JRo+Mp%sa(DybPcGK*n?+@B)0g?#_1M;N`Hozo9(QpISK4- z+ex;J&;C9y{@azQFm0S+%K~{<*_6cp=&F5B>AOpXrX+jK_1k!qJY!QAXn zTY#Dw?(a36%4h3ChLGn>_r%omWBn_knY53(uWMq#euk0=0q#SU&t)E2JT5IU>(0nQ zz~Cl13x7NGdWoMg&S2h9UL`#+@Bw8$d0^KPj}?)FY60pIgEj;Bsfl}Eh<^MuDpIKK zv%7B8Y^s8_VNB+chquL633Cl!BkkGRei{!ZBjTCjX zwiymnuEo{BGq457z!w)c=jUl@)%mr(zpDUXwfs#cl|Dj3Keo%p@4=|6)#S}BL_xs7 zox%IbM9=I%@K8YuiV1f}dv_`pVy4_s9xnqaKAf@Ymm4oEgQ4*gK#iIR)X| zUO zoS0SB01qIPD+hOk--8_i%p0x7D@@Yic}*9~L8Ht53NNfQIGA7#!=_zboq)0zV#?ic zc;b#TjAWNCQ(1&9X-&IFtn;E6sNL-ZNmI1Sw&<6tb8Ou>EId3B(%q`m_H&Q0{2bqR zako(A0BN~szK!vZp*nDmy9d$xjxZpiy-kdP_HooFBej>@GF@f1%&A_NvXd%TxLQy% zh31cFQ(x4`Td1rFG0aMJSzlhXl`A!aOXhXKKl1nn$t^l~ob4r3*ZDeeFsd^;+~}jz zYfXtqoU?8-^1L@4uubXvacc>x18QFZRrhqkth%uk)h=%7{J7xzfWW{EnrlE%@m2F} z*d&Y*O!?>1_50vO7Q->|oM;oQL?m**zbE z+4am{i9m71$+aVvMmVYeWPOjGlUkjYzn(F&K;gfR8B~5}4JL|4I~{UQi07 zG_92;X5bfrP5H=1xJv|vo%Kp)P5JGx^D@c@dV7R2P}mG{@BftX_j z-dSDs%TzY(qEo^_j`qAk!1YhAIBiaD(OC}M{Y{3YJafo%!?gKPKz8Zl0aoi>=KHbi zW1!RyBg1n->I0IWlQ>!gOr=vERc#eiNCtauV&P8$tusPxR&R0G?3JZ;o%LH+nPVf4 z7vSQRLkZou{ADBT#a-+p79J%8OZGH!v)7XmV z?V|3(83_E}5oKZpcuOy`qK73vR7l^F|vxGTkT52$}y7=$`~!-U6-ig zX6%6JJeC8$KD?|R)PcSR*WF2#kDC#GwDLH*mT3uXM!s1bzug@)SANXw&}|a@V0D;X z@MAJ}T`^vkaSirtGd5`Sv;&n(n}Mhy;t*lJ^#*wf4v!r5n~}-ywNy|V+ZwLj8z{LG z`Py8hS$PG4g%^|x@_tdv@6znAd#Yla;WZWyMM=_M!vx4P^XNRHfC2ZXXume{n_9u= z;hHj2!+xV|KQjc>8@Ay)96ZvyE=gGxc!QA4AO8N?U3ID-MO6*gXh~x6G{+mCbub*J zLKq24>6E@4ySd2uDlYG-v%E5!d7DVkww=60%8n`NcnLPo%Zp0W?uWHb9m*)Uj_-FR zDWYojJ3JvEeV|V`0Y4I&PIlWPd>lKhB^KN9dGfw9kjO&oyYLDEhqfv^gYL3cN+d-8 zaD7WsuS`%?gL4UEQ6ii)gsf(EPz(cCDNLy~vJ^ac)f&}3@g(^fVTTlOKAvOEuNEi6 z3vvqnY(1s!twGS*?9alw?3R&>?{jet2Y?D-3!Xr5Sq+!wlxb5WzJ8>|w;7{q^etFZ zrXL`!+58F{BbxFvp?styh|v?H9Vlae<_ZohUebRtlGAgMyojKV`+|!oyYp(4MHIQ3 z3bGO6Z_LD5v<$nqKv(M0%8}0f0>!u^oany&0mZ;uB;46r`64r}0o>0u(}4&Z(-MBU z4n^N}n*mPBqk7Q`d^srWWR4Hj0L56U@2^+CgcaR91)G{(N%F2hC*>w+j$}oE6$lG1 zDTq!*<^)9=w2T@VW#6O`;%14@)T!X8L*0nhfN}RgQlWz3qSyNB*mu{^RLFm12_uZ+E_Hj+&^{U{+d35Ra6%2d0sC(ckl8h>&|pf^@AYJh zo*mm!0qU^YjN(ug=n#&04OtnykaVC^L(g_f4XHLmj1|aR7|Uzk(f-&k%0w4(~d!)AA%28zk5yFxx#3ai;ouhLvBZF*sIEj9iXH+jm+dq z6XWLO3T{vphS;9eN=5z2CdRDA6i@O}>?%$y#nh}{@W_2{V2dP^JZTOCvs64De8RYb z+Z*n1i}N_ZnwEk396t})v4UtwkUOoV1Bs!}RiB4gdbEA)B(p^T3+lk+9jWs)Q>63g z5o2&Uu~e%2lP-oe7D#`ixF0)HFPgrvAgIhuSY8s0C1w9?xl)XyII`c@ka5g$)_rNgv+-`vnd&t{bS2-5-(&jTu5MTAG6c61 z2^TN;!`uPG1mo6YV9p)DGh4%BIT56Z%aB|0UynQ$O@F~LX5g_5Rq*Oj{8<>nj(nlo z5=X{)P0Xr8Exe2qoMc7=eaWrHhbT?IJ+?^)6fT~H>vdVj9xdH%*x>PQPB~|{SEd_= z9n$}hZvTfYY8SI?$hZ$Jlgj1G*>4E|>rNW1_+R3R=vlN&K@zlh+vDDb4(PoWui(D56GmkXTLU#TAv;z)NQ}^w_7)nW-)GzLB z(1X7xIhh@=q#vmQRu(Ai5Wxq*T3>C*-ts_C+nOqpck7c#_?j?Ix_Hz&Hq0k(w!9WV zbqT!K?4}5~u)p-3b3ISlmW1$OQ6)5Lk@IN6l2j~PTyn;y>F`z1S<}sf)q4QOHbAys zc+uun+P9=Np5TMm{^kpsoqr}h@E)|978(fmC$-cvS ze6t>T>V~Wbt_(wtKM4kJ`ktIE?dyZcI37j1S9GyK8PO;UO*Rsm)T3gk=W~3gAaAd# zytVzudPa)h8P+y@m5ZYhLhTenCF5J5`C~_O*-;ARj?Oi6w-6nczIkz38g<{7dMVZ) zl?_NVDW@lt;Ocd_vPAtT3b)$2bGyO9`{J%pfE3l&I_ATgF#e}vv5qnZ=ZF&aZ`28U zZ)@6}2Q>Q-c%HJwpZ;VjkD~o-t)X{~8HGJwEC(KW>|_Hs)O)#B>?~Bf)G)4xv1G~y zGK9W^Rrxid&8_zkNhkm$&k~4Pa;qr#VPeMcQIG+-ppWybmg2g_X942uBz`X(5#peY z3mpBTTDB|dKM6i2gt7J$$xN}EbqSx;USv!Dua0L$7pfkAfaKZV=1+O!)dYq4zMi(T zw=wsIJf^LR{{5MHy+JNdmyNZ{cdyiQ<#ejX8QyPK8^aYCpLC{j^jRnDtqcp={+-G( z?b5N-buMoQIv!4IEGEDIKT%x4Rxn|tV+f}$eI-wCo#CU}aJ0HuIuATME)L*r$DK!x z^?MVH;riYrGxk@v?`Nl0D4B>vM~5>uf^-2jjxbYV##oI-cCS76FiK9ZUTq=4(VT~d zFER)|0t!o3J-1Od!eku^qp~5#=;J)sAF=4)NMc_gbof? z-AmSv(G5sYtd9+m4`1{FvUoXo$jEsY#vN4ZB*5DKYS#=;4*sgg?goCj>M%Yz`? z?a{wjB#~xZlzLagC|-GFO(ocxA=RdeN;~+6x`ii_rk=pXa+u^sC^tt*j%Gl8-f@O| zuFNh!)FoF!JLPfSO5&%Gwky5^Huqp|Q#itp5WS0^P$aT70&%JZbO>$s1xOgDN`9{o z)Hj!rqkbUd3cD^YR?PeseM5L@*BKvD4*LcVEa?ly824;jJBfAU!ijpVZ{@|~Kc{zxTn_h;zoIp7`jP?0f%uZIw21Uykea>yUBHK_)S9Yz9i>ETrzi1{ilE;E= zYlLVuYwefj?hAM%I83;r|HiJNyGcbxG5lnO6tf_j?O&YB%M+BgxEhD+!@Q$wPEqo6 z@Y)sndxvb&O8cPA-hkTOAE|y1m3c2FVDyZg7t@O>b@Y+JO$f3bvz)esf&Y7g{+P1J z^wz?uk0g$ z4z4!vAW>7%I-4D+lDLeZ>o;DYl_E(vRIn6TU0GAmaA;hwFn=*7_I2E#7PknA8Gu!r zhg%mN{%w6aFh6mz4fxd8J%4Q`g(q#ZEHm{1%8v4GI#LQPSDRAlLCgtTlS$s($c*%gmFa z0lVYVBeYVN`>dX>cb5Q!s0*$d#we7nDgga;2t2fvsArJmi1UF(%BPn#R!nJS!`JX0i2#Tj zswswkFxTEBwpWk02_FI+^j?U0PB8~YEUs&-{chVj$ISsK@nb!@zMGGe`~qPwCY=Qr z!(=@R_=ekP1qGqw0LheGB4B)Pr|a=iya`oMbc59?j0~_I%as9ZGSf+5hnEl6D29Y% z!u5ixL7llNIj`Y#*Dgl!@&&Q&7JI9mI+yE}6uU`VT(My2=^M0f=I^gFO>9~)ZoBY` zJf_{1gpnyM^90Z@-XmYx@M&|vJ089gnCcV0GY<;j{NgZDJ5&ZGei~9l>*Kd5cCCpG zwE@(DCDoBk1@*C_xsKGKhw>_-^V4Igvl$+*aF4>g9PCr-gFc*J`?v6d7b8+;6wUOlz z0T?6ySR8oj24lS9ix~`L0VvJnheY~(e)=tmHZhnqs@}@Mzh;rDpzTDEdTMrCGjV1!NP!9#xSvDgk;5H4GR2Zn zZ>>cDHfzff@-1mIKgi=+2)A`gCdz7?jS zac%&d#Vp6bUWnG5cci#Y9KHc`{1BoRk#k^e70i1f(&C(&vbE3nU{8d6Ta@&<^LMpW z9uD2xsvw*knYT4n37FTpVTNcG1#w()lWpMub^9bK#Ko%%ijRg2>(O;{bGGdA9AY*w z&C#h%$Z?s7YlG-_!6pb{5gK5=`xBlUG3>eYCc7kVTDDJxux9RX907ca$ruAYM;e8= zJan?t39J>ILy-8i*4|a`4<#OYv0UV+}~!zTU74ea`W# z-qLAKf&T4VeLzZQbp%j}1j$+b zukmoOj%iy9n#NcZmQ-AXL@@%XSt2$ZteJ>BxOkrfyyX2YS1ZUkCt7j*^JB`6!Jx4h zjYYbXMa&YbCS-G_@TPKGRY)=XirztcSXxEQ<>zq|2&`+SX|`3BZqz|K0}+ToZkEtS z60w6l*7~CWbZU0(hIbLkhI|D)w#qX3CrLSlpAkl4aTJ8($Btv)myp>T0Tp#{YE{t& zSj_4o(c~a#RdVgM_@X}R!oVav4376X1th*nVljnGREtEn~;HYdcO-&HnHFg@D zRXw1H+7Ln#Y(kUk;?G4_G1I^C&&BUte8uO3^ZCebrI1w&=t$^B&WBPmbBw%OH=5<& zOnK>qWAAaBuzWJ4qX=&)7`iA+Be}h;aSC6peq9aW1=&NQG)wWSonu3$&kNGTs%fqH zBb{z8-t0%=4uEDT&;vCvXrRG z5W|#a{vhE!-HEimMo83m^f%ij4kEVO{O1OXiz+R&!@g+G^q-k%nw-x;tgR+jT}LDZ zz&@vTYHsABJG5T@e}-1)a~K9EypgH5%~|fE>|ymH46|i{dvWH%<@HIGPE&Z0$n?TJ zjTl8ZHHTd6VIFBC58z?B0HX)u(xob8TCQBT8L`kU#;%WxunMb1O(((PMS}6^iGA*q zJfAcX^u#=IVZvhL)pQ#-{gubA1XHzHh20QO=%;1m!D7IobG!VO*pq0b8sp>+;lcgZ zG&^DQ9kON>pg=TujMk=KlL#8i7lLd;RNW?&2ksI`f8R2$7z#4AfL zd0%Aq(kn>hogdxkJ0eLy9+FG9eV9bdTev@aR(lHt-`D}VdaqkyC7&?vhX&cywrUnV zd?Y)Rpq9pF%Og#gY4742(`a}n|2qh^f;gL5x>|mPe}BPQ7A1#7df+yMC(Imy zTr)W-`ZZksOwynu^=j;wbANI|eE@gH>*pNqt=--(S)lm^L5I|XN{?M7OuLvLiv-^< zfl`0KW26Sn#KIyov@vOBHDR&%NRP<{G(%x}H9v)>Id#Vz6*h@l*d=nP3E&Fl2H~pp zj$k$pvGCQp9TW;9Pg+LK$#)&40h=k}YvN++d8ZFZWk6i+4R9zC$eExiB1ZY}%am`Z zeLUxO>~7}|g`-*wNHLVFV~bIC$}e_eK=q}HvdX!Mjt z%=zRD2-u8HV|RIn70=~m@2UUi8ro7xAc=J0wIJ^xy=^@j#nQ;aF`6eUILr?W@BFG- z^}&q{V84h5@5W!W*pvVrj}O&#BE(t11lxM!{^;zyPPOIV8|zlL?Q<|6*?qoPF2}zh zCCzh`#{J`)NmJW~{vO`au_OCJQkT%hS^4chs8l-djaHg8HGTY4Yt*jc?V za4ba9cn~^G@sEWIChSA$bz|lr9B~f1Pu1gsxoRnxM7Y4ONOHb94DqJrQ?k%peuJp_ zKH4mE@uthUwvxJDGTfPTs9FZ8@$*znVkj18C{3jsg37%fUHxo3_qom)*r8>xLw3LO zK?JHBOrtYE)CzhO21i#;cI0+thx%ty1rw5i$x(HdwN(AW0( z0P*;Q3X>6_T#usyqH7K?=dM|z+@Fo1b>)qIG-3#Q5tV^HnjOSP*V1mdo7mhyvt7BkUDH(G)+4p|wkQd` zq9$Gq8SY`@d2J5dGD*CuAMa0!`B}wVZB${*Ns5X&jFK$YRie%zkGXhdBBKDz0o)z1 zTs4fs#OZ)|3+D4&_GfEsu-HGTkKUu}rTON^jgoEL0H|ycHXLSOY#p0v8hWzm0!(=K z7xkoK-k=fV)8VXVa4stP=(1gF% zNpzYNAYJyHs%g6kCLXB&nMN_YG+ar4@t^%c@|HlBbIa#eX>a2O@bTp06p<#*Fw@?4 z45+yt|ILR3h*@wG1*hz6pnhQ42MmXn8M40-B(pxl>1i8AoV%>rRwbho{2fPp+Ayoq08~FsNx!&()oOkGr$$+=PqlP?2*$Al!5<1Jj4W?>Iwiix?^i|_w>;|r z7~Y(1o_GwEJk(sb(;~X@jSmc9z|W_vy#ZOQQ0fA*0Unguoy=2@unAc}*TCJf?E1$! zj;1BoHp&A+T%#1$)VM>1KIOAGetr2JRm40RGrG2lL3dsFi`0z(`#97jhl|$wdc7Jg zR6l=yj|msgwt*2uoQkV@9;kXHr6_ptKk2%N3}Z8`Dz9BIgt#X~BIzfuQqbH3X1N** zqxF8I=APr{NxZrKy?0&}ZvC=ODCucAi6bdB#1th8UV@>5qv>6wkaYq%+xC8W%Np6L=8fm0$tEQmsw;xEoog1M2059xh(#{~&Z2tF4rB~#k zm5$fz*}t{zHltLb=E-B2Oe6n{mzJaB?)0~$Ws_A0aB#Curr64ind z@1N9{T}4F^F9{^WLzxYX2CPDp^9df5NoTa6O;xIdMJ+G{V!t?Y%G=E@ibhG@dCrJLh-kW*tes%v#6BVF= zh5gtz3WuRgJallPA5dp}ldZ}EQ?p{F>HqqeZYdJDH&f2<)Os8=?3a)KPc}=d#wAZb z?TQvJ1;WgiW@HIIn?ZjV)j^uc&bCb6(C4jaZ_rRy5hJir#g%^7o41L^L-UgfI8i~>-yK8Oh$NVPvQF_6%eYc}e{m(qlb*rAwx zW)wERczz(HJv-!h<_=w<-1KHv?+aLNbn^<2|M~aP7E!Hel|>ngi;FR!q_SaO{K)J5 zQk!;z*^O+9xB_?@;Y0D$zBx)WNC_#9fv=&oN2^o|5JTVvufX5b{%_QWY$j5HUbb`W z9SGNw*vBC3+(L^e?Z^1F9WaXF608}+_1w{jPean;<4e*012PSJo4_2L{xR>|| zP=0GI^9>DhF;}IbT-GTiI`hJVBDM_&Y`gf~`uUdNTGXo+Z0n8oqkP?0z6NtV5b^%I z_aeLRM5fW!^xjm;2E(&LY%&H&t&wmoWn!VhogjDXA5&H4?GLEvJWdMgSX%5-M2spE zOF&$bNK91<(%0XVv*H^#TNyf#P)C=e`d=kjN^7{lX0z_67JNLKv0k;F#ICEKA(qe& z4DoG}8qcY{k1!5PCmz#G!sPqB3W@}e*Sa^O>DxV{K3FI#6{x3_kFzIAP! zz&skps~%k&?n{K33|S8c&`!?EuGZw25s>Jw$Vz!SzMfT19`#W+A0mRpgnr#U8Xn(W z6Lyx*8o@jju1UOeYOC53!;dU%{6BD)q}_#_#us;+=B9^BllRi7EP(0*6GG-asYfkk zpDGZmzc%+kW0!ftduIm#O474lArpeB*38AOs?q&hGmhyMN~YE{)e4clo30rK8v3&sXP z*TjJ9vLLIHrib02olt9)Z^xY=M?6=|A&cFZpc(qfN(-6;C;ekuf>jve?hZgrFr67; zuahuU%Z50Cl0iM38$atql^U8?@kij35vjdCfMho&;T{qUA;pdY^N= z(rh&nBSPIOb13)0KO!yh8S~7kvmw_`?O5J`+z8uW{b6EKoA@xnnzc26m%o@bTc?ly z<00rX8EA5B9<`t32*T}~2>a;|Zj7||r%*ZecB`H2S3wao=yFa+A4sgOoWShs+C1>| zatvb&$O5)Yr${u~SK6B<=XcWMHAMQ;j)@f}Ia+l~gFEkDF%ICM003G~L7K`*;R;e^ zFeLxW9|H=iH6M}bVHnEbp<3SHmZ_}mluvYR9;54&t+RL z`pkgE;@e&b5PgWSMQky+M#S8dYnh?%W=0RtSpr(NLH_}Jg#rPmM977}ZF+pL2j7!y zQxSkKoE3?M*_A(tk0Zd5`Fhq0wy8kOFq;muR@CAOK8p|v+i@GL!9lViSD{BpLWf*>eR}j->%5A z&1@Tgpy(*NV5Xh`0>lAJ+da)O{S17V55h>o*>fr?dW-J`Mf8GQ8l{7xlnM@9-^;9l zuOWwBYZn?t3ayS0_v2SQg=>o)%`&Rev@C2oz(IuMXkCRhn$*eQjtVHv?GSpg&pX-8 z`-Pm(T)_M0)_`VR{yzOTnJp zq^cQiKIkESSNOpFYnB{!7G+btR1DR4!A<>>f(E%DJ&cwKaYQjw?zsJ`?zj5vK5vr6 zntV*(US(LCIOT$F_?ArOgC`h+3)u_M6|h5r;0I+J4_Xjv2OfSHt3!4xD&)H;M2Njw z_`~UmU2mMDh+*-D4087INe(@fW{`&?DXHm59(+I*2spqj*tCzHXFSG>*kxV?DFzX* z01Z8N)$c50OJJXiIx7%#{!7*(B3&|2T==|jF}~t`r`0k;+}XpUQjS7=B(>R0J!q>F zpE`JX6klZ6@qcfUNf#u{f0i~(%Ewh*YU#F_LeBO^6&iy{x1};_QI@wF?toyz5Mq_4 zIrkk{kVpNHI6TS>+~ZwgGcWciq(o*ruElIObTnNC>k2`!tm2wt_0j+Oa@H;iINiNV zV%qtAtps+nhMj}pTbh5yyi0HAb>uJny3LLz+EYbk^5;%m+z^(!W5qHK<&9?Y3|#!W zY3*JCniLi`TKt`7|Sqha1I9%3|~O8-aNYL(PLOQL_H7CSb#PDNq-6+mA2#t|!k9f4L$E?aS0ZcIJ0ZcSZ63&gmqO4)1;W z?y9NXBsiPby9-%ka? zl=D%AfH@N|iQeiT4BhpeU0etZy)%4xr{+uigLl`$ki~(%2YPPCLG>ABJtVJNIXW6@ ze};t|LH-C0Jy2gBcpFvRVMK*bQmLCpKbLk#CJ6`ag2nJ+j!Ww4K-luQWYo{c*v5(N%PI#GlWxk1}rAfO>e72C>R#Z^w-MubofjUUgeK}2&#JkL(myG@z*?ZqS}ut z2)Xp-M5i@nJ)TPju4vE$0Q&>vC7DZFz>1to@-AvSr~3VN{{|=DvgC&E_SD5JzW6Df zEMohb|Es^8CiVm&8~967N;dcY46A%fpozt7jO>*t5g;D2lTed=``3)-;{RGh)Dn4$ z!~g6H)zUmBY!VJ@doTG4fv@_()Jf_*SZPyc%KSY`op(@79#R2O;Jdrz5f{067T$bn zofYLO_%Ca((6`-S(N^o@=@jkqH?+A+As6DcVzOG@*bBaYM&XwHK^`~$)kywbkj%~y z*;w@=XmYKKD?hw7U3!rURFx`xLEZVs>+B~1#2AH}!einsp3pOd zTL3@V{GrtceD~fAW%<+E&B9m)=2UiR$ceGh2Pkf zSH8dHz$}b~OLUe@JD>7Z+yboElK}2{+%;-)1DzPf=?+zOZNaturlV_rnz?E@I=I&wCD zeF-rFccf~d49iDR*`dN`!@S>yPsr+Tv%(>~VEFsu}CFF~&3f*oiA{2`w^xJMc zlq(-?5RSGh`3@%#yFApyqZs|}8xqHl5qa_5EE3Z{FF4s$zz@*s%hYMXMWftWot112 zrgbgN1cipWm+=pqJl8@18eu1qYVy}9qNp8=$&19EMtKVm;l;|u7EW*K zM}-_4GYnN3QzbtdGv^)o35xGM?uqQq{ZI|aw+)32PF%u#Q^ZO?3*1T^vY6u=#xL1s z!PyXclQ7sKS1*_Zhgubd!1l9uB%Hd(w~%U?6kI0w=jT8~B2a~QbfOo7JDBHG^^su> zm-iKzSet=kX)3MpY|RgOxdppFn-&J_f51lI;oyLL>cc?Gt*$WI6Ee7SK%<~hrIn2f zkN!xf_f;Jptas9eBQU4L!V3aoT*h!KI~JpZCbP4h_{)-6(O95-j;z_t){^{l{Gse5|8#rAjZ1h-4`84sX*W5=dWa&pnzCQHWfa^;o>?fOCkA| zn++eGw-gHnJvm7~$@f8qf)0K3M^q-HO=?~NvjL%$#vec)R6sk`q*1PEGw^73Fi0N^ zPu-D$SnS*-jnXJyIZO$)sl4io3YSq7!@Z(YwlWmyQysp8`zol^5_a?mu&{vVq=f@R zVmHOMG4vrrPx5{YmsC7Gy{#l&+R@)W_`HNeR_i zmpl^jOY|Kp)wTj%^*U#R6zjY|-ff!>9UKxTV=WQ@qBGof?AIA*f#Ro!q^;jmef(Y; zflk*1?5}(@nGaRe8fD0ok!!-nB?FE=CLJ%Pzst_oAKZz%~umZU1K@drc`ji0q? z&aFNOldL_s>oAB3$(gf~iJ_sZn-|=GvmHnVk|cd&;LFfIpeq-0xV`XD02`eqQrDWqgKtHxPH)$u2q%jE`ZZFZ8lcyuoCq8UCeh6ki))+qL``4t~AF zn7r3=IXH4j3_UZ^tq?_$XHp`dFqn}FP(u47;c`P!|Mh~f>i_;3f*Cu2=kEp>uk=QS zqG9>fIFJtQduO;3PVo7N%h!2@-zMiYqbotm??H&mm>30(kjHvKgnJY&6H{Vk&whwY zFc2Gk={>m)3r?LIy{WV83R>K6a;hki0(0K)&7zii2~%I>_wMadz?78Mzfz@D%fu>q zLuS$04f@%!_$S5nbXboz*i6L%550q(l>gG7k_0N2$8Ow)h}bM&nbCJGuPPfH zyp{Mskx0p!bheO(kGnD*a;98<@e&sc5EyY$L=V8aTL2D%V{2hzsTf#XLfCTi)>FRV z|9vJ+b9#v6P^HOfJs8dEU(B=wVHQHl14gwy`Z=nN5DZj0Z+|75F}D=yAf?LEa@8G| z!yUy<_pi~lTCK4|4S060s9u8!1p#)9Y5B99V*cJzBEuGuZX^qF3FddIeg@W*h%FjO zivlct!>gu*Yw&yOy|3I?YB|OrVJ)~Fj-g(}eD&19{A+tbj`*X~aof|& z<{Qk@Ucpj#mj`jerF%$Yx(|IAt@hvAhwn8h&(37pd(ha<-)^k+frkH-Y|+32 z0u%SY;B>1cUuq2u=1BTTl}Y~cX5mi>UAiOqvS5XfTuFd=z8V{7>ts@&u)hgVYj;nO z4_|!0_wG~{^ky)xbYXxmcYXOdbPms=*A*Bt<2;UW_R_s6#ls)-Vbt&m8BQkF7mf8< zWVT1iYIdFnZ~67)ile4+=f%*LBM4exZAj`$`7zlD+s?by!~fgXha<{Hb-1NDh$b0A zEITP^x}xh7>{R&J*J@|o5vmu$3Fqa4OS^+J59fYXqGr6Kc4^i>1fx`K`EbKbhMWe9Xad8wm@z&TE0k3#-w`cUpV^f46V4T zWR3JJZo-kVUl1~-?*`YKczoHtl`)t2!xUTE(SJ)s;<;W>k2eK1?@LsDzdy1i_6<87XFmIfFZ@)*p!n zf@uab(Js^y9Gq&319%$qiPi+=8BvXwA2+C&Wx`-HO&a7M!^G0!S4yvA>oll$AV$mJ z?>Xa{Dbai+pSx2`JfNH79qEi|k{p?AFsO4;M0!aAnxMebza$xg zwE~p0fGWQJnt<31Mu|qWJ!`~U!3L1C&PS=})(He>+{pF{gvxUuSC#P#)z-qZh_av) z2Kou0R2D8z(JhM<=SkBA>c=1NWu~q9#Dx-axP4G~<5Km;NVS zjQfcqUH$cpN5Ls)VUaZa2!UOe|*8wI4bda=4? z{O`z9iY8;j>~o26$Xl9YkujYT^GmHTNldRU81{gqrWSAXbKD804s!Tu@ZcZ(o~Tr2 zJlO$*i{Hr!`IO0$d8|_UcFt=Y!Pm?d!@pOOl?Hjacx9sZaZfL9RN1)t_+F*yRGOO{ zd2w8>O^uj9lhPZxtm;dYemL}N78ixs>$~29;tI;=9&oTT_A5}AEP=xIh#+RBM$04n zGZmV=eT#?jGdZ@ZTLTJrKQV-9Ho_WE2UE(JIpObseM#BWhoUzni>2tdL#3j*#{&$X z{7wEg5lXXIX(2Lb7No2&yZ~N*WgKfvXHrN>uLZ|X;5IV>y_B(wz<5hGC(EfSMEiIT z2c<*jad|F7OA4)M{dPnV`V=h;nieU7SEp5mof!Wd87n(1ydsPTSmyVIK0jAZCoNAAH2oF>1URu=jk0S}IuuneR@UZZN-MD%HG?%x|$ zxZ0SRx+0Zf)e?Nw)lU}JDXLuw1jCxDW%op+ZGqtv&jxZ6)`-GUB1f zefo58afHHuBY{~Y9a!At`Qe#b%B|pRwljg1L}*%ofN)R#hnj$E_im?uO`)=L_t01U z6>+Jsq``nqF3uG#op!Y?WtvaC0Y@widVcn|iYKuX@~4jXdy)Y>bZGpJ#t?J$a1d!1 zZGu!5B=rI(U)&l0dLJ^W{1klbvt#)I{~oie1vk_uiuxTt3)l<0|CD`9aZ4b7rb0HzvDUVcx_+ia0* z)U})B_Mi0_{YSDor|qMLd~<|`+<>iCi4S6h;wHGy9+l?$Ij0CWaFp7@d8cMtKL--tFB)keWG@hqQsfeY0ADO<&pujy&mt^{k9 zIY`<~rNVNA!z0*MTaE zE*0IT@n^r4mjwkaRLNTI_PcP8s(MC`BdiLIajBpHB@!7T7HP{@5B>0-6*!U;&20^h ze8PKvCbBvzsp z^ajD4Wl2o`*12hD`!s`^U@e%b;LbUKqq5H_wDAz?AaYwgdz}aHrK4jym{D`b)%wTi4!bgR~%)Yr? zUJb1Miol6(tn=+!C|yOjC|XKb^$uLQVi9Vt`SgTAEnKEQ*TU_;`g-)+9W-LD%~a6U zrKhsyp=hqEvX1aY5%CNZrse#jw!6u*JTy~t8~x$UrF$=j9UGc2WJq=y>A;S-n*X|P zW|gxvCzBwCE|WIa)Y`fvRe?Z*PGo&S{1qgp0Uu2nn1$R6*a`i8Sa2GrxL%lu81Bt< zBCyqG3GpCqPX>)90>xm7Qt%4{{2KGn3QM)2?{NrO%0ATJYorgS6Sls^;+91 zQte1%+OJXbAYZidIBH!l>gXQ!9_u39pyA0h+#l}bh_mU$QS=W^g$~8HSk;!UkS^NIQw zbCUnzTHHRSTl%_p2ml*mJ}Ry07r~?h`$b~Y`~}+D4uuSITowoH`Df;7#D}|>>w)RJ z>bT|F&BRg)mEN;v^RA+Tvs*y1)PUa!H<9EufFU3vn-;$eQ39uixHfw8uu6Vd)avGJ z09LFi<j%^Qu;YV<{tcgnGdQj!~J9tv0f1Ae?#KR&lMq= zjWuZyUmnLU&$L(1VL(5G8=S=i!f^`SOmo;v1`YdqqOMaFI9bChe9>YEB$Ic zNvU0vzDc&OxXVdbW^Hoh9ABu_5i=vjG)ilY*qj#PTYT;nWpU{%)A|x^jIx#NhrGz! z`r?p@Czhg9X*4B|es6#h-Tcu!=INpRa*HQEA}kGRn$!)EFEfwdf@0F>7Ukw^H?m!& zwVpb=*-fdZt)i8fJ_4zz8Q>k@TVdr0N68gtp}`p{%Lx>Y2Z#DDL(; z5}~k2veI=BS+?^|AP@wD<3AQ{VUgQ5{Teh<-^YDVTsw5OngkTe%}x(2;?-73JI}DW z#%5O=Qi<)>C&S$=OLmx8`I@_G{pT*gO|??kERDoNvu%8{jR&7;^YyA^=*;I5>vUx7 zjFjjz4TILDs6o`bx|`K+s14@qRoxzBP=c3&mdRIJgI{3TpYeX;OjRNC;t@ycIPQ2T zs!PC)C2qmQDghKGgl5d_4Bdp;tsAk{LpqiLwrk9gK((M)yt8|?hK=aRKmcQ$cWv|G z;4ji|Z$6gU>N3?Lnj2n2{3!``Jd>UslDnNo=92vC`8@T)6g7pHgek!@EBYKcs)y~_ z^&fhaK0^#b{hTgieHbiwrb{_Q@mx%M4V&70E}yv%}>O%@c8?!bc$hBNTfl}x-1;5Fj)bybg_5dIjesX1`5FpM`t$!aS=9Pv};4R5gF^1Q1k_Q1T)5 zjKiCU$}3^v*#MZ7R^X3gW|_<&V+h7-*0_9>qNSPYc{1RY)E&TkRPdi={+3Sx7J|wN z>fee(W8A)C@(yjFQv7*1vzLy1`6!zl8$40OQf!K2_(;IgAfMx*I}8=mivm+)M#eHO z!d5>)>~%a${<+(YtrsXbQvGbSe~%jHl4*!^OHSxZi=#Z$c$!)0(7Rx+gn)IfNZQb_ z&#!I<#tta21JxaP#qN>)d&DC`eS!Z>u6fWyG_zUz#CQ8)1sK%qpaG+SBjyo0!m+j* z&LdWSNwvn3GlZtkWa&x;aZ}i=JFaQRnXs6_B8wvo;+KP?qj+n7zYIW1!@7GN#r^jSojlMx5IoT3uQ|u#96^qxlYv5H zaxek3f(I00hWfY#Q~5-wPS_rH$05I^MS*R7;^scv^$8Mawu|eWx%|{N93Q%35W&*4 z^RlO*<}H00Y8+>Sr|x#;Tsv1{ZG3Sh$3pUY@|@pJGUNQA!NrjBcIO+DJ))oCQBQIS z)ShyCldV~0Cym3GbsKa3GT@sYJi90e`TU>tZri~s=(t~^ydPRYZ_ZJXn#jSSKhccJ zyJfj7OEXCWx_)E~=}_4Y=eH>Ko^2{Cb_k_V3s$_GD3{<-J? zR+3%)@6FT_BabhI=&uWZvAR&KDz-)vI=Y>bS~{D#lw54){ov z0nfaYA(wiRWoq#z`vQ*s6iIgwQ&m~T#^(?l0CU+8=Mjh232-oJ;?>kSVsfNHe;(Kr=1vIQh%v7Wcik#)j;dxwPaCco2uT@2a+RcjIpirkrK3GjbDx?(P zoP68mbeLK(Db^C;Hd|wM6e5WM@a49vP3c1yUW@-xA%LpzDbj3sv2+L`Z$3Ug*Qf0s%yoPT+vnNL0YYGBQua;Dq)ukY*9Ho||GT$;`)cCe=nKVUMuA`( ztrr`j83jEX{GF9jPH#2I6Wy+&^mtT92ycSQG$vs(a&Ke``F}GSGR|!cl}~1 zBAZppmkMLZ2$@}Ij8qkJA3jO8V$mMlrAYSG(BG{S$Z*hUb{v@l=&d@6DcoX^&$gL^ zBQ?%)2Z@8|cOIn-k?iDOJnTaN3->i^kre{qlt(jkLFDId2`O2fv{#LQm4>%R&8vXO zj^pV+17<}^SxtGDhAQlJ!+ruQA|+V6gS!Jt;+9O@T<-XY@&<(GQIT9&JiRW#>pm~i zrlweIZT=uXZ_2HxI!r5;qh2mb)cxD9e7b7M(=hhhIv1EOWW3Q=H4kk4yk@v{gg7o# zt2%|Au-gf4fI~VlS_>$L<7&e)=~e|Ay}x9N9f!6)1i=0TAdF z7P=PxI4(+8=nAmZeQ$PyRZQb*`;**aI+nH0fq+f`>o8dS#@4JcH>LaBLb<3o`V1$9 z4LFCJ$*mq|?dS$Wkv{)#M4U;Yfkc*GA(Lj8(yI?Iv9xgo6(|L=SeSvoH4Y5xh?^9C zjVW;qj#rudiPOlh_z31tyC0LxR%7X7 zc~M3TZCY2$6H3+Y>KRY&W>7d@ANB@_4$Y1(yg2!z-9OK zd8dq;;NX@J5|Lo;Hs`0ZUk+#jk-ONln`y!N!w`HSr9eJ%HP5u;9Xv>BfMMxvR0!M< zjcQb93UY=YEL!tw@~7CYhysuAC(h2ASv>Ga-eQ{iTf8!Fr={Ir>ElJyzKFDbOWx~JPtN}>UcfPv~&q?HN7ShbyX zc73(F9Idlw-J2hgZ^%wo!Tx#x7eutWpCkSJgrO-zV$f81HaH}4)rO}+B6PIH2_cp? z&4o(_l^D9bE?TChj_=x3%!Ko+LnL|!@zk5u91u1U?2>>3;OYVNup)&;3r5C>aQ*eETZkJwybDNDSbS2dftomp94a@n#^hs%G@EE1Gib zeVWk%0x<)_lUsI{c`9pU)J`xCtsLd?*AzY?u>3L5T6!`mnaTA_%le9VsQWIpep#)ik)c+#-h zEc7UZl)#6>{>tHk*%CVNa&b=~ZL4<@Ka*)KZ4GhPhEZcGHb0Nze${Nbh%Rna1``wQ z2n+3G{2d3sbtC40o8WiSn1E2TH-0rz;k)bgxUuovbP%=CU<4C_h+bA^k^x<{Rhsm4 z9VomrO2p@MJu2v)F2aMifa*k#mfk1#Wj<3p77WNr7$0<=CNuA<+gGLlfiT25%stoH zXZziibh^4=Sc)D&T>8-=Kx zoO!Qhpa#-SM)5j{$^YxadufvxLP}u?>tjeg!Yq)R@za{1k=8k}k+FsyuT_sIlwQBk ztJl*ie34^#Gbn}briI1jLwvrBnicTA%0tf<2^WXNT zqfA-ucHLshfrZR|(oH(&h->`-YX%qDjvHn>E@$4n-+1a9fyX+x0=gKWg2Zl^*T^gV zxL!AB)eHLLJ)a{DHq=)&_WWp5qne~AwJ^UWjWj=dVY29;b#MXQ&4w2^y-nx!l*REh zMx1}aBo(^%T4FSlHlK4$m&VR)_c*2~>tN1q)j397IqZ31c zxuZ^}V}zLa*vO%|YP@#>WTHDK{9mk~1-Pm~FvbV$;lM#B4INfG7&Up+zz}O(S?|C7 z><$WvF#5aLpGxBubZh;3V>E)r4Z7jh;!nj~dE@-K`7?XL!b1t%`u#N=ml-s(jryd- zBA0Xc(06)1jajO0tiF`eAkF|nT2i44WWs1%3?3~E#1#traxDy_TI-M3_ zx{MCUd)IqdD8vqOD_7&PZ;bXDxwf^=|P!cD+oPT<6>w$ z(x^+I%@^Xi=ze;D*4s8S&@a>+XHvCO){Wxdr>NQ0oN;!TAkD*V1lVe>yt@ltVn!(A z!XG0cMTo{&7&v7^XPJ^gqS0-F8&`4rO*!SzfY_%ktG1<;Z%?f+@y<18WDp;8VUvCVB1Q3Ul0Ns*UzIl$B}a~vu|#W z+<~5Y$@)o)^|O$Yv|t1lQTmEJ1eypBTvD|^h==;YQD{xDfD9+%GVc$YB^Ta;8wu2(;=BU1t+}!nH*XF`Uk=R3U>P zPcl3NV#@!gAzGyXaKk(fbF1aOZ>(J{40tB*VG<};nh%joo?`Q<7$D351}zMjs>#&)wOLAM z3jwvf9_ESV?N7et+J=VNJlK`mgrWUEnmeji{;F%g3cLH4qc(Q8fj|f)hGX-b$Q6jomPT=8`_=|vbFmQzXtvPMq zLgA-~hCAtGAwyGs~t02|EpD0Dxm*jSsD{ zKK==$*#{Bbe6mTByz|d-1eG1_`S&^keCoB~sGyfLPRL3v^FKQ;ELsH~o{^V+y%F6- za(E+o382mJhD1?Vn$#RAM!|JZeiD9k^XoX36ShzXtV3=t-L|e1;L8kspb-ZlWOza> z0 z7*9x770889I_fjgZIGl3`jjSM`52D_!|rg+RxuekV8pSmy~)5UQX1|8acTpw$>q|+ zBc2nFm73^%o-cyU^44;Z=#KZA?&HbaNf!TC;O?skj#uv6?y zzJwONYWZ}qbbMOpk_O3f)RcL&7l9X$c!GG{a>ZlP0d+HcSk4bDexO)R<5N%}576p> zb~o?smXTwg4i+*HQ!?yPFyq(di6a#7fA$y>k)EQsO0(GX#4=BMXsGN5rYol~)iC;u z?1NUKeWI;zJ(~hy`Dbp)@N_U%XHE!qJ=^Aq@J6xjiQx$@pNHHzDbE&NXoERT+8B>kx zF|+0!?TjtvRtj!2j39#8(O{F69i)*;g5~`~(-+38@8lw=7byyP4a|nhTc6}=fW_L3 zvd80u>lJsFO{4HVFvU&AJp63Z=QQbh>!i5_Xeqe`+50p@&U*x>r)qA184%D8v7_I% zCY{B(n?-e~D4guS`-8K0+USM$PHsHK+)p&A1lnKh#~}x)MiQIs_n@jAT@lU~@bLVn zdp7w6(B4js!b)gKmGb%f))<<*v@!00`Z2yP4{=BVwQWO)lt634g*sf3WS?6zZR4$b@;jMc#5V2--W(5SV& zpk5_$&~W4!K0DY1*ObMFF@uTNbk^836=rj1KMv{1MAC`3^1jZY8k%U zti6ry80GaMAFv|+o6#nxy|Fo278-_g?L+0vc!qLrPDBZmH&{IfKj!0ivtjKTC_6o6 z;!MOZQDm0%XNi29VjG#h@6C;rTt0UL!N)zfM2*1?qSK_cCo(Bs=c?oR8EZ^hz}2QP zev5Z{9Qu4W9+3beoPS*JIK+I!cRh=689q`LZ&n$sHpVoUd|cf4CTlf2#srV3feeGf zLf|4cIhZ0|_V4+fOqTC}_X-OMf$Ugf(BWYi!|aFIpT!2o@P-noNijwk8(1w++k93n z%qCxS{*AcKA0PR8__uizb#%>$x|o&zEmC*OsOJj4-i@KKq7eWka?6l*SU(kMSrD%l zGGo!=LSMR$CTgNmI)wl)kny9{Z`zCOqqXq9B~|2aWrClW5XmRusv2q9T6g94?8ZY*ij9Ik^()tqDtV@Ijw^=dpfehxsPcL{Rr({|B&Bl*5 zUXAgzR-a#|Z|mIj$Dwy(!?~`?4gz3pftjh%`nirm2%TOlJ*}oRj$WX7(Q$iQ$u*Jd ziYqhoMjYsTT4Ea-*V|I=6ITe8%H6x!}E%AcpXOj87O&+Ldk7d)j|s^!5Z7102mk8q(E^p&upAruF#qNH~86kO@{yW3$m)f2%Ga(%P{R1!heNYf z_Ub0w!dORqj8FN98cos8>bj7qP+_NepxScPXSe%~ZXGsp4Uy^!*bbnmD-qh;aE4zxxqLxaf`P(ysw8_sR~W-3w@|qt1x&UVIQmZ1ktv;1udxp})Td zm?8QCNv?CVO_06W;Eb6|G*)RMl{=b-VM5e&c($qJkyOM0P*X(a$c|S3c7$e0$WfU( zx0wuhM4DHb=fg%b`g-UYZ@ct{onj4eA2t8j6fbJ}cybGq?W#U)1jcP+EU@v1w4u;K ziuzWsWxt!E=81>J;A$0tyEWAhKcCc*jH^D5c++Z60mA0k1(7c^eE zDqCZ6A9kF1?HHFHNP8JhI5*a3Q)WzugJIK0An*{YfTeh170B(fwuuy>o>Czm>)2ycA5~ zUbrUUQQ_K#kV~vDdeN9-UQRvtF`iaXl&GRx*T{T{km(3lZz3rxaAp(QP?IQPA!B>R zos3q8fN`=A&Q*BtbvQ%#YoCuz24|q_i($5VtK0VJg3+4um%C0$4$Hlm(j6ayqjf4t z;G@E80!y5KWjSX9iGkL8zSxDhbrokS5T%JCWZ6p!A5tLS!#3aUY`+TX>vqro)C`;0 ztq#6Q%27E2N>MFv-UNmZfS0gL9h=Vu8X;2xQ@x$#Ux4(f ze+35)4C4IeoC0L+?a)o7i6j5KiyRQ@V2J5ggj% zbm1`e2sxRlFXT!1f-O%IqHZEGA8&~b^H-@eA?P`?^doU@^|V|IL?6N1LxEa)It&pBo~;q zklu+3Q8=C}aX1F2(0}4y?l19!5>`+;mKl%kjz;nk6+SD0`&4QylaGv*gN2J?QmA8h zNvk=l(MZ9R>!-$2ryP5>F3PW55pD`GTktx*7`6q0ma6D>A&qP6YJ!};sT6##mZ=p{IkxpfCUa? zKLi#x(GASWlN?GKt8GD@dax6YixYPjh7j`iodr2Dk3p8Q?T8p%MimS9s zbAHxQ=3bE09s$Xr?+(hD*h2X8;37t5Nx&EU`kh4wH3q|a@f7?OKXt7sI?5HEy#*>SB zMSBz8+gbZ9Kc7DDT*~Es9K)z@3F((!B2sfGHl*AwQzzG871@Gv;fW!8&+kT7OjNk{ z4?a~8q1Js*P>bJ)uv&r>fEDiDsq-*IV~7}t0$$sjkW0-jNv7W%@9h)mtRSOe5(%nnPly?WRu~! zX#E=@p8$AD8P$hYTeDEDEi;eYuBp!l?JxoLV4PBByLqDx9l4nlS9wKc}dG_PQMHAs78353rYq8R_XiPoM z5>NcbD~YECwq_euFE7${4A}N=h!@s6KYU0kdMjDAljUpypFm0r*Y#0#Sfw?0DUvos z;BIg&fe0(dDsUW0@M(K0VlyO@O9y0ruj=;=A#dI_6No!7r6EbEM9q2`D5&YY#K|z1 zMmdf|g|nU8qLa>h<0l4hjg*UzX{&Q#0JOKPKgQfswUjuNekGxHYZ8q;^b&0dK233I z5Jz|_+R)f_YQIeVc1e*1C1g z7~^j}_;%!{1wkhN^y9yV~`Ot`X|M#-f9PLd(HYeKaloPK-Hs_!^5h;(x79 z0!5mx!x&qP|ILcv^%ow}r1yuM)Dw?e4b9gJRtvszxjHa4cydfQS)zQVM>|_wueg%B zNvvqWfIu%JKgM6vsu9En^nQZmM5e>C?2U+Ba7|F=)uvf3J2I&zMM_8h=Qc!%jvA_Z z{*MaSR9pbWH>Lzqin<+vZ41E6&I-;@@*x{AMH!EPnCE_GvSVbFPBzaa;BD_#YgfcE ztn@M?;8Lp=6>LRy6RymL!ekWc=N@_=RDAar+wgb9jV$zM5aP=B}orF}Vz3d*db64}eri2#y=K{DEYo z`Pc~;clAwochno8MU$7lvJ0IHmJ@v_@t9qi2UJsPGX#9>`RU>DJ%^g0Crx5FV}T9_4$z&XyM^y0hb0o6=a?u_?XIw}G8 zUSWj@9e>E7(_8WP9g}0gp<=c$_iVTNy;^OdYC16#>eA0={Mee~*btQr852T**ilY* zaKB^@nW0gTZCigg% zRc&Oky@}{PrW)x0JEe5J#);w@(p2VIi1lf`PZFs}P{i!P(-|buV5rV}4b<2J(=Urs zn+D^Fo64%eURv4$!>ZpBL0(xE1L^Tk~NGA%3rufvC z{%LA%7&hq#B-=}$!UJUAxW)6VGwh-**|?81xoMI$pU{>^0pvO4Tu~<)^hjFb``D?f z$#yb+`%dKEyJC*4l$u_iLy~WHkXe?pv_9lYUa4R)b1p+uZ!{`%_nR{c7;~25;BB?% zE;$F)nw$NcqLha*b*P^<+-<~gx}Vfj{AZv#>!o~?C;TW?;l9QB-}buN#T0|eDA=sB zN6~S6o2Z=~-?BE%1PF1Y=#k>236f9#tm9pW01Fo9 z^K?QLs70Kjq`>ofrgr^wHeJ%1@JPr(1rzil9;f8iQtXYn<>Ty$X=?B&5HVj2xUADS z;h7~5ox8fTT+{bE=X2yFh(lZY1hGo4g)2-g%8aOX3wg$ivAF09N^)cTK(@FxnpBVa#hMV6 zM{wx!jRiJ&gBo8g;xdXLCq^ha9_a{?L(_;;|LI)H3b#bZuAYblnT&o8g zB^g3_@Va)+@?5`F7gi!G#4|w>#Y))ZA6jE>J%xJ3g-sVTnZUCw4)tPyZ2=flV)M?j zYzlS5N?hlbJ2trhDJwu#`b&Zlfq=J!^{J}o=>Dx+0Gj520L(-h)C1NUeldMff16z~~S-(iVv zN$wM=g(IU#cL@;M@8}v?>D%kvw;_Kk^`ag#p9y0&xTXBc z%4}88Ne_|ziQS9)0@b6f`jfE(5J1t~us7BplOpnh;Brp`Z?Mz8>4wS))WuBp_=IQ8 zo>D_Wq!3B4G;Ntp!|*x^hPl?gm7A!>RT=J;S?sV*{iH3Z5jmA%)ig9vCuQ+R(~Lq0 z8&77cBM)qF67EU>!N)=e#dJaP)`K30jLlZp&C2^}5A+Lv$rj0hXcXPW3HXWM3?dVf z9oUM2xqHI@6xB!;_0E3b&3L82RBW$X{QLJxccGl(9C#IN}4}=s~el zKMxvwoJNURa#KDYzB9Yd!yOyEHdNKMbw4-6pe46KM+<$(hmxpvSOyVLA?&aj=}S2* zRl+G``k1dmNx-xU)sof02G%e7Xe%3wJ9NP$&j@u$_}=)sdw> zX|eekBa2;BcakQw(hkK@XHws)mr?}*=-<5#M@xg>b-#bzP2_z8p%&cd5O#N zm8^V9?nL^KaA@^g1psO+p?Wq$^kkxkeqEfCk@~6ahvBG2QAjXKY+a+4ghq*5Xq5~9 zj?QJ>G!ADRMHcc644x&c4#O`L{cMRi{SgR|WiQ8izNOh#;QnNd%I(LsuFr+9ob%K!O=1EjC<;{w%DDU}v}-^pg&KFbQ%tXe z6u=H@mvIQo>6e@KcU;)esmO%#UPB58pan_j1eT*S+SA{q`fenTvKnA@-*r;w#BpUW zE8g+6mA0Z%K?ez^grC5gu>}Hzpliiozxr!lwnaeZ@iTZep0$A>;Y$mzRXg>X9;f&X zgFubcjm!BTA-4D9R>w#Wt8{cVs}lB`7qqW|{xOQ3p=6F0~;_e=REOJx}{*C%j# zj>ckPs|A7OlTY~^L|;%pK?S^no>~AX(Q!;j41-ov4qp;XOMZEp!~A|!+_KAW3fHei zS@~;wt@Z1&Qtoi9mCM>IkvwCk?a#Qdf)Ab#_K$g57Fi6jV9;Pjx|n_#l*b=Ah>*?O z84ACmCKepT?ienH8Q5U{@%Qpe(F+!zu+J+`=7ist6D?}nXQ_K8cuh*=*s=pIYOT^# zLq>nR8YpgD;M~+BPr`n{-dd?z%R?+cWY&15! zb-wu!kK_6VU6^V{qiyO)*^SS$uElJ7A|J5-4%^P3BLC-f2fe5)^06q6%|p(L%KJgUZGqF z@Zpj)n?-w4G-LkhyFY9_x3V8L-5V~?i$|~1pdg)HXNOr}_O!@vJv|Lt*AC(UcG($; zn`*9RStp2oUgKTP-H-jRh+S{ma*-&Nu{vvbX*W0_0g?tV-8NxrBWsP#?z|J<-D%>G zoh@92&b7W{k#Mr20pZjbI@iad!f)1QPq@3ObyaB^6%)9((yD86->SbwuLl{Px+P9j zlcjq!8HA_&X|on-4!>`snrQlGE5BR_-gvRB`ArUJx8U&w46S=p|0rM{hx+FYmi8!m z^!Vrgwvr<=8O4-UwL}hn-MDfvExG{!NTX9xbqCTM8l5o|kMvzTDq2-gPzqyx>jH?o zGt=mjw$+p}t8pM2r3O{|>_1*9b!^niwRBF%gfP{+?DqQhpO2SXyM;A9=y6KFP|>>R zwvD?}l_H{JxJd*0Gk=NczL_XkdH+bbD{ zM=;S1AG&lvgY z=RgIaW(ZEA;qhJ!RB%IXI%+lDw`@_vU5JbxQJFOoJh6mM;SUm?a!%ERdOz_(aBG07|1kr z&lxJw+MmSgl4j)D&Q5H_X?B~1Nkuv72D_2PGVgq9WaXjNbymbFBz(1eOy;=#N$+I7 zi=vrt#o5{8u)>>PanWSDDF>=IqZ2cc!u!3O?5$+cF?;Y^@c*}}MHPRbIlK5FXMs-l z?FR7SH3V9!MUzU7^d(vIB!4B{z=#?!>r3vEvpO&bR=Q+0Q^j3fE%V3p`fZA7ufBrT zt7DXOB0DgC1aR=SgHa96RVgo*S%{e@gMo#s3;g4;2K`2B6DWTUP`@KF3Nn4eT)*Wf z+=kg&W%?pO#oN3}DIHDLO6TMPh{S|st81qTlXn+C&BSwPaTKZ9=D2Ua`aewh8Z+}U zxK|PiVPX5Cc~JRN_J3C1lg8>rW8`sTQJzRzl3;BlCJ3g^LRLq5AY!SD&FBI}uAk!S zm$!pM;MF?`62;Fvcrm%5x_Nl~6*aaT2+(GBkSp>-+a_n}IK1FS5p6MCJwTAJM@H2J97wM@hkP{&G@?LnScB#3S}9j2a+7;c}a- zxVtAtaOTZJ$tpr4q05kZ=e_vm7$4yI#L=tENEq2Yn1=vNUQ&+{0yIQlO-Px=H)9B) zQdUgvTC!b2VANoFfm`25ZOq_i0TQcehR}P_a#qU&CcWQ1PI!w~P4RL&C0Qe56Lwi0 zCyw!2LZ=Jj61TzTm`Q*`_UH#{S~ayqJig@dXdtW z=1R+0lp=KWK)P$rYflnBJDzE#D$Cn93(i0K{fm{kKb4s_qh)=aE`S|WC2?{@^}47A z8zfT{#VfA66O{Alpn#k!D)erQ?@5r;0iLU#%)dsr2<;z%EWut-oq47AdY`c}_d$vC z!YDlmQ<{!n)f|UK=LBAiCg(<$C+exCSq-+HP~;+=>#`Ov<7oOgNF2-ILf;Rs{_k-; zawpLd5r532&4cD@I2;46<)g;t=VPerlZYIS2#?%0nyH5ZCTo+hGU{7X4HRll$K>|* zNfJO-nSP-zZf~~k+kL&>Cd{a(fbMJ-zW0UrjMXi(pP4F$a?$g)4+;wFX>~u!9JG+u zAO3$qv~I4@tw{obgUlLbtB0Qt?Rqnn#QZ``cH69) zBb^kUHmtFwnpk6=PZl%jv;?Wg!FouFp^$OTha_K*Gy8}?2M2OZ$l!4`N0^TUT9yyv zUojRP{(!-5}M zVm4iPXBUW%EEaylwVB(lC0Rh%oh2x#P~XaNSjt=uZn^Htte7>&1@`w(c{P8GtNRN| zS_#Ic3lY^_EURaPi$7>KFOixx>G0i?mrA z1FpnwMO!!*LLXLsT!QL})9)Ft@j1KZEkv-H!cI&B1gD$xXpTd}GAPNe?sQsite-q8 zZkFrMn(z3l)%WS~?eK}c{T&scishqzgpvKmnQ0QL0qOY39)A`Zk^YB!zjF3wfNj)^6Ftqk=DWocIx1q@{H3y(KxlJh#d_ul|-; zt0-?{ya)L3w~t4fae*9Lq3v85qTz1do!5utHdgS1f(&?3B&KTQKOkdEr$3I0oer zwCpyqo2ywnLS1jY&jySYX!yjJ?5Wfik?SKEa}hX7PD4*Zt=POa!yL*7XU~a+n4IUBtLh=em2bx~!I3<%VZxwI? z-UR(55NF0ZY0;9y0OfW?UCV+d5X-cF-Wb8jpq_LCU`m&rx#J))1QV@W4g})3p2EhC z>@^+!O)jrCtxC%AJ$RXRcN}z42A4}qPG zZ+loZlbKfHD(G-u5Q3iK}cSgGdt6t(Y z`WVK4#IGDf30h1kX}_Z0e9)TjI$daU2Vv1i7yu2pa;{$g(eXd%F%Bz(Jft z3^{PTo#DqAzouI>Ggk-1qyXe12|9)CP~QQ7If}QzlrMolrZ-WQyCWN4?Q(3nsc^}_ zIYX!%t-L;>=*Zv-=#Rn|+rGVr5pm3J!ds^b1uVL7lQJ4jO3P-;`#L!>lQ0EVLldNU z^t{N~v;%wYy9^DZJv$nTcoowj-RxGXACvTv@s(;YgpB*8>;bb`2XRonflCtFL3;NF ziXP+$kXq>8NDVPi1-?fjm)jhv2fT;c4LrINN@jz2Y;0R{d7Kk(bj3h1FgLP1kHGHY zZW}>QK9gxdlhBPAW+_b*Q_*m4kp4uPzjOW<%SuRHBBMOI-p!MA$>?nYE(Mn~nG~XG zdG&Zq7Id8EJrGC5LNQ<0r{JMhL2P)c$_A7VyX;FHpU<>hLzA{h1D49BILl@10a@uL zj-1`FABhOtIpCN?lsMCL(E+T?MAI8=5S@O(005{(L7MGJ;SPzEz^}z)Ho{uj5zP+q zu^gQs*baOk;(I~R;U`voGsgJB^_3Oqz4icPrQy1#oa0GV0H(ch_(EG=6zV0G+K&k+GO61 z7-uhfkT)VGPBthYE(k3`S+iGDV@RRWVVIes^EO@izX&P;kNh4Z=rtxnyl66?<>1g; zY5mfqTaDm$vbB9UdEs^L`+ybv%ICI3rQ?uQB9)ulb_e7*XPPn=Bi?bjc*kd z6Tz+yABzD|# zCgD5fz3h~iOk25G5~J&JiQ5QX@FF3)iiiM}(f3@cr8Hk=N9M8I0__cC;T=4a) zls4$SDoq+dKOG{K)4>JOy1$el(Y{mKxLiEI;EY4L%4~&nJ{-&}mq+;sH73wYdH5v~ z0i&@;*%rE$OlUyo>$`xoMzEaw6F$95COudEIgcd8c)(5D@8O278IGRn@e414TIM-RRUEEKiEm zq>e_sfXG^85vv4~G%Kt~T4hL6n>|c}hb-wARd}kzE{1T><2mv3+$zu>jbygiJuq4i zMSBG}1Fcd*Q@3hc$I2B=YR5{84Q9)mIF&JlI$BUYK1tYoIP1_1vn36n*C3A9D**@M z%>ofN==H?o$I0hGj$5 z&G-?6X|tP9xh44ECGcyVFxS@=nAw8jn_+*PFcU_w*}~hbpEtrmBPWweM%Kyu3#Jdv z66EAu9!~OtxNISk#F>j0DeYpz)yW1wlIPnY-0^kfn#QPjm-Xdm*pF`UqvX9UjTgpz z<9SY+UvnbA9EymBdNogQXyuA@%r376pkIBP714siY&{q-+G^qQK(nrrB@I&l!XTm{Q4`FVlBD%)(_qmv*e?u8`Jnh~OR2Dccz&_@Gg zln+sBXEb-K9iT{@@GB1+(m_U5nU_ID^e;OOim#AIro)izTTtWkKZH--l5}yxlZ1C|4rUCOvVE>u;hpB0 zHIg%JMKPa#iDS@iqh3dDJmwxWgd^H8lyzS^SfoUojF-_C2esfs)v*Cw72%COBddtu zht3L5cISsLEseR3U!d$^Ebp$XP@qk5g^o$p)~pH8DvK}%^LV}@=AA`ZrRN-A z&#c||Mi((KQa+=Hjig}PD41`tr!qyuJa)qmi+i`sZGpXU;))MCB-d}s9|itfj-v=N zWlDgQO|V`J(H=mjlJ+(hp9wg9?Z(w@s&9GL7hpXy`nOB0C^)gJPxKS?bz&Dr4t&+1i74mn^G(y?NJW@dbI?0+wk6ma? zQ+<7L5h&s2;lD8c_BlN6`Emwo^+LuwQ{!e#^;_D#;d2q)l#b+X2k!eP=hAVFY8P2s z8mcxetK-TWOumpxuW zCjyqB5qvEIP+S%sNt#9$IqAVjEhC4Cn4+%Ytuopu@tpjK%pym?p{nc`c+Jw3(}ei! zb5Srh^MR`y-Htb47FqVcGB>+5A!rZ6g1C!5hy`I_$nL5*G+PS7M~D~v7Gp$tNhgyw zZ6Kn262mj_c8=lG+Y4e(;N?17fOdgI>^j{37)=`#P2D?)#xn8Fsj=h$=D(FS;9Vap zLB7*4;2N9hUpX(S{wa&VJzN}e#WRQ!kGo~(nukD0!USacO3Dxmln*wI+cAG$F*R)g&W4L>Yu zh;)>Bj|$8aQMwv!bJR-yAsf92RRD9d+h^bT7O&+o>4 zZ#1kNF+4MZR-;kA1U#V&&ArP(-;n($&|hOAVr%@WnICBlDP7dBfhV@YD%EZ8b=qi# zm?wU%Q*2g2DnWQjGLfq#DA+ zV>9=CP!5OhNp`|Qcu(!d%~v6U)km}sFj{(OpvBkb4%oYNw{KUI4Bu}YfDrS44MWy4)3OPgmibcRGZv+G2GDEBQ`Z;;~~ zdevj_dTwrRZt8+Hjr!yBAt z-&*}HpjGzbv6-%ig7-*cvBYG$!`_!vM)aFyaEr1IB9$o3IhEPl^`2sgqSb+|2qGcv znj=6S46T6yZen_^|;biww}(GBa} zsJBSv*Z)d&vb_4m#~Tn$F}zzo7FS=#qc-R|4GMn1t;v>b3)$Igs$~F23O*^?^PvOk z*W=$Rx`M=|A$7j`Fyc!^$@8vt1q)b&NgE*LN`Nym1KyNd(Qt#~jc_8r(h9s7QtH1X zW$ePbEoUP30sX59b%e_dps;VtsslGPdt#(W_4g0d7SW=F_gNX?Y6)L8PWTQOc~gx&Qe*Bw1TfCJM$=C$dkrhY9V5@eIgPd`M} zCQA{T{<_XigkpCYe+CO6CIsfF2=)R@@U{1VhEkmESv$sIP96 zEJDc84)~#)kn32KKO0fudmJVTK>_j@x+g`fMrn`;2>1B~uQAZf`=`=J7H5i zkwS3`9DJm-6PB!S9?k zg)#@gD*NLXl?RqTMUVR&L=U3V$jd0PPna<8+y<=as~P{0=?Ier@HP4Sjbuu8FrZi4 z@|YDVq!i?Lm#QpQO7L=Rt=wY$s56G$l9M|6Hx0!raKkiS-a+j)q6xHFWuPk?5t-`U zm&^67j+C1l=P`n@A#DtJ0=mG0an?OA+UZ^t1N#Zkfz_-Sd4 zqDi+JIr0aeWCsn>Wm?}*@3VgLbbEg%IVQ0(~@IY5YS z1u7d?ThA&X1+p&5m4bC0^q{+V@3#JY@gd7HfE`mBWkb$1Cc~KKyi?5%b%(zGV=@1J z5cPYZRlaO)Yp6sW5x_`KBA+RaY6n*fFc;N(=>!5f{v3bosw<=NPzQ*TNjefzBT1IS z*iDtM$ro&_9F|VP2=V~ro+FL61jnMf+#8P4vYCMgHYabF4|)m21S#eM)_Ta_Pmwg- ztzpV)pYe9*smSXLV)O`3m!wzdQC)vn1Rdide?Q>`*45gwkmY0CdUVLEJnxE`Ego9! zti`)a>69PWn!OqE&e{`3fY}p>Yti(fttCI-en;y8BIxhGN0@3D#}y_yPxsoy#wheF z#QfKEV*_fU!AabROMl61%kWISyhrZTPI06tXIy!4loWHYLshN0(WkeKNmQMS^?WFK z@!d)whVm?+20p0V5*TwtKS3qjsfsX`JdmhwqFifn|BXqIFG+$*V*Kvdm@hiB-2SS< z-70um$s=ta_KMA}yg9Ca8z0?-|FHbK2PVsKGp>ETf<|dgQ*V=ca4l3{VOQH$w_sHx zq2G$FSKX{M`#W4D;vr7U$-?QQ{vO^{A#!MaWfjQltl_nPKSMeYG|{81$gO_KDn`)N zN4ne-fvl1CBcJj+Z+OBp>1vvovWi(?9TF3#!-deC=FQ@f%`Nb~JDAqe4_7KFLc-Pe zuw)>}DjA&s^;qlRS^2((14F@0S>jFTv=w*-IA1MnLn`q?lgCZ{^cH$jPFx^+l*fLz z7XtwPs{WsbEZxV$dgO)R!kXV~SY`IeKe9uMs_AahdsP_!FSF)zfq}J*=le~t?!L4K zXFw{>C+sj5q`r{~;G^$cfiQ18Av(}X(D{Ab8^N0JrLc%6y1#Maqgifyr%R{Jl9(1hKU;*EAcnCk z5}SABBroNW*j)sc=Je6*6mEaF06~y?f3f1ZQDtx{p4yH7k>DUreowhJ!QF%ruqYZUo==7R z$B`L(qhM0&U85mQ=*R>zRjl*_d;1$bK>}ihHdv}v@V*v;{0_9c0mL#w5Y@)G3pss9 zi-HRsrASxR^6+H+JdZp^;L*CK4eQD3u`f>}yQvz5VmoBQF574A6Ro!p<}*d3U*aIl zE>&SLZfrF0Ic?3+FBV8kAG#bj2`8=b&uK~XMW#%H1bT>5$z~yGP96t2E!D?CX*sI5 z5k@>lvdk8yLp~M=UM)bVGzL}K$XCFvO$Zd>Y!fQtXn&eiYuA_CTopeh@eD1baf%Id z!DH56|H2|B<$pM0o1e>c5kscVPX^d2t-p<`kE=ISHLe)Z8zf``0AKWTeFYn#UJbIF zKZz(e32XEbBT_#?VDzxvnlg@s(D^{blleDVhS#O?bD{_mbX`ufm} z4)aOGFo@Qq2J8sgg%9;P~ zcpCk1P!waNuuyQP5c$OpBfUGFv~ebzFL%NaJ-Ewaz@WBRhj|ehNdu3MysdMg)Ck=? z#~1$fX@+6J`P+K1?aPk0&E#Jc$6KI3r1SAycgRdmw zO@GcR%lO{2BPosLux2g=Q>;v|wM>rDB@#_sjS?BJMutZrZs{#!05F}ONFBC`bZqC7 z`-!><0S;wd@HDo7+7v>1ie8P*=Bet;c@)_CP_bPS7U}CW=dcTZTC>WNpw-#m-me}o zb0K`ivI=e+q&bWnr@MWi8MwzYN*d4Z(hA$A<5s5sBgk}GBsTZq(51I2(XVu^*~)wB zhz4dY=G=4$gR23&7JoK^`nGiJmp!}WqTeA@_p*b?vAF;bWE1CzTELNQ0hy~f$seL| z-ng|s%&&q{8DMNseA&v<(lFV+Z1qR(-~KpT`ONx?bX};L)^K1L`&vsr1ufm- zRA3Bo1xj|Od2ZdHols4&x|ZTEoJ~$D)>n3Jd zq}TaPWUL1jCx$D75S~t*iP*d!iQ|KwyckUeY`sPE@@6?G!T>!$!oQ*&nc{%Hm$ekA z#cO&*J^F9*%O8aGo>f1ko2#&vI0bX=^rE!YIU_VJEjObpVOZL6Q9LjeneIcNt=HEp zdTo(aFwIigrUuidi2*Spx8wjmwqK0YQbX^A|MfFUAPPXo*!}B4p8@)VCSGFb+wSoY6VZ~rNJMq5=*XkrTw)s3}%AhtHkET0k7{MWNfkha2MkgO;cEX@?rGp z?QEmsnMerAqoRC^K!~&`g5-`)?bym;ivF z5J2Xn+0>hdmaO7E?TSQih?MFr)W0bEwUgz;VT+H-N9+5&UHm_M zQE%2KU^Y#-=A>J{df!#4f+T**@#f|V&`@dN@_bJvSTYE7G+~QmcTJ8iw{S$~Uzgim zw!tq^4<~!i3n5%DvoBew6PbO>MlcZ_ngnH~Z(E)dh%d+n;;bA(e~FvPAnUA6@)D7f z$Ku?6bHj2c!XwC<8iLI=2mcrr~I?(Y@9Px}VC#+UkHX zaB9U?i;9pvR!7S+^ptd{R94U2g2u|sUg~E_+MD)1C;$zbJH~hLZc0z2pE}QX7(f=^ zaoyTJ_0B)(8WDdc8hDO%fxP;X(x#kMXUWoIH5_P{-#8F)z$csQ4mn|$#4#|#-sYa& zXJ;g4Rp7mgaWSl5mTC&S6(G`pB(DXSiu=fBZho*mazmB1yL*+hsLLsZ;Xm|i0X{aX ziDwgu6_fx&Io=NXly43hi7S#bC*I>hHOOD8{0?zz%DcBSskyaY$yACi#po#QSDe&u zSq%(>c=}#0*~d|5l2;`4N$+`-7e)QDG_(Ym>0UM@LEuW~S?pmADO+N!-@^KHW1kV- z^d{IbopTzPCyYWXRX+Y`WSC6dlZE?nN2YNNpRyhLMW#L4iB65{0mATDvJq^pFMB~~ z*C>?!lz)vrE-BFo%iJpOPvsSz8~EMe0#RsoWDI$R2?G{YVpy#nT{tU^ z0!KbPjuIf%WKP87l{qB{$^6{i8$n%Ve2bj);H|5dKhPnWVrS+8AzV7s`ho{{5fvMh z$s}?jsr;U-^w#Ig3{vwj8A%)f7yOB*)5qj?I~c@P6I|f}aA_%VxlN)aVB-djSAo=| ztPP#!lDUBjA`v_WH_&3c%qMhbpp)&`vl+2omoC!(I*SOUMGJH-F$WS(;I?vEw<0d! z*3Z;x3I;SKrRDZ04v; zCe1()ep4?XCTBxwbl;jT;dKWSzRKv6>A89IQ4*8#pXksdGMn10bNkD#sECLr+^#j) z_y@aOsQJ?>p;>F>C@e5|cNI>~y^LE}kNr}w%g5H^2;OBrN`~e%TDKHUkp%(O>o+$I z{I<`C@C6TDxuSw#N>sa9O*}p+4!$$3ENAMHhUO8KqbI?>H{{l^aF(^3AX&s1*U~<{ z2Gf?p8?&CGql4-|Qd$G(CngAd~j_mZxPcuuSlM`WR8%N-8 zKSN*MuCgzZeE+MvvKi4LreLS6_Hq-}`k)GnK-U-iMIF9{1Y?Um_W{$$<$EK!Pn0bu)$6%cVEb@n?grKu}u{huWe|07a(RY99G*E^#QLif!s{7U}Bp@q;F@o#-@^#zT% z+(xG*kPOmEtqzTNa$-v*Ux@4Mt6Gah7UK1t<+tW|Ub3wuJ?@tp#rgyTL6QT13ap+6 zgwSL=UgAMS-b}~R3s>AS_h%x3Yf{|_UYv`&&-&$8>i>mQgT;K?u={;>62OnsBz>HJ zEU>0qmpx!8fkt75T{6)Xq~6Ro#QQrI&sZ-(PP{YAkVlOcP-vc}cD2?^6 zquq<&_5P{iJU%P_Ph9ZGRHG3LT!@>)*uR*i0?>iLt^_xl``w)U8bi6o8!>8Zo+xNz zjxSQAXI(DZ{CBwx^&Hx2OQ|#1a*r?pJs@t=_Yu?ze2)t7CLv5#U zE#Q`E3(3nhx34DT{qz911xu2RK;Q%ew5@TZFX{%q#PTW;PBU#&d*rzA9RIW6I74;S zYL;qIg^O{g7V)Uh6f&{9qeaTbM?yR}9Kk9?NV-a)$;C!0wQhVrhTjaBYvisWXA=(Z(x zt%xFLsiSkP>HhseiU z`Y_`batb!5YJ=mLSD7hukgg1K7^qx4eGL|@?=kF%n6(?*Fys*benmF14zifgH&dNN z<;7o|R8_)$idxl|0D!(3+E5LHN81@19E|fnT4BAihguW7<4!@(b>pAs@YYvazOw!1 zbMAi!>EU%}qY&>W5y^lz;imj9CXQZO!@KioxY8k&KPu80LKwGFQ0iL*C-5luPY=dw zdf)u)o(Q(M*N>D#<-sqo`68i3l=I6GZUY_-vriO-JvmC2OQo4~E16y92;9$oqlHt- zt=V)Q69x03zO52q}Ljj}g=wa~~nvc%r>Hb>#Jo@^L006hn*p zNY*ER#yJMKv3D>7()Pr+`Y4MPCia*cZw;J0NWACN)-ivl;9-CL@g60Dfv5rjw!omiyfv1kF3+RHQrM{o*r1>>@ksmgufE7yPt{oeZ~ z5QIAAevb}=dvpEpbUA!zhMggEb!33JEa~xrR~2~*MDT>Q+V-^5a+#f&EK7K`DEN9N zHc@qw&ZmsTDM^qiJVuDe;nhNhQNxLN|9{<{ZYC$f(o4p>Z*|Oe({%6L1=FqW;hS)> zNl**uOO-xfN@%=Kh7Wp9C#~${rADT>BXE~Q;JUxSPHozq?|`*02*yVX!dGO}W&w1W zv1kxC6L??~W2;UOTT2_-lvG?MOjnF_6Iuag$DXx$IB+AJmYaDL$DNF-8rvtsV2vUa z0rgF$t}M|-%<OuEnFZ`iOr()~V9k+x2>81KkoJ1T+Cgc39j2$sy83??=} z8*Q$YWhK#jsL@m)#K`uX6mnodw>Lg{Am+4`23)>sDWbx;rs}%%9l|d`RY*oGR%sB& z5i1rm3fSecW4YB;S0(l#JtH($A41ltp$;6r)~{_jb8pC!Pp&uuSio=MO4DI;OXRgC z8N~665m^1dy5&$F=i#3tz)>IGvY#WqV3G9esW!&ARVzs?ekHCvh|@UzWmfN4o(|vz z8HppcFt1-RJyat6&q56@31`3fnF~JAZ>ky60uQd9gl=+A^#bN>-gM-7u^x>7lrMYQHl3+Zi9Qj745Gy(!uVB z4jBMll6mgmP-C#Efo4Bo5odu=7APA9pVL}64J8#L96xmu!b>sX>Jrb#o9WBbSiPUl zNThfxL6UmfeapAL7cCLrF5Map<+n?M*cj-w-+`jS)0&&K^9qhi80k-GgoE$Dx`SlN zbK2yVLRB13v^g`Z^$=^W8Hn4y?V`ggIkAv@QXE3^c*vmCCWF^VT<5a_^-m`5m9J`x z_Mlg7EtpfLtSKc|jJPF5?$|?v+IUdz|zAq~Vob_!%E+&GAo0-)q-{K~&;I|U$4>NOOz*A0%dOff= z5@zaFCT1Z9;ZPWKlPVUgIKXzmzw`wmho|&Br@?ZD=(!vq{5Lme&h~hCX6n@wd{JzVT%F*F7klqdy|ch5;j7h9!KI1#gD~J4fH3~5Xyn*Y|zdvJ_Eu-@oug1yZiN+U--o^XCIss!G z?^x@g^JY6zv9biwdpJzLekgghw$+u*hAR7Ot^W(Af-IfaLGPyG7}f(ue{Tp$9jl!S zbFq_8v%6+-OK6~fVkN}_F_sxQ0LA&@5~(X+byaEq!mddSuEU@$qJd<9VQtG_NQ@@2x%cuP^>)q;)5-i*BN z%fQ;!Foz1-v>W+lhqaOPqxm@PB6CXCk2&ClU+oXvT%%ht#PserZkG6^RLY(C!+t_? z9PHLY0{g-;+BeUoEUS5;k-AI&9DQUCqRis@vbh0M%NK%C*N)sZXl0UZ0c1`LJDj&^ zrUn)BjD-%YC7l?d&kevk?4OoK*lNF9c^N7u98MJWz9q8(1m+2y-d6!pw$|gZlxX$< zWlz45#fYNaNfR*DLJGTdv3Z~9;J2SwDJax55;GmDAC|j3*TLuIPuL?#JG-@f%%)w> zGgq`x%fW0YF~LF_YAA znuzdZ;83s#A1s*l;h?fB+H}a1vZZK&obAE{87evBB_SRww*xUGC_Qr@c+( z=u6n3FAG72^eAeoN)^~^Uhx=7QM%5O-$W@ntLakP^OM@cz@fBu_mLlkS%!tg>I4kd z!PgVi8>Rx345pS?+p_e-kfaoTtleyti%XR8g~IzmemRw97Y2yZ9}mX68XEM57Si{! z>>`3MIA}?mEo#OTT(<>|E(0%dD;Bl9<&9$)K=3yc#Qx(UJv|IVXsNW#kwdi#wsC01*G6osX!-xh z?Ir)zPSt>->0;)j?$uiHC1@weKpP9$J*4goq~RP4Elt5py($XY~AV z+z1>t(QE9pm0k32zeKYTFm>ue(({8orpxrEby`$U5QJOvl3oe4+l>?_{Mp$nKzVn$ z4}cl~kCzYvMdIYt@#N_ZRY3?3yQE6TLx}pchJni6fz+V1i#9V1W9ph|T4TspvGJFp zrBgMVVJrhy;@I<<1U_^nR{n_34ibdWZ*?BIjF@e7ebQ&wT+$xeVS2k#6T>j z9djIsZQAjj6K^C^qAD{UY*h#f6efM(KOLQevYP>P#iPLNz>7%RHo%?&Z7Fo<9trUP z$#6t4RofLYGzmhs4FWkQX#%+hVJQdb{6gWNB`wA-M+MXlgo}wRj*lY-`cWb|6@a7& z{b8(u9c!^u2`O<;t$t#{Oos29@K8y3o#+_O?pmMO{lQv4!kYXm}M__9zS zsU~}yG2=0&5J*{w!k~@+g&CmI2BHntVA@U=geAkhwIQM?D~Urr<|IlHXf(`i!xCuA zE(p;4V3w^2--BIdGi{uf`jAjbLXsxesbe2YX+ovC#1@@wx!t$uKa6hO3#Jpr_2_Zp zN`A@v#g-PnyTw%`?(k;l?i%FDL)IMzg09i=4=s`&ajvozTohtMNeR3KvirjhO8f%- zi`RwAeU5<28T;QNx7R_~m*{kc6aox+V6TOsr|a!obn7=~`SL0rzYKs*kF$M?wG``t zR~gl(Sj_>DpS(t2brKt1fBtc5f%ntG)0ht6!~v!q^RPfaE~;f$$XC2xjNsMa2r5mQ z)|y1Uhraf<^M>BFpBxHTDHefyOUy{MD}ef;?OcK<7|Trk5N=64vVHgR4xN{ZUMfsh zqe;l9aabeyKvD%MX}S&@KBQJ$yV))tbKjPAz4&Nigu{I??|~0<3W2tyNWBrztXbUZ zEB0Q*>6}PppJ=11DsxW9&sY)C3!m^lH4(TBqeTQ2Zxvqr7*2Av^F@Cg*w?2{)Rn*2 z-9KY``A7;BX+>O&GQ2(0e)^}f_maQV!29WKPIghL=`mqmr!(XEs+aQgha(iTuZFlcjv8l`BE18)|NTN6Gxq$?JzE9!6_T0mI;cWya6#{L|Vd)cO5zj zos?J*6DkR(-K1u!*cisRM@1k{i)g1Un1pRhe;rO@1rIOoO05zbM7}#aZRn#x*G9|D z^yJUPy)QpwQ+Ezt<7?ew2PI~wt}J<^jqa^LK>bSku*~9AlgS0KKg3+AvbEKly4GA` zu*3KkUkHY<(~_3fT|FmFeB)X6*m!66{EJLn6X#i8eROmlkow_oZSy294^8{UfAM2d zdEkjUgYJ3$ADxWfFF8{)X(-zJ3Jy~0H;BCoE@dkXt3VavrW;*Fu5eXK;-*WJx~&|S z;-R)y^}0FRmC&e3(lHd6Y*Cxzj&bh(V3bGDDm*5L+KS>G<33k=!>~XJQk8+Fr)X7t zF@ngR*rqM+D8*vZIR=Ilh)Ya7D4RXA&fx%GOBXsYw@B@r3_Gl(; z4v`zNYF>ff=7+qYDYA4?{P&D1F|P4@Gb#?{`c0pa%{!jEn`jxCP7GLKCJt5p`md%G zA?GX+CbzK+tX6lZZd|MEj4Dtj%tMEtlu zz9~)k9jCmQyeP)FU*E0Nv*t;a?NVdTYdvmCIiXytK!=4Os!a3_@4$bK2i;<7Msh@3 zIl%iaNzc_Zqz1VepR9Ov`A5(V2rb!mfI4}NO_W$F^6ytS$Hrc2tG3J0*%8!TP z4jCZc$P^P4BcDFTABqTG(hJx0zerd8g)j!gDlkrTf6Ne5ZAguypSOk#{`7dq-=5hQyOYg9o=&+Nz)|8U?Rw*#iPM2O^S@nENu^tB)ELFP4`S-m zkgYa7D(D#4qHTgJwtC-njJ)f`^4JUxmr+nrF2t9ppX=_euKoXGv5V%Qm!rZlaz%JW zekih+K4jF8snLW2=jd)39-CRT`@ecw{;i#Jx<2nmvzEuex*fPA#QYxPH0#nB zO}F)QC6sge0~4pu=qUqoM*Z?+e5hv?#z>wj;bpkAd*-yxgvNhI%FeqKBCv;Bv5vH=q-r^{u_F5dlU|gyVA%! z*}kXq!>s8w-jJG2t#IKP#qJ50v)=OBlCG|maPQpio6~R5VfkZv$M%F zs39sXlfCf-`OJCo#5Kj$*htfrnNI2y2z3T~aZxlMao}2zw23^tc`|eFVflYLEU0WH zj7{Qy-M)-kL|6o>uF)~&ea2gMTSR~WoI^)tN7^YelkEEAHlI}K(XlR{hJAf5%v^|=(S9OC)UZ4-xz7(q}z>CD_WjrZT% zOy#@BgC;BUjUJ;}|NVvBeuET=VaD{Zf$1tm7x@S#m#`1T&V%J#&_--3vvbvdEyijQ1tyf;st%DNZumL1+JqEI&s(57 zu>pVW*i1g_FL8(I#skQ_UvkH83zMi*y5Mo#XQWYQeDzqeE7ed0{emk$>2mJ~QLt23lpc3kj|B|61?n|@%c7(3{s_NZz%mo6ix(eB$us8tM4^+0QQ-A4p zuM&lzA6IG?+?3n%9xYB^t&<#f6!VcWk8Ims`JxwEs`jV-bHZh-J9K&Qi?PL=gMECA zpcfv&&)`2`9W;kh6!=%rOTDDFrLdp9pe0qc%JMl++ja|C~fX4=v=0tKmYE zNio6KFSBqVqo6;@#oehnWe<;=6zl7o9R6i?W10vEI(yAs?VQ`h{;_J*iGZ~^!a~6V zc!}G(U|o(fBm3~>f$ssJhJUw6Ewd>TH=oDL4Ra{27(O{T#Wm*4`~Ty^mvoIUQ^4|>8kk-psEzhtwC+CG>8NS8nQ(IYHMhHEI@=RUN zx}+b81U^HJa0^^Y=egWmWf9-Zg*ftXLEbdYwwLnGYo7>!*>DWyrrvCTI@r8EpEb>O z9(4Yq#4EJsm-X5`>)f21he%?LCb**~^pQc7HFnQWPsfDK*}VVJCviMN+cvq*28N4Z z*^6+Npoc^Im(DRqCrnzxl?Tg!=>HN%_FZXimk`~Abm)_>Uw@1hjKGSloD@-@CH?qSr0Ix;dDiHe$5IjEOqn54f$YtN5}9?!-!->%OPZ> zH~!A-iWS=LL6pcVa-m>DAc%5EKed<`Z7T)KZ#ajU=O&AqJ3T@n-g3|(iZtwxqrW+_ z*i0q2bJ4#ovZTwg&|$J74C+0GPU8>cF9K|-v``;x{SAHi&zSGPcTri`u*glD`H?1? z>6@$D;}IeB*6n@O`CtU3;y>0R3F?#$&DwL>>u?(`v2p7Ltb=nd@}T|2%<@~@Xq&KD zh^{63dtxa;9wkbIY}A29OJq(ML&xm(#DL{MTgqs zdbsl{1V9X8m{-8@B+>um0$79BDWbqgK~AaxDNDWwllGjMWHFJv!Z5J2+lt9jHnru? ziR)4%tif_y#>WX!;RNi_!Wl`%b{0<}k}E`I{0wVXUfi`o_^Ja#!F20eW-&N+G-3!G z`){KzqhFmzr9|rRwo@a4EI70~-*gakyb?mm%y(yqpi03nw@ulcpvE3i8lmu`uIQG>r+6 zHn#inNi<#t`M@&&3S48lpP>=UCO-v|Y#_bJf@9KNnoclZh7$ECRsD}{dtrg8UfCM+ zF$dJzR-iHyL;Y87C`0}c$~OlBXrZo~BuK}fgi@2DIE@utWy$a>fi*6oXG9Y#GBGl* z_y7tzS3dYZlafA~oX*c=_jSzxexyU=>MD$*N6Cl<_J$EO^U2jqc3VU~7Q2F(FEjVMF3;`OE(*mXsmNij(S`jKu zpe;t`ji=sYOh)0h7gRNm*^@X%w%U>~@@>P(Rjip*m9(RMI70~i?wpMGPjh0#h_LZ7vrNjuG%Q`B^R0H;7|-KyT*i>#C%jzE{^wZze1V2f3G z;5*DZgA^)(P^8}}BVp7hExM!9>iyX$6`~eVbCC0LLJv|gaWFEKtAn0o2PwgPAJ17M zGd{&^$oY13?Bp!P=u$e%XdGNY&?&;f7HJBU`)4` z$MaK5upfzj(_TCVsy`)PM`G=nf|K89$OTUwsB50V_bMKlMADpQ(hyvg8&=IL1W!FESj z>tlgM9w=_d(X`#`?hg4H`$#+A1pFRr0-mT*p3!QPA8<9)Jue2b3^43|>t%0weI%j$ zy^KJS-CRDU9F*bQVbO+Ask*n5%t9>(W(YZR2mAOTZj?-O8Yv7$4duR@&;5w|Wzh7S_XK(_V-! zJQ75!!pQw+yB$GSrlmFJ{!J@Z1e0}HB0i-!F8Iguxa^ZriA%~urzxFfBdss>uNU*#G*0@eZ2o=9 zcBl;~3R!i4&p>aIIr!(Io&PG+pz(@eT?q*C6&b<=P1<`=%0&KssbfQ77oMA4kr9#> z-zjqpZRADb&iWBG-QQ$1MsOzFPy#-x5UO7F?JR?Q*R>m*BMgAfs0Uv{C|0tRie!Fg z2&+J}Fn4eg$|kn})vV8wBl*ubvqbHe7upHh3Q!cRUdZmVW}pm=(>MF;GSIOj6O|4! za1;s_*4*dQ9GHD<;W+sU@SjP|TJ{o<|cA2*?=jHW=ALs2Vt8ehR{S=ts6MNG05BBJJ&Mma%gmHFo0EnTI~Y=LIf z5uwZ7QcPPH7*uXI5)iCp1V)(ng1HGCG|s6H(J?KIiD`5XL_Xv!e2Q%>X5wqAe_!?_E}?k~wV zH6vJz+Vq-Rqn!Y>=)1SeP(9-3jwQta+1`t5TuC@O5tkg%B1D_ODoW(?-(vR73*L&r z`=sD;Os3|<%_E{~0>`EVre^3)!PR3lb%+BPBSUZc;QG&9L0YHT&AT@%D0iRA+~=n^ zs{~A-RU=DtM4Dyug>~~uyVsZ8? z)=jFuBCwixJTQn%b^nBU8rmvrY(P@BI(q~@URQLq7HP{v5DK)R(&|MJW7XyXA4^I& z@KQUX4tsSA9mWu}2sg>He1m-U}KXbnO4jOC7R@&)4 z3gPGQ6uNvNTGZDV(FCDj17;vf8U&RG z@f#j$)R^-m5hJ}2%Hi>1l=P+pr`%B5p`#EuXK)ZS0!M=!P<|_w;Ci+?i{%Zn{hnq& zuV%-SYDo4Y^ycR2fM7TYUR0H`VkQ%D!cF*yaIn)grI7ZWLxW76XU1=S3GzA!SwGXpu4kRMEj_$h+)9i(0kEXhE-H3%vIy`keqk)WV^a3@{+ z_86%d^f3SWO`7%8cn=cWB_pmve-1bii;Y+#FVB7Sr&PrC$N^EQ(!MKTk3U>ygd9L${LEJaF!bV7SD<;s} zQtTEUUlEOp2d!?K2jfWO!*1<{Bs|$}64KX-cp(7Gc{FkR&NQj51gQE-zB&!>6M|&C zZ1c=s79~i$9ma=?a4-Dh!I9!XR^S|}ZZ?~}U}~Mp-@#E{jgYs!MXEe^^g^N4EmJ9! zW>XGj&)5dXk3IkS^*svrR16c;b|4#ORNwGvpR)#E>f^^hE~PVzhNC8wIqQjACCPv5 z{=rP#xn|-g&DX#5XVR@mf%swzG<{AdZXXUG9WC##*7yhL@ zyG$~r$ZFEO}3@onKyb3{7Z)zr(J7wb3IAJ=JKbN&(&J(s-fi0WB zHVAi9J?+2XgNj10pO3LMKO9B1-EXpGhTDD5fy?Vx?g-1Bhxd)bB4(%r!e&u6=3(uT z&WK(P(PN~>(F~n^x@$#wcA)uF?1tLI(bE7G&b4;PUqWPC&V1x7*x~%tFfJu-F`jndLePNZ*W5c;RgBZ71`4QQ=A0$Y`%moC*@^Q36xX#zt|a;30GOz01Z5<$3> z<;fw)-(TRB;0G@yl_YI)5mgX*)MTl`MYvL9(j3@7RV+#8E%>-;N9Xp z8;urT4=7dqA9+%Z(iiNXQ81n^q!2_mvgo^5ZENFU9;U6PKIFJK{r|vJJ%!V19r?{(PC8MZ7idWLM`7)#4c^wUkisgQWYA0<472=Pg(vAaX6_LcTiqF&h|&j^hJpHrcfVWwP&sE(?`oK)983T;y#q^x!roQ6 zt}u{uqMvfCr|2%7m_jn>^8Du8+&IELwvrG~zKL>LnI-WTZuHVlbi9(DV{6#`Nggz3 z2z`RqrUJouX2ndtNfKFao2dR6D<_V!T4lPEK+LH-k*xK%WVT*L0!$JsPq8Bmrmar|=z$HrB{t+j~w@w3WWvt870T_a7e z-oHJL?yM!@nTI$twQRpw7u5zCoiO&UTvRc(EVVEEGYrDS64go9gGi5^osH6iZ*=tU zM^;2QbN{t;;iZ<=w%jy$XnlwRSi(-BO#K(RocSeTtr1(U0CUtFbb1@e89N%E<_HL8 z30bs(yAlEyD(39U{Yl^0A~5o|5cUdky_L3gt8x^!gNeq>Cy0RRFpUM*X`j$?CklS zy%dSH7Q6dqQh-Q}4N^X)umiKEEz<#D$GW>vLEYpc9c-MsC6+QV2k&FzCMg^oqRh<| z@xhSNPlt+LW$kbuK7;f@nTA(Nmaq=D8nRF*e+|lv;VSag4E#QzIQpVf`f{&p zX*Hd;2iLOi1(+w^=1ZwbH@!RlirSClL8cn7hH}M>-B@|C$xf0IGZFN$xu<66tP&78 z*5I)K*is4ThYvGJO$mjE^muk|s}+@Z8oXR+bCh?(ir-tHq<2kRKgAOYI=8NLK`-ni zX#%C`$rB@5ww<0rOj}77y0yX!X7S|`{#32jiCd$2eiZ6#|`@(Hq#bfw1rR>5BVYGFsjns zuIfOeKGlvt#)|s@z6A)oet5N-Td{F^9;-md=gO-l_e!Z0TeTdVh?@A>iw6!aR>#G`!*xr3XLBj6 zbcOikM85M@eye|OROyqa)sB!JddMMl$u1$ItBK!Ys<`PzSHL7vr+|yJ`yUC+M%Fyh zPNFHgGOC%?DwEa!C0wKd3l0RMoHI%Y`gRdF!LqvL0R;aU6?2XIgc<>A@H3F71CGtB z6{OCDCW7)eNVr>7s7=Q-ePVb<>EKMkvsuw;R_#!#fZtQQ8Af+0xaD4Cc!$gg$^l$@ zZ(Bpr;jp+u@iw!>q6N<*b{Ig7sEahI%lmz+*le=$I?MLsn?-0B{P*ysd37-)gBDz& z;*NvlL&O`~X*gprJ$UG6V-aAp#Ko6D{|Y3B-ZbJ+r3ce;q~#avlI5?YVvbHWmHF3i zk1{uIDbeN2b*LAW5(wZD(vJhZ93qc|t)2%0jLyqIUw?Er?H7PHbqAbz3f7qhZcp@<9Ik)n8u|tsxzS@##li1%&odJPTgdn zNUucp$)tx>*hr5M(aDk|&$a+hCYE{f%orB0yc2t{T@z9Q#6ODW@1&7`D)LzErgBDd zvF)DHEAuHAisYopJC%w@W^n4~&^$}SM_}5XdB~Dr3CCtrui&l${_3PDr8GwEcJos3 zePcDjsJAp-vNd8&b`_|dGokL5aruvaeijV$;fvPd)n zSm*P#4+=fAId4UKl3}dAQyM*-i^&U7n0MT>r#xFJ8XHTxy@X8gn4f2tGW~+M(4OMX z_^{3{q{6EnKwoQoq#K~@a?K;Y`q7Diz>h!sslI(oU*L2(2HZXIQ{mm{4(O``z-=L0 zPlt?<&&&dM7V=m1jio+RV;82Y-QbS?q(YL8hS;H0!q;*2<_5@o@0l%k)Fx-!r~v2T zhmF=|n<#O+%d+JM0sTUZr`7iO)!N!t++TP!1OyW--7Lv=dIe74 zn)b$kG)<&NW|aSB{|Vu(KB@{xKnL9C$ZEfwh)zAwjn0&~S_{l*SzIXQzN8ZF@oH3F z6g*F$wft4@tj3vSv}pSx0luMy2zxOWWw*E#W!A(SfGgFNRkneSu*+`&xY|yf?Q--< zN+;i=(rxt?|>&z`3KMTy&zdp>I0m`=*N?1Vci3o6P!mff?B0V z;pTiE_cScPQ5Gd+69Ne`5Zno~(#Tqls0WE<IgOsw*WZZ_iAZcQjz+U6@NBlj&^JUK}Y|Y0l2@1 zGz&`978w{{nlG2!{B!7X3c`jRCD+*Nw_i*GuZKcw53PADJ{p{-fKhYAt$BiWUP*zY zQDu`_$?q)NN=!1iqFETGn+`wCls&D!hihXz+ErLi59vdR!!{e<|7K1qX>sC5#3iPN zEIR}cr)EgLiJ+fPf)_iuf`Kj>nJY!bDOY^hcApG_(0G^H9CRChwvdELVmMT_`|1xa zvftz|oI9NW;%?=A+2BFjhZq4N?jfA?3{6AmofGdl-PZBUDHG!hI>;r8u8y%r>@E;QH}4VdKOlqj9CL0t6f=?)k}5#}i}3R(`IJSy95+x&WsOkCm>5*M#l zI%fz7B_YWb;NcY7yi&}~6T?Uhf1V8)K+Me@u3~@(#Y2qISc%I;l9v)_#*UsmI!0KS9j$Yzpl%G8wL;*`nILrdI++&Jqly05E4>xnj2#Q&&9`bO1{{D}A zDnd1uEvS)hv-7fHDV36l*R>p`WGRzrYc$`9=kvhIsS$4kH1?N%L)45L66yE_N1PD!gM+3qt)^$vr8qyYhZ#xx_SA;oMSR3PJ+B`a9 zd>u5B;Gsr;&hLig4e)nnmKC`u*GFu1j0 zYP*8_xOKA~KWkE(pind}ZQkEbOdBR;JQlA7gnf}xSn?oFW+96-X2XlkkZ;{%zHN%;`!6_Q?>xE=m+O#9IS%D%DW-k=sSqD^PNz*SsV7J*j`)PY4ID` zUQe$dgd_5i)|h_K){0QP6A^R$bGH*f%xrKXS1(O1*XA&E%$J0dK|3<#BcR(kSRE%k zXUt2kt~|QEB?bB@28CW+Q=*fPWH&a`AyQ=H{XMiD&X{M>R(S42#X5qne2d*! z8n>H33OL+C9}XAq`^*P8=3+3Q<@qJWN7*37EC*~=qR15Di7c39%jMDokdLA^Ar0@p zLK~I{CHUV&VzTz-nfE&UrzmNXEM!o{v`-}Q()UmJv|GSJ6k~`O_gkwP6F)5BY+6u@qvM%aUxEulf^)&CI53dW{sw<3zF7q*41jr1GbhUp#w#jBrb z7MLlV>@XKKFg$4H zvgGTH)r?g4%8&|3V5?Fj8dN8EGqlW)cm(ruh|K2oq0R^=2t|CBR;NhE-Abii-4dcT zlkb;<{Yojo!>lg*&2L9k4TC&Cn|xzO#EB`&Q;Zk!Y}b{^XgjD$oCWd1KLF+_3PRL+ z&7N{8RuWTPBd6k#Rd6?&0o-a}6B#tRl`yCYI$EIWSL*(#UU^nbg}~`q2h?s6aqq9{ z^r27td{l~=JwgsRT7peAGrm1F(D(&Aa4O^4vdwk5EfX*ftY-BYjpk>Jh&anBoksF@ zjL%hu)#>G0uqSlKHuwWvE4%vsz`UVUH6r@5SCo2XBD|9EnFi~$DQ1J%y3*?qUPR9f z^$%pXZ6?@^?_B2dtuGEUj+L+0wOxYVwKwP*+A#-YE163#nh2eFi_6KvDzWV!#f zpvzcXK2||RqrQp47p>u8IbeqC=BCv2clEJ%aiSa|+jM2lE3Nm&xP0teCt{m;piLNQ zs%6-voTIVQndK!R_|nD|zCf z2h=_Zqo709ATyP$Uo?u-P4z>DgyoF&3H_zka%O5~R~K5IePQgmCGN2N%q)|p%X$VX zH~3-tp%QT{()~glIuKl97Ql}W{Sn<5NDZ()VDaaypjr68UR?5x-Mat&_~mLlTp65+ z_ww}nI*e#%X?Kxc@Moum6}o8zFb%;5viet;U<}8At81~ z)tNVMB(1^hR|39V{wPVog4hNrHItDpNucMCOsI8_N1EeN*I-Svu95N+Jrh+tg}}{E zlv72!|D$g0ijpt*sBJgoBy37P)HeQS`jXI^$L!y+x~*<9HXL%yj-U}%iT@YhPWyQ|p9s;M`Yh3$(<^c`dfEqAd zXQ^0t@W(Ql=Fe2Ni355*=Y3SUKV~qbA@c0yeuk&D7rOjXX zRW$h%sGjUc;6$pWP1*1PW^cJQM|L}RrUyoM}z zMve;7YD&f$dGCvUZC{sd_-5VQ)drGb29MZFse)t#{OsW;)@&2>+7*Pr#qBo?<_S>z z18y^|{cQ)CyS(J!91gpi<(+1<&Wd_oDvS;lYXcQU@m{s#2bIQynh7y+SD$YVNDoA9 z`iMJ_iW3*AWd6y9-SasB*>ax4uAH7uS6BFTFi z7CHt@ExDAnWXaaeaTvnurzoQuF{#v8WS6HKxlqR79BBiFV$?)FS!kTVC^M|O0`wv1 zx>mv=EqZmvlrG%|HIk+@nUHL=J}^ zMlQhsda}Sy4bP#fl&J2~u6x6zq4lAoETlhf`-4?Cp{?2;z(N}wM>rhjGPC2yO1)jF zAl`%u?ZU{9z{M)V>g zi84GrNE`#EMJ_byHnrd6zA0}y1YDX%GT_NsT%>)%8}{~F3D`-_*dGMUZa4;64pFBi zPN7y|g6{(gE-87%!Meff%o7?Wiw>lHFy`$k@8I~}`bUGo1r2RlN*Bg^1lBiDAg%m%*osv}Q)q@ilr#Wy|q40e63ER+a$(T`2?x{ZoHPGwnNPOMu zrhrH9{M|XBeXsFXis39^tzm1VpCYa)qL+j^oG=J~qA;~PVbWYWquCGjHCY}T`hj|S zNpi{1BbzvxDsXR`#!znH^;c=7t-3y!I5|g-S%C9ey`=`tx!dbw4R#uV1GY4ZJ(EEV zTMy6=m3o=ZNv#~TB5gEm3ISn0zm{b>HKF3?#S(AjLjl~IsXgyyU>|ccEVNJ8Thi1% zcE#BQ!6Mn}{TBWYbiv-ov=k8?Gsoj_ZrNsGc7m zDn!zwEH4smh0qSjc333!cEE@t@u4hzu6eqLPjwA1s6?CV8ShKvz*THIO8`R&I&n8e z7=iXa>WYP;0vHXJuh=RU2M?BzjMdEx0%rk%;{I{uzF9}wm72^sv;`{n}@P^0SE zn8!`kre?{QF^`Vy&h!EhU#>)ptp6hm{hBLRgNVm?7U+(JgIM@t*{L?&reIQrawcM+ znp3#4y|6XqBmaP96GYE%0x5npbT<%e#Sd(cjjVO>(1*nK7cGO|dVn#(BQ=&gRKN0% zl0J7*vo%0&8~DVnj*$@jHs4~f(M3|CSNK(J<=JvZ&k|M6nwF zv6yG0b+EdJ6=xhVfy)Uqz9?Ax^;9)L8*gN#)F-eH5rw2-S=-9CYqh=!SN?xLpJNy^qD9L=hrtdH zpvlip`j%QmrqRtrn_FzN(B8xQ9xtq+cjN8f<+00ULd_w-VSjWPMF9noD7PVjLJq@4 zk6<0*Z&&#dLFtoyOsGF^MS371PZLy}4lvP8CRTK*oW}1^yo%=bmS2F=+99VQAuV26 ziUwwpxb2u&Ujb_D7F6dw=VIG8vYXC#ZtJHa8W)+N$rppNxV#d{v+p+C#B|gpQzk9z zjRrRjNmn7mu8?FuS_{8f0jm@8cFlrfxHw&e8d7_FkK|dyRWuQY$G*IQeB3zy_r1Zn zKB-{hVwqVt>guHrSwDu_D&^cSNC|^ z+c&)F>VZ&y=7g>f9~pH0a0Qb_FTRI)B9g0+G1aR}1Mq5^I!|SB8ZOinB9AP`^9oa;*fM>W54Y2f8*Vhu-$}7a!%ll<&r)3CK&P9N#50rqc zQ^Yc#xOuR3_5xe@`Avxf&h@-B*hr3HpOI#AJM0P|A z)feZm$(kLBH3_gA>p|SlJT-cvSE?4IL^f#^gIxRKlZgb_H}77#VG!#4&|Ov+@%td< zDI7Wy?63zY-l{cNFq~#63S~_!0nO@zQHxq~5d<*ePMY-ze<7E_vmW$x3*9?DpS)7b z#?gPC{f$jx*()oMilZ(L$+Un{D!Wj{E~Kn9>@m5aQ8LQ8O->p^w3mTreI?9HJ*9GjjC zhLRujex7SbS4?+AuP|tqE;}v8+tm@2aSKzp=Smo)SniG98UMJ%yNrmc`|ZFVl|0sJ&izmw4k(!I4c;!v$al5x|rRcT<_i(EyU2~A2;fG&S2d(*F8 zlV)_UDT%5`CE-2zvK63~bpuo8M)a}~mekpiYsMT(wdfxdu8G2wG2mGA>6-HVoAzXqq1`6>o`ouhdU0gYTdf>0`LcoH{Amh6ltjnl_ ziZ@`5MT){AbCgR4TmAlSzKem<6QRZg%?z>#eA;|r#s8W;k9nt5{Cr_mt65sKOF}Qw zQ~ESw?t+x0opdwvpkVwZKkpgf)`c4Qn;d596@3FDIbYZ6H6{m}H8pJDphndq*1FTs zNRDLCj=&7E6B#3A)m?`ZcV47fgcsn%0z%udt+&w~jsvP*;-}urf<@x76m8ojV=N=f zEn0U%tOJ{$-eGk8k^F11W*B+sc9YR&+riYs@A70pkIuTM1g74pfIqjJs(^#r$}$;8 zjo)J}h$=xhn5vEF_ASZ?>4xZSlPvZdMD6$7K(WBj3W9R}8iKU@&vFl8-~`(D*Yyys zD+Fh9M(!f3!uqK)U6dJM{mT=OtNXT4fo0Ou1`JO$o$h$U2CR37dd*dV2mT9`Bx$)P z6tT+}IC(PF%0)oXfCC1ClD=_vWH_)rdkal&R%+W~Ac3j;*N-__Cs4{F*I=no0-V4V z@I#%cKr8s~B{D)V>|pCA7=8z5-lWiI56d7H?LNmy(}8*li1^3KfCXby?^&<(4sHM# zTLFs##m=Iv4#=Dm52n#7B>NI8t2101>wcJ~k;OnGv1TpX(4-QB- zP2tHYir%mj8b+wrXCxMQKj~XS>CC}B6EVUcoV!RVk-5zA&BSEQ1Hwb@)Lj|A(K0^E z*!I-p1bfUUlDIK0lj3HqXn*d6PGZF+nWe$vu zc&$GoWED`w8lQ~wS}oFEKZsn0fltsd$3M4wlGtXjBvQ)Disqc1I0zJbYZPfq7dyUu zkzEL|h>~h2(nq~1`T{H+GM)-z=$pl%GwTm{g4rO#LowhK>$56Fe~+o>0b>O;SE`NLZgj<7 zfR)KzHP^%Fi9$wW-kuT+@k`?Pys*Hcc<%6!$7~S$;L7!hB^rHqN*~PLin^Im@kfqMyKfA{-OmShT=kWEaC#oTj0=W+A&6<+OrYObt~$QuHHaf+H1_iNa>;4)#&^R!Re5p#g4yYi{pdW zOMw~Zf0}m=WjouF^*Q>0C2BeAnnIi`91{zPJN<7mRZ*~pZTQ0}w$Rtr^`9$_;H>2$bcz4fPK zfERCvKw2?)qTQGJXM%nvr)~4c?pwXL#v1D*ge=Aw`_@MIhQ+SVkkb#iRO%;RTr2lI zk`!U(9#E6KP86N#aaBLfsQQY)vZ8?mAja%y;dxUTKA1i@QhjjT_x%upfRy?VxbP_9 ztzwfu%&LH*(ouAq{aX*N%7m%T{>10jB0Qf9zJrx0QF61_LTnWJb}kvYbSJ4%J0f3_ zRwo~*x^l7q7w@_V(xVUv=$2tQBWX%+3Q_Aqinu>f$aocDB*(-K@QK#mG~U-tpH3UB zU25xspw#$>-|-nq6jJvJM>kzfH^od1CGDFWD>>{>L`tzkcd&9SeHWug2YBm;-BA(- z;CXTbr%5mb6gqRa#0gkMY1u*O+&8=42u4`&G;lqc2*$Vy@HsPZzcY? z1{k%1KATaRAkb>scI9c(&=O?W;<25-B^n7=HMF$Ii}v4^Z5^&QFR(ag$rbHbDR$hj zoKy{n9n4tr62n5o?gn*1AIglCBFwA|uI>ItSD4y`cD1_wDYJV#hRHhbvz4qI;^)mY zy&kj|NY$?qcx307fgw9XPScRqq9bpK*%?3TA*`*ToL~cj4*AchLxv(s}ByN zNqCew+?N}K>ctQEeH6~nZqk*HfD=#fE2OvJANLvZ;Cro%gmi5b4LMcy#E>CxANYZc zr=BL^G*!OF?wn9PB9uN~v5^J#j&KB|ZeI=t+#pzMsWDYl z@jyp>$_%dR?Q*e-UDjr~_X8U?aI+U&s1b#3XA+BA{*YCD{`UMAqKL7Msr;cV?GYD& zOS|qjsY$T~B{z4Q-PmrC=tt)fzkJrE`T2N9;Q6xQlhJ}MDZJ!SN!=NQrp*3hwaUl;99$aR}BWFBC` z$KpjjC`vWg1pScnQcTvK{HJq0tma6C?MP+S6QC-dVqjl86ZrtT5K47<-DOnJgz${j zT&CR&vS!piS0tsGKYQ3BT?A7@QXAoAVLW>X@?XC(JOSi`l- z{juK#)9|LSmd(!Rv+|k;d#9PH$;W4?rsZFMCi+O$r}Q+Xp^R3Hl~F~BqwTlX&t#Jj ztGnOX4GTe#fMP0n!Pyjca<-%_?nbR}smb;8CN|_wyZ#XDUC+qjeC-&3?hmx;=jEV4jG#Xs*yxrixB!>Vhq-1R{8|u9he0_Ohmq*Q5z4 zOa6w}hoWN%exNUC2HqK+P>^bae2!jR?{S3WM97DR7A~M21f6Nqo>k?M3{UF7n)fPn zWVUcujziLkp;7lOdyvr;Iu$vOr8>_eU1>7sE|@;0fXgRJRta#3pC59I)4)C}H2TOQfrpPe1W)BmR^+1LfplKN-u7(QCh zXE;f!CklV|+>QnS)&FG*BS#s^T$`^nAz_MZvoiD+f}s3->U2INWv@mWo% zev8hekfk^rS7-J?3kiUp(-^4tCSb!74U^7Z&KJg);fXxl`p9&?$1i70q3=pQ!7S9~ z=_09hxYD>jUOEkB8DWchf5}^Uh)rbSD2TTng}i^{8hGap${oGKZxpWoD#Ic1dPDga zQ(Eb|CY#~AU3MQM%plqvi}NW7VS_36?Du?&n?=m;T?Oi{vP}yHsdV@ZZ#+4bo}GO| z++^mOz>hz^Gc4-$;{4r?dRKBMVF-?gO@ptr%8InmHZq9SC3SvOZ%- zDF5l*EGi7CQyw@z!T=}(8}PW{VwsS5F?Qw3Md=%zr!&80jG%KQwF39WH-sFyNqD7; zNT|>FU9;Yl$n*=o{Ppm>Ga`d=%R;T;2q?5+lb~Nk1A!zFD2mkr>`Gz66_G{=h1O%C zJ#Ccs*_!`(rT(Zv9*wK?I)y1U99l*sKROiUTEOVWtO)=|YCJXkco-k#Y;h$7v;d6k zJ*AHuNTL`?$Z1xz$49hspg=`UO+a9>^Z-K)C0&<(of(|$5PdV}(piX$hEJG-hit#*Kri)UKH;nE|J5kd+FoyWXJQF40z8KGn- za1e-J*EV!(EiOc*@ye$Z@6_n&Td>~Mt8_5I)s2ju#YUALq;Plhm$5E|;p#d(_9Vx& zs5~7Tgyt^5j&$ZgdhWk2b;NhK9OqKs*(s4%-J*= z3ZMFMgYObxhg%nu>+SnZ=GP*C2Jno~+2NNG2bzYuXaqMZ9|C#X1y&8J;Q3(Uv>cFr zLOCrq_L#_HBj?NM?&^ZSiB0P{vd>Gl#&(-*A1OOS7W0kA?&v|T4bq)@{XCcZMByp*N6H+ICe)s5|N%bh8fqo+I*B)(4j`}4mDp~kF*hu`U7_kmGV=*Z3{OphzBT(jLc{Q zQ#_oyVt6%FL@P*TXA3fpTkWlO#F)g7Obl)zwPEhO2qdW@$~Q#}3vNUXPGahA^Z_3T zYUmYAIlOon)+1mJw2kWsakntcP33t;dH1VpFJ@oxZ{Xyd%xe z3hcY7I2JsygT}~K=Zt*WUJcaWx@!k-uj6laynrN8tad6lIi(>BZ(YLs^Gp&u2VWG` z(#YiCl}?%lwv_Q)-Q1ZBZVo)qYkuof$`4l0MA1gse=slQ@VlDnI=`kNiiPf&mQ+{+ zIOozYbptEz;s7;XASjuv7hA-0JgsfBU{*S55zdW%NKkHT*zCiOzoS8O4WFi=i6$Dv z!_|{x{$@V6(vo(zi+}ozKdTkF1!4}i7Ll}!dJ%ffU zxrKi;f4u`C2m6OuuFk3E;kIe(fKVX-gniJA{FUnd0}G0KNDBq#{z9z|bH8yZCY$ZRP5svobN@$9w?6 z(+TRfS;-i^jCHs_ttuj!4J?6Oi{R<2t(CVZ)gTs9rW6(OEBF+YYCw55F9Wu-qNs?B zV{#+FqGV8K9R0qD@Q5zcilrB?kUZ(Alb)_xsOL#Z=LEvpSuIF{U)JYS3dVwKJmEey zzQ{dQXeBv>%d9 zki2?Hc0Z5tbaIT?K8ce3MjhcZ&fU&71h@feU;&exlcquHf)E_qh#GM) z7Fl*-uuLD2yi=&X9Stt!psbsxLIEi~OpFW3=1sAAYdF08YA_$0!V%s6&wxS4 zRxk$Vrgf!tubqfP1?@1)!f`Q?SyW0KJ)l_4xiWU`fvWT@^idXT?ZiF*X&h=N0X`OW z{wB)*p+==iAfrCJz7|55d8X%H%z70U4=@<=H0p<)spn$iP186pj<>l)E4s1qVIloy zEylliLH`{y2{5baxNVdA*Fv8f6d(D(sg4?eJ=6HKvp*vblYjh_y73t`29^BvW38yr z9miA5mxBj^MCKXE1pImT=KUq+H12Ccs~|